358 lines
12 KiB
Python
Executable File
358 lines
12 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Signal Generator Debugger
|
|
Analyzes why signals aren't being generated
|
|
"""
|
|
|
|
import sqlite3
|
|
import pandas as pd
|
|
import numpy as np
|
|
import talib
|
|
import json
|
|
from datetime import datetime
|
|
import sys
|
|
|
|
|
|
def load_config():
|
|
with open("config.json", "r") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def fetch_data(candles_db, analysis_db, timeframe, lookback=200):
|
|
"""Fetch and enrich data exactly like signals.py does"""
|
|
try:
|
|
conn_c = sqlite3.connect(f"file:{candles_db}?mode=ro", uri=True, timeout=10)
|
|
conn_c.execute(f"ATTACH DATABASE 'file:{analysis_db}?mode=ro' AS analysis_db")
|
|
|
|
query = """
|
|
SELECT
|
|
c.timeframe, c.timestamp, c.open, c.high, c.low, c.close,
|
|
c.volume, c.buy_volume,
|
|
a.ema_9, a.ema_21, a.sma_50, a.sma_200,
|
|
a.rsi_14, a.macd, a.macd_signal, a.macd_hist,
|
|
a.bb_upper, a.bb_middle, a.bb_lower, a.bb_squeeze,
|
|
a.volume_ma_20
|
|
FROM candles c
|
|
JOIN analysis_db.analysis a
|
|
ON c.timeframe = a.timeframe
|
|
AND c.timestamp = a.timestamp
|
|
WHERE c.timeframe = ?
|
|
ORDER BY c.timestamp DESC
|
|
LIMIT ?
|
|
"""
|
|
|
|
df = pd.read_sql_query(query, conn_c, params=(timeframe, lookback))
|
|
conn_c.close()
|
|
|
|
if df.empty:
|
|
return None
|
|
|
|
df = df.sort_values("timestamp").reset_index(drop=True)
|
|
df["datetime"] = pd.to_datetime(df["timestamp"], unit="s")
|
|
|
|
# Filter closed candles
|
|
import time
|
|
current_time = int(time.time())
|
|
window = {"1m": 60, "5m": 300, "15m": 900, "1h": 3600}.get(timeframe, 60)
|
|
df = df[df["timestamp"] < (current_time - window)]
|
|
|
|
if len(df) < 50:
|
|
return None
|
|
|
|
df = df.dropna(subset=["open", "high", "low", "close", "volume"])
|
|
|
|
if len(df) < 50:
|
|
return None
|
|
|
|
# Add Stochastic
|
|
df["stoch_k"], df["stoch_d"] = talib.STOCH(
|
|
df["high"].values,
|
|
df["low"].values,
|
|
df["close"].values,
|
|
fastk_period=14,
|
|
slowk_period=3,
|
|
slowd_period=3,
|
|
)
|
|
|
|
df["buy_ratio"] = df["buy_volume"] / df["volume"].replace(0, np.nan)
|
|
df["net_flow"] = df["buy_volume"] - (df["volume"] - df["buy_volume"])
|
|
|
|
return df
|
|
|
|
except Exception as e:
|
|
print(f"Error fetching data: {e}")
|
|
return None
|
|
|
|
|
|
def analyze_scalping(df, weights, min_confidence):
|
|
"""Analyze scalping signal generation"""
|
|
if len(df) < 21:
|
|
print(" ❌ Insufficient data for scalping (need 21+ rows)")
|
|
return
|
|
|
|
latest = df.iloc[-1]
|
|
prev = df.iloc[-2]
|
|
|
|
print(f"\n📊 SCALPING ANALYSIS ({len(df)} candles)")
|
|
print("=" * 70)
|
|
|
|
# Check for NULL values
|
|
required = ["ema_9", "ema_21", "rsi_14", "stoch_k", "stoch_d", "macd", "macd_signal"]
|
|
null_cols = [col for col in required if pd.isna(latest[col])]
|
|
|
|
if null_cols:
|
|
print(f" ❌ SKIPPED: Missing indicators: {', '.join(null_cols)}")
|
|
return
|
|
else:
|
|
print(" ✓ All required indicators present")
|
|
|
|
print(f"\n Latest candle: {latest['datetime']}")
|
|
print(f" Close: ${latest['close']:.2f}")
|
|
|
|
# EMA Analysis
|
|
print(f"\n EMA Crossover Check:")
|
|
print(f" Current: EMA9={latest['ema_9']:.2f} vs EMA21={latest['ema_21']:.2f}")
|
|
print(f" Previous: EMA9={prev['ema_9']:.2f} vs EMA21={prev['ema_21']:.2f}")
|
|
|
|
ema_cross_up = latest["ema_9"] > latest["ema_21"] and prev["ema_9"] <= prev["ema_21"]
|
|
ema_cross_down = latest["ema_9"] < latest["ema_21"] and prev["ema_9"] >= prev["ema_21"]
|
|
|
|
if ema_cross_up:
|
|
print(f" ✓ BULLISH CROSSOVER DETECTED!")
|
|
signal_type = "BUY"
|
|
score = weights["ema_cross"]
|
|
elif ema_cross_down:
|
|
print(f" ✓ BEARISH CROSSOVER DETECTED!")
|
|
signal_type = "SELL"
|
|
score = weights["ema_cross"]
|
|
else:
|
|
print(f" ❌ No crossover (EMA9 {'above' if latest['ema_9'] > latest['ema_21'] else 'below'} EMA21)")
|
|
|
|
# Show trend direction
|
|
ema_diff = latest["ema_9"] - latest["ema_21"]
|
|
prev_diff = prev["ema_9"] - prev["ema_21"]
|
|
trend = "converging" if abs(ema_diff) < abs(prev_diff) else "diverging"
|
|
print(f" EMAs are {trend} (diff: {ema_diff:.2f} vs prev: {prev_diff:.2f})")
|
|
return
|
|
|
|
# We have a crossover, check other indicators
|
|
print(f"\n Signal Type: {signal_type}")
|
|
print(f" Base Score: {score:.3f} (from EMA crossover)")
|
|
|
|
# Stochastic
|
|
print(f"\n Stochastic:")
|
|
print(f" K={latest['stoch_k']:.1f}, D={latest['stoch_d']:.1f}")
|
|
|
|
if signal_type == "BUY":
|
|
if latest["stoch_k"] > latest["stoch_d"] and latest["stoch_k"] < 30:
|
|
score += weights["stoch"]
|
|
print(f" ✓ Oversold crossover (+{weights['stoch']:.3f})")
|
|
else:
|
|
print(f" ❌ Not oversold crossover (K>D: {latest['stoch_k'] > latest['stoch_d']}, K<30: {latest['stoch_k'] < 30})")
|
|
else:
|
|
if latest["stoch_k"] < latest["stoch_d"] and latest["stoch_k"] > 70:
|
|
score += weights["stoch"]
|
|
print(f" ✓ Overbought crossover (+{weights['stoch']:.3f})")
|
|
else:
|
|
print(f" ❌ Not overbought crossover (K<D: {latest['stoch_k'] < latest['stoch_d']}, K>70: {latest['stoch_k'] > 70})")
|
|
|
|
# RSI
|
|
print(f"\n RSI: {latest['rsi_14']:.1f}")
|
|
|
|
if signal_type == "BUY" and latest["rsi_14"] < 40:
|
|
score += weights["rsi"]
|
|
print(f" ✓ Undersold (+{weights['rsi']:.3f})")
|
|
elif signal_type == "SELL" and latest["rsi_14"] > 60:
|
|
score += weights["rsi"]
|
|
print(f" ✓ Oversold (+{weights['rsi']:.3f})")
|
|
else:
|
|
print(f" ❌ Not in range (BUY needs <40, SELL needs >60)")
|
|
|
|
# Volume
|
|
vol_ratio = latest["volume"] / latest["volume_ma_20"] if latest["volume_ma_20"] else 0
|
|
print(f"\n Volume: {latest['volume']:.2f} vs MA20: {latest['volume_ma_20']:.2f}")
|
|
print(f" Ratio: {vol_ratio:.2f}x")
|
|
|
|
if vol_ratio > 1.5:
|
|
score += weights["volume"]
|
|
print(f" ✓ Volume surge (+{weights['volume']:.3f})")
|
|
else:
|
|
print(f" ❌ No surge (need >1.5x)")
|
|
|
|
# MACD
|
|
print(f"\n MACD: {latest['macd']:.2f} vs Signal: {latest['macd_signal']:.2f}")
|
|
|
|
if signal_type == "BUY" and latest["macd"] > latest["macd_signal"]:
|
|
score += weights["macd"]
|
|
print(f" ✓ Bullish (+{weights['macd']:.3f})")
|
|
elif signal_type == "SELL" and latest["macd"] < latest["macd_signal"]:
|
|
score += weights["macd"]
|
|
print(f" ✓ Bearish (+{weights['macd']:.3f})")
|
|
else:
|
|
print(f" ❌ Not aligned")
|
|
|
|
# Final score
|
|
print(f"\n {'='*70}")
|
|
print(f" FINAL SCORE: {score:.3f}")
|
|
print(f" THRESHOLD: {min_confidence:.3f}")
|
|
|
|
if score >= min_confidence:
|
|
print(f" ✅ SIGNAL WOULD BE GENERATED!")
|
|
else:
|
|
print(f" ❌ Below threshold (need {min_confidence - score:.3f} more)")
|
|
|
|
|
|
def analyze_swing(df, weights, min_confidence):
|
|
"""Analyze swing signal generation"""
|
|
if len(df) < 200:
|
|
print(f" ❌ Insufficient data for swing (need 200+ rows, have {len(df)})")
|
|
return
|
|
|
|
latest = df.iloc[-1]
|
|
prev = df.iloc[-2]
|
|
|
|
print(f"\n📊 SWING ANALYSIS ({len(df)} candles)")
|
|
print("=" * 70)
|
|
|
|
# Check for NULL values
|
|
required = ["sma_50", "sma_200", "bb_upper", "bb_lower", "bb_squeeze", "macd", "macd_signal", "buy_ratio"]
|
|
null_cols = [col for col in required if pd.isna(latest[col])]
|
|
|
|
if null_cols:
|
|
print(f" ❌ SKIPPED: Missing indicators: {', '.join(null_cols)}")
|
|
return
|
|
else:
|
|
print(" ✓ All required indicators present")
|
|
|
|
print(f"\n Latest candle: {latest['datetime']}")
|
|
print(f" Close: ${latest['close']:.2f}")
|
|
|
|
# Regime Analysis
|
|
print(f"\n Regime Analysis:")
|
|
print(f" Price: ${latest['close']:.2f}")
|
|
print(f" SMA50: ${latest['sma_50']:.2f}")
|
|
print(f" SMA200: ${latest['sma_200']:.2f}")
|
|
|
|
bull_regime = latest["close"] > latest["sma_50"] > latest["sma_200"]
|
|
bear_regime = latest["close"] < latest["sma_50"] < latest["sma_200"]
|
|
|
|
score = 0
|
|
signal_type = None
|
|
|
|
if bull_regime:
|
|
signal_type = "BUY"
|
|
score += weights["regime"]
|
|
print(f" ✓ BULL REGIME (Price > SMA50 > SMA200) (+{weights['regime']:.3f})")
|
|
elif bear_regime:
|
|
signal_type = "SELL"
|
|
score += weights["regime"]
|
|
print(f" ✓ BEAR REGIME (Price < SMA50 < SMA200) (+{weights['regime']:.3f})")
|
|
else:
|
|
print(f" ❌ No clear regime")
|
|
print(f" Price vs SMA50: {'above' if latest['close'] > latest['sma_50'] else 'below'}")
|
|
print(f" SMA50 vs SMA200: {'above' if latest['sma_50'] > latest['sma_200'] else 'below'}")
|
|
return
|
|
|
|
print(f"\n Signal Type: {signal_type}")
|
|
print(f" Base Score: {score:.3f} (from regime)")
|
|
|
|
# BB Squeeze
|
|
print(f"\n Bollinger Bands:")
|
|
print(f" Squeeze: {latest['bb_squeeze']} (prev: {prev['bb_squeeze']})")
|
|
print(f" Upper: ${latest['bb_upper']:.2f}, Lower: ${latest['bb_lower']:.2f}")
|
|
|
|
if latest["bb_squeeze"] == 1 or prev["bb_squeeze"] == 1:
|
|
if signal_type == "BUY" and latest["close"] > latest["bb_upper"]:
|
|
score += weights["bb_squeeze"]
|
|
print(f" ✓ Squeeze breakout upside (+{weights['bb_squeeze']:.3f})")
|
|
elif signal_type == "SELL" and latest["close"] < latest["bb_lower"]:
|
|
score += weights["bb_squeeze"]
|
|
print(f" ✓ Squeeze breakout downside (+{weights['bb_squeeze']:.3f})")
|
|
else:
|
|
print(f" ❌ Squeeze present but no breakout")
|
|
else:
|
|
print(f" ❌ No squeeze")
|
|
|
|
# MACD
|
|
print(f"\n MACD:")
|
|
print(f" Current: {latest['macd']:.2f} vs Signal: {latest['macd_signal']:.2f}")
|
|
print(f" Previous: {prev['macd']:.2f} vs Signal: {prev['macd_signal']:.2f}")
|
|
|
|
macd_cross_up = latest["macd"] > latest["macd_signal"] and prev["macd"] <= prev["macd_signal"]
|
|
macd_cross_down = latest["macd"] < latest["macd_signal"] and prev["macd"] >= prev["macd_signal"]
|
|
|
|
if signal_type == "BUY" and macd_cross_up:
|
|
score += weights["macd"]
|
|
print(f" ✓ Bullish crossover (+{weights['macd']:.3f})")
|
|
elif signal_type == "SELL" and macd_cross_down:
|
|
score += weights["macd"]
|
|
print(f" ✓ Bearish crossover (+{weights['macd']:.3f})")
|
|
else:
|
|
print(f" ❌ No crossover or not aligned")
|
|
|
|
# Net flow
|
|
print(f"\n Buy/Sell Pressure:")
|
|
print(f" Buy Ratio: {latest['buy_ratio']:.2%}")
|
|
|
|
if signal_type == "BUY" and latest["buy_ratio"] > 0.55:
|
|
score += weights["flow"]
|
|
print(f" ✓ Strong buy pressure (+{weights['flow']:.3f})")
|
|
elif signal_type == "SELL" and latest["buy_ratio"] < 0.45:
|
|
score += weights["flow"]
|
|
print(f" ✓ Strong sell pressure (+{weights['flow']:.3f})")
|
|
else:
|
|
print(f" ❌ Neutral pressure")
|
|
|
|
# RSI
|
|
print(f"\n RSI: {latest['rsi_14']:.1f}")
|
|
|
|
if signal_type == "BUY" and latest["rsi_14"] < 50:
|
|
score += weights["rsi"]
|
|
print(f" ✓ Not overbought (+{weights['rsi']:.3f})")
|
|
elif signal_type == "SELL" and latest["rsi_14"] > 50:
|
|
score += weights["rsi"]
|
|
print(f" ✓ Not oversold (+{weights['rsi']:.3f})")
|
|
else:
|
|
print(f" ❌ Unfavorable")
|
|
|
|
# Final score
|
|
print(f"\n {'='*70}")
|
|
print(f" FINAL SCORE: {score:.3f}")
|
|
print(f" THRESHOLD: {min_confidence:.3f}")
|
|
|
|
if score >= min_confidence:
|
|
print(f" ✅ SIGNAL WOULD BE GENERATED!")
|
|
else:
|
|
print(f" ❌ Below threshold (need {min_confidence - score:.3f} more)")
|
|
|
|
|
|
def main():
|
|
config = load_config()
|
|
|
|
print("🔍 SIGNAL GENERATOR DEBUGGER")
|
|
print("=" * 70)
|
|
print(f"Min Confidence: {config['min_confidence']}")
|
|
print(f"Timeframes: {', '.join(config['timeframes'])}")
|
|
print(f"Lookback: {config['lookback']} candles")
|
|
|
|
for timeframe in config["timeframes"]:
|
|
print(f"\n\n{'='*70}")
|
|
print(f"TIMEFRAME: {timeframe}")
|
|
print(f"{'='*70}")
|
|
|
|
df = fetch_data(config["candles_db"], config["analysis_db"], timeframe, config["lookback"])
|
|
|
|
if df is None:
|
|
print(f" ❌ No data available")
|
|
continue
|
|
|
|
print(f" ✓ Loaded {len(df)} candles")
|
|
|
|
# Analyze both personalities
|
|
analyze_scalping(df, config["weights"]["scalping"], config["min_confidence"])
|
|
analyze_swing(df, config["weights"]["swing"], config["min_confidence"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|