├── data ├── trade_signals.csv ├── trades_taken.csv └── market_analysis.json ├── requirements.txt ├── .env.example ├── src ├── __init__.py ├── level_detector.py ├── signal_generator.py ├── market_analysis_manager.py ├── memory_manager.py └── fvg_analyzer.py ├── .claude └── settings.local.json ├── .gitignore ├── config ├── risk_rules.json └── agent_config.json ├── ninjascripts ├── SecondLifeFeed.cs ├── SecondHistoricalData.cs └── claudetrader.cs ├── tests ├── check_recent_fvgs.py ├── test_fvg_direction_logic.py ├── test_fvg_detection.py └── test_market_analysis.py ├── QUICKSTART.md ├── docs ├── SYSTEM_SUMMARY.md ├── MARKET_ANALYSIS_UPDATE.md ├── AGENT_README.md ├── PRICE_ACTION_PHILOSOPHY.md ├── README.md └── ARCHITECTURE.md ├── README.md └── main.py /data/trade_signals.csv: -------------------------------------------------------------------------------- 1 | DateTime,Direction,Entry_Price,Stop_Loss,Target 2 | -------------------------------------------------------------------------------- /data/trades_taken.csv: -------------------------------------------------------------------------------- 1 | DateTime,Direction,Entry_Price 2 | 11/25/2025 19:11:39,SHORT,25127.50 3 | 11/25/2025 19:21:53,SHORT,25144.50 4 | 11/25/2025 23:00:25,SHORT,25169.25 5 | 11/26/2025 00:00:27,SHORT,25198.75 6 | 11/26/2025 07:00:35,SHORT,25173.50 7 | 11/26/2025 08:01:37,SHORT,25196.50 8 | 11/28/2025 12:02:27,SHORT,25487.00 9 | 11/30/2025 17:25:09,SHORT,25519.25 10 | 11/30/2025 18:17:17,SHORT,25423.25 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # MNQ Claude Trading Bot - Dependencies 2 | 3 | # Core dependencies 4 | anthropic>=0.40.0 # Claude AI API 5 | pandas>=2.0.0 # Data manipulation 6 | numpy>=1.24.0 # Numerical computations 7 | python-dotenv>=1.0.0 # Environment variable management 8 | 9 | # Additional dependencies for trading system 10 | typing-extensions>=4.0.0 # Type hints support 11 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Anthropic API Configuration 2 | ANTHROPIC_API_KEY=your_anthropic_api_key_here 3 | 4 | # Trading Configuration 5 | TRADING_MODE=backtest # backtest, live, monitor 6 | DATA_DIR=data 7 | LOG_DIR=logs 8 | 9 | # Risk Management 10 | MAX_DAILY_TRADES=10 11 | MAX_DAILY_LOSS=500 12 | STOP_LOSS_DEFAULT=100 13 | 14 | # Claude Configuration 15 | CLAUDE_MODEL=claude-sonnet-4-5-20250929 16 | CLAUDE_TEMPERATURE=0.3 17 | 18 | # Logging 19 | LOG_LEVEL=INFO 20 | ENABLE_CONSOLE_LOG=true 21 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Claude NQ Trading Agent - Source Package 3 | """ 4 | 5 | __version__ = "1.0.0" 6 | 7 | from .fvg_analyzer import FVGAnalyzer 8 | from .level_detector import LevelDetector 9 | from .trading_agent import TradingAgent 10 | from .memory_manager import MemoryManager 11 | from .signal_generator import SignalGenerator 12 | from .backtest_engine import BacktestEngine 13 | 14 | __all__ = [ 15 | 'FVGAnalyzer', 16 | 'LevelDetector', 17 | 'TradingAgent', 18 | 'MemoryManager', 19 | 'SignalGenerator', 20 | 'BacktestEngine' 21 | ] 22 | -------------------------------------------------------------------------------- /.claude/settings.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "allow": [ 4 | "Bash(git push)", 5 | "Bash(git commit:*)", 6 | "Bash(git log:*)", 7 | "Bash(git add:*)", 8 | "Bash(tree:*)", 9 | "Bash(nul)", 10 | "Bash(dir:*)", 11 | "Bash(findstr:*)", 12 | "Bash(python fvg_bot.py:*)", 13 | "Bash(python:*)", 14 | "Bash(cat:*)", 15 | "Bash(git init:*)", 16 | "Bash(gh repo create:*)" 17 | ], 18 | "deny": [], 19 | "ask": [] 20 | }, 21 | "enableAllProjectMcpServers": true, 22 | "enabledMcpjsonServers": [ 23 | "claude-flow", 24 | "ruv-swarm" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | .env 3 | 4 | # Invalid files 5 | nul 6 | 7 | # Python 8 | __pycache__/ 9 | *.py[cod] 10 | *$py.class 11 | *.so 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | venv/ 29 | ENV/ 30 | env/ 31 | 32 | # Logs 33 | logs/ 34 | *.log 35 | 36 | # Data files (if sensitive) 37 | # Uncomment if you don't want to commit data files 38 | # data/*.csv 39 | # !data/.gitkeep 40 | 41 | # IDE 42 | .vscode/ 43 | .idea/ 44 | *.swp 45 | *.swo 46 | *~ 47 | 48 | # OS 49 | .DS_Store 50 | Thumbs.db 51 | 52 | # NinjaTrader specific 53 | *.bak 54 | 55 | # Test outputs 56 | backtest_results.json 57 | test_results/ 58 | -------------------------------------------------------------------------------- /config/risk_rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "mandatory_rules": [ 3 | "Every trade must have a stop loss", 4 | "Stop loss must be between 15-50 points", 5 | "Minimum risk/reward ratio must be 3:1", 6 | "No trading after 3 consecutive losses", 7 | "No trading after daily loss limit is hit", 8 | "Maximum 5 trades per day", 9 | "Position size must not exceed configured maximum", 10 | "All trades must meet minimum confidence threshold" 11 | ], 12 | "validation": { 13 | "check_before_signal": true, 14 | "log_violations": true, 15 | "halt_on_violation": true, 16 | "alert_on_near_limits": true 17 | }, 18 | "safety_thresholds": { 19 | "daily_loss_warning_pct": 0.8, 20 | "consecutive_loss_warning": 2, 21 | "daily_trades_warning": 4 22 | }, 23 | "recovery_protocol": { 24 | "pause_after_max_losses": true, 25 | "pause_duration_minutes": 60, 26 | "require_manual_resume": true, 27 | "reset_counters_daily": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /config/agent_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "trading_params": { 3 | "min_gap_size": 5.0, 4 | "max_gap_age_bars": 1000, 5 | "min_risk_reward": 1.3, 6 | "confidence_threshold": 0.65, 7 | "position_size": 1 8 | }, 9 | "risk_management": { 10 | "stop_loss_min": 20, 11 | "stop_loss_default": 40, 12 | "stop_loss_max": 100, 13 | "stop_buffer": 10, 14 | "max_daily_trades": 5, 15 | "max_daily_loss": 100, 16 | "max_consecutive_losses": 3 17 | }, 18 | "levels": { 19 | "psychological_intervals": [1000], 20 | "confluence_tolerance": 10.0, 21 | "track_historical_strength": true 22 | }, 23 | "memory": { 24 | "max_trades_stored": 1000, 25 | "query_similar_count": 20, 26 | "enable_adaptive_learning": true 27 | }, 28 | "claude": { 29 | "model": "claude-sonnet-4-5-20250929", 30 | "temperature": 0.3, 31 | "max_tokens": 2000 32 | }, 33 | "logging": { 34 | "level": "INFO", 35 | "log_file": "logs/trading_agent.log", 36 | "enable_console": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ninjascripts/SecondLifeFeed.cs: -------------------------------------------------------------------------------- 1 | #region Using declarations 2 | using System; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.IO; 6 | using NinjaTrader.Cbi; 7 | using NinjaTrader.NinjaScript; 8 | using NinjaTrader.NinjaScript.Indicators; 9 | #endregion 10 | 11 | namespace NinjaTrader.NinjaScript.Strategies 12 | { 13 | public class SecondLiveFeed : Strategy 14 | { 15 | private string filePath; 16 | private bool isFileInitialized = false; 17 | 18 | protected override void OnStateChange() 19 | { 20 | if (State == State.SetDefaults) 21 | { 22 | Description = @"Live price feed - Real-time current price"; 23 | Name = "SecondLiveFeed"; 24 | Calculate = Calculate.OnEachTick; // Real-time price updates 25 | EntriesPerDirection = 1; 26 | BarsRequiredToTrade = 20; 27 | } 28 | else if (State == State.Configure) 29 | { 30 | filePath = @"C:\Users\Joshua\Documents\Projects\Claude Trader\data\LiveFeed.csv"; 31 | } 32 | } 33 | 34 | protected override void OnBarUpdate() 35 | { 36 | if (CurrentBar < BarsRequiredToTrade) 37 | return; 38 | 39 | // Safety check to ensure bar data is valid 40 | if (Bars == null || CurrentBar < 0) 41 | return; 42 | 43 | if (!isFileInitialized) 44 | { 45 | InitializeFile(); 46 | isFileInitialized = true; 47 | } 48 | 49 | AppendDataToFile(); 50 | } 51 | 52 | private void InitializeFile() 53 | { 54 | try 55 | { 56 | // Create file with header - open and close immediately 57 | using (StreamWriter writer = new StreamWriter(filePath, false)) 58 | { 59 | writer.WriteLine("DateTime,Last"); 60 | } 61 | } 62 | catch (Exception ex) 63 | { 64 | Print($"Error initializing file: {ex.Message}"); 65 | } 66 | } 67 | 68 | private void AppendDataToFile() 69 | { 70 | try 71 | { 72 | // Safety checks before accessing bar data 73 | if (Time.Count == 0 || Open.Count == 0 || High.Count == 0 || Low.Count == 0 || Close.Count == 0) 74 | return; 75 | 76 | // Write current price tick to file 77 | using (StreamWriter writer = new StreamWriter(filePath, true)) 78 | { 79 | writer.WriteLine( 80 | $"{DateTime.Now:MM/dd/yyyy HH:mm:ss},{Close[0]:F2}" 81 | ); 82 | } 83 | } 84 | catch (Exception ex) 85 | { 86 | Print($"Error writing to file: {ex.Message}"); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /data/market_analysis.json: -------------------------------------------------------------------------------- 1 | { 2 | "current_bar_index": 1, 3 | "overall_bias": "bearish", 4 | "waiting_for": "SHORT SETUP EVOLVED - Previous target (25303-25324 FVG) was FILLED as price dropped to 25319.75. New fresh bullish FVG formed at 25094-25112 (220pts below). Monitoring for continuation setup or waiting for price stabilization before next move.", 5 | "long_assessment": { 6 | "status": "none", 7 | "setup_type": null, 8 | "entry_plan": null, 9 | "stop_plan": null, 10 | "raw_target": null, 11 | "target_plan": null, 12 | "risk_reward": null, 13 | "confidence": 0.0, 14 | "reasoning": "NO LONG SETUP - Price dropped another 102.5pts from 25422 to 25319.75, continuing the bearish momentum we identified. The previous bullish FVG at 25303-25324 has been FILLED (price at 25319.75 is within that zone), which validates our short thesis perfectly. However, this also means the magnetic pull is satisfied. Bearish FVG above at 25509-25550 is now 225pts away - too distant for quality entry (would require chasing 225pts up). Price is now BELOW EMA21 (25366.57) by 46.82pts, confirming bearish structure shift. Stochastic at 79.41 still elevated but dropping from 80.95. While trend remains bullish (EMA21>75>150), price action is bearish. Would need to see: (1) price stabilize and form base near current levels, (2) stochastic drop below 50 and turn up, (3) bullish rejection candle from support zone before considering longs. Currently in no-man's land between filled FVG and distant resistance." 15 | }, 16 | "short_assessment": { 17 | "status": "waiting", 18 | "setup_type": "FVG_FILL", 19 | "entry_plan": 25319.75, 20 | "stop_plan": 25370.0, 21 | "raw_target": 25094.25, 22 | "target_plan": 25099.25, 23 | "risk_reward": 4.39, 24 | "confidence": 0.7, 25 | "reasoning": "SETUP MONITORING - Previous short setup EXECUTED PERFECTLY. Price dropped 102.5pts from 25422 to 25319.75, filling the bullish FVG at 25303-25324 we targeted. NEW FRESH BULLISH FVG formed at 25094-25112 (0 bars old), now 220pts below. CONFLUENCE FACTORS: (1) Momentum continuation - 200pt drop over 2 bars shows strong bearish commitment. (2) Price broke BELOW EMA21 (25366.57) for first time, confirming trend shift from pullback to reversal. (3) Stochastic at 79.41 still elevated, supporting further downside. (4) Psychological level 25300 just 19.75pts below - likely to break given momentum. (5) Fresh FVG at 25094-25112 provides clear magnetic target. RISK/REWARD: Entry 25319.75, Raw Target 25094.25, Final Target 25099.25 (with 5pt buffer) = 220.5pt potential. Stop 25370.00 (50.25pts above entry, positioned above EMA21 resistance) = 4.39:1 R/R - EXCEPTIONAL. CONCERN: 220pt target is VERY ambitious in single move. Price may consolidate or bounce before reaching. Setting status to WAITING rather than READY because: (1) Just filled previous FVG - may see temporary bounce/consolidation, (2) 220pt distance suggests this could be multi-leg move requiring patience, (3) Stochastic still elevated - prefer to see it drop below 70 for confirmation. IDEAL SCENARIO: Wait for minor bounce to 25340-25360 zone (retest of EMA21/broken support) for better entry, OR wait 1-2 bars to confirm continuation momentum before entering at current levels." 26 | }, 27 | "bars_since_last_update": 0, 28 | "bars_since_last_trade": 1, 29 | "last_updated": "2025-11-30T19:24:57.849233" 30 | } -------------------------------------------------------------------------------- /ninjascripts/SecondHistoricalData.cs: -------------------------------------------------------------------------------- 1 | #region Using declarations 2 | using System; 3 | using System.ComponentModel; 4 | using System.ComponentModel.DataAnnotations; 5 | using System.IO; 6 | using System.Windows.Media; 7 | using NinjaTrader.Cbi; 8 | using NinjaTrader.Gui; 9 | using NinjaTrader.NinjaScript; 10 | using NinjaTrader.NinjaScript.Indicators; 11 | #endregion 12 | 13 | namespace NinjaTrader.NinjaScript.Strategies 14 | { 15 | public class SecondHistoricalData : Strategy 16 | { 17 | private string filePath; 18 | private bool isFileInitialized = false; 19 | 20 | // EMA Indicators 21 | private EMA ema21; 22 | private EMA ema75; 23 | private EMA ema150; 24 | 25 | // Stochastic Indicator 26 | private Stochastics stochastic; 27 | 28 | protected override void OnStateChange() 29 | { 30 | if (State == State.SetDefaults) 31 | { 32 | Description = @"Historical hourly data feed with EMAs and Stochastic D"; 33 | Name = "SecondHistoricalData"; 34 | Calculate = Calculate.OnBarClose; // Only write completed bars 35 | EntriesPerDirection = 1; 36 | BarsRequiredToTrade = 150; // Ensure enough bars for all indicators 37 | IsOverlay = true; // Overlay EMAs on price chart 38 | } 39 | else if (State == State.Configure) 40 | { 41 | filePath = @"C:\Users\Joshua\Documents\Projects\Claude Trader\data\HistoricalData.csv"; 42 | } 43 | else if (State == State.DataLoaded) 44 | { 45 | // Initialize EMA indicators 46 | ema21 = EMA(21); 47 | ema75 = EMA(75); 48 | ema150 = EMA(150); 49 | 50 | // Initialize Stochastic indicator (periodD: 7, periodK: 24, smooth: 3) 51 | stochastic = Stochastics(7, 24, 3); 52 | } 53 | } 54 | 55 | protected override void OnBarUpdate() 56 | { 57 | if (CurrentBar < BarsRequiredToTrade) 58 | return; 59 | 60 | // Safety check to ensure bar data is valid 61 | if (Bars == null || CurrentBar < 0) 62 | return; 63 | 64 | // Ensure all indicators are ready 65 | if (ema21 == null || ema75 == null || ema150 == null || stochastic == null) 66 | return; 67 | 68 | if (!isFileInitialized) 69 | { 70 | InitializeFile(); 71 | isFileInitialized = true; 72 | } 73 | 74 | AppendDataToFile(); 75 | } 76 | 77 | private void InitializeFile() 78 | { 79 | try 80 | { 81 | // Create file with header 82 | using (StreamWriter writer = new StreamWriter(filePath, false)) 83 | { 84 | writer.WriteLine("DateTime,Open,High,Low,Close,EMA21,EMA75,EMA150,StochD"); 85 | } 86 | } 87 | catch (Exception ex) 88 | { 89 | Print($"SecondHistoricalData Error initializing file: {ex.Message}"); 90 | } 91 | } 92 | 93 | private void AppendDataToFile() 94 | { 95 | try 96 | { 97 | // Safety checks before accessing bar data 98 | if (Time.Count == 0 || Open.Count == 0 || High.Count == 0 || Low.Count == 0 || Close.Count == 0) 99 | return; 100 | 101 | // Get Stochastic D value (index 0 is the D line) 102 | double stochDValue = stochastic.D[0]; 103 | 104 | // Write completed bar to file with EMA and Stochastic D values 105 | using (StreamWriter writer = new StreamWriter(filePath, true)) 106 | { 107 | writer.WriteLine( 108 | $"{Time[0]:MM/dd/yyyy HH:mm:ss},{Open[0]:F2},{High[0]:F2},{Low[0]:F2},{Close[0]:F2},{ema21[0]:F2},{ema75[0]:F2},{ema150[0]:F2},{stochDValue:F2}" 109 | ); 110 | } 111 | } 112 | catch (Exception ex) 113 | { 114 | Print($"SecondHistoricalData Error writing to file: {ex.Message}"); 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/check_recent_fvgs.py: -------------------------------------------------------------------------------- 1 | """ 2 | Check Recent Bars for FVG Formation 3 | Shows last 20 bars to see if gaps should be forming 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | sys.path.insert(0, str(Path(__file__).parent.parent)) 9 | 10 | import pandas as pd 11 | 12 | 13 | def check_recent_bars(): 14 | """Check recent bars for potential FVG formation""" 15 | 16 | print("="*80) 17 | print("RECENT BAR ANALYSIS - FVG Formation Check") 18 | print("="*80) 19 | 20 | # Load historical data 21 | df = pd.read_csv('data/HistoricalData.csv') 22 | df['DateTime'] = pd.to_datetime(df['DateTime']) 23 | 24 | # Get last 20 bars 25 | recent = df.tail(20).copy() 26 | recent = recent.reset_index(drop=True) 27 | 28 | print(f"\nShowing last 20 bars (most recent at bottom)") 29 | print(f"Current (latest) bar: {recent.iloc[-1]['DateTime']}") 30 | print(f"Price: {recent.iloc[-1]['Close']:.2f}") 31 | print("\n" + "="*80) 32 | 33 | # Check for FVG formation on each bar 34 | fvg_count = 0 35 | 36 | for i in range(2, len(recent)): 37 | candle1 = recent.iloc[i - 2] 38 | candle2 = recent.iloc[i - 1] 39 | candle3 = recent.iloc[i] 40 | 41 | bar_time = candle3['DateTime'] 42 | 43 | # Check for bullish FVG (gap up) 44 | if candle3['Low'] > candle1['High']: 45 | gap_size = candle3['Low'] - candle1['High'] 46 | if gap_size >= 5.0: 47 | fvg_count += 1 48 | print(f"[BULLISH FVG] {bar_time}") 49 | print(f" Gap: {candle1['High']:.2f} to {candle3['Low']:.2f} = {gap_size:.2f}pts") 50 | print(f" Zone: {candle1['High']:.2f} - {candle3['Low']:.2f}") 51 | print(f" Current Price: {recent.iloc[-1]['Close']:.2f}") 52 | 53 | # Check if filled 54 | filled = False 55 | for j in range(i + 1, len(recent)): 56 | if recent.iloc[j]['Low'] <= candle1['High']: 57 | filled = True 58 | print(f" STATUS: FILLED on {recent.iloc[j]['DateTime']}") 59 | break 60 | 61 | if not filled: 62 | relative = "ABOVE" if candle1['High'] > recent.iloc[-1]['Close'] else "BELOW" 63 | print(f" STATUS: UNFILLED - {relative} current price") 64 | print() 65 | 66 | # Check for bearish FVG (gap down) 67 | elif candle3['High'] < candle1['Low']: 68 | gap_size = candle1['Low'] - candle3['High'] 69 | if gap_size >= 5.0: 70 | fvg_count += 1 71 | print(f"[BEARISH FVG] {bar_time}") 72 | print(f" Gap: {candle3['High']:.2f} to {candle1['Low']:.2f} = {gap_size:.2f}pts") 73 | print(f" Zone: {candle3['High']:.2f} - {candle1['Low']:.2f}") 74 | print(f" Current Price: {recent.iloc[-1]['Close']:.2f}") 75 | 76 | # Check if filled 77 | filled = False 78 | for j in range(i + 1, len(recent)): 79 | if recent.iloc[j]['High'] >= candle1['Low']: 80 | filled = True 81 | print(f" STATUS: FILLED on {recent.iloc[j]['DateTime']}") 82 | break 83 | 84 | if not filled: 85 | relative = "ABOVE" if candle3['High'] > recent.iloc[-1]['Close'] else "BELOW" 86 | print(f" STATUS: UNFILLED - {relative} current price") 87 | print() 88 | 89 | print("="*80) 90 | print(f"FVGs formed in last 20 bars: {fvg_count}") 91 | 92 | if fvg_count == 0: 93 | print("\n[!] NO FVGs formed in recent bars!") 94 | print("This explains why no valid setups exist.") 95 | print("\nPossible reasons:") 96 | print(" 1. Market consolidating without creating gaps") 97 | print(" 2. Price action too smooth (no violent moves)") 98 | print(" 3. Need to wait for next gap-creating move") 99 | 100 | print("="*80) 101 | 102 | # Show bar details for manual inspection 103 | print("\nLast 10 Bars Detail:") 104 | print("-"*80) 105 | print(f"{'DateTime':<20} {'Open':>8} {'High':>8} {'Low':>8} {'Close':>8} {'Range':>8}") 106 | print("-"*80) 107 | 108 | for i in range(len(recent) - 10, len(recent)): 109 | bar = recent.iloc[i] 110 | bar_range = bar['High'] - bar['Low'] 111 | print(f"{str(bar['DateTime']):<20} {bar['Open']:>8.2f} {bar['High']:>8.2f} " 112 | f"{bar['Low']:>8.2f} {bar['Close']:>8.2f} {bar_range:>8.2f}") 113 | 114 | 115 | if __name__ == "__main__": 116 | check_recent_bars() 117 | -------------------------------------------------------------------------------- /QUICKSTART.md: -------------------------------------------------------------------------------- 1 | # Claude NQ Trading Agent - Quick Start 2 | 3 | ## Setup (5 minutes) 4 | 5 | ### 1. Install Dependencies 6 | ```bash 7 | pip install -r requirements.txt 8 | ``` 9 | 10 | ### 2. Configure Environment 11 | ```bash 12 | # Copy example env file 13 | cp .env.example .env 14 | 15 | # Edit .env and add your API key 16 | # ANTHROPIC_API_KEY=your_key_here 17 | ``` 18 | 19 | ### 3. Verify Data Files 20 | Ensure these files exist: 21 | - `data/HistoricalData.csv` - Historical OHLC data ✓ 22 | - `data/LiveFeed.csv` - Real-time price feed ✓ 23 | - `data/trade_signals.csv` - Trade output (auto-created) 24 | 25 | --- 26 | 27 | ## Usage 28 | 29 | ### Backtest (Recommended First) 30 | Test strategy on historical data: 31 | ```bash 32 | # Test last 30 days 33 | python main.py --mode backtest --days 30 34 | 35 | # Test last 100 days 36 | python main.py --mode backtest --days 100 37 | 38 | # Results saved to data/backtest_results.json 39 | ``` 40 | 41 | ### Monitor Performance 42 | View current performance and signals: 43 | ```bash 44 | python main.py --mode monitor 45 | ``` 46 | 47 | ### Live Trading 48 | **⚠️ Only after successful backtest validation:** 49 | ```bash 50 | # Ensure FairValueGaps.py is running in another terminal 51 | python python fvg_bot.py 52 | 53 | # Then start trading agent 54 | python main.py --mode live 55 | ``` 56 | 57 | --- 58 | 59 | ## Configuration 60 | 61 | Edit `config/agent_config.json` to adjust: 62 | - **Stop loss range**: 15-50 points (default: 20) 63 | - **Min risk/reward**: Default 3:1 64 | - **Confidence threshold**: Default 65% 65 | - **Daily trade limits**: Default 5 trades/day 66 | - **Max daily loss**: Default 100 points 67 | 68 | --- 69 | 70 | ## Important Notes 71 | 72 | ### Stop Loss Sizing 73 | ✅ Default: **20 points** (NQ appropriate) 74 | - Minimum: 15 points (volatility floor) 75 | - Maximum: 50 points (risk control) 76 | - **Your feedback noted**: 8 points is too small ✓ 77 | 78 | ### Risk Management 79 | System enforces: 80 | - Maximum 5 trades per day 81 | - Maximum 100 point daily loss 82 | - No trading after 3 consecutive losses 83 | - Mandatory stops on every trade 84 | 85 | ### Trade Flow 86 | ``` 87 | FairValueGaps.py → Claude Analysis → trade_signals.csv → NinjaTrader 88 | (detects gaps) (makes decision) (CSV output) (execution) 89 | ``` 90 | 91 | --- 92 | 93 | ## Testing Without API Key 94 | 95 | Run backtest with simple logic (no Claude): 96 | ```bash 97 | # Unset API key temporarily 98 | unset ANTHROPIC_API_KEY 99 | 100 | # Run backtest - uses confluence detection only 101 | python main.py --mode backtest --days 30 102 | ``` 103 | 104 | --- 105 | 106 | ## File Structure 107 | 108 | ``` 109 | Claude Trader/ 110 | ├── main.py # ← Start here 111 | ├── FairValueGaps.py # Existing FVG detector (keep running) 112 | ├── src/ # Trading agent modules 113 | ├── config/ # Configuration files 114 | ├── data/ 115 | │ ├── HistoricalData.csv # Your historical data 116 | │ ├── LiveFeed.csv # Your live feed 117 | │ ├── trade_signals.csv # Output to NinjaTrader 118 | │ └── trade_history.json # Performance tracking 119 | ├── docs/ 120 | │ ├── AGENT_README.md # Full documentation 121 | │ └── ARCHITECTURE.md # System design 122 | └── logs/ # System logs (auto-created) 123 | ``` 124 | 125 | --- 126 | 127 | ## Troubleshooting 128 | 129 | ### "No API key found" 130 | Add to `.env` file: 131 | ``` 132 | ANTHROPIC_API_KEY=your_key_here 133 | ``` 134 | 135 | ### "Cannot import module" 136 | Install dependencies: 137 | ```bash 138 | pip install -r requirements.txt 139 | ``` 140 | 141 | ### "File not found: HistoricalData.csv" 142 | Check that data files are in the `data/` directory 143 | 144 | ### "Trading blocked: Daily limit reached" 145 | Risk management kicked in. Reset happens automatically at midnight or edit config. 146 | 147 | --- 148 | 149 | ## Next Steps 150 | 151 | 1. ✅ Run backtest on 30 days 152 | 2. ✅ Review results in `data/backtest_results.json` 153 | 3. ✅ Adjust stop loss if needed (config/agent_config.json) 154 | 4. ✅ Run monitor mode to see current state 155 | 5. ✅ Start FairValueGaps.py in one terminal 156 | 6. ✅ Start live trading in another terminal 157 | 7. ✅ Watch trade_signals.csv for signals 158 | 8. ✅ NinjaTrader executes trades 159 | 160 | --- 161 | 162 | ## Performance Tracking 163 | 164 | System automatically tracks: 165 | - Win rate by setup type (confluence vs FVG-only) 166 | - Average risk/reward achieved 167 | - Trade history with reasoning 168 | - Performance metrics 169 | 170 | View anytime: 171 | ```bash 172 | python main.py --mode monitor 173 | ``` 174 | 175 | --- 176 | 177 | ## Support 178 | 179 | - **Full docs**: See `docs/AGENT_README.md` 180 | - **Architecture**: See `docs/ARCHITECTURE.md` 181 | - **Trading philosophy**: See `docs/PRICE_ACTION_PHILOSOPHY.md` 182 | - **Logs**: Check `logs/trading_agent.log` 183 | 184 | --- 185 | 186 | **🚀 You're ready to trade with Claude!** 187 | 188 | Remember: Start with backtesting, validate performance, then go live. 189 | -------------------------------------------------------------------------------- /docs/SYSTEM_SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Claude NQ Trading System - Summary 2 | 3 | ## ✅ What's Built and Working 4 | 5 | ### Core Components 6 | 1. **FVG Analyzer** (`src/fvg_analyzer.py`) ✓ 7 | - Detects Fair Value Gaps from price data 8 | - Calculates distances and identifies nearest gaps 9 | 10 | 2. **Trading Agent** (`src/trading_agent.py`) ✓ 11 | - Claude-powered decision engine 12 | - Analyzes: FVG + EMA Trend + Stochastic Momentum 13 | - **Full discretion** - no hard rules, pure reasoning 14 | 15 | 3. **Memory Manager** (`src/memory_manager.py`) ✓ 16 | - Stores past trade outcomes 17 | - Provides historical context to Claude 18 | - Tracks performance by setup type 19 | 20 | 4. **Signal Generator** (`src/signal_generator.py`) ✓ 21 | - Writes trades to `data/trade_signals.csv` 22 | - Format: `DateTime,Direction,Entry_Price,Stop_Loss,Target` 23 | 24 | 5. **Backtest Engine** (`src/backtest_engine.py`) ✓ 25 | - Tests strategy on historical data 26 | - Simulates trade execution 27 | - Generates performance reports 28 | 29 | --- 30 | 31 | ## 🎯 How Claude Makes Decisions 32 | 33 | ### Input Data Claude Receives: 34 | ``` 35 | FAIR VALUE GAPS: 36 | - Nearest Bullish FVG: zone, size, distance, age 37 | - Nearest Bearish FVG: zone, size, distance, age 38 | 39 | EMA TREND: 40 | - EMA21, EMA75, EMA150 values 41 | - Trend alignment (strong up/down/neutral) 42 | 43 | MOMENTUM: 44 | - Stochastic value 45 | - Status (oversold/overbought/neutral) 46 | 47 | MEMORY (if available): 48 | - Past trade performance 49 | - Win rates by setup type 50 | - Historical context 51 | ``` 52 | 53 | ### Claude's Analysis Process: 54 | 1. **Where is price likely heading?** 55 | - Toward bullish FVG (up)? 56 | - Toward bearish FVG (down)? 57 | - No clear direction? 58 | 59 | 2. **Do indicators align or conflict?** 60 | - FVG direction + EMA trend + Stochastic 61 | - Confirming signals or mixed? 62 | 63 | 3. **Is there a tradeable setup?** 64 | - Clear directional bias? 65 | - Acceptable risk/reward (>= 3:1)? 66 | - Appropriate stop loss (15-50pts)? 67 | 68 | 4. **Final Decision** 69 | - LONG / SHORT / NONE 70 | - Entry, stop, target prices 71 | - Confidence level (0.0-1.0) 72 | - Detailed reasoning 73 | 74 | --- 75 | 76 | ## 📁 File Outputs 77 | 78 | ### `data/trade_signals.csv` 79 | When Claude decides to trade: 80 | ```csv 81 | DateTime,Direction,Entry_Price,Stop_Loss,Target 82 | 11/25/2025 14:30:00,SHORT,24712.00,24730.00,24650.00 83 | 11/25/2025 16:15:00,LONG,24603.00,24585.00,24665.00 84 | ``` 85 | 86 | ### `data/trade_history.json` 87 | After trades complete: 88 | ```json 89 | { 90 | "trade_id": "2025-11-25_14:30:00", 91 | "setup": {...}, 92 | "outcome": { 93 | "result": "WIN", 94 | "profit_loss": 62.00, 95 | "risk_reward_achieved": 3.44 96 | }, 97 | "decision": { 98 | "confidence": 0.78, 99 | "reasoning": "..." 100 | } 101 | } 102 | ``` 103 | 104 | --- 105 | 106 | ## 🚀 Usage 107 | 108 | ### Backtest Mode 109 | ```bash 110 | python main.py --mode backtest --days 100 111 | ``` 112 | - Tests on historical data 113 | - Claude analyzes every bar with active FVGs 114 | - Results in `data/backtest_results.json` 115 | 116 | ### Live Trading Mode 117 | ```bash 118 | # Terminal 1: Run FVG detector 119 | python FairValueGaps.py 120 | 121 | # Terminal 2: Run Claude trading agent 122 | python main.py --mode live 123 | ``` 124 | - Monitors real-time FVG zones 125 | - Claude analyzes setups as they form 126 | - Writes signals to `trade_signals.csv` 127 | - NinjaTrader executes from CSV 128 | 129 | ### Monitor Mode 130 | ```bash 131 | python main.py --mode monitor 132 | ``` 133 | - View performance stats 134 | - Recent signals 135 | - Memory context 136 | 137 | --- 138 | 139 | ## 🔑 Key Features 140 | 141 | ### ✓ No Confluence Requirement 142 | - Dropped the FVG + Level alignment requirement 143 | - Claude has full discretion 144 | 145 | ### ✓ Multi-Factor Analysis 146 | - FVGs (gap attraction points) 147 | - EMAs (trend direction) 148 | - Stochastic (momentum/timing) 149 | 150 | ### ✓ Contextual Learning 151 | - Not traditional ML training 152 | - Builds memory of what worked 153 | - Claude queries past similar setups 154 | - Adapts reasoning based on outcomes 155 | 156 | ### ✓ Full Transparency 157 | - Every decision has detailed reasoning 158 | - No black box 159 | - Audit trail in logs 160 | 161 | ### ✓ Risk Management 162 | - Stop loss: 15-50pts (configurable) 163 | - Min risk/reward: 3:1 164 | - Daily limits enforced 165 | - Position sizing controlled 166 | 167 | --- 168 | 169 | ## 🐛 Known Issues 170 | 171 | ### Backtest Currently Returns 0 Trades 172 | **Possible causes:** 173 | 1. FVGs being marked as filled before analysis 174 | 2. Claude being too conservative 175 | 3. Loop logic issue with active FVGs 176 | 4. Need more frequent analysis triggers 177 | 178 | **Next steps:** 179 | - Debug with verbose logging 180 | - Check FVG fill logic 181 | - Verify Claude is being called 182 | - Test simple logic mode 183 | 184 | --- 185 | 186 | ## 📝 Configuration 187 | 188 | ### `config/agent_config.json` 189 | ```json 190 | { 191 | "trading_params": { 192 | "min_gap_size": 5.0, 193 | "max_gap_age_bars": 100, 194 | "min_risk_reward": 3.0, 195 | "confidence_threshold": 0.65 196 | }, 197 | "risk_management": { 198 | "stop_loss_min": 15, 199 | "stop_loss_default": 20, 200 | "stop_loss_max": 50, 201 | "max_daily_trades": 5, 202 | "max_daily_loss": 100 203 | } 204 | } 205 | ``` 206 | 207 | --- 208 | 209 | ## 🔄 The Learning Loop 210 | 211 | ``` 212 | Backtest runs 213 | ↓ 214 | Claude analyzes each bar 215 | ↓ 216 | Makes trade decisions 217 | ↓ 218 | Outcomes recorded 219 | ↓ 220 | Memory built 221 | 222 | When live trading: 223 | ↓ 224 | Claude queries memory 225 | ↓ 226 | "Show me past trades where EMA uptrend + bullish FVG above + Stoch oversold" 227 | ↓ 228 | Memory returns: "15 trades, 73% win rate" 229 | ↓ 230 | Claude reasons with this context 231 | ↓ 232 | Higher confidence on proven patterns 233 | ``` 234 | 235 | --- 236 | 237 | ## 🎓 Philosophy 238 | 239 | **Traditional ML:** 240 | - Train model on data 241 | - Update weights via gradient descent 242 | - Deploy frozen model 243 | - Retrain periodically 244 | 245 | **Our Approach:** 246 | - Claude reasons about current setup 247 | - Queries memory for similar past setups 248 | - Uses historical outcomes as context 249 | - Adapts reasoning in real-time 250 | - No retraining needed 251 | 252 | **Benefits:** 253 | - Transparent decisions 254 | - Fast adaptation 255 | - Works with small sample sizes 256 | - Explainable reasoning 257 | - Can incorporate new rules instantly 258 | 259 | --- 260 | 261 | **Status:** System built, debugging backtest loop 262 | **Next:** Fix 0-trade issue, then validate with real backtests 263 | -------------------------------------------------------------------------------- /tests/test_fvg_direction_logic.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test FVG Direction Logic - Verify FVGs are treated as magnetic targets 3 | """ 4 | 5 | import sys 6 | from pathlib import Path 7 | sys.path.insert(0, str(Path(__file__).parent.parent)) 8 | 9 | from src.fvg_analyzer import FVGAnalyzer 10 | 11 | 12 | def test_fvg_direction_logic(): 13 | """Test that FVGs are correctly identified as directional targets""" 14 | 15 | print("="*60) 16 | print("Testing FVG Direction Logic") 17 | print("="*60) 18 | 19 | analyzer = FVGAnalyzer(min_gap_size=5.0, max_gap_age=100) 20 | 21 | # Test scenario: Price at 21000 22 | current_price = 21000.0 23 | 24 | # Sample FVGs 25 | sample_fvgs = [ 26 | # Bullish FVG ABOVE (LONG opportunity - price drawn up) 27 | { 28 | 'type': 'bullish', 29 | 'top': 21115, 30 | 'bottom': 21110, 31 | 'gap_size': 5.0, 32 | 'datetime': '2025-11-30 14:00:00', 33 | 'filled': False, 34 | 'age_bars': 12, 35 | 'index': 100 36 | }, 37 | # Another bullish FVG ABOVE (further away) 38 | { 39 | 'type': 'bullish', 40 | 'top': 21205, 41 | 'bottom': 21200, 42 | 'gap_size': 5.0, 43 | 'datetime': '2025-11-30 13:00:00', 44 | 'filled': False, 45 | 'age_bars': 24, 46 | 'index': 88 47 | }, 48 | # Bearish FVG BELOW (SHORT opportunity - price drawn down) 49 | { 50 | 'type': 'bearish', 51 | 'top': 20895, 52 | 'bottom': 20890, 53 | 'gap_size': 5.0, 54 | 'datetime': '2025-11-30 12:00:00', 55 | 'filled': False, 56 | 'age_bars': 36, 57 | 'index': 76 58 | }, 59 | # Another bearish FVG BELOW (further away) 60 | { 61 | 'type': 'bearish', 62 | 'top': 20805, 63 | 'bottom': 20800, 64 | 'gap_size': 5.0, 65 | 'datetime': '2025-11-30 11:00:00', 66 | 'filled': False, 67 | 'age_bars': 48, 68 | 'index': 64 69 | }, 70 | # Bullish FVG BELOW current price (should be FILTERED OUT) 71 | { 72 | 'type': 'bullish', 73 | 'top': 20915, 74 | 'bottom': 20910, 75 | 'gap_size': 5.0, 76 | 'datetime': '2025-11-30 10:00:00', 77 | 'filled': False, 78 | 'age_bars': 60, 79 | 'index': 52 80 | }, 81 | # Bearish FVG ABOVE current price (should be FILTERED OUT) 82 | { 83 | 'type': 'bearish', 84 | 'top': 21095, 85 | 'bottom': 21090, 86 | 'gap_size': 5.0, 87 | 'datetime': '2025-11-30 09:00:00', 88 | 'filled': False, 89 | 'age_bars': 72, 90 | 'index': 40 91 | } 92 | ] 93 | 94 | print(f"\nCurrent Price: {current_price:.2f}") 95 | print(f"Total FVGs: {len(sample_fvgs)}") 96 | print("") 97 | 98 | # Analyze market context 99 | context = analyzer.analyze_market_context(current_price, sample_fvgs) 100 | 101 | print("\n" + "="*60) 102 | print("ANALYSIS RESULTS") 103 | print("="*60) 104 | 105 | # Check LONG setup (bullish FVG above) 106 | if context['nearest_bullish_fvg']: 107 | fvg = context['nearest_bullish_fvg'] 108 | print(f"\n[OK] LONG OPPORTUNITY FOUND:") 109 | print(f" Bullish FVG ABOVE at {fvg['bottom']:.2f} - {fvg['top']:.2f}") 110 | print(f" Target: {fvg['bottom']:.2f} (bottom of gap)") 111 | print(f" Distance: {fvg['distance']:+.2f} points") 112 | print(f" Strategy: Enter LONG now, ride price UP to {fvg['bottom']:.2f}") 113 | 114 | # Verify distance calculation 115 | expected_distance = fvg['bottom'] - current_price 116 | assert fvg['distance'] == expected_distance, f"Distance calculation error: {fvg['distance']} != {expected_distance}" 117 | assert fvg['distance'] > 0, "LONG target should be ABOVE (positive distance)" 118 | print(f" [OK] Distance calculation correct: {fvg['distance']:+.2f}pts") 119 | else: 120 | print("\n[X] NO LONG OPPORTUNITY (no bullish FVG above)") 121 | 122 | # Check SHORT setup (bearish FVG below) 123 | if context['nearest_bearish_fvg']: 124 | fvg = context['nearest_bearish_fvg'] 125 | print(f"\n[OK] SHORT OPPORTUNITY FOUND:") 126 | print(f" Bearish FVG BELOW at {fvg['bottom']:.2f} - {fvg['top']:.2f}") 127 | print(f" Target: {fvg['top']:.2f} (top of gap)") 128 | print(f" Distance: {fvg['distance']:+.2f} points") 129 | print(f" Strategy: Enter SHORT now, ride price DOWN to {fvg['top']:.2f}") 130 | 131 | # Verify distance calculation 132 | expected_distance = fvg['top'] - current_price 133 | assert fvg['distance'] == expected_distance, f"Distance calculation error: {fvg['distance']} != {expected_distance}" 134 | assert fvg['distance'] < 0, "SHORT target should be BELOW (negative distance)" 135 | print(f" [OK] Distance calculation correct: {fvg['distance']:+.2f}pts") 136 | else: 137 | print("\n[X] NO SHORT OPPORTUNITY (no bearish FVG below)") 138 | 139 | # Verify filtering worked for NEAREST FVGs (the ones that matter for trading) 140 | print("\n" + "="*60) 141 | print("FILTERING VERIFICATION (Nearest FVGs)") 142 | print("="*60) 143 | 144 | print("\nVerifying nearest FVGs are correctly directional:") 145 | 146 | # Nearest bullish should be ABOVE current price 147 | if context['nearest_bullish_fvg']: 148 | fvg = context['nearest_bullish_fvg'] 149 | if fvg['bottom'] > current_price: 150 | print(f" [OK] Nearest Bullish FVG correctly ABOVE price: {fvg['bottom']:.2f}") 151 | else: 152 | print(f" [X] ERROR: Nearest Bullish FVG not above price: {fvg['bottom']:.2f}") 153 | 154 | # Nearest bearish should be BELOW current price 155 | if context['nearest_bearish_fvg']: 156 | fvg = context['nearest_bearish_fvg'] 157 | if fvg['top'] < current_price: 158 | print(f" [OK] Nearest Bearish FVG correctly BELOW price: {fvg['top']:.2f}") 159 | else: 160 | print(f" [X] ERROR: Nearest Bearish FVG not below price: {fvg['top']:.2f}") 161 | 162 | print("\nNote: all_fvgs list contains ALL quality FVGs (for reference)") 163 | print(" Trading decisions use only nearest_bullish/nearest_bearish (correctly filtered)") 164 | 165 | print("\n" + "="*60) 166 | print("[OK] ALL TESTS PASSED - FVG logic correctly treats gaps as targets") 167 | print("="*60) 168 | 169 | # Print summary 170 | print("\n" + analyzer.get_fvg_summary(context)) 171 | 172 | 173 | if __name__ == "__main__": 174 | test_fvg_direction_logic() 175 | -------------------------------------------------------------------------------- /tests/test_fvg_detection.py: -------------------------------------------------------------------------------- 1 | """ 2 | FVG Detection Diagnostic Script 3 | Checks if FVGs are being detected from historical data 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | sys.path.insert(0, str(Path(__file__).parent.parent)) 9 | 10 | import pandas as pd 11 | from FairValueGaps import FVGDisplay 12 | 13 | 14 | def diagnose_fvg_detection(): 15 | """Check FVG detection from historical data""" 16 | 17 | print("="*60) 18 | print("FVG DETECTION DIAGNOSTIC") 19 | print("="*60) 20 | 21 | # Create FVG display instance 22 | fvg_display = FVGDisplay() 23 | 24 | # Check if historical data exists 25 | try: 26 | df = pd.read_csv('data/HistoricalData.csv') 27 | print(f"\n[OK] Historical data loaded: {len(df)} bars") 28 | print(f"Date range: {df.iloc[0]['DateTime']} to {df.iloc[-1]['DateTime']}") 29 | except Exception as e: 30 | print(f"\n[ERROR] Cannot load historical data: {e}") 31 | return 32 | 33 | # Load historical FVGs 34 | print("\n" + "="*60) 35 | print("LOADING HISTORICAL FVGs") 36 | print("="*60) 37 | 38 | fvg_display.load_historical_fvgs() 39 | 40 | # Show FVG summary 41 | total_fvgs = len(fvg_display.active_fvgs) 42 | bullish_fvgs = [f for f in fvg_display.active_fvgs if f['type'] == 'bullish' and not f.get('filled', False)] 43 | bearish_fvgs = [f for f in fvg_display.active_fvgs if f['type'] == 'bearish' and not f.get('filled', False)] 44 | filled_fvgs = [f for f in fvg_display.active_fvgs if f.get('filled', False)] 45 | 46 | print(f"\nTotal FVGs detected: {total_fvgs}") 47 | print(f" - Unfilled Bullish: {len(bullish_fvgs)}") 48 | print(f" - Unfilled Bearish: {len(bearish_fvgs)}") 49 | print(f" - Filled: {len(filled_fvgs)}") 50 | 51 | # Get current price 52 | try: 53 | live_df = pd.read_csv('data/LiveFeed.csv') 54 | current_price = float(live_df.iloc[-1]['Last']) 55 | print(f"\nCurrent Price: {current_price:.2f}") 56 | except: 57 | current_price = df.iloc[-1]['Close'] 58 | print(f"\nCurrent Price (from historical): {current_price:.2f}") 59 | 60 | # Show bullish FVGs (SHORT opportunities - gap UP leaves gap BELOW) 61 | print("\n" + "="*60) 62 | print("BULLISH FVGs (SHORT OPPORTUNITIES - BELOW PRICE)") 63 | print("="*60) 64 | 65 | if bullish_fvgs: 66 | bullish_below = [f for f in bullish_fvgs if f['top'] < current_price] 67 | bullish_above = [f for f in bullish_fvgs if f['bottom'] > current_price] 68 | bullish_at = [f for f in bullish_fvgs if f['bottom'] <= current_price <= f['top']] 69 | 70 | print(f"\nBullish FVGs BELOW current price (valid for SHORT): {len(bullish_below)}") 71 | # Sort by distance and show closest 10 72 | bullish_below_sorted = sorted(bullish_below, key=lambda f: abs(f['top'] - current_price)) 73 | for i, fvg in enumerate(bullish_below_sorted[:10], 1): 74 | distance = fvg['top'] - current_price 75 | print(f" {i}. Zone: {fvg['bottom']:.2f}-{fvg['top']:.2f} | " 76 | f"Target: {fvg['top']:.2f} | Distance: {distance:+.2f}pts | " 77 | f"Size: {fvg['gap_size']:.2f}pts") 78 | 79 | if bullish_above: 80 | print(f"\nBullish FVGs ABOVE current price (wrong direction - impossible): {len(bullish_above)}") 81 | for i, fvg in enumerate(bullish_above[:3], 1): 82 | print(f" {i}. Zone: {fvg['bottom']:.2f}-{fvg['top']:.2f} (gap UP can't be above)") 83 | 84 | if bullish_at: 85 | print(f"\nBullish FVGs AT current price: {len(bullish_at)}") 86 | for i, fvg in enumerate(bullish_at[:3], 1): 87 | print(f" {i}. Zone: {fvg['bottom']:.2f}-{fvg['top']:.2f} (price inside)") 88 | else: 89 | print("\n[!] NO BULLISH FVGs FOUND") 90 | 91 | # Show bearish FVGs (LONG opportunities - gap DOWN leaves gap ABOVE) 92 | print("\n" + "="*60) 93 | print("BEARISH FVGs (LONG OPPORTUNITIES - ABOVE PRICE)") 94 | print("="*60) 95 | 96 | if bearish_fvgs: 97 | bearish_above = [f for f in bearish_fvgs if f['bottom'] > current_price] 98 | bearish_below = [f for f in bearish_fvgs if f['top'] < current_price] 99 | bearish_at = [f for f in bearish_fvgs if f['bottom'] <= current_price <= f['top']] 100 | 101 | print(f"\nBearish FVGs ABOVE current price (valid for LONG): {len(bearish_above)}") 102 | # Sort by distance and show closest 10 103 | bearish_above_sorted = sorted(bearish_above, key=lambda f: abs(f['bottom'] - current_price)) 104 | for i, fvg in enumerate(bearish_above_sorted[:10], 1): 105 | distance = fvg['bottom'] - current_price 106 | print(f" {i}. Zone: {fvg['bottom']:.2f}-{fvg['top']:.2f} | " 107 | f"Target: {fvg['bottom']:.2f} | Distance: {distance:+.2f}pts | " 108 | f"Size: {fvg['gap_size']:.2f}pts") 109 | 110 | if bearish_below: 111 | print(f"\nBearish FVGs BELOW current price (wrong direction - impossible): {len(bearish_below)}") 112 | for i, fvg in enumerate(bearish_below[:3], 1): 113 | print(f" {i}. Zone: {fvg['bottom']:.2f}-{fvg['top']:.2f} (gap DOWN can't be below)") 114 | 115 | if bearish_at: 116 | print(f"\nBearish FVGs AT current price: {len(bearish_at)}") 117 | for i, fvg in enumerate(bearish_at[:3], 1): 118 | print(f" {i}. Zone: {fvg['bottom']:.2f}-{fvg['top']:.2f} (price inside)") 119 | else: 120 | print("\n[!] NO BEARISH FVGs FOUND") 121 | 122 | # Summary 123 | print("\n" + "="*60) 124 | print("DIAGNOSTIC SUMMARY") 125 | print("="*60) 126 | 127 | bullish_below_count = len([f for f in bullish_fvgs if f['top'] < current_price]) 128 | bearish_above_count = len([f for f in bearish_fvgs if f['bottom'] > current_price]) 129 | 130 | if bearish_above_count > 0: 131 | print(f"[OK] {bearish_above_count} valid LONG opportunities (bearish FVGs above price)") 132 | else: 133 | print("[!] NO LONG opportunities - no bearish FVGs above current price") 134 | 135 | if bullish_below_count > 0: 136 | print(f"[OK] {bullish_below_count} valid SHORT opportunities (bullish FVGs below price)") 137 | else: 138 | print("[!] NO SHORT opportunities - no bullish FVGs below current price") 139 | 140 | if bearish_above_count == 0 and bullish_below_count == 0: 141 | print("\n[WARNING] No valid FVG setups detected!") 142 | print("Possible reasons:") 143 | print(" 1. All FVGs have been filled by price action") 144 | print(" 2. No new FVGs created on recent bars") 145 | print(" 3. Price is between all FVGs (gaps above AND below)") 146 | print(" 4. Minimum gap size (5.0 pts) filters out small gaps") 147 | 148 | print("\n" + "="*60) 149 | 150 | 151 | if __name__ == "__main__": 152 | diagnose_fvg_detection() 153 | -------------------------------------------------------------------------------- /src/level_detector.py: -------------------------------------------------------------------------------- 1 | """ 2 | Psychological Level Detector Module 3 | Identifies round number levels for EMS zones 4 | """ 5 | 6 | import logging 7 | from typing import Dict, List, Optional, Tuple, Any 8 | from datetime import datetime 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class LevelDetector: 14 | """Detects psychological price levels (EMS zones)""" 15 | 16 | def __init__(self, level_intervals: List[int] = None): 17 | """ 18 | Initialize Level Detector 19 | 20 | Args: 21 | level_intervals: List of point intervals for levels (default: [100]) 22 | """ 23 | self.level_intervals = level_intervals or [100] 24 | logger.info(f"LevelDetector initialized (intervals={self.level_intervals})") 25 | 26 | def round_to_level(self, price: float, interval: int) -> int: 27 | """ 28 | Round price to nearest level 29 | 30 | Args: 31 | price: Price to round 32 | interval: Level interval (e.g., 100) 33 | 34 | Returns: 35 | Rounded level as integer 36 | """ 37 | return int(round(price / interval) * interval) 38 | 39 | def find_nearest_levels(self, current_price: float, interval: int = 100) -> Dict[str, Any]: 40 | """ 41 | Find nearest psychological levels above and below current price 42 | 43 | Args: 44 | current_price: Current market price 45 | interval: Level interval (default: 100) 46 | 47 | Returns: 48 | Dict with levels above and below 49 | """ 50 | # Calculate nearest level 51 | nearest_level = self.round_to_level(current_price, interval) 52 | 53 | # If price is exactly on level, find levels above/below 54 | if current_price == nearest_level: 55 | level_above = nearest_level + interval 56 | level_below = nearest_level - interval 57 | # If price is below nearest level 58 | elif current_price < nearest_level: 59 | level_above = nearest_level 60 | level_below = nearest_level - interval 61 | # If price is above nearest level 62 | else: 63 | level_above = nearest_level + interval 64 | level_below = nearest_level 65 | 66 | return { 67 | 'level_above': level_above, 68 | 'distance_above': level_above - current_price, 69 | 'level_below': level_below, 70 | 'distance_below': current_price - level_below, 71 | 'on_level': abs(current_price - nearest_level) < 1.0, # Within 1 point of level 72 | 'nearest_level': nearest_level 73 | } 74 | 75 | def find_nearby_levels(self, current_price: float, interval: int = 100, count: int = 3) -> List[int]: 76 | """ 77 | Find multiple levels above and below current price 78 | 79 | Args: 80 | current_price: Current market price 81 | interval: Level interval 82 | count: Number of levels to find in each direction 83 | 84 | Returns: 85 | List of levels sorted by proximity 86 | """ 87 | nearest = self.round_to_level(current_price, interval) 88 | levels = [] 89 | 90 | # Add levels above 91 | for i in range(count): 92 | if nearest + (i * interval) >= current_price: 93 | levels.append(nearest + (i * interval)) 94 | 95 | # Add levels below 96 | for i in range(1, count + 1): 97 | if nearest - (i * interval) <= current_price: 98 | levels.append(nearest - (i * interval)) 99 | 100 | # Sort by distance from current price 101 | levels.sort(key=lambda x: abs(x - current_price)) 102 | return levels 103 | 104 | def analyze_level_context( 105 | self, 106 | current_price: float, 107 | fvg_context: Dict[str, Any], 108 | interval: int = 100 109 | ) -> Dict[str, Any]: 110 | """ 111 | Complete level analysis for EMS zones 112 | 113 | Args: 114 | current_price: Current market price 115 | fvg_context: Market context from FVGAnalyzer (not used, kept for compatibility) 116 | interval: Level interval 117 | 118 | Returns: 119 | Complete level context dictionary 120 | """ 121 | # Find nearest levels 122 | levels = self.find_nearest_levels(current_price, interval) 123 | 124 | # Get nearby levels for context 125 | nearby_levels = self.find_nearby_levels(current_price, interval, count=5) 126 | 127 | context = { 128 | 'current_price': current_price, 129 | 'timestamp': datetime.now().isoformat(), 130 | 'nearest_level_above': levels['level_above'], 131 | 'distance_to_level_above': levels['distance_above'], 132 | 'nearest_level_below': levels['level_below'], 133 | 'distance_to_level_below': levels['distance_below'], 134 | 'on_level': levels['on_level'], 135 | 'nearest_level': levels['nearest_level'], 136 | 'nearby_levels': nearby_levels 137 | } 138 | 139 | logger.info(f"Level context analyzed: Price={current_price:.2f}, " 140 | f"Nearby levels={len(nearby_levels)}") 141 | 142 | return context 143 | 144 | def get_level_summary(self, context: Dict[str, Any]) -> str: 145 | """ 146 | Generate human-readable summary of level analysis 147 | 148 | Args: 149 | context: Level context dictionary 150 | 151 | Returns: 152 | Summary string 153 | """ 154 | lines = [] 155 | lines.append(f"Current Price: {context['current_price']:.2f}") 156 | lines.append(f"Nearest Level Above: {context['nearest_level_above']} ({context['distance_to_level_above']:+.2f}pts)") 157 | lines.append(f"Nearest Level Below: {context['nearest_level_below']} ({context['distance_to_level_below']:+.2f}pts)") 158 | 159 | if context['on_level']: 160 | lines.append(f"\n*** PRICE ON PSYCHOLOGICAL LEVEL (EMS): {context['nearest_level']} ***") 161 | 162 | lines.append(f"\nNearby EMS Levels: {', '.join(map(str, context['nearby_levels'][:5]))}") 163 | 164 | return "\n".join(lines) 165 | 166 | 167 | # Example usage 168 | if __name__ == "__main__": 169 | logging.basicConfig(level=logging.INFO) 170 | 171 | # Sample FVG context 172 | sample_context = { 173 | 'current_price': 14685.50, 174 | 'nearest_bullish_fvg': { 175 | 'top': 14715, 'bottom': 14710, 'size': 5.0, 176 | 'distance': 29.50, 'age_bars': 12 177 | }, 178 | 'nearest_bearish_fvg': { 179 | 'top': 14655, 'bottom': 14650, 'size': 5.0, 180 | 'distance': 30.50, 'age_bars': 45 181 | } 182 | } 183 | 184 | detector = LevelDetector() 185 | context = detector.analyze_level_context(14685.50, sample_context) 186 | print(detector.get_level_summary(context)) 187 | -------------------------------------------------------------------------------- /docs/MARKET_ANALYSIS_UPDATE.md: -------------------------------------------------------------------------------- 1 | # Market Analysis Persistence System 2 | 3 | ## Overview 4 | 5 | Implemented a persistent market analysis system that allows the trading agent to maintain continuity across 1-hour bars, encouraging patience and strategic setup waiting. 6 | 7 | ## Problem Solved 8 | 9 | **Before:** 10 | - Agent performed fresh analysis on every new bar 11 | - No memory of previous assessments 12 | - No concept of "waiting for a setup to develop" 13 | - Tendency to analyze each bar in isolation 14 | 15 | **After:** 16 | - Agent maintains running assessment of long/short setups 17 | - Updates analysis incrementally based on what changed 18 | - Tracks setup development over multiple bars 19 | - Explicitly encouraged to wait for quality setups 20 | 21 | ## Components 22 | 23 | ### 1. Market Analysis Manager (`src/market_analysis_manager.py`) 24 | 25 | Manages persistent state across trading sessions. 26 | 27 | **Key Features:** 28 | - Saves/loads analysis to `data/market_analysis.json` 29 | - Tracks setup status: `none`, `waiting`, `ready` 30 | - Increments setup age and bars since last trade 31 | - Formats previous analysis for agent prompt 32 | 33 | **Analysis Structure:** 34 | ```json 35 | { 36 | "last_updated": "2025-11-27T14:00:00", 37 | "current_bar_index": 1245, 38 | "overall_bias": "bullish", 39 | "waiting_for": "Price to reach 14600 bearish FVG", 40 | "bars_since_last_trade": 15, 41 | 42 | "long_assessment": { 43 | "status": "waiting", 44 | "target_fvg": {"bottom": 14600, "top": 14605}, 45 | "entry_plan": 14602, 46 | "stop_plan": 14590, 47 | "target_plan": 14700, 48 | "risk_reward": 8.3, 49 | "confidence": 0.75, 50 | "reasoning": "Waiting for pullback to FVG...", 51 | "setup_age_bars": 5 52 | }, 53 | 54 | "short_assessment": { 55 | "status": "none", 56 | "reasoning": "No quality short setup..." 57 | } 58 | } 59 | ``` 60 | 61 | ### 2. Updated Agent Prompt (`src/trading_agent.py`) 62 | 63 | **New Philosophy Section:** 64 | ``` 65 | YOUR TRADING PHILOSOPHY: 66 | - PATIENCE IS KEY: It's perfectly acceptable to wait for quality setups 67 | - Don't force trades - wait for confluence and proper setup development 68 | - Maintain continuity in your analysis across bars 69 | - Update your assessment incrementally based on what changed 70 | ``` 71 | 72 | **Incremental Analysis Instructions:** 73 | - "You are NOT doing a fresh analysis. You are UPDATING your previous assessment." 74 | - "Ask yourself: What changed with this new bar?" 75 | - "If nothing meaningful changed, keep the same assessment" 76 | - "It's OKAY to stay in 'none' status - don't force trades" 77 | 78 | **New Response Format:** 79 | ```json 80 | { 81 | "long_assessment": { 82 | "status": "none" | "waiting" | "ready", 83 | "target_fvg": {...}, 84 | "entry_plan": 14602, 85 | "stop_plan": 14590, 86 | "target_plan": 14700, 87 | "reasoning": "..." 88 | }, 89 | "short_assessment": {...}, 90 | "overall_reasoning": "What changed from previous bar..." 91 | } 92 | ``` 93 | 94 | ### 3. Integration with Main Loop (`main.py`) 95 | 96 | **Flow:** 97 | 1. New bar arrives 98 | 2. Load previous analysis state 99 | 3. Send previous analysis + new market data to agent 100 | 4. Agent updates assessment incrementally 101 | 5. Save updated analysis to file 102 | 6. If trade executes, reset that setup's state 103 | 104 | **Key Updates:** 105 | - Added `MarketAnalysisManager` initialization 106 | - Previous analysis formatted and passed to agent 107 | - Analysis state saved after each bar 108 | - Trade execution resets setup state 109 | 110 | ## Benefits 111 | 112 | ### 1. Strategic Patience 113 | - Agent can wait 5-10+ bars for quality setup 114 | - No pressure to trade every bar 115 | - Tracks "bars_since_last_trade" to show patience is acceptable 116 | 117 | ### 2. Setup Development Tracking 118 | - Monitor setups as they develop over time 119 | - Track "setup_age_bars" to see how long agent has been watching 120 | - Understand if setup is improving or deteriorating 121 | 122 | ### 3. Context Continuity 123 | - Agent remembers what it was waiting for 124 | - Builds on previous reasoning instead of starting fresh 125 | - More coherent decision-making across bars 126 | 127 | ### 4. Reduced Analysis Churn 128 | - Less redundant analysis 129 | - Focus on "what changed" rather than full re-analysis 130 | - More efficient API usage 131 | 132 | ## Testing Results 133 | 134 | Test suite (`tests/test_market_analysis.py`) validates: 135 | 136 | ✅ **Basic Operations** 137 | - Save/load analysis state 138 | - Format for prompt inclusion 139 | 140 | ✅ **Incremental Updates** 141 | - Setup age increments correctly 142 | - Bars since trade tracks properly 143 | - Status transitions: none → waiting → ready 144 | 145 | ✅ **Patience Test** 146 | - Agent can stay in "none" status for 5+ bars 147 | - Bars_since_last_trade increments correctly 148 | - No forced trades 149 | 150 | ## Usage 151 | 152 | ### Running Live Trading with Persistence 153 | ```bash 154 | python main.py --mode live 155 | ``` 156 | 157 | The system will: 158 | 1. Load previous analysis (if exists) 159 | 2. On each new bar, show agent its previous assessment 160 | 3. Agent updates based on what changed 161 | 4. Save updated analysis for next bar 162 | 163 | ### Monitoring Analysis State 164 | ```python 165 | from src.market_analysis_manager import MarketAnalysisManager 166 | 167 | manager = MarketAnalysisManager() 168 | print(manager.get_summary()) 169 | ``` 170 | 171 | ### Checking Analysis File 172 | ```bash 173 | cat data/market_analysis.json 174 | ``` 175 | 176 | ## Future Enhancements 177 | 178 | ### 1. Historical Pattern Matching 179 | - Search for similar setups in historical data 180 | - Learn which setup patterns have highest success 181 | - Reference past similar situations 182 | 183 | ### 2. Setup Quality Scoring 184 | - Track which types of setups perform best 185 | - Adjust confidence based on historical performance 186 | - Filter for only highest-quality setups 187 | 188 | ### 3. Multi-Timeframe Analysis 189 | - Track 1H, 4H, daily assessments separately 190 | - Ensure alignment across timeframes 191 | - Higher confidence when all timeframes agree 192 | 193 | ### 4. Dynamic Risk Adjustment 194 | - Increase position size on high-confidence setups 195 | - Reduce size when confidence lower 196 | - Track setup quality vs actual outcomes 197 | 198 | ## Configuration 199 | 200 | No changes needed to `config/agent_config.json` - works with existing settings. 201 | 202 | The system automatically creates `data/market_analysis.json` on first run. 203 | 204 | ## Files Modified 205 | 206 | 1. **Created:** `src/market_analysis_manager.py` (330 lines) 207 | 2. **Modified:** `src/trading_agent.py` 208 | - Added `previous_analysis` parameter 209 | - Updated prompt to emphasize patience 210 | - New response format with assessments 211 | 3. **Modified:** `main.py` 212 | - Added MarketAnalysisManager integration 213 | - Load/save analysis on each bar 214 | - Reset state on trade execution 215 | 4. **Created:** `tests/test_market_analysis.py` (212 lines) 216 | 5. **Created:** `docs/MARKET_ANALYSIS_UPDATE.md` (this file) 217 | 218 | ## Summary 219 | 220 | The trading agent now has **persistent memory** of its market analysis, enabling it to: 221 | - Wait patiently for quality setups 222 | - Track setup development over multiple bars 223 | - Update assessments incrementally 224 | - Maintain strategic continuity 225 | 226 | This transforms the agent from a bar-by-bar reactor into a strategic planner that can wait for the right opportunities. 227 | 228 | --- 229 | 230 | **Implementation Date:** 2025-11-27 231 | **Status:** ✅ Complete and Tested 232 | -------------------------------------------------------------------------------- /tests/test_market_analysis.py: -------------------------------------------------------------------------------- 1 | """ 2 | Test script for Market Analysis Manager 3 | Verifies the persistence and incremental update functionality 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | sys.path.insert(0, str(Path(__file__).parent.parent)) 9 | 10 | from src.market_analysis_manager import MarketAnalysisManager 11 | 12 | 13 | def test_basic_operations(): 14 | """Test basic save/load operations""" 15 | print("=" * 60) 16 | print("TEST 1: Basic Operations") 17 | print("=" * 60) 18 | 19 | # Create manager 20 | manager = MarketAnalysisManager("data/test_analysis.json") 21 | 22 | # Show initial state 23 | print("\nInitial State:") 24 | print(manager.get_summary()) 25 | 26 | # Create a sample analysis 27 | analysis = { 28 | 'current_bar_index': 1, 29 | 'overall_bias': 'bullish', 30 | 'waiting_for': 'Price to reach 14600 bearish FVG for long entry', 31 | 'long_assessment': { 32 | 'status': 'waiting', 33 | 'target_fvg': {'bottom': 14600, 'top': 14605, 'size': 5.0}, 34 | 'entry_plan': 14602, 35 | 'stop_plan': 14590, 36 | 'target_plan': 14700, 37 | 'risk_reward': 8.3, 38 | 'confidence': 0.75, 39 | 'reasoning': 'Strong bullish trend. Waiting for pullback to bearish FVG at 14600 for long entry.', 40 | 'setup_age_bars': 0 41 | }, 42 | 'short_assessment': { 43 | 'status': 'none', 44 | 'target_fvg': None, 45 | 'entry_plan': None, 46 | 'stop_plan': None, 47 | 'target_plan': None, 48 | 'risk_reward': None, 49 | 'confidence': 0.0, 50 | 'reasoning': 'No quality short setup. Trend is bullish.', 51 | 'setup_age_bars': 0 52 | }, 53 | 'bars_since_last_trade': 5, 54 | 'bars_since_last_update': 0 55 | } 56 | 57 | # Update analysis 58 | success = manager.update_analysis(analysis) 59 | print(f"\nAnalysis Update: {'SUCCESS' if success else 'FAILED'}") 60 | 61 | # Show updated state 62 | print("\nUpdated State:") 63 | print(manager.get_summary()) 64 | 65 | # Get formatted for prompt 66 | print("\nFormatted for Prompt:") 67 | print(manager.format_previous_analysis_for_prompt()) 68 | 69 | return manager 70 | 71 | 72 | def test_incremental_updates(): 73 | """Test incremental updates (simulating new bars)""" 74 | print("\n" + "=" * 60) 75 | print("TEST 2: Incremental Updates (Simulating New Bars)") 76 | print("=" * 60) 77 | 78 | manager = MarketAnalysisManager("data/test_analysis.json") 79 | 80 | # Simulate bar 2 - price moving closer to target 81 | print("\n--- BAR 2: Price moving closer to FVG ---") 82 | analysis_bar2 = { 83 | 'current_bar_index': 2, 84 | 'overall_bias': 'bullish', 85 | 'waiting_for': 'Price to reach 14600 bearish FVG for long entry (getting closer)', 86 | 'long_assessment': { 87 | 'status': 'waiting', 88 | 'target_fvg': {'bottom': 14600, 'top': 14605, 'size': 5.0}, 89 | 'entry_plan': 14602, 90 | 'stop_plan': 14590, 91 | 'target_plan': 14700, 92 | 'risk_reward': 8.3, 93 | 'confidence': 0.78, # Increased confidence 94 | 'reasoning': 'Price moving closer to target FVG. Still waiting for entry.', 95 | 'setup_age_bars': 0 # Will be incremented by manager 96 | }, 97 | 'short_assessment': { 98 | 'status': 'none', 99 | 'target_fvg': None, 100 | 'entry_plan': None, 101 | 'stop_plan': None, 102 | 'target_plan': None, 103 | 'risk_reward': None, 104 | 'confidence': 0.0, 105 | 'reasoning': 'No quality short setup. Trend is bullish.', 106 | 'setup_age_bars': 0 107 | }, 108 | 'bars_since_last_trade': 0, # Will be incremented by manager 109 | 'bars_since_last_update': 0 110 | } 111 | 112 | manager.update_analysis(analysis_bar2) 113 | print(manager.get_summary()) 114 | 115 | # Simulate bar 3 - setup ready 116 | print("\n--- BAR 3: Setup ready! ---") 117 | analysis_bar3 = { 118 | 'current_bar_index': 3, 119 | 'overall_bias': 'bullish', 120 | 'waiting_for': 'Long setup READY at 14600', 121 | 'long_assessment': { 122 | 'status': 'ready', # Changed to ready! 123 | 'target_fvg': {'bottom': 14600, 'top': 14605, 'size': 5.0}, 124 | 'entry_plan': 14602, 125 | 'stop_plan': 14590, 126 | 'target_plan': 14700, 127 | 'risk_reward': 8.3, 128 | 'confidence': 0.82, 129 | 'reasoning': 'Price reached target FVG zone. Setup is ready for entry.', 130 | 'setup_age_bars': 0 131 | }, 132 | 'short_assessment': { 133 | 'status': 'none', 134 | 'target_fvg': None, 135 | 'entry_plan': None, 136 | 'stop_plan': None, 137 | 'target_plan': None, 138 | 'risk_reward': None, 139 | 'confidence': 0.0, 140 | 'reasoning': 'No quality short setup.', 141 | 'setup_age_bars': 0 142 | }, 143 | 'bars_since_last_trade': 0, 144 | 'bars_since_last_update': 0 145 | } 146 | 147 | manager.update_analysis(analysis_bar3) 148 | print(manager.get_summary()) 149 | 150 | # Simulate trade execution 151 | print("\n--- TRADE EXECUTED: LONG ---") 152 | manager.mark_trade_executed('LONG') 153 | print(manager.get_summary()) 154 | 155 | 156 | def test_waiting_patience(): 157 | """Test that agent can wait patiently for setups""" 158 | print("\n" + "=" * 60) 159 | print("TEST 3: Patience Test (No Setup for Multiple Bars)") 160 | print("=" * 60) 161 | 162 | manager = MarketAnalysisManager("data/test_analysis.json") 163 | 164 | # Simulate 5 bars with no quality setup 165 | for bar in range(1, 6): 166 | print(f"\n--- BAR {bar}: No quality setup ---") 167 | analysis = { 168 | 'current_bar_index': bar, 169 | 'overall_bias': 'neutral', 170 | 'waiting_for': 'Quality FVG setup to develop', 171 | 'long_assessment': { 172 | 'status': 'none', 173 | 'target_fvg': None, 174 | 'entry_plan': None, 175 | 'stop_plan': None, 176 | 'target_plan': None, 177 | 'risk_reward': None, 178 | 'confidence': 0.0, 179 | 'reasoning': f'No quality long setup on bar {bar}. Nearest FVG too far away.', 180 | 'setup_age_bars': 0 181 | }, 182 | 'short_assessment': { 183 | 'status': 'none', 184 | 'target_fvg': None, 185 | 'entry_plan': None, 186 | 'stop_plan': None, 187 | 'target_plan': None, 188 | 'risk_reward': None, 189 | 'confidence': 0.0, 190 | 'reasoning': f'No quality short setup on bar {bar}. Market choppy.', 191 | 'setup_age_bars': 0 192 | }, 193 | 'bars_since_last_trade': 0, 194 | 'bars_since_last_update': 0 195 | } 196 | manager.update_analysis(analysis) 197 | 198 | # Show final state 199 | print("\nFinal State After 5 Bars of Waiting:") 200 | print(manager.get_summary()) 201 | print(f"\nBars since last trade: {manager.current_analysis['bars_since_last_trade']}") 202 | print("✓ Agent successfully waited patiently without forcing trades") 203 | 204 | 205 | if __name__ == "__main__": 206 | print("\nMARKET ANALYSIS MANAGER TEST SUITE") 207 | print("=" * 60) 208 | 209 | # Run tests 210 | test_basic_operations() 211 | test_incremental_updates() 212 | test_waiting_patience() 213 | 214 | print("\n" + "=" * 60) 215 | print("ALL TESTS COMPLETED") 216 | print("=" * 60) 217 | print("\nCheck data/test_analysis.json to see the persisted state") 218 | -------------------------------------------------------------------------------- /docs/AGENT_README.md: -------------------------------------------------------------------------------- 1 | # Claude NQ Trading Agent 2 | 3 | ## What It Does 4 | 5 | **Autonomous AI trading system that reasons about Fair Value Gaps and psychological levels to make NQ futures trading decisions.** 6 | 7 | No neural networks. No indicators. Pure price action reasoning powered by Claude. 8 | 9 | --- 10 | 11 | ## Core Concept 12 | 13 | ``` 14 | Price → FVG Detection → Claude Reasoning → Trade Decision → NinjaTrader 15 | (existing) (AI analysis) (signals.csv) (execution) 16 | ``` 17 | 18 | **The System:** 19 | 1. Monitors FVG zones from `FairValueGaps.py` 20 | 2. Claude analyzes price context + gap confluence 21 | 3. Reviews past trade outcomes from memory 22 | 4. Makes trade decisions based on reasoning 23 | 5. Outputs signals to `trade_signals.csv` 24 | 6. Learns from outcomes through feedback loop 25 | 26 | --- 27 | 28 | ## Architecture 29 | 30 | ### Data Layer 31 | - **FairValueGaps.py** - Real-time FVG detection (existing) 32 | - **HistoricalData.csv** - 1000 days OHLC + EMAs 33 | - **LiveFeed.csv** - Real-time price updates 34 | 35 | ### Intelligence Layer 36 | - **trading_agent.py** - Claude reasoning engine 37 | - **fvg_analyzer.py** - Parses FVG data + calculates distances 38 | - **level_detector.py** - Identifies psychological levels (100pt intervals) 39 | - **memory_manager.py** - MCP integration for trade history 40 | 41 | ### Execution Layer 42 | - **signal_generator.py** - Outputs to trade_signals.csv 43 | - **NinjaTrader** - Executes actual orders 44 | 45 | ### Learning Layer 46 | - **MCP Memory** - Stores trade outcomes 47 | - **Feedback Loop** - Claude reviews past performance 48 | - **Adaptive Reasoning** - Improves decisions over time 49 | 50 | --- 51 | 52 | ## How Learning Works 53 | 54 | **Not Machine Learning. Context Learning.** 55 | 56 | ``` 57 | Traditional ML: Claude Approach: 58 | ───────────────── ───────────────── 59 | Train on dataset → Analyze current setup 60 | Gradient descent → Query similar past trades 61 | Weight updates → Reason about outcomes 62 | Model inference → Adapt decision logic 63 | 64 | ✓ Explainable ✓ Fast adaptation 65 | ✓ No retraining needed ✓ Works with small samples 66 | ✓ Transparent logic ✓ Immediate rule updates 67 | ``` 68 | 69 | --- 70 | 71 | ## Trading Logic 72 | 73 | ### Entry Criteria (Claude Evaluates) 74 | - **Gap Confluence**: FVG aligns with psychological level 75 | - **Distance**: Price proximity to entry zone 76 | - **Gap Quality**: Size (>5pts), age (<100 bars), unfilled 77 | - **Memory**: Historical win rate for similar setups 78 | - **Risk/Reward**: Minimum 3:1 ratio (configurable) 79 | 80 | ### Example Decision 81 | ``` 82 | SETUP: 83 | Price: 14,685 84 | Bullish FVG: 14,710-14,715 (resistance) 85 | Psych Level: 14,700 (confluence!) 86 | Memory: 72% win rate on level+FVG shorts 87 | 88 | CLAUDE DECIDES: 89 | Entry: 14,712 (SHORT) 90 | Stop: 14,730 (20pt stop - NQ appropriate) 91 | Target: 14,650 (bearish FVG fill) 92 | R/R: 20pts / 62pts = 3.1:1 ✓ 93 | 94 | OUTPUT: 95 | 2025-11-25 14:30:00,SHORT,14712,14730,14650 96 | ``` 97 | 98 | ### Stop Loss Rules 99 | - **Minimum**: 15 points (NQ volatility appropriate) 100 | - **Default**: 20 points 101 | - **Maximum**: 50 points 102 | - **Placement**: Beyond FVG zone + buffer (5-10pts) 103 | 104 | --- 105 | 106 | ## Project Structure 107 | 108 | ``` 109 | Claude Trader/ 110 | ├── src/ 111 | │ ├── trading_agent.py # Main Claude reasoning engine 112 | │ ├── fvg_analyzer.py # FVG data parser 113 | │ ├── level_detector.py # Psychological level finder 114 | │ ├── signal_generator.py # trade_signals.csv writer 115 | │ ├── memory_manager.py # MCP integration 116 | │ └── backtest_engine.py # Historical testing 117 | ├── config/ 118 | │ ├── agent_config.json # Trading parameters 119 | │ └── risk_rules.json # Risk management 120 | ├── data/ 121 | │ ├── trade_history.json # Past trade outcomes 122 | │ └── performance_log.json # System metrics 123 | ├── tests/ 124 | │ └── test_*.py # Test suite 125 | ├── docs/ 126 | │ ├── AGENT_README.md # This file 127 | │ └── ARCHITECTURE.md # Detailed design 128 | ├── FairValueGaps.py # (existing - unchanged) 129 | └── main.py # System orchestrator 130 | ``` 131 | 132 | --- 133 | 134 | ## Quick Start 135 | 136 | ### 1. Install Dependencies 137 | ```bash 138 | pip install -r requirements.txt 139 | ``` 140 | 141 | ### 2. Configure API 142 | ```bash 143 | # .env file 144 | ANTHROPIC_API_KEY=your_key_here 145 | ``` 146 | 147 | ### 3. Run Backtest (Recommended First) 148 | ```bash 149 | python main.py --mode backtest --days 30 150 | ``` 151 | 152 | ### 4. Run Live Trading 153 | ```bash 154 | python main.py --mode live 155 | ``` 156 | 157 | ### 5. Monitor Performance 158 | ```bash 159 | python main.py --mode monitor 160 | ``` 161 | 162 | --- 163 | 164 | ## Configuration 165 | 166 | ### agent_config.json 167 | ```json 168 | { 169 | "min_gap_size": 5.0, 170 | "max_gap_age_bars": 100, 171 | "min_risk_reward": 3.0, 172 | "stop_loss_min": 15, 173 | "stop_loss_default": 20, 174 | "stop_loss_max": 50, 175 | "position_size": 1, 176 | "psychological_levels": [100], 177 | "confidence_threshold": 0.65 178 | } 179 | ``` 180 | 181 | --- 182 | 183 | ## Performance Tracking 184 | 185 | System automatically tracks: 186 | - **Win Rate**: Percentage of profitable trades 187 | - **Average R/R**: Risk/reward ratio achieved 188 | - **Sharpe Ratio**: Risk-adjusted returns 189 | - **Max Drawdown**: Largest equity drop 190 | - **Setup Quality**: FVG vs Level vs Confluence trades 191 | 192 | ### Memory-Based Improvement 193 | - Claude reviews outcomes before each decision 194 | - Identifies which setup types work best 195 | - Adjusts confidence levels based on history 196 | - No retraining required - instant adaptation 197 | 198 | --- 199 | 200 | ## Key Features 201 | 202 | ### ✓ Reasoning Over Training 203 | Claude analyzes each setup contextually - not pattern matching 204 | 205 | ### ✓ Explainable Decisions 206 | Every trade has a clear reasoning chain you can audit 207 | 208 | ### ✓ Adaptive Without Retraining 209 | Learns from outcomes through memory - no model updates needed 210 | 211 | ### ✓ Risk-Aware 212 | Stop losses sized appropriately for NQ volatility (15-50pts) 213 | 214 | ### ✓ Backtestable 215 | Test strategies on 1000 days of historical data 216 | 217 | ### ✓ Production Ready 218 | Outputs directly to NinjaTrader via CSV 219 | 220 | --- 221 | 222 | ## MCP Integration 223 | 224 | Uses Claude Flow MCP tools for: 225 | - **Memory Management**: `memory_usage` - Store/retrieve trade history 226 | - **Agent Adaptation**: `daa_agent_adapt` - Learn from feedback 227 | - **Performance Tracking**: `agent_metrics` - Monitor system health 228 | 229 | --- 230 | 231 | ## Backtesting Results (Coming Soon) 232 | 233 | Will compare: 234 | - **Claude Agent P&L** vs **Buy & Hold** 235 | - **Trade-by-trade analysis** with reasoning logs 236 | - **Setup quality breakdown** (confluence vs single-factor trades) 237 | - **Parameter sensitivity testing** 238 | 239 | --- 240 | 241 | ## Safety Features 242 | 243 | - **No live trading until backtest validated** 244 | - **Stop losses mandatory on every trade** 245 | - **Position sizing limits enforced** 246 | - **Maximum daily loss threshold** 247 | - **Manual override capability** 248 | 249 | --- 250 | 251 | ## Roadmap 252 | 253 | **Phase 1: Foundation** (Week 1) ✓ 254 | - FVG detection working 255 | - Claude agent architecture designed 256 | - MCP integration planned 257 | 258 | **Phase 2: Core Implementation** (Week 2) 259 | - Trading agent built 260 | - Backtesting framework complete 261 | - Memory system operational 262 | 263 | **Phase 3: Validation** (Week 3) 264 | - 1000-day backtest analysis 265 | - Parameter optimization 266 | - Risk validation 267 | 268 | **Phase 4: Production** (Week 4) 269 | - Live paper trading 270 | - Performance monitoring 271 | - Iterative improvement 272 | 273 | --- 274 | 275 | ## Why This Works 276 | 277 | **Markets seek efficiency. Gaps are inefficiency. Price fills gaps.** 278 | 279 | The edge: 280 | 1. FVGs are measurable inefficiencies 281 | 2. Psychological levels are proven S/R zones 282 | 3. Confluence = double confirmation 283 | 4. Claude reasons about context (not just patterns) 284 | 5. Memory creates adaptive intelligence 285 | 286 | This isn't speculation. It's systematic gap-fill trading with AI reasoning. 287 | 288 | --- 289 | 290 | ## Support 291 | 292 | - **Architecture Details**: See `docs/ARCHITECTURE.md` 293 | - **Trading Philosophy**: See `docs/PRICE_ACTION_PHILOSOPHY.md` 294 | - **FVG Detection**: See `FairValueGaps.py` 295 | - **Issues**: Review logs in `data/performance_log.json` 296 | 297 | --- 298 | 299 | **Last Updated**: 2025-11-25 300 | **Version**: 1.0.0 - Production Architecture 301 | **Status**: Implementation Phase 302 | -------------------------------------------------------------------------------- /docs/PRICE_ACTION_PHILOSOPHY.md: -------------------------------------------------------------------------------- 1 | # Price Action Trading Philosophy - Gap Fill Theory 2 | 3 | ## Core Concept: Markets are Gap-Filling Machines 4 | 5 | The fundamental principle underlying this trading system is that **price is always heading toward filling a gap**. Markets are efficient, and inefficiencies (gaps) create imbalances that price must eventually resolve. 6 | 7 | ## The Two Types of Magnets 8 | 9 | ### 1. Fair Value Gaps (FVGs) 10 | **What is a Fair Value Gap?** 11 | - An inefficient price zone where no trading occurred 12 | - Created when three consecutive candles show a gap 13 | - Represents unfilled orders and price imbalance 14 | - Acts as a MAGNET that draws price back 15 | 16 | **Why Price Fills Gaps:** 17 | - Market seeks equilibrium 18 | - Unfilled orders remain in the gap 19 | - Algorithms target these inefficiencies 20 | - Historical support/resistance is created 21 | 22 | **Trading FVG Zones:** 23 | ``` 24 | Bearish FVG (Gap Down): 25 | ┌─────────────────┐ Candle 1 Low 26 | │ │ 27 | │ [GAP ZONE] │ ← Price will return here 28 | │ │ 29 | └─────────────────┘ Candle 3 High 30 | ↓ 31 | SUPPORT (Long opportunity) 32 | 33 | Bullish FVG (Gap Up): 34 | ┌─────────────────┐ Candle 3 Low 35 | │ │ 36 | │ [GAP ZONE] │ ← Price will return here 37 | │ │ 38 | └─────────────────┘ Candle 1 High 39 | ↓ 40 | RESISTANCE (Short opportunity) 41 | ``` 42 | 43 | ### 2. Key Psychological Levels 44 | **Round Numbers as Magnets:** 45 | - 14500, 14600, 14700, 14800, etc. 46 | - These are "whole levels" that traders watch 47 | - Act as natural support/resistance 48 | - Price often pauses or reverses here 49 | 50 | **Why Key Levels Matter:** 51 | - Human psychology (round numbers are easy to remember) 52 | - Large orders cluster at these levels 53 | - Algorithms target these zones 54 | - Historical significance builds over time 55 | 56 | ## The Trading Thesis 57 | 58 | ### Gap-Fill Mentality 59 | Always ask yourself: 60 | 1. **Where is the nearest unfilled gap?** 61 | 2. **Is price heading toward it?** 62 | 3. **Are there key levels in the way?** 63 | 64 | ### The Perfect Setup: Confluence 65 | The BEST trades occur when: 66 | - **FVG Zone** aligns with **Key Level** 67 | - Example: Bearish FVG at 14600 (support) 68 | - Example: Bullish FVG at 14700 (resistance) 69 | 70 | This creates **double confluence** - two magnets at the same price! 71 | 72 | ## Trading Framework 73 | 74 | ### Long Setup (Buying Support) 75 | ``` 76 | Scenario: Price declining toward bearish FVG 77 | 78 | Current Price: 14650 79 | Bearish FVG: 14600 - 14605 (gap zone) 80 | Key Level: 14600 (psychological support) 81 | 82 | Thesis: 83 | - Price is heading DOWN to fill the gap 84 | - Gap coincides with key level (14600) 85 | - Once gap is filled, price should bounce 86 | 87 | Entry: 14602 (inside gap zone) 88 | Stop: 14595 (below gap + buffer) 89 | Target: 14700 (next key level or bullish FVG above) 90 | 91 | Risk/Reward: 7pts risk / 98pts reward = 14:1 🎯 92 | ``` 93 | 94 | ### Short Setup (Selling Resistance) 95 | ``` 96 | Scenario: Price rallying toward bullish FVG 97 | 98 | Current Price: 14650 99 | Bullish FVG: 14695 - 14700 (gap zone) 100 | Key Level: 14700 (psychological resistance) 101 | 102 | Thesis: 103 | - Price is heading UP to fill the gap 104 | - Gap coincides with key level (14700) 105 | - Once gap is filled, price should reject 106 | 107 | Entry: 14698 (inside gap zone) 108 | Stop: 14705 (above gap + buffer) 109 | Target: 14600 (next key level or bearish FVG below) 110 | 111 | Risk/Reward: 7pts risk / 98pts reward = 14:1 🎯 112 | ``` 113 | 114 | ## Reading Price Flow 115 | 116 | ### Example Price Journey 117 | ``` 118 | Price: 14485 → What's next? 119 | 120 | Analysis: 121 | 1. Nearest Key Level: 14500 (15pts above) ← MAGNET 122 | 2. Bearish FVG: 14450-14455 (35pts below) ← MAGNET 123 | 3. Bullish FVG: 14520-14525 (35pts above) ← MAGNET 124 | 125 | Price is likely heading toward 14500 first (nearest magnet). 126 | 127 | At 14500: 128 | - If price rejects → likely heading down to 14450 FVG 129 | - If price breaks through → likely heading up to 14520 FVG 130 | 131 | This is how price flows: from level to level, gap to gap. 132 | ``` 133 | 134 | ## Multi-Timeframe Perspective 135 | 136 | ### Hourly Chart (Trading Timeframe) 137 | - Identify immediate FVG zones 138 | - Note nearest key levels 139 | - Determine current price flow direction 140 | 141 | ### 4-Hour Chart (Context) 142 | - Bigger FVG zones (stronger magnets) 143 | - Major key levels 144 | - Overall trend bias 145 | 146 | ### The Rule 147 | - Trade hourly gaps within context of 4-hour flow 148 | - 4-hour gaps are STRONGER magnets 149 | - When hourly and 4-hour gaps align = highest probability 150 | 151 | ## Common Scenarios 152 | 153 | ### Scenario 1: Price Between Gaps 154 | ``` 155 | Bullish FVG above: 14700 156 | Current Price: 14650 157 | Bearish FVG below: 14600 158 | 159 | NO CLEAR TRADE - price could go either way 160 | Wait for price to approach one of the gaps 161 | ``` 162 | 163 | ### Scenario 2: Gap at Key Level (Perfect Setup) 164 | ``` 165 | Current Price: 14645 166 | Bearish FVG: 14598-14602 167 | Key Level: 14600 168 | 169 | ✅ HIGH PROBABILITY LONG 170 | - Gap fill thesis 171 | - Key level support 172 | - Double confluence 173 | ``` 174 | 175 | ### Scenario 3: Recently Filled Gap 176 | ``` 177 | Current Price: 14605 178 | Bearish FVG: 14600-14605 (just filled) 179 | 180 | ❌ NO TRADE - gap already filled 181 | Price may bounce from here, but entry is late 182 | Look for next unfilled gap 183 | ``` 184 | 185 | ### Scenario 4: Multiple Gaps Same Direction 186 | ``` 187 | Current Price: 14650 188 | Bullish FVG 1: 14680-14685 189 | Bullish FVG 2: 14710-14715 190 | Key Level: 14700 191 | 192 | Trade Plan: 193 | 1. SHORT at FVG 1 (14683), target 14650 (short-term) 194 | 2. If breaks through, SHORT at FVG 2 (14712), target 14700 195 | 3. FVG 2 is STRONGER (near key level) 196 | ``` 197 | 198 | ## Risk Management with Gap Theory 199 | 200 | ### Position Sizing Based on Gap Quality 201 | 202 | **High Confidence (2 contracts):** 203 | - FVG + Key Level confluence 204 | - Fresh gap (< 50 bars old) 205 | - Clear path to target gap/level 206 | - Trend alignment 207 | 208 | **Medium Confidence (1 contract):** 209 | - FVG only (no key level) 210 | - Older gap (50-100 bars) 211 | - Some obstacles to target 212 | - Neutral trend 213 | 214 | **Low Confidence (0 contracts - NO TRADE):** 215 | - No clear gaps nearby 216 | - Price between multiple gaps 217 | - Gap already partially filled 218 | - Conflicting signals 219 | 220 | ## Advanced Concepts 221 | 222 | ### Gap Hierarchy 223 | Not all gaps are equal: 224 | 225 | 1. **4-Hour FVG** > 1-Hour FVG (bigger timeframe = stronger) 226 | 2. **Gap at Key Level** > Gap away from key level 227 | 3. **Fresh Gap** (< 25 bars) > Old Gap (> 75 bars) 228 | 4. **Large Gap** (> 5pts) > Small Gap (< 3pts) 229 | 230 | ### Partial Gap Fills 231 | Sometimes price only fills part of a gap: 232 | - Watch for rejection at 50% of gap 233 | - Full fill is more common but not guaranteed 234 | - If partial fill holds, gap remains active 235 | 236 | ### Gap Clusters 237 | Multiple gaps in same zone = SUPER MAGNET 238 | - Price will return with high probability 239 | - Use widest gap range for entries 240 | - Strongest support/resistance 241 | 242 | ## The Edge 243 | 244 | Why this works: 245 | 1. **Market Efficiency**: Gaps must be filled for balance 246 | 2. **Order Flow**: Unfilled orders remain in gaps 247 | 3. **Psychology**: Key levels attract human traders 248 | 4. **Algorithms**: Programs target these inefficiencies 249 | 5. **Self-Fulfilling**: As more traders use this, it becomes stronger 250 | 251 | ## Trading Checklist 252 | 253 | Before every trade, verify: 254 | - [ ] Clear unfilled gap identified 255 | - [ ] Price heading toward gap (not away) 256 | - [ ] Gap age < 100 bars (still active) 257 | - [ ] Key level proximity noted 258 | - [ ] Risk/reward > 2:1 259 | - [ ] Stop beyond gap zone 260 | - [ ] Target = next gap or key level 261 | - [ ] Position size appropriate for confidence 262 | - [ ] No conflicting gaps in the way 263 | 264 | ## Common Mistakes to Avoid 265 | 266 | 1. **Trading without gap thesis** - always know which gap price wants 267 | 2. **Ignoring key levels** - they matter as much as gaps 268 | 3. **Chasing filled gaps** - entry is already gone 269 | 4. **Oversized positions** - not all setups are equal quality 270 | 5. **No target plan** - know your exit before entering 271 | 272 | ## Summary 273 | 274 | **The Core Truth**: Price moves from gap to gap, level to level. 275 | 276 | **The Strategy**: Wait for price to approach an unfilled gap (especially near a key level), enter when it arrives, target the next gap/level. 277 | 278 | **The Edge**: Markets are predictable in their quest for efficiency. Gaps create inefficiency. Price seeks to restore balance. 279 | 280 | This isn't speculation - it's trading the mathematics of market structure. 281 | 282 | --- 283 | 284 | *Trade the gaps. Respect the levels. Let price come to you.* 285 | -------------------------------------------------------------------------------- /src/signal_generator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Signal Generator Module 3 | Outputs trade signals to CSV for NinjaTrader 4 | """ 5 | 6 | import csv 7 | import logging 8 | from typing import Dict, Any 9 | from datetime import datetime 10 | from pathlib import Path 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class SignalGenerator: 16 | """Generates trade signals in NinjaTrader CSV format""" 17 | 18 | def __init__(self, output_file: str = "data/trade_signals.csv"): 19 | """ 20 | Initialize Signal Generator 21 | 22 | Args: 23 | output_file: Path to trade signals CSV file 24 | """ 25 | self.output_file = Path(output_file) 26 | self.output_file.parent.mkdir(exist_ok=True) 27 | 28 | # Check if header needs fixing 29 | needs_init = False 30 | if not self.output_file.exists() or self.output_file.stat().st_size == 0: 31 | needs_init = True 32 | else: 33 | # Verify header is correct 34 | try: 35 | with open(self.output_file, 'r') as f: 36 | header = f.readline().strip() 37 | if header != 'DateTime,Direction,Entry_Price,Stop_Loss,Target': 38 | logger.warning(f"Invalid header detected: {header}") 39 | needs_init = True 40 | except Exception: 41 | needs_init = True 42 | 43 | if needs_init: 44 | self._initialize_csv() 45 | 46 | logger.info(f"SignalGenerator initialized (output={self.output_file})") 47 | 48 | def _initialize_csv(self): 49 | """Initialize CSV file with headers""" 50 | try: 51 | with open(self.output_file, 'w', newline='') as f: 52 | writer = csv.writer(f) 53 | writer.writerow(['DateTime', 'Direction', 'Entry_Price', 'Stop_Loss', 'Target']) 54 | logger.info("Trade signals CSV initialized") 55 | except Exception as e: 56 | logger.error(f"Error initializing CSV: {e}") 57 | raise 58 | 59 | def validate_decision(self, decision: Dict[str, Any]) -> tuple[bool, str]: 60 | """ 61 | Validate decision data before generating signal 62 | 63 | Args: 64 | decision: Decision dictionary from TradingAgent 65 | 66 | Returns: 67 | Tuple of (is_valid, error_message) 68 | """ 69 | # Check decision type 70 | if decision.get('decision') not in ['LONG', 'SHORT']: 71 | return False, f"Invalid decision type: {decision.get('decision')}" 72 | 73 | # Check required fields 74 | required_fields = ['entry', 'stop', 'target'] 75 | for field in required_fields: 76 | if field not in decision: 77 | return False, f"Missing required field: {field}" 78 | if not isinstance(decision[field], (int, float)): 79 | return False, f"Invalid {field} value: {decision[field]}" 80 | 81 | # Validate price relationships 82 | entry = decision['entry'] 83 | stop = decision['stop'] 84 | target = decision['target'] 85 | 86 | if decision['decision'] == 'LONG': 87 | if stop >= entry: 88 | return False, f"LONG stop ({stop}) must be below entry ({entry})" 89 | if target <= entry: 90 | return False, f"LONG target ({target}) must be above entry ({entry})" 91 | 92 | elif decision['decision'] == 'SHORT': 93 | if stop <= entry: 94 | return False, f"SHORT stop ({stop}) must be above entry ({entry})" 95 | if target >= entry: 96 | return False, f"SHORT target ({target}) must be below entry ({entry})" 97 | 98 | # Validate 5pt buffer was applied correctly (if raw_target exists) 99 | if 'raw_target' in decision and decision['raw_target'] is not None: 100 | raw_target = decision['raw_target'] 101 | 102 | if decision['decision'] == 'LONG': 103 | # LONG: Final target should be 5pts BELOW raw target 104 | expected_target = raw_target - 5 105 | if abs(target - expected_target) > 0.1: # Allow 0.1pt tolerance 106 | return False, f"LONG buffer error: target ({target}) should be raw_target - 5 ({expected_target})" 107 | 108 | elif decision['decision'] == 'SHORT': 109 | # SHORT: Final target should be 5pts ABOVE raw target 110 | expected_target = raw_target + 5 111 | if abs(target - expected_target) > 0.1: # Allow 0.1pt tolerance 112 | return False, f"SHORT buffer error: target ({target}) should be raw_target + 5 ({expected_target})" 113 | 114 | # Calculate and validate R:R ratio with final target (after buffer) 115 | risk = abs(entry - stop) 116 | reward = abs(target - entry) 117 | 118 | if risk == 0: 119 | return False, "Risk cannot be zero (entry == stop)" 120 | 121 | rr_ratio = reward / risk 122 | 123 | # Minimum R:R requirement: 1.3:1 124 | min_rr = 1.3 125 | if rr_ratio < min_rr: 126 | return False, f"R:R too low: {rr_ratio:.2f}:1 (minimum {min_rr}:1 required after 5pt buffer)" 127 | 128 | logger.info(f"Validation passed: {decision['decision']} | R:R = {rr_ratio:.2f}:1 | " 129 | f"Risk: {risk:.2f}pts | Reward: {reward:.2f}pts") 130 | 131 | return True, "" 132 | 133 | def generate_signal(self, decision: Dict[str, Any], timestamp: datetime = None) -> bool: 134 | """ 135 | Generate trade signal and append to CSV 136 | 137 | Args: 138 | decision: Decision dictionary from TradingAgent 139 | timestamp: Optional timestamp (defaults to now) 140 | 141 | Returns: 142 | True if signal generated successfully 143 | """ 144 | # Validate decision 145 | is_valid, error_msg = self.validate_decision(decision) 146 | if not is_valid: 147 | logger.error(f"Signal validation failed: {error_msg}") 148 | return False 149 | 150 | # Format timestamp 151 | if timestamp is None: 152 | timestamp = datetime.now() 153 | time_str = timestamp.strftime('%m/%d/%Y %H:%M:%S') 154 | 155 | # Prepare row data 156 | row = [ 157 | time_str, 158 | decision['decision'], 159 | f"{decision['entry']:.2f}", 160 | f"{decision['stop']:.2f}", 161 | f"{decision['target']:.2f}" 162 | ] 163 | 164 | # Append to CSV 165 | try: 166 | with open(self.output_file, 'a', newline='') as f: 167 | writer = csv.writer(f) 168 | writer.writerow(row) 169 | 170 | logger.info(f"Signal generated: {decision['decision']} @ {decision['entry']:.2f}") 171 | logger.info(f" Stop: {decision['stop']:.2f} | Target: {decision['target']:.2f}") 172 | 173 | return True 174 | 175 | except Exception as e: 176 | logger.error(f"Error writing signal to CSV: {e}") 177 | return False 178 | 179 | def get_signal_summary(self, decision: Dict[str, Any]) -> str: 180 | """ 181 | Generate human-readable signal summary 182 | 183 | Args: 184 | decision: Decision dictionary 185 | 186 | Returns: 187 | Summary string 188 | """ 189 | entry = decision['entry'] 190 | stop = decision['stop'] 191 | target = decision['target'] 192 | 193 | risk = abs(entry - stop) 194 | reward = abs(target - entry) 195 | rr_ratio = reward / risk if risk > 0 else 0 196 | 197 | lines = [] 198 | lines.append(f"=== TRADE SIGNAL GENERATED ===") 199 | lines.append(f"Direction: {decision['decision']}") 200 | 201 | # Show setup type if available 202 | if 'setup_type' in decision and decision['setup_type']: 203 | lines.append(f"Setup Type: {decision['setup_type']}") 204 | 205 | lines.append(f"Entry: {entry:.2f}") 206 | lines.append(f"Stop Loss: {stop:.2f} ({risk:.2f}pts risk)") 207 | 208 | # Show raw target and buffer calculation if available 209 | if 'raw_target' in decision and decision['raw_target'] is not None: 210 | raw_target = decision['raw_target'] 211 | buffer_direction = "+" if decision['decision'] == 'SHORT' else "-" 212 | lines.append(f"Raw Target: {raw_target:.2f}") 213 | lines.append(f"Final Target: {target:.2f} ({raw_target:.2f} {buffer_direction} 5pt buffer)") 214 | else: 215 | lines.append(f"Target: {target:.2f} ({reward:.2f}pts reward)") 216 | 217 | lines.append(f"Risk/Reward: {rr_ratio:.2f}:1") 218 | 219 | # Show confidence if available 220 | if 'confidence' in decision: 221 | lines.append(f"Confidence: {decision['confidence']:.0%}") 222 | 223 | # Show reasoning if available 224 | if 'reasoning' in decision and decision['reasoning']: 225 | lines.append(f"\nReasoning: {decision['reasoning']}") 226 | 227 | lines.append(f"\nSignal written to: {self.output_file}") 228 | 229 | return "\n".join(lines) 230 | 231 | def count_signals_today(self) -> int: 232 | """ 233 | Count number of signals generated today 234 | 235 | Returns: 236 | Number of signals today 237 | """ 238 | today = datetime.now().strftime('%m/%d/%Y') 239 | count = 0 240 | 241 | try: 242 | with open(self.output_file, 'r') as f: 243 | reader = csv.DictReader(f) 244 | for row in reader: 245 | if row['DateTime'].startswith(today): 246 | count += 1 247 | except Exception as e: 248 | logger.error(f"Error counting signals: {e}") 249 | return 0 250 | 251 | return count 252 | 253 | def get_recent_signals(self, limit: int = 10) -> list: 254 | """ 255 | Get most recent signals 256 | 257 | Args: 258 | limit: Number of signals to retrieve 259 | 260 | Returns: 261 | List of signal dictionaries 262 | """ 263 | signals = [] 264 | 265 | try: 266 | with open(self.output_file, 'r') as f: 267 | reader = csv.DictReader(f) 268 | all_signals = list(reader) 269 | signals = all_signals[-limit:] if len(all_signals) > limit else all_signals 270 | except Exception as e: 271 | logger.error(f"Error reading signals: {e}") 272 | return [] 273 | 274 | return signals 275 | 276 | def clear_signals(self): 277 | """Clear all signals (reinitialize CSV)""" 278 | self._initialize_csv() 279 | logger.info("Trade signals cleared") 280 | 281 | 282 | # Example usage 283 | if __name__ == "__main__": 284 | logging.basicConfig(level=logging.INFO) 285 | 286 | generator = SignalGenerator("data/trade_signals.csv") 287 | 288 | # Sample decision 289 | sample_decision = { 290 | 'decision': 'SHORT', 291 | 'entry': 14712.00, 292 | 'stop': 14730.00, 293 | 'target': 14650.00, 294 | 'risk_reward': 3.44, 295 | 'confidence': 0.78 296 | } 297 | 298 | # Generate signal 299 | success = generator.generate_signal(sample_decision) 300 | 301 | if success: 302 | print(generator.get_signal_summary(sample_decision)) 303 | print(f"\nSignals today: {generator.count_signals_today()}") 304 | -------------------------------------------------------------------------------- /src/market_analysis_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Market Analysis Manager Module 3 | Manages persistent market analysis state across trading sessions 4 | """ 5 | 6 | import json 7 | import logging 8 | from typing import Dict, Optional, Any 9 | from datetime import datetime 10 | from pathlib import Path 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class MarketAnalysisManager: 16 | """Manages persistent market analysis state""" 17 | 18 | def __init__(self, analysis_file: str = "data/market_analysis.json"): 19 | """ 20 | Initialize Market Analysis Manager 21 | 22 | Args: 23 | analysis_file: Path to market analysis persistence file 24 | """ 25 | self.analysis_file = Path(analysis_file) 26 | self.analysis_file.parent.mkdir(parents=True, exist_ok=True) 27 | 28 | # Initialize or load existing analysis 29 | self.current_analysis = self._load_analysis() 30 | 31 | logger.info(f"MarketAnalysisManager initialized (file={analysis_file})") 32 | 33 | def _get_empty_analysis(self) -> Dict[str, Any]: 34 | """ 35 | Create empty analysis structure 36 | 37 | Returns: 38 | Empty analysis dictionary 39 | """ 40 | return { 41 | "last_updated": datetime.now().isoformat(), 42 | "current_bar_index": 0, 43 | "long_assessment": { 44 | "status": "none", 45 | "target_fvg": None, 46 | "entry_plan": None, 47 | "stop_plan": None, 48 | "target_plan": None, 49 | "reasoning": "No long setup identified yet", 50 | "confidence": 0.0, 51 | "setup_age_bars": 0 52 | }, 53 | "short_assessment": { 54 | "status": "none", 55 | "target_fvg": None, 56 | "entry_plan": None, 57 | "stop_plan": None, 58 | "target_plan": None, 59 | "reasoning": "No short setup identified yet", 60 | "confidence": 0.0, 61 | "setup_age_bars": 0 62 | }, 63 | "overall_bias": "neutral", 64 | "waiting_for": "Initial market analysis", 65 | "bars_since_last_trade": 0, 66 | "bars_since_last_update": 0 67 | } 68 | 69 | def _load_analysis(self) -> Dict[str, Any]: 70 | """ 71 | Load existing analysis from file or create new 72 | 73 | Returns: 74 | Analysis dictionary 75 | """ 76 | if self.analysis_file.exists(): 77 | try: 78 | with open(self.analysis_file, 'r') as f: 79 | analysis = json.load(f) 80 | logger.info(f"Loaded existing analysis (last updated: {analysis.get('last_updated')})") 81 | return analysis 82 | except Exception as e: 83 | logger.warning(f"Failed to load analysis file: {e}. Creating new analysis.") 84 | return self._get_empty_analysis() 85 | else: 86 | logger.info("No existing analysis found. Creating new analysis.") 87 | return self._get_empty_analysis() 88 | 89 | def save_analysis(self, analysis: Optional[Dict[str, Any]] = None) -> bool: 90 | """ 91 | Save analysis to file 92 | 93 | Args: 94 | analysis: Analysis dictionary to save (uses self.current_analysis if None) 95 | 96 | Returns: 97 | True if successful, False otherwise 98 | """ 99 | if analysis is None: 100 | analysis = self.current_analysis 101 | 102 | try: 103 | # Update timestamp 104 | analysis['last_updated'] = datetime.now().isoformat() 105 | 106 | # Write to file 107 | with open(self.analysis_file, 'w') as f: 108 | json.dump(analysis, f, indent=2) 109 | 110 | logger.info(f"Analysis saved successfully") 111 | return True 112 | 113 | except Exception as e: 114 | logger.error(f"Failed to save analysis: {e}") 115 | return False 116 | 117 | def get_previous_analysis(self) -> Dict[str, Any]: 118 | """ 119 | Get the current analysis state 120 | 121 | Returns: 122 | Current analysis dictionary 123 | """ 124 | return self.current_analysis.copy() 125 | 126 | def update_analysis(self, new_analysis: Dict[str, Any]) -> bool: 127 | """ 128 | Update the current analysis with new data 129 | 130 | Args: 131 | new_analysis: New analysis data from agent 132 | 133 | Returns: 134 | True if successful, False otherwise 135 | """ 136 | try: 137 | # Increment bars since last update for existing setups 138 | if self.current_analysis.get('long_assessment', {}).get('status') != 'none': 139 | new_analysis['long_assessment']['setup_age_bars'] = \ 140 | self.current_analysis.get('long_assessment', {}).get('setup_age_bars', 0) + 1 141 | 142 | if self.current_analysis.get('short_assessment', {}).get('status') != 'none': 143 | new_analysis['short_assessment']['setup_age_bars'] = \ 144 | self.current_analysis.get('short_assessment', {}).get('setup_age_bars', 0) + 1 145 | 146 | # Increment bars since last trade 147 | new_analysis['bars_since_last_trade'] = \ 148 | self.current_analysis.get('bars_since_last_trade', 0) + 1 149 | 150 | # Reset bars since last update 151 | new_analysis['bars_since_last_update'] = 0 152 | 153 | # Update current analysis 154 | self.current_analysis = new_analysis 155 | 156 | # Save to file 157 | return self.save_analysis() 158 | 159 | except Exception as e: 160 | logger.error(f"Failed to update analysis: {e}") 161 | return False 162 | 163 | def mark_trade_executed(self, direction: str): 164 | """ 165 | Mark that a trade was executed 166 | 167 | Args: 168 | direction: "LONG" or "SHORT" 169 | """ 170 | self.current_analysis['bars_since_last_trade'] = 0 171 | 172 | # Reset the executed setup 173 | if direction == "LONG": 174 | self.current_analysis['long_assessment']['status'] = 'none' 175 | self.current_analysis['long_assessment']['setup_age_bars'] = 0 176 | elif direction == "SHORT": 177 | self.current_analysis['short_assessment']['status'] = 'none' 178 | self.current_analysis['short_assessment']['setup_age_bars'] = 0 179 | 180 | self.save_analysis() 181 | logger.info(f"{direction} trade executed - setup reset") 182 | 183 | def format_previous_analysis_for_prompt(self) -> str: 184 | """ 185 | Format previous analysis for inclusion in agent prompt 186 | 187 | Returns: 188 | Formatted string for prompt 189 | """ 190 | analysis = self.current_analysis 191 | 192 | lines = [] 193 | lines.append("PREVIOUS ANALYSIS STATE:") 194 | lines.append("=" * 50) 195 | lines.append(f"Last Updated: {analysis.get('last_updated', 'Unknown')}") 196 | lines.append(f"Bars Since Last Trade: {analysis.get('bars_since_last_trade', 0)}") 197 | lines.append(f"Overall Bias: {analysis.get('overall_bias', 'neutral').upper()}") 198 | lines.append(f"Waiting For: {analysis.get('waiting_for', 'N/A')}") 199 | lines.append("") 200 | 201 | # Long assessment 202 | long = analysis.get('long_assessment', {}) 203 | lines.append("LONG ASSESSMENT:") 204 | lines.append(f" Status: {long.get('status', 'none').upper()}") 205 | if long.get('status') != 'none': 206 | lines.append(f" Setup Age: {long.get('setup_age_bars', 0)} bars") 207 | lines.append(f" Entry Plan: {long.get('entry_plan', 0):.2f}") 208 | lines.append(f" Stop Plan: {long.get('stop_plan', 0):.2f}") 209 | lines.append(f" Target Plan: {long.get('target_plan', 0):.2f}") 210 | lines.append(f" Confidence: {long.get('confidence', 0):.2f}") 211 | lines.append(f" Reasoning: {long.get('reasoning', 'N/A')}") 212 | else: 213 | lines.append(f" {long.get('reasoning', 'No setup')}") 214 | lines.append("") 215 | 216 | # Short assessment 217 | short = analysis.get('short_assessment', {}) 218 | lines.append("SHORT ASSESSMENT:") 219 | lines.append(f" Status: {short.get('status', 'none').upper()}") 220 | if short.get('status') != 'none': 221 | lines.append(f" Setup Age: {short.get('setup_age_bars', 0)} bars") 222 | lines.append(f" Entry Plan: {short.get('entry_plan', 0):.2f}") 223 | lines.append(f" Stop Plan: {short.get('stop_plan', 0):.2f}") 224 | lines.append(f" Target Plan: {short.get('target_plan', 0):.2f}") 225 | lines.append(f" Confidence: {short.get('confidence', 0):.2f}") 226 | lines.append(f" Reasoning: {short.get('reasoning', 'N/A')}") 227 | else: 228 | lines.append(f" {short.get('reasoning', 'No setup')}") 229 | lines.append("") 230 | 231 | return "\n".join(lines) 232 | 233 | def get_summary(self) -> str: 234 | """ 235 | Get human-readable summary of current analysis 236 | 237 | Returns: 238 | Summary string 239 | """ 240 | analysis = self.current_analysis 241 | 242 | lines = [] 243 | lines.append("=" * 60) 244 | lines.append("MARKET ANALYSIS SUMMARY") 245 | lines.append("=" * 60) 246 | lines.append(f"Last Updated: {analysis.get('last_updated', 'Unknown')}") 247 | lines.append(f"Overall Bias: {analysis.get('overall_bias', 'neutral').upper()}") 248 | lines.append(f"Waiting For: {analysis.get('waiting_for', 'N/A')}") 249 | lines.append(f"Bars Since Last Trade: {analysis.get('bars_since_last_trade', 0)}") 250 | lines.append("") 251 | 252 | long = analysis.get('long_assessment', {}) 253 | lines.append(f"LONG: {long.get('status', 'none').upper()} " 254 | f"(Age: {long.get('setup_age_bars', 0)} bars, " 255 | f"Conf: {long.get('confidence', 0):.2f})") 256 | 257 | short = analysis.get('short_assessment', {}) 258 | lines.append(f"SHORT: {short.get('status', 'none').upper()} " 259 | f"(Age: {short.get('setup_age_bars', 0)} bars, " 260 | f"Conf: {short.get('confidence', 0):.2f})") 261 | lines.append("=" * 60) 262 | 263 | return "\n".join(lines) 264 | 265 | 266 | # Example usage 267 | if __name__ == "__main__": 268 | logging.basicConfig(level=logging.INFO) 269 | 270 | # Create manager 271 | manager = MarketAnalysisManager() 272 | 273 | # Show current state 274 | print(manager.get_summary()) 275 | 276 | # Example update 277 | new_analysis = manager._get_empty_analysis() 278 | new_analysis['overall_bias'] = 'bullish' 279 | new_analysis['waiting_for'] = 'Price to reach 14600 bearish FVG' 280 | new_analysis['long_assessment'] = { 281 | 'status': 'waiting', 282 | 'target_fvg': {'bottom': 14600, 'top': 14605}, 283 | 'entry_plan': 14602, 284 | 'stop_plan': 14590, 285 | 'target_plan': 14700, 286 | 'reasoning': 'Strong bullish trend, waiting for pullback to FVG support', 287 | 'confidence': 0.75, 288 | 'setup_age_bars': 0 289 | } 290 | 291 | manager.update_analysis(new_analysis) 292 | print("\nAfter update:") 293 | print(manager.get_summary()) 294 | -------------------------------------------------------------------------------- /src/memory_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | Memory Manager Module 3 | Handles trade history storage and retrieval using file-based system 4 | (MCP integration can be added later for distributed memory) 5 | """ 6 | 7 | import json 8 | import logging 9 | from typing import Dict, List, Optional, Any 10 | from datetime import datetime 11 | from pathlib import Path 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class MemoryManager: 17 | """Manages trade history and performance tracking""" 18 | 19 | def __init__(self, data_dir: str = "data"): 20 | """ 21 | Initialize Memory Manager 22 | 23 | Args: 24 | data_dir: Directory for storing trade history 25 | """ 26 | self.data_dir = Path(data_dir) 27 | self.data_dir.mkdir(exist_ok=True) 28 | 29 | self.trade_history_file = self.data_dir / "trade_history.json" 30 | self.performance_log_file = self.data_dir / "performance_log.json" 31 | 32 | # Load existing history 33 | self.trade_history = self._load_trade_history() 34 | self.performance_log = self._load_performance_log() 35 | 36 | logger.info(f"MemoryManager initialized (trades loaded: {len(self.trade_history)})") 37 | 38 | def _load_trade_history(self) -> List[Dict[str, Any]]: 39 | """Load trade history from file""" 40 | if not self.trade_history_file.exists(): 41 | return [] 42 | 43 | try: 44 | with open(self.trade_history_file, 'r') as f: 45 | return json.load(f) 46 | except Exception as e: 47 | logger.error(f"Error loading trade history: {e}") 48 | return [] 49 | 50 | def _save_trade_history(self): 51 | """Save trade history to file""" 52 | try: 53 | with open(self.trade_history_file, 'w') as f: 54 | json.dump(self.trade_history, f, indent=2) 55 | except Exception as e: 56 | logger.error(f"Error saving trade history: {e}") 57 | 58 | def _load_performance_log(self) -> Dict[str, Any]: 59 | """Load performance log from file""" 60 | if not self.performance_log_file.exists(): 61 | return {'sessions': [], 'summary': {}} 62 | 63 | try: 64 | with open(self.performance_log_file, 'r') as f: 65 | return json.load(f) 66 | except Exception as e: 67 | logger.error(f"Error loading performance log: {e}") 68 | return {'sessions': [], 'summary': {}} 69 | 70 | def _save_performance_log(self): 71 | """Save performance log to file""" 72 | try: 73 | with open(self.performance_log_file, 'w') as f: 74 | json.dump(self.performance_log, f, indent=2) 75 | except Exception as e: 76 | logger.error(f"Error saving performance log: {e}") 77 | 78 | def store_trade(self, trade_data: Dict[str, Any]) -> str: 79 | """ 80 | Store a completed trade 81 | 82 | Args: 83 | trade_data: Trade data dictionary 84 | 85 | Returns: 86 | Trade ID 87 | """ 88 | trade_id = trade_data.get('trade_id') or f"{datetime.now().isoformat()}" 89 | trade_data['trade_id'] = trade_id 90 | trade_data['stored_at'] = datetime.now().isoformat() 91 | 92 | self.trade_history.append(trade_data) 93 | self._save_trade_history() 94 | 95 | logger.info(f"Trade stored: {trade_id} - {trade_data.get('outcome', {}).get('result', 'UNKNOWN')}") 96 | return trade_id 97 | 98 | def get_trade(self, trade_id: str) -> Optional[Dict[str, Any]]: 99 | """ 100 | Retrieve a specific trade by ID 101 | 102 | Args: 103 | trade_id: Trade ID to retrieve 104 | 105 | Returns: 106 | Trade data or None 107 | """ 108 | for trade in self.trade_history: 109 | if trade.get('trade_id') == trade_id: 110 | return trade 111 | return None 112 | 113 | def query_trades(self, filters: Dict[str, Any], limit: int = 20) -> List[Dict[str, Any]]: 114 | """ 115 | Query trades with filters 116 | 117 | Args: 118 | filters: Dictionary of filter criteria 119 | limit: Maximum number of trades to return 120 | 121 | Returns: 122 | List of matching trades 123 | """ 124 | results = [] 125 | 126 | for trade in reversed(self.trade_history): # Most recent first 127 | match = True 128 | 129 | # Apply filters 130 | if 'setup_type' in filters: 131 | if trade.get('setup', {}).get('type') != filters['setup_type']: 132 | match = False 133 | 134 | if 'direction' in filters: 135 | if trade.get('setup', {}).get('direction') != filters['direction']: 136 | match = False 137 | 138 | if 'result' in filters: 139 | if trade.get('outcome', {}).get('result') != filters['result']: 140 | match = False 141 | 142 | if 'min_confidence' in filters: 143 | if trade.get('decision', {}).get('confidence', 0) < filters['min_confidence']: 144 | match = False 145 | 146 | if match: 147 | results.append(trade) 148 | if len(results) >= limit: 149 | break 150 | 151 | return results 152 | 153 | def calculate_stats(self, trades: List[Dict[str, Any]]) -> Dict[str, Any]: 154 | """ 155 | Calculate performance statistics for a list of trades 156 | 157 | Args: 158 | trades: List of trade dictionaries 159 | 160 | Returns: 161 | Statistics dictionary 162 | """ 163 | if not trades: 164 | return { 165 | 'total_trades': 0, 166 | 'wins': 0, 167 | 'losses': 0, 168 | 'breakeven': 0, 169 | 'win_rate': 0.0, 170 | 'avg_rr': 0.0, 171 | 'total_pnl': 0.0, 172 | 'avg_pnl': 0.0 173 | } 174 | 175 | wins = 0 176 | losses = 0 177 | breakeven = 0 178 | total_pnl = 0.0 179 | total_rr = 0.0 180 | 181 | for trade in trades: 182 | outcome = trade.get('outcome', {}) 183 | result = outcome.get('result', 'UNKNOWN') 184 | 185 | if result == 'WIN': 186 | wins += 1 187 | elif result == 'LOSS': 188 | losses += 1 189 | elif result == 'BREAKEVEN': 190 | breakeven += 1 191 | 192 | total_pnl += outcome.get('profit_loss', 0.0) 193 | total_rr += outcome.get('risk_reward_achieved', 0.0) 194 | 195 | total_trades = len(trades) 196 | completed_trades = wins + losses # Exclude breakeven from win rate calc 197 | 198 | return { 199 | 'total_trades': total_trades, 200 | 'wins': wins, 201 | 'losses': losses, 202 | 'breakeven': breakeven, 203 | 'win_rate': wins / completed_trades if completed_trades > 0 else 0.0, 204 | 'avg_rr': total_rr / total_trades if total_trades > 0 else 0.0, 205 | 'total_pnl': total_pnl, 206 | 'avg_pnl': total_pnl / total_trades if total_trades > 0 else 0.0 207 | } 208 | 209 | def get_memory_context(self) -> Dict[str, Any]: 210 | """ 211 | Generate memory context for Claude analysis 212 | 213 | Returns: 214 | Memory context dictionary with recent performance stats 215 | """ 216 | # Get recent FVG-only trades 217 | fvg_trades = self.query_trades({'setup_type': 'fvg_only'}, limit=20) 218 | fvg_stats = self.calculate_stats(fvg_trades) 219 | 220 | # Get recent level-only trades 221 | level_trades = self.query_trades({'setup_type': 'level_only'}, limit=20) 222 | level_stats = self.calculate_stats(level_trades) 223 | 224 | # Overall recent performance 225 | recent_trades = self.trade_history[-50:] if len(self.trade_history) > 0 else [] 226 | overall_stats = self.calculate_stats(recent_trades) 227 | 228 | context = { 229 | 'fvg_only_stats': fvg_stats, 230 | 'level_only_stats': level_stats, 231 | 'overall_recent_stats': overall_stats, 232 | 'total_trades_all_time': len(self.trade_history), 233 | 'last_updated': datetime.now().isoformat() 234 | } 235 | 236 | return context 237 | 238 | def log_session(self, session_data: Dict[str, Any]): 239 | """ 240 | Log a trading session 241 | 242 | Args: 243 | session_data: Session data dictionary 244 | """ 245 | session_data['timestamp'] = datetime.now().isoformat() 246 | self.performance_log['sessions'].append(session_data) 247 | self._save_performance_log() 248 | 249 | logger.info(f"Session logged: {session_data.get('mode', 'unknown')} mode") 250 | 251 | def update_summary(self): 252 | """Update overall performance summary""" 253 | all_stats = self.calculate_stats(self.trade_history) 254 | self.performance_log['summary'] = { 255 | **all_stats, 256 | 'last_updated': datetime.now().isoformat() 257 | } 258 | self._save_performance_log() 259 | 260 | def get_performance_summary(self) -> str: 261 | """ 262 | Generate human-readable performance summary 263 | 264 | Returns: 265 | Summary string 266 | """ 267 | stats = self.calculate_stats(self.trade_history) 268 | 269 | lines = [] 270 | lines.append("=== OVERALL PERFORMANCE ===") 271 | lines.append(f"Total Trades: {stats['total_trades']}") 272 | lines.append(f"Wins: {stats['wins']} | Losses: {stats['losses']} | Breakeven: {stats['breakeven']}") 273 | lines.append(f"Win Rate: {stats['win_rate']:.1%}") 274 | lines.append(f"Average R/R: {stats['avg_rr']:.2f}:1") 275 | lines.append(f"Total P&L: {stats['total_pnl']:+.2f} points") 276 | lines.append(f"Average P&L: {stats['avg_pnl']:+.2f} points") 277 | 278 | # By setup type 279 | memory_context = self.get_memory_context() 280 | 281 | lines.append("\n=== BY SETUP TYPE ===") 282 | 283 | fvg_stats = memory_context['fvg_only_stats'] 284 | lines.append(f"\nFVG-Only Trades: {fvg_stats['total_trades']}") 285 | if fvg_stats['total_trades'] > 0: 286 | lines.append(f" Win Rate: {fvg_stats['win_rate']:.1%}") 287 | lines.append(f" Avg R/R: {fvg_stats['avg_rr']:.2f}:1") 288 | 289 | return "\n".join(lines) 290 | 291 | 292 | # Example usage 293 | if __name__ == "__main__": 294 | logging.basicConfig(level=logging.INFO) 295 | 296 | manager = MemoryManager() 297 | 298 | # Sample trade 299 | sample_trade = { 300 | 'trade_id': '2025-11-25_14:30:00', 301 | 'setup': { 302 | 'type': 'fvg_only', 303 | 'direction': 'SHORT', 304 | 'entry': 14712, 305 | 'stop': 14730, 306 | 'target': 14650 307 | }, 308 | 'outcome': { 309 | 'result': 'WIN', 310 | 'exit_price': 14651.00, 311 | 'profit_loss': 61.00, 312 | 'risk_reward_achieved': 3.39, 313 | 'bars_held': 8 314 | }, 315 | 'decision': { 316 | 'confidence': 0.78, 317 | 'reasoning': 'FVG setup near EMS level' 318 | } 319 | } 320 | 321 | # Store trade 322 | # manager.store_trade(sample_trade) 323 | 324 | # Get context 325 | context = manager.get_memory_context() 326 | print(json.dumps(context, indent=2)) 327 | 328 | # Print summary 329 | print("\n" + manager.get_performance_summary()) 330 | -------------------------------------------------------------------------------- /src/fvg_analyzer.py: -------------------------------------------------------------------------------- 1 | """ 2 | FVG Analyzer Module 3 | Parses Fair Value Gap data and prepares it for Claude analysis 4 | """ 5 | 6 | import logging 7 | from typing import Dict, List, Optional, Any 8 | from datetime import datetime 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class FVGAnalyzer: 14 | """Analyzes Fair Value Gaps and calculates trading context""" 15 | 16 | def __init__(self, min_gap_size: float = 5.0, max_gap_age: int = 100): 17 | """ 18 | Initialize FVG Analyzer 19 | 20 | Args: 21 | min_gap_size: Minimum gap size in points to consider 22 | max_gap_age: Maximum age in bars before gap is considered stale 23 | """ 24 | self.min_gap_size = min_gap_size 25 | self.max_gap_age = max_gap_age 26 | logger.info(f"FVGAnalyzer initialized (min_gap_size={min_gap_size}, max_gap_age={max_gap_age})") 27 | 28 | def parse_fvg_zones(self, active_fvgs: List[Dict]) -> Dict[str, List[Dict]]: 29 | """ 30 | Parse FVG zones and separate by type 31 | 32 | Args: 33 | active_fvgs: List of active FVG zones from FairValueGaps.py 34 | 35 | Returns: 36 | Dict with 'bullish' and 'bearish' FVG lists 37 | """ 38 | bullish_fvgs = [] 39 | bearish_fvgs = [] 40 | 41 | for fvg in active_fvgs: 42 | if fvg.get('filled', False): 43 | continue 44 | 45 | fvg_data = { 46 | 'top': fvg['top'], 47 | 'bottom': fvg['bottom'], 48 | 'size': fvg['gap_size'], 49 | 'datetime': fvg['datetime'], 50 | 'age_bars': fvg.get('age_bars', 0), 51 | 'index': fvg.get('index', 0) 52 | } 53 | 54 | if fvg['type'] == 'bullish': 55 | bullish_fvgs.append(fvg_data) 56 | elif fvg['type'] == 'bearish': 57 | bearish_fvgs.append(fvg_data) 58 | 59 | return { 60 | 'bullish': bullish_fvgs, 61 | 'bearish': bearish_fvgs 62 | } 63 | 64 | def calculate_distance(self, current_price: float, fvg: Dict, fvg_type: str) -> float: 65 | """ 66 | Calculate distance from current price to FVG target (the gap to fill) 67 | 68 | Args: 69 | current_price: Current market price 70 | fvg: FVG data dictionary 71 | fvg_type: 'bullish' or 'bearish' 72 | 73 | Returns: 74 | Distance in points (positive = target above, negative = target below) 75 | """ 76 | if fvg_type == 'bullish': 77 | # Bullish FVG (gap UP) leaves gap BELOW = SHORT opportunity (price drawn down to fill) 78 | # Target: top of the gap (where price will fill from above) 79 | return fvg['top'] - current_price # negative = target is below 80 | else: # bearish 81 | # Bearish FVG (gap DOWN) leaves gap ABOVE = LONG opportunity (price drawn up to fill) 82 | # Target: bottom of the gap (where price will fill from below) 83 | return fvg['bottom'] - current_price # positive = target is above 84 | 85 | def find_nearest_fvgs(self, current_price: float, fvg_zones: Dict[str, List[Dict]]) -> Dict[str, Optional[Dict]]: 86 | """ 87 | Find nearest bullish and bearish FVGs to current price 88 | Only considers FVGs in the correct direction (targets price can move toward) 89 | 90 | Args: 91 | current_price: Current market price 92 | fvg_zones: Dictionary of bullish and bearish FVG zones 93 | 94 | Returns: 95 | Dict with nearest_bullish and nearest_bearish FVGs 96 | """ 97 | result = { 98 | 'nearest_bullish': None, 99 | 'nearest_bearish': None 100 | } 101 | 102 | # Find nearest bullish FVG BELOW current price (for SHORT setups) 103 | # Bullish FVG = gap created by UP move, leaves gap BELOW 104 | # Price will be drawn downward to fill the gap 105 | if fvg_zones['bullish']: 106 | bullish_below = [ 107 | fvg for fvg in fvg_zones['bullish'] 108 | if fvg['top'] < current_price # Gap must be BELOW current price 109 | ] 110 | if bullish_below: 111 | bullish_with_distance = [ 112 | { 113 | **fvg, 114 | 'distance': self.calculate_distance(current_price, fvg, 'bullish'), 115 | 'distance_abs': abs(self.calculate_distance(current_price, fvg, 'bullish')) 116 | } 117 | for fvg in bullish_below 118 | ] 119 | bullish_with_distance.sort(key=lambda x: x['distance_abs']) 120 | result['nearest_bullish'] = bullish_with_distance[0] 121 | 122 | # Find nearest bearish FVG ABOVE current price (for LONG setups) 123 | # Bearish FVG = gap created by DOWN move, leaves gap ABOVE 124 | # Price will be drawn upward to fill the gap 125 | if fvg_zones['bearish']: 126 | bearish_above = [ 127 | fvg for fvg in fvg_zones['bearish'] 128 | if fvg['bottom'] > current_price # Gap must be ABOVE current price 129 | ] 130 | if bearish_above: 131 | bearish_with_distance = [ 132 | { 133 | **fvg, 134 | 'distance': self.calculate_distance(current_price, fvg, 'bearish'), 135 | 'distance_abs': abs(self.calculate_distance(current_price, fvg, 'bearish')) 136 | } 137 | for fvg in bearish_above 138 | ] 139 | bearish_with_distance.sort(key=lambda x: x['distance_abs']) 140 | result['nearest_bearish'] = bearish_with_distance[0] 141 | 142 | return result 143 | 144 | def check_price_in_zone(self, current_price: float, fvg: Dict) -> bool: 145 | """ 146 | Check if current price is inside an FVG zone 147 | 148 | Args: 149 | current_price: Current market price 150 | fvg: FVG data dictionary 151 | 152 | Returns: 153 | True if price is in zone, False otherwise 154 | """ 155 | return fvg['bottom'] <= current_price <= fvg['top'] 156 | 157 | def find_active_zone(self, current_price: float, fvg_zones: Dict[str, List[Dict]]) -> Optional[Dict]: 158 | """ 159 | Check if price is currently inside any FVG zone 160 | 161 | Args: 162 | current_price: Current market price 163 | fvg_zones: Dictionary of bullish and bearish FVG zones 164 | 165 | Returns: 166 | FVG data if price is in zone, None otherwise 167 | """ 168 | # Check bullish zones 169 | for fvg in fvg_zones['bullish']: 170 | if self.check_price_in_zone(current_price, fvg): 171 | return {'type': 'bullish', **fvg} 172 | 173 | # Check bearish zones 174 | for fvg in fvg_zones['bearish']: 175 | if self.check_price_in_zone(current_price, fvg): 176 | return {'type': 'bearish', **fvg} 177 | 178 | return None 179 | 180 | def filter_quality_fvgs(self, fvg_zones: Dict[str, List[Dict]]) -> Dict[str, List[Dict]]: 181 | """ 182 | Filter FVGs based on quality criteria 183 | 184 | Args: 185 | fvg_zones: Dictionary of bullish and bearish FVG zones 186 | 187 | Returns: 188 | Filtered FVG zones meeting quality criteria 189 | """ 190 | filtered = { 191 | 'bullish': [], 192 | 'bearish': [] 193 | } 194 | 195 | for fvg_type in ['bullish', 'bearish']: 196 | for fvg in fvg_zones[fvg_type]: 197 | # Check gap size 198 | if fvg['size'] < self.min_gap_size: 199 | continue 200 | 201 | # Check gap age 202 | if fvg.get('age_bars', 0) > self.max_gap_age: 203 | continue 204 | 205 | filtered[fvg_type].append(fvg) 206 | 207 | return filtered 208 | 209 | def analyze_market_context(self, current_price: float, active_fvgs: List[Dict]) -> Dict[str, Any]: 210 | """ 211 | Complete market analysis combining all FVG data 212 | 213 | Args: 214 | current_price: Current market price 215 | active_fvgs: List of active FVG zones 216 | 217 | Returns: 218 | Complete market context dictionary 219 | """ 220 | # Parse and separate FVG zones 221 | fvg_zones = self.parse_fvg_zones(active_fvgs) 222 | 223 | # Filter by quality 224 | quality_zones = self.filter_quality_fvgs(fvg_zones) 225 | 226 | # Find nearest FVGs 227 | nearest = self.find_nearest_fvgs(current_price, quality_zones) 228 | 229 | # Check if price is in any zone 230 | active_zone = self.find_active_zone(current_price, quality_zones) 231 | 232 | context = { 233 | 'current_price': current_price, 234 | 'timestamp': datetime.now().isoformat(), 235 | 'total_bullish_fvgs': len(quality_zones['bullish']), 236 | 'total_bearish_fvgs': len(quality_zones['bearish']), 237 | 'nearest_bullish_fvg': nearest['nearest_bullish'], 238 | 'nearest_bearish_fvg': nearest['nearest_bearish'], 239 | 'price_in_zone': active_zone, 240 | 'all_fvgs': quality_zones 241 | } 242 | 243 | logger.info(f"Market context analyzed: Price={current_price:.2f}, " 244 | f"Bullish FVGs={context['total_bullish_fvgs']}, " 245 | f"Bearish FVGs={context['total_bearish_fvgs']}") 246 | 247 | return context 248 | 249 | def get_fvg_summary(self, context: Dict[str, Any]) -> str: 250 | """ 251 | Generate human-readable summary of FVG analysis 252 | 253 | Args: 254 | context: Market context dictionary 255 | 256 | Returns: 257 | Summary string 258 | """ 259 | lines = [] 260 | lines.append(f"Current Price: {context['current_price']:.2f}") 261 | lines.append(f"Active Bullish FVGs: {context['total_bullish_fvgs']}") 262 | lines.append(f"Active Bearish FVGs: {context['total_bearish_fvgs']}") 263 | 264 | if context['nearest_bullish_fvg']: 265 | fvg = context['nearest_bullish_fvg'] 266 | lines.append(f"\nNearest Bullish FVG BELOW (SHORT setup - price drawn down to fill gap):") 267 | lines.append(f" Zone: {fvg['bottom']:.2f} - {fvg['top']:.2f}") 268 | lines.append(f" Size: {fvg['size']:.2f}pts") 269 | lines.append(f" Distance to target: {fvg['distance']:+.2f}pts") 270 | lines.append(f" Age: {fvg.get('age_bars', 0)} bars") 271 | 272 | if context['nearest_bearish_fvg']: 273 | fvg = context['nearest_bearish_fvg'] 274 | lines.append(f"\nNearest Bearish FVG ABOVE (LONG setup - price drawn up to fill gap):") 275 | lines.append(f" Zone: {fvg['bottom']:.2f} - {fvg['top']:.2f}") 276 | lines.append(f" Size: {fvg['size']:.2f}pts") 277 | lines.append(f" Distance to target: {fvg['distance']:+.2f}pts") 278 | lines.append(f" Age: {fvg.get('age_bars', 0)} bars") 279 | 280 | if context['price_in_zone']: 281 | zone = context['price_in_zone'] 282 | lines.append(f"\n*** PRICE IN ZONE ***") 283 | lines.append(f"Type: {zone['type'].upper()}") 284 | lines.append(f"Zone: {zone['bottom']:.2f} - {zone['top']:.2f}") 285 | 286 | return "\n".join(lines) 287 | 288 | 289 | # Example usage 290 | if __name__ == "__main__": 291 | logging.basicConfig(level=logging.INFO) 292 | 293 | # Sample FVG data 294 | sample_fvgs = [ 295 | {'type': 'bullish', 'top': 14715, 'bottom': 14710, 'gap_size': 5.0, 296 | 'datetime': '2025-11-25 14:00:00', 'filled': False, 'age_bars': 12}, 297 | {'type': 'bearish', 'top': 14655, 'bottom': 14650, 'gap_size': 5.0, 298 | 'datetime': '2025-11-25 13:00:00', 'filled': False, 'age_bars': 45}, 299 | ] 300 | 301 | analyzer = FVGAnalyzer() 302 | context = analyzer.analyze_market_context(14685.50, sample_fvgs) 303 | print(analyzer.get_fvg_summary(context)) 304 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # FVG Trading AI - Level & Gap-Based Trading System 2 | 3 | ## Project Vision 4 | 5 | Build an AI-driven trading system that mimics human trading behavior based on **price levels** and **Fair Value Gaps (FVGs)**. The core philosophy: **Price is always moving toward something - either a psychological level or a gap that needs to be filled.** 6 | 7 | --- 8 | 9 | ## Trading Philosophy 10 | 11 | ### Core Principles 12 | 13 | 1. **Price Magnetism to Round Numbers** 14 | - Price gravitates toward psychological levels: 6,700 | 6,800 | 6,900 15 | - These act as magnets and decision points 16 | - Markets respect these levels as support/resistance 17 | 18 | 2. **Fair Value Gaps (FVGs) as Price Targets** 19 | - Price seeks to fill imbalances (gaps) in the market 20 | - FVGs represent unfilled orders and inefficient price discovery 21 | - When a gap exists, price will eventually return to fill it 22 | 23 | 3. **Directional Bias** 24 | - Price is always heading TOWARD something 25 | - Either toward a psychological level (100-point intervals) 26 | - Or toward an unfilled FVG zone 27 | - The AI should predict which target is most likely 28 | 29 | ### How I Trade 30 | 31 | - **Identify active FVG zones** (bullish/bearish gaps in price) 32 | - **Monitor psychological levels** (6,700, 6,800, 6,900, etc.) 33 | - **Watch price behavior** as it approaches these zones/levels 34 | - **Enter trades** when price enters an FVG zone or bounces off a level 35 | - **Exit when the gap is filled** or the next level is reached 36 | 37 | --- 38 | 39 | ## Project Goals 40 | 41 | ### Phase 1: Foundation (Current) 42 | - [x] FVG zone identification from historical data 43 | - [x] Detection of bullish/bearish gaps 44 | - [x] Zone filtering (active vs filled) 45 | - [ ] Psychological level detection (round number identification) 46 | - [ ] Feature engineering for ML model 47 | 48 | ### Phase 2: AI Model Development 49 | - [ ] Build dataset with labeled features: 50 | - Current price position 51 | - Distance to nearest FVG (bullish/bearish) 52 | - Distance to nearest psychological level 53 | - Gap size and age 54 | - Market direction/momentum 55 | - [ ] Train neural network to predict: 56 | - Which target price is heading toward (FVG vs level) 57 | - Probability of reaching target 58 | - Optimal entry/exit points 59 | - [ ] Backtest model on historical data 60 | 61 | ### Phase 3: Advanced Intelligence 62 | - [ ] Pattern recognition (how price behaves near levels/gaps) 63 | - [ ] Multi-timeframe analysis 64 | - [ ] Volume/momentum integration 65 | - [ ] Reinforcement learning for trade timing 66 | - [ ] Risk management and position sizing 67 | 68 | ### Phase 4: Deployment 69 | - [ ] Real-time trading signals 70 | - [ ] Integration with trading platform 71 | - [ ] Performance monitoring and model retraining 72 | - [ ] Dashboard for visualization 73 | 74 | --- 75 | 76 | ## Technical Architecture 77 | 78 | ``` 79 | ┌─────────────────────────────────────────────────────────────┐ 80 | │ DATA LAYER │ 81 | │ - Historical OHLC data (1hr bars) │ 82 | │ - FVG zones (bullish/bearish) │ 83 | │ - Psychological levels (100-point intervals) │ 84 | └─────────────────────────────────────────────────────────────┘ 85 | ↓ 86 | ┌─────────────────────────────────────────────────────────────┐ 87 | │ FEATURE ENGINEERING │ 88 | │ - Distance to nearest FVG (up/down) │ 89 | │ - Distance to nearest level (up/down) │ 90 | │ - Price momentum and direction │ 91 | │ - Gap characteristics (size, age, type) │ 92 | │ - Level proximity (how close to 00 level) │ 93 | └─────────────────────────────────────────────────────────────┘ 94 | ↓ 95 | ┌─────────────────────────────────────────────────────────────┐ 96 | │ AI MODEL LAYER │ 97 | │ Option A: Neural Network (TensorFlow/PyTorch) │ 98 | │ Option B: Reinforcement Learning (Q-Learning/PPO) │ 99 | │ Option C: Ensemble (Multiple models voting) │ 100 | └─────────────────────────────────────────────────────────────┘ 101 | ↓ 102 | ┌─────────────────────────────────────────────────────────────┐ 103 | │ DECISION ENGINE │ 104 | │ - Target prediction (FVG vs Level) │ 105 | │ - Entry signal generation │ 106 | │ - Exit signal generation │ 107 | │ - Risk management │ 108 | └─────────────────────────────────────────────────────────────┘ 109 | ↓ 110 | ┌─────────────────────────────────────────────────────────────┐ 111 | │ EXECUTION LAYER │ 112 | │ - Signal output (CSV/API) │ 113 | │ - Performance tracking │ 114 | │ - Model feedback loop │ 115 | └─────────────────────────────────────────────────────────────┘ 116 | ``` 117 | 118 | --- 119 | 120 | ## Current Project Structure 121 | 122 | ``` 123 | Neural Network/ 124 | ├── data/ 125 | │ ├── HistoricalData.csv # Raw OHLC price data 126 | │ ├── fvg_zones.csv # Identified FVG zones (output) 127 | │ └── [future: levels.csv] # Psychological level data 128 | ├── src/ 129 | │ └── [future: AI models] 130 | ├── docs/ 131 | │ └── README.md # This file 132 | ├── fvg.py # FVG zone identifier (current tool) 133 | └── fvgbot.py # Original trading bot (archived) 134 | ``` 135 | 136 | --- 137 | 138 | ## Data Features (Planned) 139 | 140 | ### Input Features for AI Model 141 | 142 | | Feature | Description | Example | 143 | |---------|-------------|---------| 144 | | `current_price` | Current market price | 6758.25 | 145 | | `nearest_fvg_bull_dist` | Distance to nearest bullish FVG | -11.25 pts | 146 | | `nearest_fvg_bear_dist` | Distance to nearest bearish FVG | +51.00 pts | 147 | | `nearest_level_up` | Distance to level above | +41.75 (→ 6800) | 148 | | `nearest_level_down` | Distance to level below | -58.25 (→ 6700) | 149 | | `fvg_bull_size` | Size of nearest bullish gap | 20.25 pts | 150 | | `fvg_bear_size` | Size of nearest bearish gap | 7.50 pts | 151 | | `fvg_bull_age` | Age of bullish gap | 4 bars | 152 | | `fvg_bear_age` | Age of bearish gap | 27 bars | 153 | | `price_momentum` | Rate of price change | +2.5 pts/bar | 154 | | `bars_since_level` | Bars since touching a level | 15 bars | 155 | 156 | ### Target Labels 157 | 158 | - **Target Type:** `FVG_BULL` | `FVG_BEAR` | `LEVEL_UP` | `LEVEL_DOWN` 159 | - **Confidence:** 0.0 - 1.0 (probability of reaching target) 160 | - **Action:** `LONG` | `SHORT` | `HOLD` 161 | 162 | --- 163 | 164 | ## Next Steps (Immediate) 165 | 166 | ### 1. Add Psychological Level Detection 167 | Create a module to identify 100-point levels: 168 | - Detect levels like 6,700, 6,800, 6,900 169 | - Track price behavior near these levels 170 | - Calculate distance to nearest level (up/down) 171 | 172 | ### 2. Feature Engineering Pipeline 173 | Build a data preparation script: 174 | - Combine FVG data + level data + price data 175 | - Calculate all features in table above 176 | - Label historical data with outcomes (did price reach FVG or level first?) 177 | 178 | ### 3. Exploratory Data Analysis (EDA) 179 | Analyze patterns: 180 | - Correlation between gap size and fill probability 181 | - How often price respects levels vs gaps 182 | - Optimal entry/exit timing patterns 183 | 184 | ### 4. Initial Model Prototype 185 | Start simple: 186 | - Binary classifier: "Will price fill this FVG?" (Yes/No) 187 | - Train on historical data with known outcomes 188 | - Evaluate accuracy and iterate 189 | 190 | --- 191 | 192 | ## Technologies & Tools 193 | 194 | ### Current Stack 195 | - **Python 3.x** 196 | - **pandas** - Data manipulation 197 | - **numpy** - Numerical computing 198 | - **CSV** - Data storage 199 | 200 | ### Planned Additions 201 | - **TensorFlow/Keras** or **PyTorch** - Neural network framework 202 | - **scikit-learn** - Feature engineering, preprocessing 203 | - **matplotlib/seaborn** - Visualization 204 | - **TA-Lib** - Technical indicators (if needed) 205 | - **Gymnasium** - Reinforcement learning environment (optional) 206 | 207 | --- 208 | 209 | ## Success Metrics 210 | 211 | ### Model Performance 212 | - **Accuracy:** >65% on predicting correct target (FVG vs level) 213 | - **Precision:** >70% on trade signals (reduce false positives) 214 | - **Sharpe Ratio:** >1.5 in backtesting 215 | - **Win Rate:** >55% of trades profitable 216 | 217 | ### Business Goals 218 | - Automate trading decisions based on levels + gaps 219 | - Reduce emotional trading errors 220 | - Consistent profitability over time 221 | - Scalable to multiple instruments (ES, NQ, etc.) 222 | 223 | ### Final Performance Visualization 224 | **The ultimate test:** Side-by-side equity curve comparison 225 | - **Strategy P&L** - AI-driven FVG/Level trading system 226 | - **Buy & Hold Baseline** - Simple long position from start to end 227 | - **Visual proof** that the strategy outperforms passive holding 228 | - Charts showing cumulative returns, drawdowns, and win rate over time 229 | 230 | --- 231 | 232 | ## Key Questions to Answer with AI 233 | 234 | 1. **Which target is price heading toward?** 235 | - Nearest FVG vs nearest psychological level 236 | - Confidence level for each target 237 | 238 | 2. **When to enter a trade?** 239 | - Price enters FVG zone 240 | - Price bounces off a level 241 | - Combination of both signals 242 | 243 | 3. **When to exit a trade?** 244 | - Gap is filled 245 | - Level is reached 246 | - Stop loss hit (risk management) 247 | 248 | 4. **What is the probability of success?** 249 | - Based on gap size, age, distance 250 | - Based on level strength (historical bounces) 251 | - Based on momentum and volume 252 | 253 | --- 254 | 255 | ## Development Roadmap 256 | 257 | ### Milestone 1: Data Foundation (Week 1-2) 258 | - [x] FVG identification complete 259 | - [ ] Psychological level detection 260 | - [ ] Feature engineering pipeline 261 | - [ ] Labeled dataset creation 262 | 263 | ### Milestone 2: Model Prototype (Week 3-4) 264 | - [ ] Simple binary classifier (FVG fill prediction) 265 | - [ ] Baseline model evaluation 266 | - [ ] Feature importance analysis 267 | - [ ] Initial backtesting framework 268 | 269 | ### Milestone 3: Advanced Model (Week 5-6) 270 | - [ ] Multi-class prediction (target selection) 271 | - [ ] Deep neural network architecture 272 | - [ ] Hyperparameter tuning 273 | - [ ] Cross-validation and robustness testing 274 | 275 | ### Milestone 4: Production Ready (Week 7-8) 276 | - [ ] Real-time prediction pipeline 277 | - [ ] Integration with trading platform 278 | - [ ] Monitoring and alerting 279 | - [ ] Performance dashboard 280 | 281 | --- 282 | 283 | ## Research & Learning Resources 284 | 285 | ### Trading Concepts 286 | - Fair Value Gaps (FVG) / Liquidity Voids 287 | - Institutional Order Flow 288 | - Market Structure (higher highs, higher lows) 289 | - Support/Resistance levels 290 | 291 | ### AI/ML Techniques 292 | - Time series prediction 293 | - Reinforcement learning for trading 294 | - LSTM/GRU for sequential data 295 | - Feature engineering for financial data 296 | 297 | --- 298 | 299 | ## Notes & Observations 300 | 301 | ### What Makes This Different? 302 | - **Not indicator-based** (no RSI, MACD, etc.) 303 | - **Level-focused** (psychological price points) 304 | - **Gap-focused** (price inefficiencies) 305 | - **Simple and explainable** (not a black box) 306 | 307 | ### Trading Psychology 308 | - Price doesn't move randomly 309 | - It seeks balance and fills gaps 310 | - Round numbers matter psychologically 311 | - Patterns repeat over time 312 | 313 | --- 314 | 315 | ## Contributing & Collaboration 316 | 317 | This is a personal trading system development project. As we build this: 318 | - Document all assumptions and observations 319 | - Test every hypothesis with data 320 | - Keep the system simple and explainable 321 | - Iterate based on results 322 | 323 | --- 324 | 325 | ## License & Disclaimer 326 | 327 | **This is an experimental trading system for educational purposes.** 328 | 329 | Trading involves risk. Past performance does not guarantee future results. 330 | This AI model should not be used with real money without extensive testing and validation. 331 | 332 | --- 333 | 334 | ## Contact & Questions 335 | 336 | For questions about this project or trading methodology: 337 | - Review code in [fvg.py](../fvg.py) 338 | - Check data outputs in [data/fvg_zones.csv](../data/fvg_zones.csv) 339 | - Analyze patterns in historical data 340 | 341 | --- 342 | 343 | **Last Updated:** November 16, 2025 344 | **Version:** 0.1.0 - Foundation Phase 345 | **Status:** Active Development 346 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Claude Trader - NinjaTrader Strategy 2 | 3 | ## Current Status: ✅ WORKING 4 | 5 | An automated trading strategy for NinjaTrader 8 that receives trade signals from CSV files and executes limit orders with stop loss and take profit levels. 6 | 7 | --- 8 | 9 | ## How It Works 10 | 11 | ### Signal Flow 12 | ``` 13 | Python/Agent → trade_signals.csv → ClaudeTrader.cs → NinjaTrader Execution 14 | (generates) (CSV file) (NinjaScript) (broker orders) 15 | ``` 16 | 17 | ### Trade Execution Process 18 | 19 | 1. **Signal Detection**: ClaudeTrader monitors `trade_signals.csv` every 2 seconds 20 | 2. **Limit Order Entry**: Places limit order at specified entry price 21 | 3. **Position Management**: Once filled, automatically sets: 22 | - Stop Loss (as stop market order) 23 | - Take Profit (as limit order) 24 | 4. **Trade Logging**: Logs executed trades to `trades_taken.csv` 25 | 26 | --- 27 | 28 | ## CSV File Format 29 | 30 | ### Input: `trade_signals.csv` 31 | Located at: `C:\Users\Joshua\Documents\Projects\Claude Trader\data\trade_signals.csv` 32 | 33 | ```csv 34 | DateTime,Direction,Entry_Price,Stop_Loss,Take_Profit 35 | 11/25/2025 14:30:00,LONG,6700.00,6690.00,6710.00 36 | 11/25/2025 15:45:00,SHORT,6720.00,6730.00,6705.00 37 | ``` 38 | 39 | **Fields:** 40 | - `DateTime`: Signal timestamp 41 | - `Direction`: LONG or SHORT 42 | - `Entry_Price`: Limit order price 43 | - `Stop_Loss`: Stop loss price (absolute price level) 44 | - `Take_Profit`: Take profit price (absolute price level) 45 | 46 | ### Output: `trades_taken.csv` 47 | Located at: `C:\Users\Joshua\Documents\Projects\Claude Trader\data\trades_taken.csv` 48 | 49 | ```csv 50 | DateTime,Direction,Entry_Price 51 | 11/25/2025 14:32:15,LONG,6700.50 52 | 11/25/2025 15:47:22,SHORT,6719.75 53 | ``` 54 | 55 | --- 56 | 57 | ## Features 58 | 59 | ### Current Working Features ✅ 60 | - **CSV Signal Monitoring**: Checks for new signals every 2 seconds 61 | - **Limit Order Entry**: Places limit orders at specified price levels 62 | - **Automatic Stop Loss**: Sets stop market orders at CSV-specified prices 63 | - **Automatic Take Profit**: Sets limit orders at CSV-specified prices 64 | - **Duplicate Prevention**: Tracks processed signals to avoid re-entry 65 | - **Position Tracking**: Won't take new signals while in position 66 | - **Order State Management**: Handles rejected/cancelled orders gracefully 67 | - **Trade Logging**: Records all executed trades with timestamps 68 | - **Detailed Logging**: Comprehensive output for debugging 69 | 70 | ### Configurable Parameters 71 | - **Contract Quantity**: Number of contracts per trade (default: 2) 72 | - **File Check Interval**: How often to check for signals (default: 2 seconds) 73 | - **File Paths**: Customizable signal and log file locations 74 | 75 | --- 76 | 77 | ## Installation 78 | 79 | ### 1. Copy Strategy to NinjaTrader 80 | ``` 81 | Copy: src/claudetrader.cs 82 | To: Documents\NinjaTrader 8\bin\Custom\Strategies\ 83 | ``` 84 | 85 | ### 2. Compile in NinjaTrader 86 | 1. Open NinjaTrader 8 87 | 2. Tools → Edit NinjaScript → Strategy 88 | 3. Find "ClaudeTrader" 89 | 4. Click Compile (F5) 90 | 91 | ### 3. Create Data Directories 92 | ``` 93 | C:\Users\Joshua\Documents\Projects\Claude Trader\data\ 94 | ├── trade_signals.csv (input - agent writes here) 95 | └── trades_taken.csv (output - strategy writes here) 96 | ``` 97 | 98 | ### 4. Initialize Signal File 99 | Create `trade_signals.csv` with header: 100 | ```csv 101 | DateTime,Direction,Entry_Price,Stop_Loss,Take_Profit 102 | 103 | ``` 104 | 105 | --- 106 | 107 | ## Usage 108 | 109 | ### 1. Apply Strategy to Chart 110 | 1. Open NinjaTrader chart (e.g., NQ 12-25) 111 | 2. Strategies → ClaudeTrader 112 | 3. Configure parameters: 113 | - **Contract Quantity**: 2 (or desired size) 114 | - **Signals File Path**: Verify correct path 115 | - **File Check Interval**: 2 seconds 116 | 4. Click OK 117 | 118 | ### 2. Send Trade Signals 119 | Add a line to `trade_signals.csv`: 120 | ```csv 121 | DateTime,Direction,Entry_Price,Stop_Loss,Take_Profit 122 | 11/25/2025 14:30:00,LONG,25100.00,25090.00,25120.00 123 | ``` 124 | 125 | ### 3. Strategy Execution 126 | - Strategy detects new signal within 2 seconds 127 | - Places LONG limit order at 25100.00 128 | - Once filled: 129 | - Stop Loss at 25090.00 130 | - Take Profit at 25120.00 131 | - Clears signal file (keeps header) 132 | - Logs trade to `trades_taken.csv` 133 | 134 | --- 135 | 136 | ## Output Log Example 137 | 138 | ``` 139 | ClaudeTrader Initialized - Monitoring signals every 2 seconds 140 | Signals File: C:\Users\Joshua\Documents\Projects\Claude Trader\data\trade_signals.csv 141 | [SIGNAL] LONG LIMIT @ 25100.00 (2 contracts) 142 | Target SL: 25090.00 | Target TP: 25120.00 143 | [ORDER UPDATE] CT_Long | State: Submitted | Price: Limit=25100.00, Stop=0 | Qty: 2/0 144 | [ORDER WORKING] CT_Long is now active 145 | [FILLED] LONG 1 contracts @ 25100.00 146 | Position Size: 1 | Target: 2 147 | [DEBUG] Checking position: Qty=1, Target=2, Match=False 148 | [WAITING] Position partially filled (1/2) - waiting for full fill before placing SL/TP 149 | [FILLED] LONG 1 contracts @ 25100.00 150 | Position Size: 2 | Target: 2 151 | [DEBUG] Checking position: Qty=2, Target=2, Match=True 152 | [SUBMITTING] Placing SL and TP orders now... 153 | [LONG EXIT] Submitting SL @ 25090.00 and TP @ 25120.00 for 2 contracts 154 | [ORDERS SUBMITTED] Long exit orders sent to broker 155 | [ORDER UPDATE] SL | State: Working | Price: Limit=0, Stop=25090.00 | Qty: 2/0 156 | [ORDER UPDATE] TP | State: Working | Price: Limit=25120.00, Stop=0 | Qty: 2/0 157 | Trade logged to file: LONG @ 25100.00 158 | [EXIT TP] 2 contracts @ 25120.00 | P/L: $40.00 159 | ``` 160 | 161 | --- 162 | 163 | ## Configuration 164 | 165 | ### Strategy Parameters (NinjaTrader UI) 166 | 167 | | Parameter | Default | Description | 168 | |-----------|---------|-------------| 169 | | Signals File Path | `C:\Users\Joshua\Documents\Projects\Claude Trader\data\trade_signals.csv` | Input signal file | 170 | | Trades Log File Path | `C:\Users\Joshua\Documents\Projects\Claude Trader\data\trades_taken.csv` | Trade log output | 171 | | File Check Interval | 2 seconds | How often to check for signals | 172 | | Contract Quantity | 2 | Number of contracts per trade | 173 | 174 | ### Built-in Settings (in code) 175 | - **Calculate**: OnEachTick (real-time monitoring) 176 | - **Order Fill Resolution**: Standard 177 | - **Stop Target Handling**: PerEntryExecution 178 | - **Entry Handling**: AllEntries 179 | - **Entries Per Direction**: 1 180 | 181 | --- 182 | 183 | ## Project Structure 184 | 185 | ``` 186 | Claude Trader/ 187 | ├── src/ 188 | │ └── claudetrader.cs # NinjaScript strategy 189 | ├── data/ 190 | │ ├── trade_signals.csv # Input: trade signals 191 | │ └── trades_taken.csv # Output: executed trades 192 | ├── README.md # This file 193 | └── QUICKSTART.md # Agent setup guide 194 | ``` 195 | 196 | --- 197 | 198 | ## Safety Features 199 | 200 | ### Duplicate Prevention 201 | - Tracks processed signals by unique ID (DateTime + Direction) 202 | - Won't process same signal twice 203 | - Clears signal file after processing 204 | 205 | ### Position Management 206 | - Only one position at a time 207 | - Won't accept new signals while in position 208 | - Won't accept new signals while limit order pending 209 | 210 | ### Order Error Handling 211 | - Detects rejected orders 212 | - Detects cancelled orders 213 | - Resets state on order failures 214 | - Detailed error logging 215 | 216 | ### Risk Management 217 | - Stop loss submitted FIRST (highest priority) 218 | - Take profit submitted after stop loss 219 | - Uses actual position quantity for exit orders 220 | - Waits for full position fill before placing exits 221 | 222 | --- 223 | 224 | ## Troubleshooting 225 | 226 | ### Signal Not Detected 227 | - Check file path is correct 228 | - Verify CSV format matches exactly 229 | - Ensure file has been modified (timestamp check) 230 | - Look for "ERROR reading signals file" in output 231 | 232 | ### Orders Not Placed 233 | - Check output for "[ORDERS SUBMITTED]" message 234 | - Look for "[ORDER UPDATE]" messages 235 | - Check for "[ERROR] Order rejected" messages 236 | - Verify SL/TP prices are valid for direction: 237 | - LONG: SL < Entry < TP 238 | - SHORT: TP < Entry < SL 239 | 240 | ### Partial Fills 241 | - Strategy waits for full position before placing SL/TP 242 | - Look for "[WAITING] Position partially filled" message 243 | - SL/TP placed only when `Position.Quantity == ContractQuantity` 244 | 245 | ### File Permission Errors 246 | - Ensure data directory exists 247 | - Check Windows file permissions 248 | - Close CSV files in Excel before strategy runs 249 | 250 | --- 251 | 252 | ## Things To Do 253 | 254 | ### 1. Live Market Context Persistence - Market Ideas File 255 | **Problem**: Agent makes fresh assessments on every bar without maintaining market context. It forgets what it just analyzed (goldfish memory). 256 | 257 | **What's Needed**: 258 | Save a file with **what the agent currently sees in the market** - its live market assessment of long and short trade ideas. 259 | 260 | **Implementation**: 261 | - Create `data/market_ideas.json` file that stores: 262 | - **Current LONG trade ideas** the agent sees right now 263 | - **Current SHORT trade ideas** the agent sees right now 264 | - Key support/resistance levels identified 265 | - Market bias (bullish/bearish/neutral) 266 | - Recent confluence zones 267 | 268 | **Workflow on Each New Bar**: 269 | 1. **Load** existing `market_ideas.json` 270 | 2. **Send to agent**: "Here's what you saw last bar: [market_ideas]. New bar just closed: [OHLC data]. Update your assessment." 271 | 3. **Agent updates/refines** existing ideas instead of starting from scratch 272 | 4. **Save** updated market_ideas.json back to file 273 | 274 | **Key Point**: Instead of making new assessments every time, we give the agent a good starting point - what it already knows about the market. 275 | 276 | **Benefits**: 277 | - Agent maintains continuity across bars 278 | - Faster analysis (updates existing ideas vs. full analysis) 279 | - More consistent trade ideas 280 | - Tracks evolving market structure 281 | - Reduces API calls 282 | 283 | --- 284 | 285 | ### 2. Patience in Entry Execution - "It's Okay to Wait and See" 286 | **Problem**: Current system enters immediately when price is near a zone, even when the chart is screaming the opposite direction. 287 | 288 | **Example Scenario**: 289 | ``` 290 | Price near long zone above 291 | BUT chart is screaming SHORT 292 | ❌ Current: Takes long entry anyway 293 | ✅ Desired: WAIT and see what develops 294 | ``` 295 | 296 | **Philosophy**: 297 | **It's okay to wait and see.** If price is near a zone but the chart structure/momentum disagrees, don't force the trade. Wait for alignment. 298 | 299 | **Implementation Ideas**: 300 | - Add "signal_status" field to trade_signals.csv: 301 | - `WATCHING`: Zone identified but waiting for confirmation 302 | - `READY`: All conditions aligned, safe to enter 303 | - `ACTIVE`: Order placed/filled 304 | 305 | - Agent workflow: 306 | 1. Identifies potential zone/setup → Status: `WATCHING` 307 | 2. Monitors price action and momentum 308 | 3. **Only** when everything aligns → Status: `READY` 309 | 4. ClaudeTrader.cs **only acts on `READY` signals** 310 | 311 | **Wait For**: 312 | - Price action confirmation (rejection wicks, engulfing candles, etc.) 313 | - Momentum alignment (not fighting the trend) 314 | - Structure agreement (higher highs for longs, lower lows for shorts) 315 | - No conflicting signals 316 | 317 | **Benefits**: 318 | - Better entry timing 319 | - Fewer false signals 320 | - Trade WITH the flow, not against it 321 | - Higher win rate through patience 322 | - Avoid "forcing" trades that don't set up properly 323 | 324 | ### 3. Future Enhancements 325 | - [ ] Multi-timeframe context integration 326 | - [ ] Dynamic position sizing based on setup quality 327 | - [ ] Partial exit capabilities (scale out) 328 | - [ ] Time-based filters (avoid news events) 329 | - [ ] Session-based trading rules 330 | - [ ] Advanced order types (trailing stops) 331 | - [ ] Performance analytics dashboard 332 | - [ ] Integration with backtesting framework 333 | 334 | --- 335 | 336 | ## Development Notes 337 | 338 | ### Version History 339 | - **v1.0** - Initial market order implementation 340 | - **v1.1** - Changed to limit orders with CSV-based SL/TP 341 | - **v1.2** - Added partial fill handling 342 | - **v1.3** - Enhanced logging and error handling (current) 343 | 344 | ### Known Issues 345 | - None currently 346 | 347 | ### Testing Recommendations 348 | 1. Test with 1 contract first 349 | 2. Verify SL/TP prices are correct for direction 350 | 3. Monitor output window for detailed logs 351 | 4. Check both CSV files after each trade 352 | 5. Use Strategy Analyzer for backtesting 353 | 354 | --- 355 | 356 | ## Support & Documentation 357 | 358 | - **Strategy Code**: [src/claudetrader.cs](src/claudetrader.cs) 359 | - **Agent Setup**: [QUICKSTART.md](QUICKSTART.md) 360 | - **NinjaTrader Docs**: https://ninjatrader.com/support/helpGuides/nt8/ 361 | 362 | --- 363 | 364 | ## License 365 | 366 | Proprietary - For personal trading use only. 367 | 368 | --- 369 | 370 | **Last Updated**: November 25, 2025 371 | **Status**: Production Ready ✅ 372 | **Tested On**: NinjaTrader 8, NQ Futures 373 | -------------------------------------------------------------------------------- /ninjascripts/claudetrader.cs: -------------------------------------------------------------------------------- 1 | #region Using declarations 2 | using System; 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using System.Windows; 10 | using System.Windows.Input; 11 | using System.Windows.Media; 12 | using System.Xml.Serialization; 13 | using System.IO; 14 | using NinjaTrader.Cbi; 15 | using NinjaTrader.Gui; 16 | using NinjaTrader.Gui.Chart; 17 | using NinjaTrader.Gui.SuperDom; 18 | using NinjaTrader.Gui.Tools; 19 | using NinjaTrader.Data; 20 | using NinjaTrader.NinjaScript; 21 | using NinjaTrader.Core.FloatingPoint; 22 | using NinjaTrader.NinjaScript.Indicators; 23 | using NinjaTrader.NinjaScript.DrawingTools; 24 | #endregion 25 | 26 | //This namespace holds Strategies in this folder and is required. Do not change it. 27 | namespace NinjaTrader.NinjaScript.Strategies 28 | { 29 | public class ClaudeTrader : Strategy 30 | { 31 | #region Variables 32 | 33 | // Signal file monitoring 34 | private string signalsFilePath = @"C:\Users\Joshua\Documents\Projects\Claude Trader\data\trade_signals.csv"; 35 | private string tradesLogFilePath = @"C:\Users\Joshua\Documents\Projects\Claude Trader\data\trades_taken.csv"; 36 | private DateTime lastFileCheckTime = DateTime.MinValue; 37 | private HashSet processedSignals = new HashSet(); 38 | private DateTime lastFileModified = DateTime.MinValue; 39 | 40 | // Current signal being processed 41 | private string currentSignalId = ""; 42 | private double signalEntryPrice = 0; 43 | private double signalStopLoss = 0; 44 | private double signalTakeProfit = 0; 45 | private string signalDirection = ""; 46 | private DateTime signalDateTime = DateTime.MinValue; 47 | 48 | // Position tracking 49 | private bool inPosition = false; 50 | private bool hasLimitOrder = false; 51 | private DateTime entryTime = DateTime.MinValue; 52 | private double actualEntryPrice = 0; 53 | 54 | // File check interval (seconds) 55 | private int fileCheckInterval = 2; 56 | 57 | // Position sizing 58 | private int contractQuantity = 12; // Total contracts to trade 59 | 60 | #endregion 61 | 62 | protected override void OnStateChange() 63 | { 64 | if (State == State.SetDefaults) 65 | { 66 | Description = @"ClaudeTrader - Receives signals from trade_signals.csv"; 67 | Name = "ClaudeTrader"; 68 | Calculate = Calculate.OnEachTick; 69 | EntriesPerDirection = 1; 70 | EntryHandling = EntryHandling.AllEntries; 71 | IsExitOnSessionCloseStrategy = true; 72 | ExitOnSessionCloseSeconds = 30; 73 | IsFillLimitOnTouch = false; 74 | MaximumBarsLookBack = MaximumBarsLookBack.TwoHundredFiftySix; 75 | OrderFillResolution = OrderFillResolution.Standard; 76 | Slippage = 0; 77 | StartBehavior = StartBehavior.WaitUntilFlat; 78 | TimeInForce = TimeInForce.Gtc; 79 | TraceOrders = false; 80 | RealtimeErrorHandling = RealtimeErrorHandling.StopCancelClose; 81 | StopTargetHandling = StopTargetHandling.PerEntryExecution; 82 | BarsRequiredToTrade = 1; 83 | IsInstantiatedOnEachOptimizationIteration = true; 84 | 85 | // Parameters 86 | SignalsFilePath = @"C:\Users\Joshua\Documents\Projects\Claude Trader\data\trade_signals.csv"; 87 | TradesLogFilePath = @"C:\Users\Joshua\Documents\Projects\Claude Trader\data\trades_taken.csv"; 88 | FileCheckInterval = 2; 89 | ContractQuantity = 2; 90 | } 91 | else if (State == State.Configure) 92 | { 93 | // Nothing to configure 94 | } 95 | else if (State == State.DataLoaded) 96 | { 97 | // FORCE CORRECT FILE PATHS (override any cached config) 98 | signalsFilePath = @"C:\Users\Joshua\Documents\Projects\Claude Trader\data\trade_signals.csv"; 99 | tradesLogFilePath = @"C:\Users\Joshua\Documents\Projects\Claude Trader\data\trades_taken.csv"; 100 | 101 | // Initialize processed signals tracking 102 | processedSignals = new HashSet(); 103 | 104 | // Force immediate file check by setting lastFileCheckTime to far past 105 | lastFileCheckTime = DateTime.MinValue; 106 | 107 | Print($"ClaudeTrader Initialized - Monitoring signals every {FileCheckInterval} seconds"); 108 | Print($"Signals File: {signalsFilePath}"); 109 | } 110 | } 111 | 112 | protected override void OnBarUpdate() 113 | { 114 | if (CurrentBar < BarsRequiredToTrade) 115 | return; 116 | 117 | // Check for new signals periodically 118 | if ((DateTime.Now - lastFileCheckTime).TotalSeconds >= FileCheckInterval) 119 | { 120 | CheckForNewSignals(); 121 | lastFileCheckTime = DateTime.Now; 122 | } 123 | 124 | // Manage existing position 125 | ManagePosition(); 126 | } 127 | 128 | private void CheckForNewSignals() 129 | { 130 | if (!File.Exists(signalsFilePath)) 131 | { 132 | Print($"Signal file not found: {signalsFilePath}"); 133 | return; 134 | } 135 | 136 | try 137 | { 138 | // Check if file has been modified 139 | DateTime currentModTime = File.GetLastWriteTime(signalsFilePath); 140 | 141 | if (currentModTime <= lastFileModified) 142 | return; // File hasn't changed 143 | 144 | lastFileModified = currentModTime; 145 | 146 | // Read all lines from the CSV file 147 | string[] lines = File.ReadAllLines(signalsFilePath); 148 | 149 | // Skip header row 150 | if (lines.Length <= 1) 151 | return; 152 | 153 | // Process the last (most recent) signal 154 | string lastLine = lines[lines.Length - 1]; 155 | 156 | // Skip empty lines 157 | if (string.IsNullOrWhiteSpace(lastLine)) 158 | return; 159 | 160 | ProcessSignalLine(lastLine); 161 | 162 | // Clear the signal file after processing (keep only header) 163 | ClearSignalsFile(); 164 | } 165 | catch (Exception ex) 166 | { 167 | Print($"ERROR reading signals file: {ex.Message}"); 168 | } 169 | } 170 | 171 | private void ClearSignalsFile() 172 | { 173 | try 174 | { 175 | // Rewrite file with only header row 176 | using (StreamWriter sw = new StreamWriter(signalsFilePath, false)) 177 | { 178 | sw.WriteLine("DateTime,Direction,Entry_Price,Stop_Loss,Take_Profit"); 179 | } 180 | } 181 | catch (Exception ex) 182 | { 183 | Print($"ERROR clearing signals file: {ex.Message}"); 184 | } 185 | } 186 | 187 | private void ProcessSignalLine(string line) 188 | { 189 | try 190 | { 191 | // Parse CSV line: DateTime,Direction,Entry_Price,Stop_Loss,Take_Profit 192 | string[] parts = line.Split(','); 193 | 194 | if (parts.Length < 5) 195 | { 196 | Print($"ERROR: Invalid signal format (expected 5 fields, got {parts.Length})"); 197 | Print($"Expected: DateTime,Direction,Entry_Price,Stop_Loss,Take_Profit"); 198 | return; 199 | } 200 | 201 | // Create unique signal ID based on datetime and direction 202 | string signalId = $"{parts[0]}_{parts[1]}"; 203 | 204 | // Skip if already processed 205 | if (processedSignals.Contains(signalId)) 206 | { 207 | Print($"Signal already processed: {signalId}"); 208 | return; 209 | } 210 | 211 | // Skip if already in position or has pending limit order 212 | if (Position.MarketPosition != MarketPosition.Flat || hasLimitOrder) 213 | { 214 | Print($"Already in position or has pending order, skipping signal: {signalId}"); 215 | return; 216 | } 217 | 218 | // Parse signal data 219 | DateTime.TryParse(parts[0].Trim(), out signalDateTime); 220 | signalDirection = parts[1].Trim(); // LONG or SHORT 221 | double.TryParse(parts[2].Trim(), out signalEntryPrice); 222 | double.TryParse(parts[3].Trim(), out signalStopLoss); 223 | double.TryParse(parts[4].Trim(), out signalTakeProfit); 224 | 225 | // Store signal information 226 | currentSignalId = signalId; 227 | 228 | // Execute trade based on direction 229 | if (signalDirection.ToUpper() == "LONG") 230 | { 231 | ExecuteLongEntry(); 232 | } 233 | else if (signalDirection.ToUpper() == "SHORT") 234 | { 235 | ExecuteShortEntry(); 236 | } 237 | else 238 | { 239 | Print($"ERROR: Unknown direction '{signalDirection}' in signal"); 240 | return; 241 | } 242 | 243 | // Mark signal as processed 244 | processedSignals.Add(signalId); 245 | } 246 | catch (Exception ex) 247 | { 248 | Print($"ERROR processing signal: {ex.Message}"); 249 | } 250 | } 251 | 252 | private void ExecuteLongEntry() 253 | { 254 | // Place MARKET order for immediate entry 255 | EnterLong(0, contractQuantity, "CT_Long"); 256 | hasLimitOrder = false; // Using market order, not limit 257 | Print($"[SIGNAL] LONG MARKET ORDER ({contractQuantity} contracts)"); 258 | Print($" Reference Entry: {signalEntryPrice:F2}"); 259 | Print($" Target SL: {signalStopLoss:F2} | Target TP: {signalTakeProfit:F2}"); 260 | } 261 | 262 | private void ExecuteShortEntry() 263 | { 264 | // Place MARKET order for immediate entry 265 | EnterShort(0, contractQuantity, "CT_Short"); 266 | hasLimitOrder = false; // Using market order, not limit 267 | Print($"[SIGNAL] SHORT MARKET ORDER ({contractQuantity} contracts)"); 268 | Print($" Reference Entry: {signalEntryPrice:F2}"); 269 | Print($" Target SL: {signalStopLoss:F2} | Target TP: {signalTakeProfit:F2}"); 270 | } 271 | 272 | private void ManagePosition() 273 | { 274 | if (Position.MarketPosition == MarketPosition.Flat && inPosition) 275 | { 276 | inPosition = false; 277 | hasLimitOrder = false; 278 | currentSignalId = ""; 279 | } 280 | } 281 | 282 | protected override void OnExecutionUpdate(Execution execution, string executionId, double price, int quantity, MarketPosition marketPosition, string orderId, DateTime time) 283 | { 284 | if (execution.Order != null && execution.Order.OrderState == OrderState.Filled) 285 | { 286 | // Check if this is an entry order 287 | if (execution.Order.Name == "CT_Long" || execution.Order.Name == "CT_Short") 288 | { 289 | actualEntryPrice = execution.Price; 290 | entryTime = execution.Time; 291 | inPosition = true; 292 | hasLimitOrder = false; 293 | 294 | Print($"[FILLED] {signalDirection} {quantity} contracts @ {actualEntryPrice:F2}"); 295 | Print($" Position Size: {Position.Quantity} | Target: {contractQuantity}"); 296 | 297 | // Only set SL/TP when position is fully filled 298 | Print($"[DEBUG] Checking position: Qty={Position.Quantity}, Target={contractQuantity}, Match={Position.Quantity == contractQuantity}"); 299 | 300 | if (Position.Quantity == contractQuantity) 301 | { 302 | Print($"[SUBMITTING] Placing SL and TP orders now..."); 303 | 304 | // Set profit targets and stop loss based on CSV signal prices 305 | if (Position.MarketPosition == MarketPosition.Long) 306 | { 307 | Print($"[LONG EXIT] Submitting SL @ {signalStopLoss:F2} and TP @ {signalTakeProfit:F2} for {Position.Quantity} contracts"); 308 | 309 | // Submit stop loss FIRST as a stop market order (highest priority) 310 | ExitLongStopMarket(0, true, Position.Quantity, signalStopLoss, "SL", "CT_Long"); 311 | // Then place limit order for profit target 312 | ExitLongLimit(0, true, Position.Quantity, signalTakeProfit, "TP", "CT_Long"); 313 | 314 | Print($"[ORDERS SUBMITTED] Long exit orders sent to broker"); 315 | } 316 | else if (Position.MarketPosition == MarketPosition.Short) 317 | { 318 | Print($"[SHORT EXIT] Submitting SL @ {signalStopLoss:F2} and TP @ {signalTakeProfit:F2} for {Position.Quantity} contracts"); 319 | 320 | // Submit stop loss FIRST as a stop market order (highest priority) 321 | ExitShortStopMarket(0, true, Position.Quantity, signalStopLoss, "SL", "CT_Short"); 322 | // Then place limit order for profit target 323 | ExitShortLimit(0, true, Position.Quantity, signalTakeProfit, "TP", "CT_Short"); 324 | 325 | Print($"[ORDERS SUBMITTED] Short exit orders sent to broker"); 326 | } 327 | 328 | // Log trade to CSV 329 | LogTradeToFile(signalDirection, actualEntryPrice); 330 | } 331 | else 332 | { 333 | Print($"[WAITING] Position partially filled ({Position.Quantity}/{contractQuantity}) - waiting for full fill before placing SL/TP"); 334 | } 335 | } 336 | 337 | // Check if this is an exit order (TP or SL) 338 | else if (execution.Order.Name == "TP" || execution.Order.Name == "SL") 339 | { 340 | double exitPrice = execution.Price; 341 | double pnl = 0; 342 | 343 | if (execution.Order.Name == "TP") 344 | { 345 | if (signalDirection.ToUpper() == "LONG") 346 | pnl = (exitPrice - actualEntryPrice) * quantity; 347 | else 348 | pnl = (actualEntryPrice - exitPrice) * quantity; 349 | 350 | Print($"[EXIT TP] {quantity} contracts @ {exitPrice:F2} | P/L: ${pnl:F2}"); 351 | } 352 | else if (execution.Order.Name == "SL") 353 | { 354 | if (signalDirection.ToUpper() == "LONG") 355 | pnl = (exitPrice - actualEntryPrice) * quantity; 356 | else 357 | pnl = (actualEntryPrice - exitPrice) * quantity; 358 | 359 | Print($"[EXIT SL] STOP LOSS @ {exitPrice:F2} | {quantity} contracts | P/L: ${pnl:F2}"); 360 | } 361 | } 362 | } 363 | } 364 | 365 | protected override void OnOrderUpdate(Order order, double limitPrice, double stopPrice, int quantity, int filled, double averageFillPrice, OrderState orderState, DateTime time, ErrorCode error, string comment) 366 | { 367 | // Log all order state changes for debugging 368 | Print($"[ORDER UPDATE] {order.Name} | State: {orderState} | Price: Limit={limitPrice}, Stop={stopPrice} | Qty: {quantity}/{filled}"); 369 | 370 | if (orderState == OrderState.Rejected) 371 | { 372 | Print($"[ERROR] Order rejected: {order.Name} - {comment} | Error: {error}"); 373 | 374 | // If entry limit order was rejected or cancelled, reset flag 375 | if (order.Name == "CT_Long" || order.Name == "CT_Short") 376 | { 377 | hasLimitOrder = false; 378 | } 379 | } 380 | else if (orderState == OrderState.Cancelled) 381 | { 382 | // If entry limit order was cancelled, reset flag 383 | if (order.Name == "CT_Long" || order.Name == "CT_Short") 384 | { 385 | hasLimitOrder = false; 386 | Print($"[INFO] Entry limit order cancelled: {order.Name}"); 387 | } 388 | } 389 | else if (orderState == OrderState.Working) 390 | { 391 | Print($"[ORDER WORKING] {order.Name} is now active"); 392 | } 393 | } 394 | 395 | protected override void OnPositionUpdate(Position position, double averagePrice, int quantity, MarketPosition marketPosition) 396 | { 397 | if (marketPosition == MarketPosition.Flat) 398 | { 399 | inPosition = false; 400 | hasLimitOrder = false; 401 | } 402 | } 403 | 404 | private void LogTradeToFile(string direction, double entryPrice) 405 | { 406 | try 407 | { 408 | bool fileExists = File.Exists(tradesLogFilePath); 409 | 410 | using (StreamWriter sw = new StreamWriter(tradesLogFilePath, true)) 411 | { 412 | // Write header if file doesn't exist 413 | if (!fileExists) 414 | { 415 | sw.WriteLine("DateTime,Direction,Entry_Price"); 416 | } 417 | 418 | // Write trade data 419 | string timestamp = DateTime.Now.ToString("MM/dd/yyyy HH:mm:ss"); 420 | sw.WriteLine($"{timestamp},{direction},{entryPrice:F2}"); 421 | } 422 | 423 | Print($"Trade logged to file: {direction} @ {entryPrice:F2}"); 424 | } 425 | catch (Exception ex) 426 | { 427 | Print($"ERROR logging trade to file: {ex.Message}"); 428 | } 429 | } 430 | 431 | #region Properties 432 | 433 | [NinjaScriptProperty] 434 | [Display(Name="Signals File Path", Description="Path to trade_signals.csv file", Order=1, GroupName="ClaudeTrader Parameters")] 435 | public string SignalsFilePath 436 | { 437 | get { return signalsFilePath; } 438 | set { signalsFilePath = value; } 439 | } 440 | 441 | [NinjaScriptProperty] 442 | [Display(Name="Trades Log File Path", Description="Path to trades_taken.csv file", Order=2, GroupName="ClaudeTrader Parameters")] 443 | public string TradesLogFilePath 444 | { 445 | get { return tradesLogFilePath; } 446 | set { tradesLogFilePath = value; } 447 | } 448 | 449 | [NinjaScriptProperty] 450 | [Range(1, 60)] 451 | [Display(Name="File Check Interval", Description="Interval in seconds to check for new signals", Order=3, GroupName="ClaudeTrader Parameters")] 452 | public int FileCheckInterval 453 | { 454 | get { return fileCheckInterval; } 455 | set { fileCheckInterval = value; } 456 | } 457 | 458 | [NinjaScriptProperty] 459 | [Range(1, 100)] 460 | [Display(Name="Contract Quantity", Description="Number of contracts to trade", Order=4, GroupName="ClaudeTrader Parameters")] 461 | public int ContractQuantity 462 | { 463 | get { return contractQuantity; } 464 | set { contractQuantity = Math.Max(1, value); } 465 | } 466 | 467 | #endregion 468 | } 469 | } 470 | -------------------------------------------------------------------------------- /docs/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Claude NQ Trading Agent - System Architecture 2 | 3 | ## System Overview 4 | 5 | **Autonomous trading system that uses Claude's reasoning capabilities to make NQ futures trading decisions based on Fair Value Gaps and psychological levels.** 6 | 7 | --- 8 | 9 | ## Design Principles 10 | 11 | 1. **Reasoning Over Training**: Claude analyzes context, not pattern matching 12 | 2. **Explainability**: Every decision has transparent logic 13 | 3. **Adaptability**: Learning through memory, not gradient descent 14 | 4. **Safety First**: Stop losses mandatory, risk limits enforced 15 | 5. **Production Ready**: Direct integration with NinjaTrader 16 | 17 | --- 18 | 19 | ## Architecture Diagram 20 | 21 | ``` 22 | ┌─────────────────────────────────────────────────────────┐ 23 | │ DATA SOURCES │ 24 | │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 25 | │ │ Historical │ │ LiveFeed │ │ FairValueGaps│ │ 26 | │ │ Data (OHLC) │ │ (Real-time) │ │ (FVG Zones) │ │ 27 | │ └──────────────┘ └──────────────┘ └──────────────┘ │ 28 | └─────────────────────────────────────────────────────────┘ 29 | ↓ 30 | ┌─────────────────────────────────────────────────────────┐ 31 | │ ANALYSIS LAYER │ 32 | │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 33 | │ │ FVG Analyzer │ │ Level │ │ Market │ │ 34 | │ │ (Parse FVGs) │ │ Detector │ │ Context │ │ 35 | │ └──────────────┘ └──────────────┘ └──────────────┘ │ 36 | └─────────────────────────────────────────────────────────┘ 37 | ↓ 38 | ┌─────────────────────────────────────────────────────────┐ 39 | │ INTELLIGENCE LAYER (CLAUDE) │ 40 | │ ┌─────────────────────────────────────────────────┐ │ 41 | │ │ Claude Trading Agent │ │ 42 | │ │ - Analyzes FVG confluence with levels │ │ 43 | │ │ - Reviews past trade outcomes from memory │ │ 44 | │ │ - Reasons about setup quality │ │ 45 | │ │ - Calculates risk/reward │ │ 46 | │ │ - Makes trade decision with explanation │ │ 47 | │ └─────────────────────────────────────────────────┘ │ 48 | └─────────────────────────────────────────────────────────┘ 49 | ↓ 50 | ┌─────────────────────────────────────────────────────────┐ 51 | │ MEMORY & LEARNING LAYER (MCP) │ 52 | │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 53 | │ │ Trade │ │ Performance │ │ Pattern │ │ 54 | │ │ History │ │ Metrics │ │ Recognition │ │ 55 | │ └──────────────┘ └──────────────┘ └──────────────┘ │ 56 | └─────────────────────────────────────────────────────────┘ 57 | ↓ 58 | ┌─────────────────────────────────────────────────────────┐ 59 | │ EXECUTION LAYER │ 60 | │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ 61 | │ │ Signal │ │ trade_signals│ │ NinjaTrader │ │ 62 | │ │ Generator │ │ .csv (output)│ │ (execution) │ │ 63 | │ └──────────────┘ └──────────────┘ └──────────────┘ │ 64 | └─────────────────────────────────────────────────────────┘ 65 | ``` 66 | 67 | --- 68 | 69 | ## Component Specifications 70 | 71 | ### 1. FVG Analyzer (`src/fvg_analyzer.py`) 72 | 73 | **Purpose**: Parse FVG data and prepare for Claude analysis 74 | 75 | **Responsibilities**: 76 | - Read active FVGs from FairValueGaps.py output/state 77 | - Calculate distance from current price to each FVG 78 | - Identify nearest bullish and bearish gaps 79 | - Determine if price is inside any zones 80 | - Format data for Claude consumption 81 | 82 | **Input**: 83 | - FVG zones (from FairValueGaps.py) 84 | - Current price (from LiveFeed.csv) 85 | 86 | **Output**: 87 | ```python 88 | { 89 | "current_price": 14685.50, 90 | "nearest_bullish_fvg": { 91 | "top": 14715.00, 92 | "bottom": 14710.00, 93 | "size": 5.0, 94 | "distance": 24.50, # pts to entry (top) 95 | "age_bars": 12 96 | }, 97 | "nearest_bearish_fvg": { 98 | "top": 14655.00, 99 | "bottom": 14650.00, 100 | "size": 5.0, 101 | "distance": 30.50, # pts to entry (bottom) 102 | "age_bars": 45 103 | }, 104 | "price_in_zone": None # or FVG object if inside 105 | } 106 | ``` 107 | 108 | --- 109 | 110 | ### 2. Level Detector (`src/level_detector.py`) 111 | 112 | **Purpose**: Identify psychological levels and confluences 113 | 114 | **Responsibilities**: 115 | - Detect round number levels (100pt intervals: 14600, 14700, 14800) 116 | - Calculate distance to nearest levels above/below 117 | - Identify FVG + Level confluences 118 | - Track historical strength of levels 119 | 120 | **Input**: 121 | - Current price 122 | - Price history 123 | 124 | **Output**: 125 | ```python 126 | { 127 | "nearest_level_above": 14700, 128 | "distance_above": 14.50, 129 | "nearest_level_below": 14600, 130 | "distance_below": 85.50, 131 | "confluences": [ 132 | { 133 | "level": 14700, 134 | "fvg": {bullish FVG object}, 135 | "type": "resistance", 136 | "strength": "high" 137 | } 138 | ] 139 | } 140 | ``` 141 | 142 | --- 143 | 144 | ### 3. Claude Trading Agent (`src/trading_agent.py`) 145 | 146 | **Purpose**: Main reasoning engine for trade decisions 147 | 148 | **Responsibilities**: 149 | - Receive market context (FVGs, levels, price) 150 | - Query memory for similar past setups 151 | - Reason about trade quality 152 | - Calculate risk/reward 153 | - Make GO/NO-GO decision 154 | - Generate detailed reasoning explanation 155 | - Output trade signal if criteria met 156 | 157 | **Decision Framework**: 158 | ```python 159 | def analyze_setup(context): 160 | """ 161 | 1. Assess confluence (FVG + Level?) 162 | 2. Check gap quality (size, age, filled status) 163 | 3. Query memory for similar setups 164 | 4. Calculate risk/reward 165 | 5. Evaluate against thresholds 166 | 6. Generate reasoning chain 167 | 7. Return decision + explanation 168 | """ 169 | ``` 170 | 171 | **Claude Prompt Structure**: 172 | ``` 173 | You are an expert NQ futures trader analyzing a potential setup. 174 | 175 | CURRENT MARKET: 176 | - Price: {current_price} 177 | - Nearest Bullish FVG: {fvg_data} 178 | - Nearest Bearish FVG: {fvg_data} 179 | - Psychological Levels: {level_data} 180 | - Confluences: {confluence_data} 181 | 182 | PAST PERFORMANCE: 183 | - Similar bullish FVG trades: {memory_stats} 184 | - Similar bearish FVG trades: {memory_stats} 185 | - Confluence trades: {memory_stats} 186 | 187 | ANALYZE: 188 | 1. Is there a high-quality setup forming? 189 | 2. What is the trade direction (LONG/SHORT/NONE)? 190 | 3. What is the entry price? 191 | 4. What is the stop loss (15-50pt range)? 192 | 5. What is the target? 193 | 6. What is the risk/reward ratio? 194 | 7. Should we take this trade? Why or why not? 195 | 196 | Respond with structured JSON decision. 197 | ``` 198 | 199 | **Output**: 200 | ```python 201 | { 202 | "decision": "SHORT", # or "LONG" or "NONE" 203 | "entry": 14712.00, 204 | "stop": 14730.00, # 18pt stop 205 | "target": 14650.00, 206 | "risk_reward": 3.44, 207 | "confidence": 0.78, 208 | "reasoning": "Bullish FVG at 14710-14715 aligns with 14700 psychological level creating strong resistance confluence. Historical data shows 72% win rate on similar setups. Current uptrend momentum suggests price will test this zone. 18pt stop provides buffer above FVG. Target is unfilled bearish FVG at 14650 with 62pt profit potential. R/R of 3.44:1 exceeds minimum threshold.", 209 | "setup_type": "confluence", # or "fvg_only" or "level_only" 210 | "timestamp": "2025-11-25T14:30:00Z" 211 | } 212 | ``` 213 | 214 | --- 215 | 216 | ### 4. Memory Manager (`src/memory_manager.py`) 217 | 218 | **Purpose**: Interface with MCP for trade history and learning 219 | 220 | **Responsibilities**: 221 | - Store completed trade outcomes 222 | - Query past trades by setup type 223 | - Calculate performance metrics 224 | - Provide historical context to Claude 225 | - Trigger adaptive learning 226 | 227 | **MCP Integration**: 228 | ```python 229 | # Store trade outcome 230 | mcp__claude-flow__memory_usage( 231 | operation="store", 232 | key=f"trade_{timestamp}", 233 | data={trade_outcome} 234 | ) 235 | 236 | # Query similar trades 237 | mcp__claude-flow__memory_usage( 238 | operation="retrieve", 239 | filter={"setup_type": "confluence", "direction": "SHORT"} 240 | ) 241 | 242 | # Adapt agent based on performance 243 | mcp__claude-flow__daa_agent_adapt( 244 | agent_id="nq_trader", 245 | feedback="Last 10 confluence trades: 80% win rate", 246 | performance_score=0.80 247 | ) 248 | ``` 249 | 250 | **Trade History Schema**: 251 | ```python 252 | { 253 | "trade_id": "2025-11-25_14:30:00", 254 | "setup": { 255 | "type": "confluence", 256 | "direction": "SHORT", 257 | "fvg": {fvg_data}, 258 | "level": 14700, 259 | "entry": 14712, 260 | "stop": 14730, 261 | "target": 14650 262 | }, 263 | "outcome": { 264 | "result": "WIN", # or "LOSS" or "BREAKEVEN" 265 | "exit_price": 14651.00, 266 | "profit_loss": 61.00, 267 | "risk_reward_achieved": 3.39, 268 | "bars_held": 8, 269 | "exit_reason": "target_hit" 270 | }, 271 | "reasoning": "...", 272 | "timestamp": "2025-11-25T14:30:00Z" 273 | } 274 | ``` 275 | 276 | --- 277 | 278 | ### 5. Signal Generator (`src/signal_generator.py`) 279 | 280 | **Purpose**: Output trade signals to NinjaTrader format 281 | 282 | **Responsibilities**: 283 | - Format Claude decision as CSV row 284 | - Append to trade_signals.csv 285 | - Validate data format 286 | - Handle file I/O errors 287 | - Log signal generation 288 | 289 | **Output Format** (trade_signals.csv): 290 | ```csv 291 | DateTime,Direction,Entry_Price,Stop_Loss,Target 292 | 2025-11-25 14:30:00,SHORT,14712.00,14730.00,14650.00 293 | 2025-11-25 16:15:00,LONG,14603.00,14585.00,14665.00 294 | ``` 295 | 296 | **Code Structure**: 297 | ```python 298 | def generate_signal(decision): 299 | """ 300 | 1. Validate decision object 301 | 2. Format as CSV row 302 | 3. Append to trade_signals.csv 303 | 4. Log signal details 304 | 5. Return confirmation 305 | """ 306 | ``` 307 | 308 | --- 309 | 310 | ### 6. Backtest Engine (`src/backtest_engine.py`) 311 | 312 | **Purpose**: Test strategy on historical data 313 | 314 | **Responsibilities**: 315 | - Load historical OHLC data 316 | - Simulate FVG detection on past bars 317 | - Generate Claude decisions for each setup 318 | - Calculate trade outcomes 319 | - Compute performance metrics 320 | - Generate detailed report 321 | 322 | **Metrics Calculated**: 323 | - Total trades 324 | - Win rate (%) 325 | - Average R/R achieved 326 | - Sharpe ratio 327 | - Maximum drawdown 328 | - Profit factor 329 | - Average bars held 330 | - Setup type breakdown 331 | 332 | **Output**: 333 | ```python 334 | { 335 | "summary": { 336 | "total_trades": 127, 337 | "wins": 89, 338 | "losses": 38, 339 | "win_rate": 0.7008, 340 | "avg_rr_achieved": 2.84, 341 | "sharpe_ratio": 1.82, 342 | "max_drawdown": -185.50, 343 | "profit_factor": 2.41 344 | }, 345 | "by_setup_type": { 346 | "confluence": {"trades": 42, "win_rate": 0.81, ...}, 347 | "fvg_only": {"trades": 58, "win_rate": 0.67, ...}, 348 | "level_only": {"trades": 27, "win_rate": 0.63, ...} 349 | }, 350 | "trade_log": [detailed_trade_list] 351 | } 352 | ``` 353 | 354 | --- 355 | 356 | ### 7. Main Orchestrator (`main.py`) 357 | 358 | **Purpose**: Coordinate all system components 359 | 360 | **Modes**: 361 | 362 | #### Backtest Mode 363 | ```bash 364 | python main.py --mode backtest --days 30 --output results.json 365 | ``` 366 | - Load historical data 367 | - Run backtest engine 368 | - Generate performance report 369 | 370 | #### Live Mode 371 | ```bash 372 | python main.py --mode live 373 | ``` 374 | - Monitor FairValueGaps.py for new FVGs 375 | - Poll LiveFeed.csv for price updates 376 | - Trigger Claude analysis when setup forms 377 | - Output signals to trade_signals.csv 378 | - Log decisions and outcomes 379 | 380 | #### Monitor Mode 381 | ```bash 382 | python main.py --mode monitor 383 | ``` 384 | - Display current FVGs 385 | - Show active positions (if any) 386 | - Track performance metrics 387 | - Real-time dashboard 388 | 389 | --- 390 | 391 | ## Data Flow: Live Trading 392 | 393 | ``` 394 | 1. FairValueGaps.py detects new FVG 395 | ↓ 396 | 2. Main.py detects new zone 397 | ↓ 398 | 3. FVGAnalyzer parses FVG data 399 | ↓ 400 | 4. LevelDetector checks for confluences 401 | ↓ 402 | 5. TradingAgent queries memory for context 403 | ↓ 404 | 6. Claude analyzes setup and decides 405 | ↓ 406 | 7. IF trade criteria met: 407 | → SignalGenerator writes to trade_signals.csv 408 | → MemoryManager logs decision 409 | ↓ 410 | 8. NinjaTrader reads trade_signals.csv 411 | ↓ 412 | 9. Trade executed 413 | ↓ 414 | 10. Outcome tracked and fed back to memory 415 | ``` 416 | 417 | --- 418 | 419 | ## Learning Loop 420 | 421 | **How the system improves over time:** 422 | 423 | ``` 424 | Trade Decision Made 425 | ↓ 426 | Execute Trade 427 | ↓ 428 | Outcome Recorded (WIN/LOSS/BREAKEVEN) 429 | ↓ 430 | Memory Updated with Outcome 431 | ↓ 432 | Next Decision: 433 | - Claude queries memory 434 | - Reviews similar past setups 435 | - Adjusts reasoning based on what worked 436 | - Higher confidence on proven patterns 437 | - Lower confidence on failed patterns 438 | ↓ 439 | Improved Decision Quality 440 | ``` 441 | 442 | **Key Insight**: Not gradient descent, but **contextual reasoning improvement** 443 | 444 | --- 445 | 446 | ## Risk Management 447 | 448 | ### Position Sizing 449 | - Default: 1 contract 450 | - Configurable based on account size 451 | - No pyramiding (one trade at a time) 452 | 453 | ### Stop Loss Rules 454 | - **Minimum**: 15 points (NQ volatility floor) 455 | - **Default**: 20 points 456 | - **Maximum**: 50 points 457 | - **Placement**: Beyond FVG zone + buffer (5-10pts) 458 | 459 | ### Daily Limits 460 | - Max trades per day: 5 461 | - Max daily loss: 100 points 462 | - Max consecutive losses before pause: 3 463 | 464 | ### Setup Quality Thresholds 465 | - Minimum gap size: 5 points 466 | - Maximum gap age: 100 bars 467 | - Minimum R/R ratio: 3.0:1 468 | - Minimum confidence: 0.65 469 | 470 | --- 471 | 472 | ## Configuration Files 473 | 474 | ### `config/agent_config.json` 475 | ```json 476 | { 477 | "trading_params": { 478 | "min_gap_size": 5.0, 479 | "max_gap_age_bars": 100, 480 | "min_risk_reward": 3.0, 481 | "confidence_threshold": 0.65, 482 | "position_size": 1 483 | }, 484 | "risk_management": { 485 | "stop_loss_min": 15, 486 | "stop_loss_default": 20, 487 | "stop_loss_max": 50, 488 | "stop_buffer": 5, 489 | "max_daily_trades": 5, 490 | "max_daily_loss": 100, 491 | "max_consecutive_losses": 3 492 | }, 493 | "levels": { 494 | "psychological_intervals": [100], 495 | "track_historical_strength": true 496 | }, 497 | "memory": { 498 | "max_trades_stored": 1000, 499 | "query_similar_count": 20 500 | } 501 | } 502 | ``` 503 | 504 | ### `config/risk_rules.json` 505 | ```json 506 | { 507 | "mandatory_rules": [ 508 | "Every trade must have a stop loss", 509 | "Stop loss must be 15-50 points", 510 | "Minimum R/R must be 3:1", 511 | "No trading after 3 consecutive losses", 512 | "No trading after daily loss limit hit" 513 | ], 514 | "validation": { 515 | "check_before_signal": true, 516 | "log_violations": true, 517 | "halt_on_violation": true 518 | } 519 | } 520 | ``` 521 | 522 | --- 523 | 524 | ## Error Handling 525 | 526 | ### Data Issues 527 | - Missing FVG data → Skip analysis, log warning 528 | - Missing price feed → Use last known price, log warning 529 | - Corrupt CSV → Alert operator, halt trading 530 | 531 | ### API Issues 532 | - Claude API timeout → Retry 3x, then skip decision 533 | - Claude API error → Log error, continue monitoring 534 | - Rate limit hit → Queue decision, process when available 535 | 536 | ### Execution Issues 537 | - Cannot write to trade_signals.csv → Alert operator, log trade 538 | - NinjaTrader offline → Store signals, replay when online 539 | 540 | --- 541 | 542 | ## Testing Strategy 543 | 544 | ### Unit Tests 545 | - FVGAnalyzer parsing accuracy 546 | - LevelDetector calculation correctness 547 | - SignalGenerator CSV formatting 548 | - MemoryManager MCP integration 549 | 550 | ### Integration Tests 551 | - End-to-end decision flow 552 | - Memory query → Claude → Signal output 553 | - Error handling scenarios 554 | 555 | ### Backtest Validation 556 | - Run on known historical periods 557 | - Verify trade execution logic 558 | - Compare manual vs automated decisions 559 | 560 | --- 561 | 562 | ## Performance Monitoring 563 | 564 | ### Real-Time Metrics 565 | - Current P&L 566 | - Win rate (rolling 20 trades) 567 | - Average R/R achieved 568 | - Trades today / this week 569 | - Current drawdown 570 | 571 | ### System Health 572 | - Claude API response time 573 | - Memory query latency 574 | - FVG detection lag 575 | - Signal generation time 576 | 577 | ### Alerts 578 | - Daily loss limit approaching (80%) 579 | - 3 consecutive losses 580 | - API errors 581 | - Data feed disruption 582 | 583 | --- 584 | 585 | ## Deployment Checklist 586 | 587 | - [ ] Backtest on full 1000-day dataset 588 | - [ ] Validate win rate >60% 589 | - [ ] Validate Sharpe ratio >1.5 590 | - [ ] Test all error handling paths 591 | - [ ] Configure stop loss rules appropriately 592 | - [ ] Set daily loss limits 593 | - [ ] Verify NinjaTrader integration 594 | - [ ] Test signal CSV format 595 | - [ ] Enable performance logging 596 | - [ ] Set up monitoring dashboard 597 | - [ ] Paper trade for 2 weeks 598 | - [ ] Review and adjust parameters 599 | - [ ] Go live with 1 contract 600 | 601 | --- 602 | 603 | ## Maintenance 604 | 605 | ### Daily 606 | - Review trade decisions and reasoning 607 | - Check system logs for errors 608 | - Verify data feeds operational 609 | 610 | ### Weekly 611 | - Analyze performance metrics 612 | - Review memory-based adaptations 613 | - Adjust parameters if needed 614 | 615 | ### Monthly 616 | - Full system backtest on recent data 617 | - Compare live vs backtest performance 618 | - Review and update trading rules 619 | 620 | --- 621 | 622 | **Version**: 1.0.0 623 | **Last Updated**: 2025-11-25 624 | **Status**: Implementation Phase 625 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | """ 2 | Claude NQ Trading Agent - Main Orchestrator 3 | Coordinates all system components for live trading, backtesting, and monitoring 4 | """ 5 | 6 | import argparse 7 | import json 8 | import logging 9 | import sys 10 | import time 11 | import os 12 | from pathlib import Path 13 | from datetime import datetime 14 | from dotenv import load_dotenv 15 | 16 | # Import modules 17 | from src.fvg_analyzer import FVGAnalyzer 18 | from src.level_detector import LevelDetector 19 | from src.trading_agent import TradingAgent 20 | from src.memory_manager import MemoryManager 21 | from src.signal_generator import SignalGenerator 22 | from src.backtest_engine import BacktestEngine 23 | from src.market_analysis_manager import MarketAnalysisManager 24 | 25 | # Load environment variables 26 | load_dotenv() 27 | 28 | # Configure logging 29 | def setup_logging(log_level: str = "INFO", log_file: str = None): 30 | """Setup logging configuration""" 31 | log_format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' 32 | 33 | # Configure stdout handler with UTF-8 encoding for Windows 34 | import io 35 | stdout_handler = logging.StreamHandler(io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')) 36 | handlers = [stdout_handler] 37 | 38 | if log_file: 39 | log_dir = Path("logs") 40 | log_dir.mkdir(exist_ok=True) 41 | # File handler with UTF-8 encoding 42 | handlers.append(logging.FileHandler(log_dir / log_file, encoding='utf-8', errors='replace')) 43 | 44 | logging.basicConfig( 45 | level=getattr(logging, log_level.upper()), 46 | format=log_format, 47 | handlers=handlers 48 | ) 49 | 50 | logger = logging.getLogger(__name__) 51 | 52 | 53 | class TradingOrchestrator: 54 | """Main orchestrator for trading system""" 55 | 56 | def __init__(self, config_path: str = "config/agent_config.json"): 57 | """ 58 | Initialize Trading Orchestrator 59 | 60 | Args: 61 | config_path: Path to configuration file 62 | """ 63 | # Load configuration 64 | with open(config_path, 'r') as f: 65 | self.config = json.load(f) 66 | 67 | # Setup logging 68 | log_level = self.config.get('logging', {}).get('level', 'INFO') 69 | log_file = self.config.get('logging', {}).get('log_file', 'trading_agent.log') 70 | setup_logging(log_level, log_file) 71 | 72 | # Initializing silently 73 | pass 74 | 75 | # Initialize components 76 | self.fvg_analyzer = FVGAnalyzer( 77 | min_gap_size=self.config['trading_params']['min_gap_size'], 78 | max_gap_age=self.config['trading_params']['max_gap_age_bars'] 79 | ) 80 | 81 | self.level_detector = LevelDetector( 82 | level_intervals=self.config['levels']['psychological_intervals'] 83 | ) 84 | 85 | self.memory_manager = MemoryManager() 86 | self.signal_generator = SignalGenerator() 87 | self.analysis_manager = MarketAnalysisManager() 88 | 89 | # Trading agent (requires API key) 90 | api_key = os.getenv('ANTHROPIC_API_KEY') 91 | if api_key: 92 | self.trading_agent = TradingAgent(self.config, api_key=api_key) 93 | else: 94 | self.trading_agent = None 95 | logger.warning("No API key found - trading agent not initialized") 96 | 97 | # State tracking 98 | self.daily_trades = 0 99 | self.daily_pnl = 0.0 100 | self.consecutive_losses = 0 101 | self.trading_paused = False 102 | 103 | # Initialization complete 104 | 105 | def check_risk_limits(self) -> tuple[bool, str]: 106 | """ 107 | Check if risk management limits allow trading 108 | 109 | Returns: 110 | Tuple of (can_trade, reason) 111 | """ 112 | max_daily_trades = self.config['risk_management']['max_daily_trades'] 113 | max_daily_loss = self.config['risk_management']['max_daily_loss'] 114 | max_consecutive_losses = self.config['risk_management']['max_consecutive_losses'] 115 | 116 | if self.trading_paused: 117 | return False, "Trading is paused (manual intervention required)" 118 | 119 | if self.daily_trades >= max_daily_trades: 120 | return False, f"Daily trade limit reached ({max_daily_trades})" 121 | 122 | if abs(self.daily_pnl) >= max_daily_loss and self.daily_pnl < 0: 123 | return False, f"Daily loss limit reached ({max_daily_loss} points)" 124 | 125 | if self.consecutive_losses >= max_consecutive_losses: 126 | return False, f"Consecutive loss limit reached ({max_consecutive_losses})" 127 | 128 | return True, "" 129 | 130 | def run_live_mode(self): 131 | """Run in live trading mode""" 132 | # Starting live mode silently 133 | 134 | if not self.trading_agent: 135 | logger.error("Trading agent not initialized - API key required") 136 | return 137 | 138 | # Import FairValueGaps display to access its state 139 | import sys 140 | import pandas as pd 141 | import os 142 | sys.path.insert(0, str(Path.cwd())) 143 | from FairValueGaps import FVGDisplay 144 | 145 | # Create FVG display instance (but don't run its main loop) 146 | fvg_display = FVGDisplay() 147 | 148 | # Load historical FVGs 149 | fvg_display.load_historical_fvgs() 150 | 151 | logger.info(f"Loaded {len(fvg_display.active_fvgs)} active FVGs") 152 | logger.info("="*60) 153 | 154 | # Track last processed bar and result 155 | last_bar_time = None 156 | last_result = None 157 | 158 | try: 159 | while True: 160 | # Reload historical data to check for updates 161 | historical_df = pd.read_csv('data/HistoricalData.csv') 162 | historical_df['DateTime'] = pd.to_datetime(historical_df['DateTime']) 163 | 164 | # Get latest bar timestamp 165 | current_bar_time = historical_df.iloc[-1]['DateTime'] 166 | 167 | # Check if new bar arrived 168 | if current_bar_time != last_bar_time: 169 | # NEW BAR DETECTED - Run full analysis 170 | logger.info(f"\n{'='*60}") 171 | logger.info(f"NEW BAR: {current_bar_time}") 172 | logger.info(f"{'='*60}") 173 | 174 | # Update last processed time 175 | last_bar_time = current_bar_time 176 | 177 | # Check for new hourly bars 178 | if fvg_display.check_historical_updated(): 179 | fvg_display.process_historical_bars() 180 | 181 | # Get current price 182 | current_price = fvg_display.read_current_price() 183 | 184 | if current_price is None: 185 | logger.warning("No current price available") 186 | time.sleep(5) 187 | continue 188 | 189 | # Check live FVG fills 190 | fvg_display.check_live_fvg_fills(current_price) 191 | 192 | # Get active FVGs 193 | active_fvgs = [fvg for fvg in fvg_display.active_fvgs if not fvg.get('filled', False)] 194 | 195 | # Debug logging 196 | total_fvgs = len(fvg_display.active_fvgs) 197 | unfilled_fvgs = len(active_fvgs) 198 | bullish_count = len([f for f in active_fvgs if f['type'] == 'bullish']) 199 | bearish_count = len([f for f in active_fvgs if f['type'] == 'bearish']) 200 | 201 | logger.info(f"FVG Status: Total={total_fvgs}, Unfilled={unfilled_fvgs} (Bullish={bullish_count}, Bearish={bearish_count})") 202 | 203 | if active_fvgs: 204 | # Show details of each FVG 205 | logger.info("Active FVGs:") 206 | for i, fvg in enumerate(active_fvgs[:5], 1): # Show first 5 207 | logger.info(f" {i}. {fvg['type'].upper()}: {fvg['bottom']:.2f}-{fvg['top']:.2f} | " 208 | f"Current Price: {current_price:.2f} | " 209 | f"Relative: {'ABOVE' if fvg['bottom'] > current_price else 'BELOW' if fvg['top'] < current_price else 'AT'}") 210 | 211 | if not active_fvgs: 212 | logger.info("No active FVGs - waiting...") 213 | time.sleep(5) 214 | continue 215 | 216 | # Analyze market context 217 | fvg_context = self.fvg_analyzer.analyze_market_context(current_price, active_fvgs) 218 | 219 | # Debug: Show filtering results 220 | logger.info(f"After filtering - Nearest Bullish: {fvg_context['nearest_bullish_fvg'] is not None}, " 221 | f"Nearest Bearish: {fvg_context['nearest_bearish_fvg'] is not None}") 222 | 223 | # Get latest bar from historical data for EMA/Stochastic values 224 | current_bar = historical_df.iloc[-1] 225 | 226 | # Extract market data (EMA and Stochastic indicators) 227 | market_data = { 228 | 'ema21': current_bar.get('EMA21', 0), 229 | 'ema75': current_bar.get('EMA75', 0), 230 | 'ema150': current_bar.get('EMA150', 0), 231 | 'stochastic': current_bar.get('StochD', 50) 232 | } 233 | 234 | # Check risk limits 235 | can_trade, reason = self.check_risk_limits() 236 | if not can_trade: 237 | logger.warning(f"Trading blocked: {reason}") 238 | time.sleep(60) 239 | continue 240 | 241 | # Get memory context 242 | memory_context = self.memory_manager.get_memory_context() 243 | 244 | # Get previous analysis for incremental updates 245 | previous_analysis = self.analysis_manager.format_previous_analysis_for_prompt() 246 | 247 | # Analyze with Claude (only on new bar) 248 | try: 249 | result = self.trading_agent.analyze_setup( 250 | fvg_context, 251 | market_data, 252 | memory_context, 253 | previous_analysis 254 | ) 255 | last_result = result 256 | 257 | # Check if we have a tradeable decision 258 | if result['success']: 259 | decision_data = result['decision'] 260 | 261 | # Save updated analysis state 262 | if 'long_assessment' in decision_data and 'short_assessment' in decision_data: 263 | # Build analysis update from decision 264 | analysis_update = { 265 | 'current_bar_index': decision_data.get('current_bar_index', 0), 266 | 'overall_bias': decision_data.get('overall_bias', 'neutral'), 267 | 'waiting_for': decision_data.get('waiting_for', 'Analyzing market'), 268 | 'long_assessment': decision_data['long_assessment'], 269 | 'short_assessment': decision_data['short_assessment'], 270 | 'bars_since_last_update': 0 271 | } 272 | self.analysis_manager.update_analysis(analysis_update) 273 | logger.info(f"Analysis state saved: {decision_data.get('waiting_for', 'N/A')}") 274 | 275 | primary = decision_data['primary_decision'] 276 | 277 | if primary != 'NONE': 278 | # Get the chosen setup 279 | chosen_setup = decision_data['long_setup'] if primary == 'LONG' else decision_data['short_setup'] 280 | 281 | # Build signal format for legacy signal generator 282 | signal = { 283 | 'decision': primary, 284 | 'entry': chosen_setup['entry'], 285 | 'stop': chosen_setup['stop'], 286 | 'target': chosen_setup['target'], 287 | 'risk_reward': chosen_setup['risk_reward'], 288 | 'confidence': chosen_setup['confidence'], 289 | 'reasoning': decision_data['overall_reasoning'], 290 | 'setup_type': 'fvg_only' 291 | } 292 | 293 | # Log signal generation attempt 294 | logger.info(f"GENERATING TRADE SIGNAL: {primary} @ {signal['entry']:.0f}") 295 | logger.info(f"R:R {signal['risk_reward']:.2f}:1 | Confidence: {signal['confidence']:.2f}") 296 | 297 | # Generate signal 298 | try: 299 | success = self.signal_generator.generate_signal(signal) 300 | 301 | if success: 302 | self.daily_trades += 1 303 | # Mark trade as executed in analysis manager 304 | self.analysis_manager.mark_trade_executed(primary) 305 | logger.info(f"SIGNAL WRITTEN TO CSV: {primary} trade signal saved") 306 | else: 307 | logger.warning(f"SIGNAL GENERATION FAILED: Could not write to CSV") 308 | except Exception as e: 309 | logger.error(f"ERROR WRITING SIGNAL: {e}") 310 | import traceback 311 | logger.error(traceback.format_exc()) 312 | else: 313 | logger.info("NO TRADE: Primary decision is NONE") 314 | else: 315 | logger.error(f"VALIDATION FAILED: {result.get('validation_error', 'Unknown error')}") 316 | logger.error(f"Full result: {result}") 317 | 318 | except Exception as e: 319 | logger.error(f"ERROR IN ANALYSIS: {e}") 320 | import traceback 321 | logger.error(traceback.format_exc()) 322 | # Create error result so display doesn't crash 323 | last_result = { 324 | 'success': False, 325 | 'error': str(e), 326 | 'decision': {} 327 | } 328 | 329 | # Clear screen and show response 330 | os.system('cls' if os.name == 'nt' else 'clear') 331 | print(self.trading_agent.format_decision_display(result, current_price)) 332 | print("\nWaiting for next bar") 333 | 334 | # Brief pause to show result 335 | time.sleep(2) 336 | 337 | else: 338 | # WAITING FOR NEW BAR - Show live updates 339 | current_price = fvg_display.read_current_price() 340 | 341 | # Clear screen 342 | os.system('cls' if os.name == 'nt' else 'clear') 343 | 344 | # Show last decision with current price 345 | if last_result: 346 | print(self.trading_agent.format_decision_display(last_result, current_price)) 347 | 348 | # Static waiting message 349 | print("\nWaiting for next bar") 350 | 351 | # Wait 5 seconds before refreshing 352 | time.sleep(5) 353 | 354 | except KeyboardInterrupt: 355 | logger.info("\nLive trading stopped by user") 356 | except Exception as e: 357 | logger.error(f"Error in live trading: {e}") 358 | import traceback 359 | logger.error(traceback.format_exc()) 360 | 361 | def run_backtest_mode(self, days: int = 30, output_file: str = "backtest_results.json"): 362 | """ 363 | Run in backtest mode 364 | 365 | Args: 366 | days: Number of days to backtest 367 | output_file: Output file for results 368 | """ 369 | logger.info(f"Starting BACKTEST mode ({days} days)") 370 | 371 | api_key = os.getenv('ANTHROPIC_API_KEY') 372 | use_claude = api_key is not None 373 | 374 | if not use_claude: 375 | logger.warning("No API key - running backtest with simple logic") 376 | 377 | engine = BacktestEngine(self.config) 378 | results = engine.run_backtest(days=days, use_claude=use_claude, api_key=api_key) 379 | 380 | # Print summary 381 | logger.info("="*60) 382 | logger.info("BACKTEST RESULTS") 383 | logger.info("="*60) 384 | logger.info(f"Period: {results['backtest_period']}") 385 | logger.info(f"Total Bars: {results['total_bars']}") 386 | logger.info(f"Total Trades: {results['total_trades']}") 387 | logger.info(f"Wins: {results['wins']} | Losses: {results['losses']} | Breakeven: {results['breakeven']}") 388 | logger.info(f"Win Rate: {results['win_rate']:.1%}") 389 | logger.info(f"Total P&L: {results['total_pnl']:+.2f} points") 390 | logger.info(f"Average P&L: {results['avg_pnl']:+.2f} points") 391 | logger.info(f"Max Win: {results['max_win']:+.2f} points") 392 | logger.info(f"Max Loss: {results['max_loss']:+.2f} points") 393 | logger.info(f"Average Bars Held: {results['avg_bars_held']:.1f}") 394 | 395 | if results.get('by_setup_type'): 396 | logger.info("\nBy Setup Type:") 397 | for setup_type, stats in results['by_setup_type'].items(): 398 | logger.info(f" {setup_type}: {stats['trades']} trades, {stats['win_rate']:.1%} win rate, " 399 | f"{stats['avg_pnl']:+.2f}pts avg") 400 | 401 | logger.info("="*60) 402 | 403 | # Export results 404 | engine.export_results(results, output_file) 405 | 406 | def run_monitor_mode(self): 407 | """Run in monitoring/dashboard mode""" 408 | logger.info("Starting MONITOR mode") 409 | 410 | # Display performance summary 411 | print("\n" + "="*60) 412 | print(self.memory_manager.get_performance_summary()) 413 | print("="*60) 414 | 415 | # Display current signals 416 | recent_signals = self.signal_generator.get_recent_signals(10) 417 | if recent_signals: 418 | print("\nRECENT SIGNALS:") 419 | print("-"*60) 420 | for signal in recent_signals: 421 | print(f"{signal['DateTime']} | {signal['Direction']:<5} | " 422 | f"Entry: {signal['Entry_Price']:<8} | " 423 | f"Stop: {signal['Stop_Loss']:<8} | " 424 | f"Target: {signal['Target']}") 425 | print("="*60) 426 | 427 | print(f"\nSignals today: {self.signal_generator.count_signals_today()}") 428 | 429 | 430 | def main(): 431 | """Main entry point""" 432 | parser = argparse.ArgumentParser(description='Claude NQ Trading Agent') 433 | parser.add_argument('--mode', choices=['live', 'backtest', 'monitor'], 434 | default='monitor', help='Operating mode') 435 | parser.add_argument('--days', type=int, default=30, 436 | help='Number of days for backtest (default: 30)') 437 | parser.add_argument('--config', type=str, default='config/agent_config.json', 438 | help='Path to configuration file') 439 | parser.add_argument('--output', type=str, default='backtest_results.json', 440 | help='Output file for backtest results') 441 | 442 | args = parser.parse_args() 443 | 444 | # Initialize orchestrator 445 | orchestrator = TradingOrchestrator(config_path=args.config) 446 | 447 | # Run in selected mode 448 | if args.mode == 'live': 449 | orchestrator.run_live_mode() 450 | elif args.mode == 'backtest': 451 | orchestrator.run_backtest_mode(days=args.days, output_file=args.output) 452 | elif args.mode == 'monitor': 453 | orchestrator.run_monitor_mode() 454 | 455 | 456 | if __name__ == "__main__": 457 | main() 458 | --------------------------------------------------------------------------------