diff --git a/margotwood/Caddyfile b/margotwood/Caddyfile new file mode 100644 index 0000000..119ccf1 --- /dev/null +++ b/margotwood/Caddyfile @@ -0,0 +1,9 @@ +{ + auto_https off +} + +:80 { + root * /usr/share/caddy + encode gzip + file_server +} diff --git a/margotwood/docker-compose.yml b/margotwood/docker-compose.yml new file mode 100644 index 0000000..175766f --- /dev/null +++ b/margotwood/docker-compose.yml @@ -0,0 +1,16 @@ +services: + caddy: + image: caddy:2-alpine + restart: unless-stopped + ports: + - 8083:80 + - 8243:443 + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile + - ./:/usr/share/caddy + - caddy_data:/data + - caddy_config:/config +volumes: + caddy_data: null + caddy_config: null +networks: {} diff --git a/margotwood/flashcards/addition.html b/margotwood/flashcards/addition.html new file mode 100644 index 0000000..1d0ee43 --- /dev/null +++ b/margotwood/flashcards/addition.html @@ -0,0 +1,430 @@ + + + + + + + Toddler Math Flash Cards - Addition Fun! + + + + + +
+
Score: 0/0
+

🎓 Addition Practice

+ +
+
5 + 3 = ?
+
+ +
+ + +
+ + + + + + + + + + + + +
+
+ + + +
+ + +
+
+ + + + + \ No newline at end of file diff --git a/margotwood/flashcards/subtraction.html b/margotwood/flashcards/subtraction.html new file mode 100644 index 0000000..2030f08 --- /dev/null +++ b/margotwood/flashcards/subtraction.html @@ -0,0 +1,431 @@ + + + + + + + Toddler Math Flash Cards - Subtraction Fun! + + + + + +
+
Score: 0/0
+

🎓 Subtraction Practice

+ +
+
5 + 3 = ?
+
+ +
+ + +
+ + + + + + + + + + + + +
+
+ + + +
+ + +
+
+ + + + + \ No newline at end of file diff --git a/margotwood/index.html b/margotwood/index.html new file mode 100644 index 0000000..af2bcbc --- /dev/null +++ b/margotwood/index.html @@ -0,0 +1,222 @@ + + + + + + + Learning Fun for Toddlers! + + + + + +
+

🎓 Learning Time! 🎓

+

Tap to start learning math!

+ + +
+ +
+ +
+ +
+ +
⏭️ 2️⃣
+ +
+ +
+ + + \ No newline at end of file diff --git a/margotwood/skipcount/twos.html b/margotwood/skipcount/twos.html new file mode 100644 index 0000000..9696b6a --- /dev/null +++ b/margotwood/skipcount/twos.html @@ -0,0 +1,471 @@ + + + + + + + Toddler Math Flash Cards - Skip Counting by 2s! + + + + + +
+
Score: 0/0
+

⏭️ Skip Counting by 2s

+ +
+
What comes next?
+
2, 4, 6, ?
+
+ +
+ + +
+ + + + + + + + + + + + +
+
+ + + +
+ + +
+
+ + + + + diff --git a/margotwood/style.css b/margotwood/style.css new file mode 100644 index 0000000..e69de29 diff --git a/media/docker-compose.yml b/media/docker-compose.yml index a786e88..4406571 100644 --- a/media/docker-compose.yml +++ b/media/docker-compose.yml @@ -41,6 +41,9 @@ services: - /data/usenet/incomplete-downloads:/incomplete-downloads # network_mode: "service:gluetun" forces sabnzbd to connect to the internet through the VPN defined in the gluetun container above network_mode: service:gluetun + depends_on: + gluetun: + condition: service_healthy restart: always labels: - diun.enable=true diff --git a/miningwood/technical-analysis/index.html b/miningwood/technical-analysis/index.html new file mode 100644 index 0000000..9e216fb --- /dev/null +++ b/miningwood/technical-analysis/index.html @@ -0,0 +1,14 @@ + + + + + + + Document + + + + + + + \ No newline at end of file diff --git a/miningwood/technical-analysis/technical_analysis_script.py b/miningwood/technical-analysis/technical_analysis_script.py new file mode 100644 index 0000000..8cde38c --- /dev/null +++ b/miningwood/technical-analysis/technical_analysis_script.py @@ -0,0 +1,327 @@ +import warnings +from datetime import datetime, timedelta +import pandas as pd +import numpy as np +warnings.filterwarnings('ignore') + + +class TechnicalAnalyzer: + def __init__(self, data): + """ + Initialize with price data DataFrame + Expected columns: ['date', 'open', 'high', 'low', 'close', 'volume'] + """ + self.data = data.copy() + self.signals = pd.DataFrame() + + def calculate_sma(self, period): + """Simple Moving Average""" + return self.data['close'].rolling(window=period).mean() + + def calculate_ema(self, period): + """Exponential Moving Average""" + return self.data['close'].ewm(span=period).mean() + + def calculate_rsi(self, period=14): + """Relative Strength Index""" + delta = self.data['close'].diff() + gain = (delta.where(delta > 0, 0)).rolling(window=period).mean() + loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean() + rs = gain / loss + rsi = 100 - (100 / (1 + rs)) + return rsi + + def calculate_macd(self, fast=12, slow=26, signal=9): + """MACD Indicator""" + ema_fast = self.calculate_ema(fast) + ema_slow = self.calculate_ema(slow) + macd_line = ema_fast - ema_slow + signal_line = macd_line.ewm(span=signal).mean() + histogram = macd_line - signal_line + return macd_line, signal_line, histogram + + def calculate_bollinger_bands(self, period=20, std_dev=2): + """Bollinger Bands""" + sma = self.calculate_sma(period) + std = self.data['close'].rolling(window=period).std() + upper_band = sma + (std * std_dev) + lower_band = sma - (std * std_dev) + return upper_band, sma, lower_band + + def calculate_atr(self, period=14): + """Average True Range""" + high_low = self.data['high'] - self.data['low'] + high_close = np.abs(self.data['high'] - self.data['close'].shift()) + low_close = np.abs(self.data['low'] - self.data['close'].shift()) + ranges = pd.concat([high_low, high_close, low_close], axis=1) + true_range = np.max(ranges, axis=1) + atr = true_range.rolling(window=period).mean() + return atr + + def calculate_volume_indicators(self): + """Volume-based indicators""" + # Volume Moving Average + vol_sma_20 = self.data['volume'].rolling(window=20).mean() + vol_ratio = self.data['volume'] / vol_sma_20 + + # On Balance Volume (OBV) + obv = (np.sign(self.data['close'].diff()) * + self.data['volume']).fillna(0).cumsum() + + return vol_ratio, obv + + def generate_all_indicators(self): + """Calculate all technical indicators""" + # Moving Averages + self.data['sma_20'] = self.calculate_sma(20) + self.data['sma_50'] = self.calculate_sma(50) + self.data['ema_12'] = self.calculate_ema(12) + self.data['ema_26'] = self.calculate_ema(26) + + # RSI + self.data['rsi'] = self.calculate_rsi() + + # MACD + macd, signal, histogram = self.calculate_macd() + self.data['macd'] = macd + self.data['macd_signal'] = signal + self.data['macd_histogram'] = histogram + + # Bollinger Bands + bb_upper, bb_middle, bb_lower = self.calculate_bollinger_bands() + self.data['bb_upper'] = bb_upper + self.data['bb_middle'] = bb_middle + self.data['bb_lower'] = bb_lower + + # ATR + self.data['atr'] = self.calculate_atr() + + # Volume indicators + vol_ratio, obv = self.calculate_volume_indicators() + self.data['vol_ratio'] = vol_ratio + self.data['obv'] = obv + + return self.data + + def identify_entry_signals(self): + """Identify potential entry points""" + signals = [] + + for i in range(1, len(self.data)): + entry_score = 0 + reasons = [] + + current = self.data.iloc[i] + previous = self.data.iloc[i-1] + + # Moving Average Crossover (Golden Cross) + if (current['sma_20'] > current['sma_50'] and + previous['sma_20'] <= previous['sma_50']): + entry_score += 2 + reasons.append("SMA Golden Cross") + + # Price above both MAs + if current['close'] > current['sma_20'] > current['sma_50']: + entry_score += 1 + reasons.append("Price above MAs") + + # RSI oversold recovery + if previous['rsi'] < 30 and current['rsi'] > 30: + entry_score += 2 + reasons.append("RSI oversold recovery") + + # MACD bullish crossover + if (current['macd'] > current['macd_signal'] and + previous['macd'] <= previous['macd_signal']): + entry_score += 2 + reasons.append("MACD bullish crossover") + + # Bollinger Band bounce + if previous['close'] <= previous['bb_lower'] and current['close'] > previous['bb_lower']: + entry_score += 1 + reasons.append("BB lower band bounce") + + # Volume confirmation + if current['vol_ratio'] > 1.5: # 50% above average + entry_score += 1 + reasons.append("High volume") + + # Strong overall conditions + if (current['rsi'] > 40 and current['rsi'] < 70 and + current['macd'] > 0): + entry_score += 1 + reasons.append("Favorable momentum") + + if entry_score >= 3: # Minimum threshold for entry + signals.append({ + 'date': current['date'], + 'type': 'ENTRY', + 'price': current['close'], + 'score': entry_score, + 'reasons': reasons + }) + + return signals + + def identify_exit_signals(self): + """Identify potential exit points""" + signals = [] + + for i in range(1, len(self.data)): + exit_score = 0 + reasons = [] + + current = self.data.iloc[i] + previous = self.data.iloc[i-1] + + # Moving Average bearish cross + if (current['sma_20'] < current['sma_50'] and + previous['sma_20'] >= previous['sma_50']): + exit_score += 2 + reasons.append("SMA Death Cross") + + # Price below key MA + if current['close'] < current['sma_20']: + exit_score += 1 + reasons.append("Price below SMA20") + + # RSI overbought + if current['rsi'] > 70: + exit_score += 1 + reasons.append("RSI overbought") + + # RSI bearish divergence (simplified) + if previous['rsi'] > 70 and current['rsi'] < 70: + exit_score += 2 + reasons.append("RSI overbought exit") + + # MACD bearish crossover + if (current['macd'] < current['macd_signal'] and + previous['macd'] >= previous['macd_signal']): + exit_score += 2 + reasons.append("MACD bearish crossover") + + # Bollinger Band upper touch + if current['close'] >= current['bb_upper']: + exit_score += 1 + reasons.append("BB upper band resistance") + + # Volume spike (could indicate distribution) + if current['vol_ratio'] > 3.0: + exit_score += 1 + reasons.append("Extreme volume spike") + + if exit_score >= 3: # Minimum threshold for exit + signals.append({ + 'date': current['date'], + 'type': 'EXIT', + 'price': current['close'], + 'score': exit_score, + 'reasons': reasons + }) + + return signals + + def analyze_stock(self): + """Complete analysis workflow""" + # Generate all indicators + self.generate_all_indicators() + + # Get entry and exit signals + entry_signals = self.identify_entry_signals() + exit_signals = self.identify_exit_signals() + + # Combine all signals + all_signals = entry_signals + exit_signals + all_signals = sorted(all_signals, key=lambda x: x['date']) + + return all_signals, self.data + +# Example usage and demo data generation + + +def generate_sample_data(days=252): + """Generate sample stock data for demonstration""" + np.random.seed(42) # For reproducible results + + start_date = datetime.now() - timedelta(days=days) + dates = [start_date + timedelta(days=i) for i in range(days)] + + # Generate realistic price movement + returns = np.random.normal(0.001, 0.02, days) # Daily returns + price = 100 # Starting price + prices = [price] + + for ret in returns[1:]: + price *= (1 + ret) + prices.append(price) + + # Generate OHLC data + data = [] + for i, (date, close) in enumerate(zip(dates, prices)): + high = close * (1 + abs(np.random.normal(0, 0.015))) + low = close * (1 - abs(np.random.normal(0, 0.015))) + open_price = low + (high - low) * np.random.random() + volume = int(np.random.normal(1000000, 300000)) + + data.append({ + 'date': date, + 'open': open_price, + 'high': high, + 'low': low, + 'close': close, + 'volume': max(volume, 100000) # Ensure positive volume + }) + + return pd.DataFrame(data) + + +# Demo execution +if __name__ == "__main__": + # Generate sample data + print("Generating sample stock data...") + sample_data = generate_sample_data(180) # 6 months of data + + # Initialize analyzer + analyzer = TechnicalAnalyzer(sample_data) + + # Run complete analysis + print("Analyzing technical indicators...") + signals, enhanced_data = analyzer.analyze_stock() + + # Display results + print("\n=== TECHNICAL ANALYSIS RESULTS ===") + print(f"Analysis period: {len(sample_data)} days") + print(f"Total signals found: {len(signals)}") + + # Show recent indicators + print("\n=== LATEST INDICATOR VALUES ===") + latest = enhanced_data.iloc[-1] + print(f"Price: ${latest['close']:.2f}") + print(f"RSI: {latest['rsi']:.2f}") + print(f"MACD: {latest['macd']:.4f}") + print(f"Volume Ratio: {latest['vol_ratio']:.2f}x") + print(f"20-day SMA: ${latest['sma_20']:.2f}") + print(f"50-day SMA: ${latest['sma_50']:.2f}") + + # Show recent signals + print("\n=== RECENT SIGNALS ===") + recent_signals = [s for s in signals if s['date'] + >= (datetime.now() - timedelta(days=30))] + + if recent_signals: + for signal in recent_signals[-5:]: # Last 5 signals + print(f"\n{signal['type']} Signal:") + print(f" Date: {signal['date'].strftime('%Y-%m-%d')}") + print(f" Price: ${signal['price']:.2f}") + print(f" Score: {signal['score']}") + print(f" Reasons: {', '.join(signal['reasons'])}") + else: + print("No recent signals found.") + + print("\n=== USAGE NOTES ===") + print("1. Replace sample data with real market data from your preferred source") + print("2. Adjust indicator parameters based on your trading style") + print("3. Modify signal thresholds based on backtesting results") + print("4. Always combine with risk management and position sizing") + print("5. Consider market conditions and fundamental analysis") diff --git a/pangolin/config/traefik/traefik_config.yml b/pangolin/config/traefik/traefik_config.yml index 2e03bb6..5f4466a 100644 --- a/pangolin/config/traefik/traefik_config.yml +++ b/pangolin/config/traefik/traefik_config.yml @@ -13,7 +13,7 @@ experimental: plugins: badger: moduleName: "github.com/fosrl/badger" - version: "v1.1.0" + version: "v1.2.0" log: level: "INFO" @@ -41,6 +41,8 @@ entryPoints: certResolver: "letsencrypt" tcp-2229: address: ":2229/tcp" + tcp-5432: + address: ":5432/tcp" serversTransport: insecureSkipVerify: true