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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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!
+
+
+
+
+
+
+
+
+
\ 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