From 724b0766cb2955bc392d0a910a54db5ff4029b33 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 28 Sep 2025 20:32:32 -0400 Subject: [PATCH 1/7] add initial Caddyfile, docker-compose.yml, and HTML files for math flashcards; set up basic structure and styles --- margotwood/Caddyfile | 5 + margotwood/docker-compose.yml | 16 + margotwood/flashcards/addition.html | 430 ++++++++++++++++++++++ margotwood/flashcards/subtraction.html | 431 +++++++++++++++++++++++ margotwood/index.html | 208 +++++++++++ margotwood/style.css | 0 miningwood/technical-analysis/index.html | 14 + 7 files changed, 1104 insertions(+) create mode 100644 margotwood/Caddyfile create mode 100644 margotwood/docker-compose.yml create mode 100644 margotwood/flashcards/addition.html create mode 100644 margotwood/flashcards/subtraction.html create mode 100644 margotwood/index.html create mode 100644 margotwood/style.css create mode 100644 miningwood/technical-analysis/index.html diff --git a/margotwood/Caddyfile b/margotwood/Caddyfile new file mode 100644 index 0000000..90497c6 --- /dev/null +++ b/margotwood/Caddyfile @@ -0,0 +1,5 @@ +: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..67d3d25 --- /dev/null +++ b/margotwood/index.html @@ -0,0 +1,208 @@ + + + + + + + Learning Fun for Toddlers! + + + + + +
+

🎓 Learning Time! 🎓

+

Tap to start learning math!

+ + +
+ +
+ +
+ +
+ +
+ + + \ No newline at end of file diff --git a/margotwood/style.css b/margotwood/style.css new file mode 100644 index 0000000..e69de29 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 From e5e51662e69e4d3c89bb9f5e9eb482fd2276f806 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Sun, 28 Sep 2025 20:33:15 -0400 Subject: [PATCH 2/7] add technical analysis script with multiple indicators and signal identification --- .../technical_analysis_script.py | 327 ++++++++++++++++++ 1 file changed, 327 insertions(+) create mode 100644 miningwood/technical-analysis/technical_analysis_script.py 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") From 77989ad88bb972cbf0b7de1517550320fa9b4593 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Mon, 29 Sep 2025 17:00:46 -0400 Subject: [PATCH 3/7] auto https was causing an infinite reload with pangolin the functionality --- margotwood/Caddyfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/margotwood/Caddyfile b/margotwood/Caddyfile index 90497c6..119ccf1 100644 --- a/margotwood/Caddyfile +++ b/margotwood/Caddyfile @@ -1,3 +1,7 @@ +{ + auto_https off +} + :80 { root * /usr/share/caddy encode gzip From 8f21967c2572131d9598c1cb1bc507038af29b75 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Mon, 29 Sep 2025 17:01:31 -0400 Subject: [PATCH 4/7] added skip counting by 2 --- margotwood/index.html | 14 + margotwood/skipcount/twos.html | 471 +++++++++++++++++++++++++++++++++ 2 files changed, 485 insertions(+) create mode 100644 margotwood/skipcount/twos.html diff --git a/margotwood/index.html b/margotwood/index.html index 67d3d25..af2bcbc 100644 --- a/margotwood/index.html +++ b/margotwood/index.html @@ -183,6 +183,16 @@ .app-link.subtraction:active { background: linear-gradient(135deg, #38f9d7 0%, #43e97b 100%); } + + /* Skip counting-specific styling */ + .app-link.skip-counting { + background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 100%); + } + + .app-link.skip-counting:hover, + .app-link.skip-counting:active { + background: linear-gradient(135deg, #fecfef 0%, #ff9a9e 100%); + } @@ -199,6 +209,10 @@
+ +
⏭️ 2️⃣
+ +
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, ?
+
+ +
+ + +
+ + + + + + + + + + + + +
+
+ + + +
+ + +
+
+ + + + + From 99ea6366d372d27b300210890b4a949a2b9d0758 Mon Sep 17 00:00:00 2001 From: Peter Wood Date: Mon, 29 Sep 2025 17:05:03 -0400 Subject: [PATCH 5/7] update badger plugin version to v1.2.0 and add tcp entry for port 5432 --- pangolin/config/traefik/traefik_config.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 From 5c15413c2cf8e421a5a9dd23d7616640e3f9fda5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 03:07:16 +0000 Subject: [PATCH 6/7] Initial plan From 5cbc976c87ffa93000910cf95af976c625dad00b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 03:11:16 +0000 Subject: [PATCH 7/7] Add gluetun service_healthy dependency to sabnzbd container Co-authored-by: acedanger <1345540+acedanger@users.noreply.github.com> --- media/docker-compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/media/docker-compose.yml b/media/docker-compose.yml index 59d3d42..f2b2fbe 100644 --- a/media/docker-compose.yml +++ b/media/docker-compose.yml @@ -40,6 +40,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