From 75dbac17613acf84f2329f32ba44acd8696c82ae Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Fri, 30 May 2025 03:03:51 +0300 Subject: [PATCH] tter pivots --- WILLIAMS_CNN_PIVOT_INTEGRATION_SUMMARY.md | 160 ++ closed_trades_history.json | 2041 +++------------------ docs/requirements.md | 12 + test_enhanced_williams_cnn.py | 346 ++++ training/williams_market_structure.py | 584 +++++- web/dashboard.py | 146 +- 6 files changed, 1459 insertions(+), 1830 deletions(-) create mode 100644 WILLIAMS_CNN_PIVOT_INTEGRATION_SUMMARY.md create mode 100644 test_enhanced_williams_cnn.py diff --git a/WILLIAMS_CNN_PIVOT_INTEGRATION_SUMMARY.md b/WILLIAMS_CNN_PIVOT_INTEGRATION_SUMMARY.md new file mode 100644 index 0000000..319b795 --- /dev/null +++ b/WILLIAMS_CNN_PIVOT_INTEGRATION_SUMMARY.md @@ -0,0 +1,160 @@ +# Williams Market Structure CNN Integration Summary + +## šŸŽÆ Overview + +The Williams Market Structure has been enhanced with CNN-based pivot prediction capabilities, enabling real-time training and prediction at each detected pivot point using multi-timeframe, multi-symbol data. + +## āœ… Key Features Implemented + +### šŸ”„ **Recursive Pivot Structure** +- **Level 0**: Raw OHLCV price data → Swing points using multiple strengths [2, 3, 5, 8, 13] +- **Level 1**: Level 0 pivot points → Treated as "price bars" for higher-level pivots +- **Level 2-4**: Recursive application on previous level's pivots +- **True Recursion**: Each level builds on the previous level's pivot points + +### 🧠 **CNN Integration Architecture** +``` +Each Pivot Detection Triggers: +1. Train CNN on previous pivot (features) → current pivot (ground truth) +2. Predict next pivot using current pivot features +3. Store current features for next training cycle +``` + +### šŸ“Š **Multi-Timeframe Input Features** +- **ETH Primary Symbol**: + - 900 x 1s bars with indicators (10 features) + - 900 x 1m bars with indicators (10 features) + - 900 x 1h bars with indicators (10 features) + - 5 minutes of tick-derived features (10 features) +- **BTC Reference Symbol**: + - 5 minutes of tick-derived features (4 features) +- **Pivot Context**: Recent pivot characteristics (3 features) +- **Chart Labels**: Symbol/timeframe identification (3 features) +- **Total**: 900 timesteps Ɨ 50 features + +### šŸŽÆ **Multi-Level Output Prediction** +- **10 Outputs Total**: 5 Williams levels Ɨ (type + price) + - Level 0-4: [swing_type (0=LOW, 1=HIGH), normalized_price] + - Allows prediction across all recursive levels simultaneously + +### šŸ“ **Smart Normalization Strategy** +- **Data Flow**: Keep actual values throughout pipeline for validation +- **Final Step**: Normalize using 1h timeframe min/max range +- **Cross-Timeframe Preservation**: Maintains relationships between different timeframes +- **Price Features**: Normalized with 1h range +- **Non-Price Features**: Feature-wise normalization (indicators, counts, etc.) + +## šŸ”§ **Integration with TrainingDataPacket** + +Successfully leverages existing `TrainingDataPacket` from `core/unified_data_stream.py`: + +```python +@dataclass +class TrainingDataPacket: + timestamp: datetime + symbol: str + tick_cache: List[Dict[str, Any]] # āœ… Used for tick features + one_second_bars: List[Dict[str, Any]] # āœ… Used for 1s data + multi_timeframe_data: Dict[str, List[Dict[str, Any]]] # āœ… Used for 1m, 1h data + cnn_features: Optional[Dict[str, np.ndarray]] # āœ… Populated by Williams + cnn_predictions: Optional[Dict[str, np.ndarray]] # āœ… Populated by Williams +``` + +## šŸš€ **CNN Training Flow** + +### **At Each Pivot Point Detection:** + +1. **Training Phase** (if previous pivot exists): + ```python + X_train = previous_pivot_features # (900, 50) + y_train = current_actual_pivot # (10,) for all levels + model.fit(X_train, y_train, epochs=1) # Online learning + ``` + +2. **Prediction Phase**: + ```python + X_predict = current_pivot_features # (900, 50) + y_predict = model.predict(X_predict) # (10,) predictions for all levels + ``` + +3. **State Management**: + ```python + previous_pivot_details = { + 'features': X_predict, + 'pivot': current_pivot_object + } + ``` + +## šŸ›  **Implementation Status** + +### āœ… **Completed Components** +- [x] Recursive Williams pivot calculation (5 levels) +- [x] CNN integration hooks at each pivot detection +- [x] Multi-timeframe feature extraction from TrainingDataPacket +- [x] 1h-based normalization strategy +- [x] Multi-level output prediction (10 outputs) +- [x] Online learning with single-step training +- [x] Dashboard integration with proper diagnostics +- [x] Comprehensive test suite + +### ⚠ **Current Limitations** +- CNN disabled due to TensorFlow dependencies not installed +- Placeholder technical indicators (TODO: Add real SMA, EMA, RSI, MACD, etc.) +- Higher-level ground truth uses simplified logic (needs full Williams context) + +### šŸ”„ **Real-Time Dashboard Integration** + +Fixed dashboard Williams integration: +- **Reduced data requirement**: 20 bars minimum (from 50) +- **Proper configuration**: Uses swing_strengths=[2, 3, 5] +- **Enhanced diagnostics**: Data quality validation and pivot detection logging +- **Consistent timezone handling**: Proper timestamp conversion for pivot display + +## šŸ“ˆ **Performance Characteristics** + +### **Pivot Detection Performance** (from diagnostics): +- āœ… Clear test patterns: Successfully detects obvious pivot points +- āœ… Realistic data: Handles real market volatility and timing +- āœ… Multi-level recursion: Properly builds higher levels from lower levels + +### **CNN Training Frequency**: +- **Level 0**: Most frequent (every raw price pivot) +- **Level 1-4**: Less frequent (requires sufficient lower-level pivots) +- **Online Learning**: Single epoch per pivot for real-time adaptation + +## šŸŽ“ **Usage Example** + +```python +# Initialize Williams with CNN integration +williams = WilliamsMarketStructure( + swing_strengths=[2, 3, 5, 8, 13], + cnn_input_shape=(900, 50), # 900 timesteps, 50 features + cnn_output_size=10, # 5 levels Ɨ 2 outputs + enable_cnn_feature=True, + training_data_provider=data_stream # TrainingDataPacket provider +) + +# Calculate pivots (automatically triggers CNN training/prediction) +structure_levels = williams.calculate_recursive_pivot_points(ohlcv_data) + +# Extract RL features (250 features for reinforcement learning) +rl_features = williams.extract_features_for_rl(structure_levels) +``` + +## šŸ”® **Next Steps** + +1. **Install TensorFlow**: Enable CNN functionality +2. **Add Real Indicators**: Replace placeholder technical indicators +3. **Enhanced Ground Truth**: Implement proper multi-level pivot relationships +4. **Model Persistence**: Save/load trained CNN models +5. **Performance Metrics**: Track CNN prediction accuracy over time + +## šŸ“Š **Key Benefits** + +- **Real-Time Learning**: CNN adapts to market conditions at each pivot +- **Multi-Scale Analysis**: Captures patterns across 5 recursive levels +- **Rich Context**: 50 features per timestep covering multiple timeframes and symbols +- **Consistent Data Flow**: Leverages existing TrainingDataPacket infrastructure +- **Market Structure Awareness**: Predictions based on Williams methodology + +This implementation provides a robust foundation for CNN-enhanced pivot prediction while maintaining the proven Williams Market Structure methodology. \ No newline at end of file diff --git a/closed_trades_history.json b/closed_trades_history.json index 82da97b..20d263f 100644 --- a/closed_trades_history.json +++ b/closed_trades_history.json @@ -2,1972 +2,493 @@ { "trade_id": 1, "side": "LONG", - "entry_time": "2025-05-28T21:20:24.414523+00:00", - "exit_time": "2025-05-28T21:21:17.208277+00:00", - "entry_price": 2644.44, - "exit_price": 2644.98, - "size": 0.00319, - "gross_pnl": 0.0017225999999998841, - "fees": 0.008436624900000002, + "entry_time": "2025-05-29T23:50:11.970081+00:00", + "exit_time": "2025-05-29T23:50:30.130190+00:00", + "entry_price": 2624.0, + "exit_price": 2625.1, + "size": 0.003292, + "gross_pnl": 0.0036211999999997005, + "fees": 0.008640018599999999, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.006714024900000117, - "duration": "0:00:52.793754", + "net_pnl": -0.005018818600000298, + "duration": "0:00:18.160109", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 2, "side": "SHORT", - "entry_time": "2025-05-28T21:21:17.208277+00:00", - "exit_time": "2025-05-28T21:21:53.222212+00:00", - "entry_price": 2644.98, - "exit_price": 2645.31, - "size": 0.003435, - "gross_pnl": -0.00113354999999975, - "fees": 0.009086073075000001, + "entry_time": "2025-05-29T23:50:30.130190+00:00", + "exit_time": "2025-05-29T23:50:39.216725+00:00", + "entry_price": 2625.1, + "exit_price": 2626.07, + "size": 0.003013, + "gross_pnl": -0.002922610000000767, + "fees": 0.007910887605, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.01021962307499975, - "duration": "0:00:36.013935", + "net_pnl": -0.010833497605000766, + "duration": "0:00:09.086535", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 3, "side": "LONG", - "entry_time": "2025-05-28T21:21:53.222212+00:00", - "exit_time": "2025-05-28T21:22:57.365551+00:00", - "entry_price": 2645.31, - "exit_price": 2647.21, - "size": 0.00345, - "gross_pnl": 0.006555000000000314, - "fees": 0.009129597, + "entry_time": "2025-05-29T23:50:39.216725+00:00", + "exit_time": "2025-05-29T23:51:00.350490+00:00", + "entry_price": 2626.07, + "exit_price": 2626.61, + "size": 0.003618, + "gross_pnl": 0.0019537199999998685, + "fees": 0.009502098120000002, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.0025745969999996857, - "duration": "0:01:04.143339", + "net_pnl": -0.007548378120000133, + "duration": "0:00:21.133765", "symbol": "ETH/USDC", "mexc_executed": true }, { "trade_id": 4, "side": "SHORT", - "entry_time": "2025-05-28T21:22:57.365551+00:00", - "exit_time": "2025-05-28T21:23:10.020073+00:00", - "entry_price": 2647.21, - "exit_price": 2647.4, - "size": 0.003361, - "gross_pnl": -0.0006385900000001834, - "fees": 0.008897592105000001, + "entry_time": "2025-05-29T23:51:00.350490+00:00", + "exit_time": "2025-05-29T23:51:02.365767+00:00", + "entry_price": 2626.61, + "exit_price": 2627.1, + "size": 0.003044, + "gross_pnl": -0.0014915599999993354, + "fees": 0.007996146619999998, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.009536182105000183, - "duration": "0:00:12.654522", + "net_pnl": -0.009487706619999335, + "duration": "0:00:02.015277", "symbol": "ETH/USDC", "mexc_executed": false }, { "trade_id": 5, "side": "LONG", - "entry_time": "2025-05-28T21:23:10.020073+00:00", - "exit_time": "2025-05-28T21:24:21.763632+00:00", - "entry_price": 2647.4, - "exit_price": 2648.8, - "size": 0.003419, - "gross_pnl": 0.004786600000000312, - "fees": 0.0090538539, + "entry_time": "2025-05-29T23:51:02.365767+00:00", + "exit_time": "2025-05-29T23:51:09.413383+00:00", + "entry_price": 2627.1, + "exit_price": 2627.01, + "size": 0.003371, + "gross_pnl": -0.0003033899999989576, + "fees": 0.008855802405000002, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.004267253899999689, - "duration": "0:01:11.743559", + "net_pnl": -0.009159192404998958, + "duration": "0:00:07.047616", "symbol": "ETH/USDC", "mexc_executed": false }, { "trade_id": 6, "side": "SHORT", - "entry_time": "2025-05-28T21:24:21.763632+00:00", - "exit_time": "2025-05-28T21:27:32.198965+00:00", - "entry_price": 2648.8, - "exit_price": 2643.9, - "size": 0.003587, - "gross_pnl": 0.017576300000000326, - "fees": 0.009492457450000002, + "entry_time": "2025-05-29T23:51:09.413383+00:00", + "exit_time": "2025-05-29T23:51:15.458079+00:00", + "entry_price": 2627.01, + "exit_price": 2627.3, + "size": 0.003195, + "gross_pnl": -0.0009265499999998837, + "fees": 0.008393760225000001, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": 0.008083842550000324, - "duration": "0:03:10.435333", + "net_pnl": -0.009320310224999885, + "duration": "0:00:06.044696", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 7, "side": "LONG", - "entry_time": "2025-05-28T21:27:32.198965+00:00", - "exit_time": "2025-05-28T21:41:41.905916+00:00", - "entry_price": 2643.9, - "exit_price": 2643.75, - "size": 0.003359, - "gross_pnl": -0.0005038500000003056, - "fees": 0.008880608175, + "entry_time": "2025-05-29T23:51:15.458079+00:00", + "exit_time": "2025-05-29T23:51:29.589023+00:00", + "entry_price": 2627.3, + "exit_price": 2626.5, + "size": 0.003194, + "gross_pnl": -0.002555200000000581, + "fees": 0.0083903186, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.009384458175000306, - "duration": "0:14:09.706951", + "net_pnl": -0.010945518600000582, + "duration": "0:00:14.130944", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 8, "side": "SHORT", - "entry_time": "2025-05-28T21:41:41.905916+00:00", - "exit_time": "2025-05-28T21:43:30.152881+00:00", - "entry_price": 2643.75, - "exit_price": 2646.07, - "size": 0.003321, - "gross_pnl": -0.007704720000000544, - "fees": 0.00878374611, + "entry_time": "2025-05-29T23:51:29.589023+00:00", + "exit_time": "2025-05-29T23:51:36.690687+00:00", + "entry_price": 2626.5, + "exit_price": 2627.31, + "size": 0.003067, + "gross_pnl": -0.0024842699999998324, + "fees": 0.008056717635, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.016488466110000547, - "duration": "0:01:48.246965", + "net_pnl": -0.010540987634999832, + "duration": "0:00:07.101664", "symbol": "ETH/USDC", "mexc_executed": true }, { "trade_id": 9, "side": "LONG", - "entry_time": "2025-05-28T21:43:30.152881+00:00", - "exit_time": "2025-05-28T21:44:39.479251+00:00", - "entry_price": 2646.07, - "exit_price": 2646.14, - "size": 0.002722, - "gross_pnl": 0.0001905399999992078, - "fees": 0.00720269781, + "entry_time": "2025-05-29T23:51:36.690687+00:00", + "exit_time": "2025-05-29T23:51:57.745006+00:00", + "entry_price": 2627.31, + "exit_price": 2628.8, + "size": 0.003616, + "gross_pnl": 0.005387840000000855, + "fees": 0.009503046880000001, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.007012157810000792, - "duration": "0:01:09.326370", + "net_pnl": -0.004115206879999145, + "duration": "0:00:21.054319", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 10, "side": "SHORT", - "entry_time": "2025-05-28T21:44:39.479251+00:00", - "exit_time": "2025-05-28T21:46:25.881371+00:00", - "entry_price": 2646.14, - "exit_price": 2648.58, - "size": 0.003252, - "gross_pnl": -0.007934880000000177, - "fees": 0.008609214720000001, + "entry_time": "2025-05-29T23:51:57.745006+00:00", + "exit_time": "2025-05-29T23:52:01.784243+00:00", + "entry_price": 2628.8, + "exit_price": 2629.69, + "size": 0.003146, + "gross_pnl": -0.002799939999999599, + "fees": 0.00827160477, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.016544094720000176, - "duration": "0:01:46.402120", + "net_pnl": -0.011071544769999598, + "duration": "0:00:04.039237", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 11, "side": "LONG", - "entry_time": "2025-05-28T21:46:25.881371+00:00", - "exit_time": "2025-05-28T21:52:50.876479+00:00", - "entry_price": 2648.58, - "exit_price": 2657.58, - "size": 0.003346, - "gross_pnl": 0.030114000000000002, - "fees": 0.00887720568, + "entry_time": "2025-05-29T23:52:01.784243+00:00", + "exit_time": "2025-05-29T23:52:19.042322+00:00", + "entry_price": 2629.69, + "exit_price": 2630.16, + "size": 0.003158, + "gross_pnl": 0.0014842599999993682, + "fees": 0.00830530315, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": 0.021236794320000002, - "duration": "0:06:24.995108", + "net_pnl": -0.006821043150000632, + "duration": "0:00:17.258079", "symbol": "ETH/USDC", "mexc_executed": true }, { "trade_id": 12, "side": "SHORT", - "entry_time": "2025-05-28T21:52:50.876479+00:00", - "exit_time": "2025-05-28T22:02:04.505086+00:00", - "entry_price": 2657.58, - "exit_price": 2652.4, - "size": 0.003575, - "gross_pnl": 0.018518499999999414, - "fees": 0.009491589250000002, + "entry_time": "2025-05-29T23:52:19.042322+00:00", + "exit_time": "2025-05-29T23:52:21.059640+00:00", + "entry_price": 2630.16, + "exit_price": 2629.8, + "size": 0.002501, + "gross_pnl": 0.0009003599999991812, + "fees": 0.00657757998, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": 0.009026910749999412, - "duration": "0:09:13.628607", + "net_pnl": -0.005677219980000819, + "duration": "0:00:02.017318", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 13, "side": "LONG", - "entry_time": "2025-05-28T22:02:04.505086+00:00", - "exit_time": "2025-05-28T22:11:27.516143+00:00", - "entry_price": 2652.4, - "exit_price": 2645.23, - "size": 0.003236, - "gross_pnl": -0.023202120000000236, - "fees": 0.00857156534, + "entry_time": "2025-05-29T23:52:21.059640+00:00", + "exit_time": "2025-05-29T23:52:22.047822+00:00", + "entry_price": 2629.8, + "exit_price": 2630.1, + "size": 0.003603, + "gross_pnl": 0.001080899999999017, + "fees": 0.00947570985, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.03177368534000023, - "duration": "0:09:23.011057", + "net_pnl": -0.008394809850000982, + "duration": "0:00:00.988182", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 14, "side": "SHORT", - "entry_time": "2025-05-28T22:11:27.516143+00:00", - "exit_time": "2025-05-28T22:17:07.879881+00:00", - "entry_price": 2645.23, - "exit_price": 2645.65, - "size": 0.00319, - "gross_pnl": -0.0013398000000002322, - "fees": 0.008438953600000002, + "entry_time": "2025-05-29T23:52:22.047822+00:00", + "exit_time": "2025-05-29T23:52:23.057350+00:00", + "entry_price": 2630.1, + "exit_price": 2630.21, + "size": 0.002891, + "gross_pnl": -0.0003180100000003681, + "fees": 0.007603778104999999, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.009778753600000235, - "duration": "0:05:40.363738", + "net_pnl": -0.007921788105000367, + "duration": "0:00:01.009528", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 15, "side": "LONG", - "entry_time": "2025-05-28T22:17:07.879881+00:00", - "exit_time": "2025-05-28T22:25:22.521208+00:00", - "entry_price": 2645.65, - "exit_price": 2644.05, - "size": 0.003462, - "gross_pnl": -0.0055391999999996845, - "fees": 0.0091564707, + "entry_time": "2025-05-29T23:52:23.057350+00:00", + "exit_time": "2025-05-29T23:52:24.064707+00:00", + "entry_price": 2630.21, + "exit_price": 2630.2, + "size": 0.003294, + "gross_pnl": -3.294000000071901e-05, + "fees": 0.00866389527, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.014695670699999684, - "duration": "0:08:14.641327", + "net_pnl": -0.00869683527000072, + "duration": "0:00:01.007357", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 16, "side": "SHORT", - "entry_time": "2025-05-28T22:25:22.521208+00:00", - "exit_time": "2025-05-28T22:39:42.164998+00:00", - "entry_price": 2644.05, - "exit_price": 2645.52, - "size": 0.003593, - "gross_pnl": -0.005281709999999281, - "fees": 0.009502712505, + "entry_time": "2025-05-29T23:52:24.064707+00:00", + "exit_time": "2025-05-29T23:52:25.116203+00:00", + "entry_price": 2630.2, + "exit_price": 2630.85, + "size": 0.00314, + "gross_pnl": -0.0020410000000002856, + "fees": 0.0082598485, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.014784422504999282, - "duration": "0:14:19.643790", + "net_pnl": -0.010300848500000286, + "duration": "0:00:01.051496", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 17, "side": "LONG", - "entry_time": "2025-05-28T22:39:42.164998+00:00", - "exit_time": "2025-05-28T22:43:33.947456+00:00", - "entry_price": 2645.52, - "exit_price": 2645.8, - "size": 0.003591, - "gross_pnl": 0.0010054800000007186, - "fees": 0.00950056506, + "entry_time": "2025-05-29T23:52:25.116203+00:00", + "exit_time": "2025-05-29T23:52:38.236771+00:00", + "entry_price": 2630.85, + "exit_price": 2630.7, + "size": 0.003321, + "gross_pnl": -0.0004981500000003021, + "fees": 0.008736803775, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.008495085059999283, - "duration": "0:03:51.782458", + "net_pnl": -0.0092349537750003, + "duration": "0:00:13.120568", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 18, "side": "SHORT", - "entry_time": "2025-05-28T22:43:33.947456+00:00", - "exit_time": "2025-05-28T22:44:15.563490+00:00", - "entry_price": 2645.8, - "exit_price": 2645.3, - "size": 0.002824, - "gross_pnl": 0.001412, - "fees": 0.007471033200000001, + "entry_time": "2025-05-29T23:52:38.236771+00:00", + "exit_time": "2025-05-29T23:52:50.383302+00:00", + "entry_price": 2630.7, + "exit_price": 2630.4, + "size": 0.002656, + "gross_pnl": 0.0007967999999992753, + "fees": 0.0069867408, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.006059033200000001, - "duration": "0:00:41.616034", + "net_pnl": -0.006189940800000725, + "duration": "0:00:12.146531", "symbol": "ETH/USDC", "mexc_executed": true }, { "trade_id": 19, "side": "LONG", - "entry_time": "2025-05-28T22:44:15.563490+00:00", - "exit_time": "2025-05-28T22:45:20.755492+00:00", - "entry_price": 2645.3, - "exit_price": 2644.7, - "size": 0.003452, - "gross_pnl": -0.002071200000001256, - "fees": 0.009130540000000001, + "entry_time": "2025-05-29T23:52:50.383302+00:00", + "exit_time": "2025-05-29T23:53:19.597394+00:00", + "entry_price": 2630.4, + "exit_price": 2633.26, + "size": 0.003426, + "gross_pnl": 0.009798360000000436, + "fees": 0.009016649580000001, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.011201740000001258, - "duration": "0:01:05.192002", + "net_pnl": 0.000781710420000436, + "duration": "0:00:29.214092", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 20, "side": "SHORT", - "entry_time": "2025-05-28T22:45:20.755492+00:00", - "exit_time": "2025-05-28T22:48:46.217118+00:00", - "entry_price": 2644.7, - "exit_price": 2643.58, - "size": 0.003592, - "gross_pnl": 0.004023039999999608, - "fees": 0.00949775088, + "entry_time": "2025-05-29T23:53:19.597394+00:00", + "exit_time": "2025-05-29T23:53:31.013991+00:00", + "entry_price": 2633.26, + "exit_price": 2633.45, + "size": 0.003379, + "gross_pnl": -0.0006420099999986478, + "fees": 0.008898106545, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.005474710880000392, - "duration": "0:03:25.461626", + "net_pnl": -0.009540116544998648, + "duration": "0:00:11.416597", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 21, "side": "LONG", - "entry_time": "2025-05-28T22:48:46.217118+00:00", - "exit_time": "2025-05-28T23:07:03.141905+00:00", - "entry_price": 2643.58, - "exit_price": 2662.4, - "size": 0.003491, - "gross_pnl": 0.06570062000000057, - "fees": 0.00926158809, + "entry_time": "2025-05-29T23:53:31.013991+00:00", + "exit_time": "2025-05-29T23:53:37.784028+00:00", + "entry_price": 2633.45, + "exit_price": 2632.8, + "size": 0.003589, + "gross_pnl": -0.0023328499999986946, + "fees": 0.009450285625, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": 0.056439031910000576, - "duration": "0:18:16.924787", + "net_pnl": -0.011783135624998695, + "duration": "0:00:06.770037", "symbol": "ETH/USDC", "mexc_executed": true }, { "trade_id": 22, "side": "SHORT", - "entry_time": "2025-05-28T23:07:03.141905+00:00", - "exit_time": "2025-05-28T23:23:14.743773+00:00", - "entry_price": 2662.4, - "exit_price": 2670.36, - "size": 0.003568, - "gross_pnl": -0.02840128000000013, - "fees": 0.00951364384, + "entry_time": "2025-05-29T23:53:37.784028+00:00", + "exit_time": "2025-05-29T23:53:51.855920+00:00", + "entry_price": 2632.8, + "exit_price": 2632.46, + "size": 0.003376, + "gross_pnl": 0.0011478400000004914, + "fees": 0.00888775888, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.03791492384000013, - "duration": "0:16:11.601868", + "net_pnl": -0.007739918879999509, + "duration": "0:00:14.071892", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 23, "side": "LONG", - "entry_time": "2025-05-28T23:23:14.743773+00:00", - "exit_time": "2025-05-28T23:28:12.786540+00:00", - "entry_price": 2670.36, - "exit_price": 2676.6, - "size": 0.003558, - "gross_pnl": 0.022201919999999223, - "fees": 0.009512241840000001, + "entry_time": "2025-05-29T23:53:51.855920+00:00", + "exit_time": "2025-05-29T23:53:54.962582+00:00", + "entry_price": 2632.46, + "exit_price": 2632.77, + "size": 0.003609, + "gross_pnl": 0.001118789999999803, + "fees": 0.009501107534999999, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": 0.012689678159999222, - "duration": "0:04:58.042767", + "net_pnl": -0.008382317535000197, + "duration": "0:00:03.106662", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 24, "side": "SHORT", - "entry_time": "2025-05-28T23:28:12.786540+00:00", - "exit_time": "2025-05-28T23:35:43.563539+00:00", - "entry_price": 2676.6, - "exit_price": 2678.21, - "size": 0.002729, - "gross_pnl": -0.004393690000000347, - "fees": 0.007306638245, + "entry_time": "2025-05-29T23:53:54.962582+00:00", + "exit_time": "2025-05-29T23:53:59.920067+00:00", + "entry_price": 2632.77, + "exit_price": 2632.9, + "size": 0.002894, + "gross_pnl": -0.00037622000000031585, + "fees": 0.00761942449, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.011700328245000348, - "duration": "0:07:30.776999", + "net_pnl": -0.007995644490000316, + "duration": "0:00:04.957485", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 25, "side": "LONG", - "entry_time": "2025-05-28T23:35:43.563539+00:00", - "exit_time": "2025-05-28T23:40:40.346268+00:00", - "entry_price": 2678.21, - "exit_price": 2681.59, - "size": 0.003532, - "gross_pnl": 0.011938160000000385, - "fees": 0.0094654068, + "entry_time": "2025-05-29T23:53:59.920067+00:00", + "exit_time": "2025-05-29T23:54:11.215478+00:00", + "entry_price": 2632.9, + "exit_price": 2632.8, + "size": 0.003527, + "gross_pnl": -0.00035269999999967925, + "fees": 0.00928606195, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": 0.0024727532000003846, - "duration": "0:04:56.782729", + "net_pnl": -0.009638761949999679, + "duration": "0:00:11.295411", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 26, "side": "SHORT", - "entry_time": "2025-05-28T23:40:40.346268+00:00", - "exit_time": "2025-05-28T23:51:05.350017+00:00", - "entry_price": 2681.59, - "exit_price": 2676.2, - "size": 0.003312, - "gross_pnl": 0.017851680000001084, - "fees": 0.008872500240000001, + "entry_time": "2025-05-29T23:54:11.215478+00:00", + "exit_time": "2025-05-29T23:54:18.047968+00:00", + "entry_price": 2632.8, + "exit_price": 2632.81, + "size": 0.003608, + "gross_pnl": -3.607999999914682e-05, + "fees": 0.009499160440000001, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": 0.008979179760001085, - "duration": "0:10:25.003749", + "net_pnl": -0.009535240439999147, + "duration": "0:00:06.832490", "symbol": "ETH/USDC", "mexc_executed": true }, { "trade_id": 27, "side": "LONG", - "entry_time": "2025-05-28T23:51:05.350017+00:00", - "exit_time": "2025-05-29T00:09:23.370423+00:00", - "entry_price": 2676.2, - "exit_price": 2675.41, - "size": 0.00355, - "gross_pnl": -0.002804499999999871, - "fees": 0.00949910775, + "entry_time": "2025-05-29T23:54:18.047968+00:00", + "exit_time": "2025-05-29T23:54:19.049526+00:00", + "entry_price": 2632.81, + "exit_price": 2632.78, + "size": 0.003337, + "gross_pnl": -0.0001001099999991502, + "fees": 0.008785636915, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.01230360774999987, - "duration": "0:18:18.020406", + "net_pnl": -0.008885746914999151, + "duration": "0:00:01.001558", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 28, "side": "SHORT", - "entry_time": "2025-05-29T00:09:23.370423+00:00", - "exit_time": "2025-05-29T00:10:47.557021+00:00", - "entry_price": 2675.41, - "exit_price": 2674.7, - "size": 0.003551, - "gross_pnl": 0.002521210000000129, - "fees": 0.009499120304999999, + "entry_time": "2025-05-29T23:54:19.049526+00:00", + "exit_time": "2025-05-29T23:54:28.110776+00:00", + "entry_price": 2632.78, + "exit_price": 2632.8, + "size": 0.003219, + "gross_pnl": -6.437999999994144e-05, + "fees": 0.008474951010000002, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": -0.0069779103049998695, - "duration": "0:01:24.186598", + "net_pnl": -0.008539331009999943, + "duration": "0:00:09.061250", "symbol": "ETH/USDC", - "mexc_executed": true + "mexc_executed": false }, { "trade_id": 29, "side": "LONG", - "entry_time": "2025-05-29T00:10:47.557021+00:00", - "exit_time": "2025-05-29T00:23:50.434702+00:00", - "entry_price": 2674.7, - "exit_price": 2695.4, - "size": 0.003256, - "gross_pnl": 0.0673992000000009, - "fees": 0.008742522800000001, + "entry_time": "2025-05-29T23:54:28.110776+00:00", + "exit_time": "2025-05-29T23:55:00.386077+00:00", + "entry_price": 2632.8, + "exit_price": 2632.81, + "size": 0.003368, + "gross_pnl": 3.3679999999203575e-05, + "fees": 0.00886728724, "fee_type": "taker", "fee_rate": 0.0005, - "net_pnl": 0.058656677200000895, - "duration": "0:13:02.877681", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 30, - "side": "SHORT", - "entry_time": "2025-05-29T00:23:50.434702+00:00", - "exit_time": "2025-05-29T00:25:41.179087+00:00", - "entry_price": 2695.4, - "exit_price": 2699.71, - "size": 0.003525, - "gross_pnl": -0.015192749999999807, - "fees": 0.009508881375, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.024701631374999807, - "duration": "0:01:50.744385", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 31, - "side": "LONG", - "entry_time": "2025-05-29T00:25:41.179087+00:00", - "exit_time": "2025-05-29T00:34:18.271516+00:00", - "entry_price": 2699.71, - "exit_price": 2697.4, - "size": 0.003519, - "gross_pnl": -0.008128889999999807, - "fees": 0.009496215045, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.017625105044999808, - "duration": "0:08:37.092429", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 32, - "side": "SHORT", - "entry_time": "2025-05-29T00:34:18.271516+00:00", - "exit_time": "2025-05-29T00:39:46.576066+00:00", - "entry_price": 2697.4, - "exit_price": 2704.21, - "size": 0.003522, - "gross_pnl": -0.023984819999999806, - "fees": 0.00951223521, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.03349705520999981, - "duration": "0:05:28.304550", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 33, - "side": "LONG", - "entry_time": "2025-05-29T00:39:46.576066+00:00", - "exit_time": "2025-05-29T00:41:59.418420+00:00", - "entry_price": 2704.21, - "exit_price": 2703.79, - "size": 0.003026, - "gross_pnl": -0.0012709200000002201, - "fees": 0.008182304000000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.009453224000000222, - "duration": "0:02:12.842354", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 34, - "side": "SHORT", - "entry_time": "2025-05-29T00:41:59.418420+00:00", - "exit_time": "2025-05-29T00:43:40.781819+00:00", - "entry_price": 2703.79, - "exit_price": 2705.17, - "size": 0.003514, - "gross_pnl": -0.004849320000000384, - "fees": 0.00950354272, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.014352862720000383, - "duration": "0:01:41.363399", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 35, - "side": "LONG", - "entry_time": "2025-05-29T00:43:40.781819+00:00", - "exit_time": "2025-05-29T00:44:52.500862+00:00", - "entry_price": 2705.17, - "exit_price": 2703.8, - "size": 0.00258, - "gross_pnl": -0.0035345999999997183, - "fees": 0.0069775713, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.010512171299999718, - "duration": "0:01:11.719043", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 36, - "side": "SHORT", - "entry_time": "2025-05-29T00:44:52.500862+00:00", - "exit_time": "2025-05-29T01:03:02.291713+00:00", - "entry_price": 2703.8, - "exit_price": 2716.47, - "size": 0.002725, - "gross_pnl": -0.03452574999999896, - "fees": 0.007385117875, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.04191086787499897, - "duration": "0:18:09.790851", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 37, - "side": "LONG", - "entry_time": "2025-05-29T01:03:02.291713+00:00", - "exit_time": "2025-05-29T01:05:29.863283+00:00", - "entry_price": 2716.47, - "exit_price": 2711.4, - "size": 0.003444, - "gross_pnl": -0.017461079999998998, - "fees": 0.00934679214, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.026807872139999, - "duration": "0:02:27.571570", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 38, - "side": "SHORT", - "entry_time": "2025-05-29T01:05:29.863283+00:00", - "exit_time": "2025-05-29T01:10:25.618519+00:00", - "entry_price": 2711.4, - "exit_price": 2708.74, - "size": 0.002446, - "gross_pnl": 0.006506360000000756, - "fees": 0.006628831219999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.00012247121999924395, - "duration": "0:04:55.755236", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 39, - "side": "LONG", - "entry_time": "2025-05-29T01:10:25.618519+00:00", - "exit_time": "2025-05-29T01:14:50.050413+00:00", - "entry_price": 2708.74, - "exit_price": 2711.81, - "size": 0.00295, - "gross_pnl": 0.009056500000000483, - "fees": 0.00799531125, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.001061188750000483, - "duration": "0:04:24.431894", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 40, - "side": "SHORT", - "entry_time": "2025-05-29T01:14:50.050413+00:00", - "exit_time": "2025-05-29T01:24:52.491054+00:00", - "entry_price": 2711.81, - "exit_price": 2711.97, - "size": 0.003035, - "gross_pnl": -0.00048559999999955834, - "fees": 0.00823058615, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.008716186149999559, - "duration": "0:10:02.440641", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 41, - "side": "LONG", - "entry_time": "2025-05-29T01:24:52.491054+00:00", - "exit_time": "2025-05-29T01:32:56.085867+00:00", - "entry_price": 2711.97, - "exit_price": 2703.55, - "size": 0.003321, - "gross_pnl": -0.027962819999998733, - "fees": 0.00899247096, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.036955290959998735, - "duration": "0:08:03.594813", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 42, - "side": "SHORT", - "entry_time": "2025-05-29T01:32:56.085867+00:00", - "exit_time": "2025-05-29T01:46:07.668848+00:00", - "entry_price": 2703.55, - "exit_price": 2710.91, - "size": 0.002934, - "gross_pnl": -0.02159423999999904, - "fees": 0.00794301282, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.02953725281999904, - "duration": "0:13:11.582981", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 43, - "side": "LONG", - "entry_time": "2025-05-29T01:46:07.668848+00:00", - "exit_time": "2025-05-29T01:47:40.231354+00:00", - "entry_price": 2710.91, - "exit_price": 2709.55, - "size": 0.003504, - "gross_pnl": -0.004765439999998853, - "fees": 0.009496645920000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.014262085919998854, - "duration": "0:01:32.562506", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 44, - "side": "SHORT", - "entry_time": "2025-05-29T01:47:40.231354+00:00", - "exit_time": "2025-05-29T01:52:59.964214+00:00", - "entry_price": 2709.55, - "exit_price": 2718.0, - "size": 0.003466, - "gross_pnl": -0.02928769999999937, - "fees": 0.009405944150000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.038693644149999366, - "duration": "0:05:19.732860", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 45, - "side": "LONG", - "entry_time": "2025-05-29T01:52:59.964214+00:00", - "exit_time": "2025-05-29T01:58:50.064787+00:00", - "entry_price": 2718.0, - "exit_price": 2715.0, - "size": 0.003205, - "gross_pnl": -0.009615, - "fees": 0.0087063825, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0183213825, - "duration": "0:05:50.100573", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 46, - "side": "SHORT", - "entry_time": "2025-05-29T01:58:50.064787+00:00", - "exit_time": "2025-05-29T02:03:29.100172+00:00", - "entry_price": 2715.0, - "exit_price": 2712.56, - "size": 0.002409, - "gross_pnl": 0.005877960000000132, - "fees": 0.00653749602, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.000659536019999869, - "duration": "0:04:39.035385", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 47, - "side": "LONG", - "entry_time": "2025-05-29T02:03:29.100172+00:00", - "exit_time": "2025-05-29T02:14:47.356081+00:00", - "entry_price": 2712.56, - "exit_price": 2708.91, - "size": 0.003246, - "gross_pnl": -0.011847900000000296, - "fees": 0.00879904581, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.020646945810000296, - "duration": "0:11:18.255909", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 48, - "side": "SHORT", - "entry_time": "2025-05-29T02:14:47.356081+00:00", - "exit_time": "2025-05-29T02:18:25.415382+00:00", - "entry_price": 2708.91, - "exit_price": 2712.17, - "size": 0.002953, - "gross_pnl": -0.009626780000000645, - "fees": 0.00800422462, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.017631004620000647, - "duration": "0:03:38.059301", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 49, - "side": "LONG", - "entry_time": "2025-05-29T02:18:25.415382+00:00", - "exit_time": "2025-05-29T02:20:32.872212+00:00", - "entry_price": 2712.17, - "exit_price": 2714.7, - "size": 0.003072, - "gross_pnl": 0.007772159999999218, - "fees": 0.008335672320000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0005635123200007823, - "duration": "0:02:07.456830", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 50, - "side": "SHORT", - "entry_time": "2025-05-29T02:20:32.872212+00:00", - "exit_time": "2025-05-29T02:21:41.064393+00:00", - "entry_price": 2714.7, - "exit_price": 2716.4, - "size": 0.003145, - "gross_pnl": -0.0053465000000008575, - "fees": 0.00854040475, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.013886904750000857, - "duration": "0:01:08.192181", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 51, - "side": "LONG", - "entry_time": "2025-05-29T02:21:41.064393+00:00", - "exit_time": "2025-05-29T02:26:59.430791+00:00", - "entry_price": 2716.4, - "exit_price": 2736.39, - "size": 0.002648, - "gross_pnl": 0.05293351999999943, - "fees": 0.00721949396, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.045714026039999425, - "duration": "0:05:18.366398", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 52, - "side": "SHORT", - "entry_time": "2025-05-29T02:26:59.430791+00:00", - "exit_time": "2025-05-29T02:28:37.526508+00:00", - "entry_price": 2736.39, - "exit_price": 2744.94, - "size": 0.003472, - "gross_pnl": -0.02968560000000063, - "fees": 0.009515588879999998, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.03920118888000063, - "duration": "0:01:38.095717", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 53, - "side": "LONG", - "entry_time": "2025-05-29T02:28:37.526508+00:00", - "exit_time": "2025-05-29T02:30:44.331791+00:00", - "entry_price": 2744.94, - "exit_price": 2763.6, - "size": 0.002668, - "gross_pnl": 0.04978487999999961, - "fees": 0.007348392359999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.042436487639999604, - "duration": "0:02:06.805283", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 54, - "side": "SHORT", - "entry_time": "2025-05-29T02:30:44.331791+00:00", - "exit_time": "2025-05-29T02:40:18.830420+00:00", - "entry_price": 2763.6, - "exit_price": 2771.29, - "size": 0.003033, - "gross_pnl": -0.023323770000000167, - "fees": 0.008393660685000002, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.031717430685000166, - "duration": "0:09:34.498629", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 55, - "side": "LONG", - "entry_time": "2025-05-29T02:40:18.830420+00:00", - "exit_time": "2025-05-29T02:46:26.569541+00:00", - "entry_price": 2771.29, - "exit_price": 2773.54, - "size": 0.003428, - "gross_pnl": 0.007713, - "fees": 0.00950383862, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0017908386199999996, - "duration": "0:06:07.739121", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 56, - "side": "SHORT", - "entry_time": "2025-05-29T02:46:26.569541+00:00", - "exit_time": "2025-05-29T02:50:08.691586+00:00", - "entry_price": 2773.54, - "exit_price": 2769.3, - "size": 0.003117, - "gross_pnl": 0.01321607999999932, - "fees": 0.00863851614, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.004577563859999321, - "duration": "0:03:42.122045", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 57, - "side": "LONG", - "entry_time": "2025-05-29T02:50:08.691586+00:00", - "exit_time": "2025-05-29T02:53:58.838205+00:00", - "entry_price": 2769.3, - "exit_price": 2771.96, - "size": 0.003064, - "gross_pnl": 0.008150239999999554, - "fees": 0.00848921032, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.00033897032000044635, - "duration": "0:03:50.146619", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 58, - "side": "SHORT", - "entry_time": "2025-05-29T02:53:58.838205+00:00", - "exit_time": "2025-05-29T03:11:51.633039+00:00", - "entry_price": 2771.96, - "exit_price": 2758.09, - "size": 0.002578, - "gross_pnl": 0.03575685999999972, - "fees": 0.007128234450000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.028628625549999717, - "duration": "0:17:52.794834", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 59, - "side": "LONG", - "entry_time": "2025-05-29T03:11:51.633039+00:00", - "exit_time": "2025-05-29T03:13:43.287739+00:00", - "entry_price": 2758.09, - "exit_price": 2757.8, - "size": 0.002653, - "gross_pnl": -0.0007693699999999035, - "fees": 0.007316828085000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.008086198084999904, - "duration": "0:01:51.654700", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 60, - "side": "SHORT", - "entry_time": "2025-05-29T03:13:43.287739+00:00", - "exit_time": "2025-05-29T03:17:13.749596+00:00", - "entry_price": 2757.8, - "exit_price": 2761.91, - "size": 0.003445, - "gross_pnl": -0.014158949999998872, - "fees": 0.009507700475, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.02366665047499887, - "duration": "0:03:30.461857", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 61, - "side": "LONG", - "entry_time": "2025-05-29T03:17:13.749596+00:00", - "exit_time": "2025-05-29T03:18:58.462056+00:00", - "entry_price": 2761.91, - "exit_price": 2758.7, - "size": 0.003401, - "gross_pnl": -0.010917210000000123, - "fees": 0.009387797304999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.020305007305000122, - "duration": "0:01:44.712460", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 62, - "side": "SHORT", - "entry_time": "2025-05-29T03:18:58.462056+00:00", - "exit_time": "2025-05-29T03:22:58.833460+00:00", - "entry_price": 2758.7, - "exit_price": 2757.99, - "size": 0.003131, - "gross_pnl": 0.002223010000000114, - "fees": 0.008636378195, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.006413368194999887, - "duration": "0:04:00.371404", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 63, - "side": "LONG", - "entry_time": "2025-05-29T03:22:58.833460+00:00", - "exit_time": "2025-05-29T03:25:49.319753+00:00", - "entry_price": 2757.99, - "exit_price": 2761.69, - "size": 0.003207, - "gross_pnl": 0.011865900000000876, - "fees": 0.00885080688, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.0030150931200008764, - "duration": "0:02:50.486293", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 64, - "side": "SHORT", - "entry_time": "2025-05-29T03:25:49.319753+00:00", - "exit_time": "2025-05-29T03:27:39.342790+00:00", - "entry_price": 2761.69, - "exit_price": 2760.18, - "size": 0.00298, - "gross_pnl": 0.0044998000000006505, - "fees": 0.008227586299999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0037277862999993494, - "duration": "0:01:50.023037", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 65, - "side": "LONG", - "entry_time": "2025-05-29T03:27:39.342790+00:00", - "exit_time": "2025-05-29T03:33:01.458411+00:00", - "entry_price": 2760.18, - "exit_price": 2762.71, - "size": 0.003307, - "gross_pnl": 0.008366710000000662, - "fees": 0.009132098615, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0007653886149993384, - "duration": "0:05:22.115621", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 66, - "side": "SHORT", - "entry_time": "2025-05-29T03:33:01.458411+00:00", - "exit_time": "2025-05-29T03:34:44.775910+00:00", - "entry_price": 2762.71, - "exit_price": 2761.79, - "size": 0.003037, - "gross_pnl": 0.002794040000000221, - "fees": 0.008388953250000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0055949132499997795, - "duration": "0:01:43.317499", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 67, - "side": "LONG", - "entry_time": "2025-05-29T03:34:44.775910+00:00", - "exit_time": "2025-05-29T03:41:04.255674+00:00", - "entry_price": 2761.79, - "exit_price": 2753.11, - "size": 0.00299, - "gross_pnl": -0.02595319999999951, - "fees": 0.0082447755, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.03419797549999951, - "duration": "0:06:19.479764", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 68, - "side": "SHORT", - "entry_time": "2025-05-29T03:41:04.255674+00:00", - "exit_time": "2025-05-29T03:56:34.689888+00:00", - "entry_price": 2753.11, - "exit_price": 2758.91, - "size": 0.002879, - "gross_pnl": -0.016698199999999216, - "fees": 0.00793455279, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.024632752789999216, - "duration": "0:15:30.434214", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 69, - "side": "LONG", - "entry_time": "2025-05-29T03:56:34.689888+00:00", - "exit_time": "2025-05-29T03:58:55.388041+00:00", - "entry_price": 2758.91, - "exit_price": 2760.52, - "size": 0.003443, - "gross_pnl": 0.005543230000000439, - "fees": 0.009501698744999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.003958468744999561, - "duration": "0:02:20.698153", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 70, - "side": "SHORT", - "entry_time": "2025-05-29T03:58:55.388041+00:00", - "exit_time": "2025-05-29T04:02:00.913576+00:00", - "entry_price": 2760.52, - "exit_price": 2764.52, - "size": 0.002407, - "gross_pnl": -0.009628, - "fees": 0.00664938564, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.01627738564, - "duration": "0:03:05.525535", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 71, - "side": "LONG", - "entry_time": "2025-05-29T04:02:00.913576+00:00", - "exit_time": "2025-05-29T04:07:40.788201+00:00", - "entry_price": 2764.52, - "exit_price": 2760.09, - "size": 0.003427, - "gross_pnl": -0.015181609999999438, - "fees": 0.009466419234999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.02464802923499944, - "duration": "0:05:39.874625", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 72, - "side": "SHORT", - "entry_time": "2025-05-29T04:07:40.788201+00:00", - "exit_time": "2025-05-29T04:26:49.001106+00:00", - "entry_price": 2760.09, - "exit_price": 2752.59, - "size": 0.003442, - "gross_pnl": 0.025815, - "fees": 0.009487322280000002, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.01632767772, - "duration": "0:19:08.212905", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 73, - "side": "LONG", - "entry_time": "2025-05-29T04:26:49.001106+00:00", - "exit_time": "2025-05-29T04:40:13.723197+00:00", - "entry_price": 2752.59, - "exit_price": 2734.98, - "size": 0.003405, - "gross_pnl": -0.05996205000000043, - "fees": 0.009342587925, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.06930463792500043, - "duration": "0:13:24.722091", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 74, - "side": "SHORT", - "entry_time": "2025-05-29T04:40:13.723197+00:00", - "exit_time": "2025-05-29T04:42:15.268388+00:00", - "entry_price": 2734.98, - "exit_price": 2726.1, - "size": 0.003474, - "gross_pnl": 0.03084912000000038, - "fees": 0.00948589596, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.021363224040000378, - "duration": "0:02:01.545191", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 75, - "side": "LONG", - "entry_time": "2025-05-29T04:42:15.268388+00:00", - "exit_time": "2025-05-29T05:01:00.746842+00:00", - "entry_price": 2726.1, - "exit_price": 2728.5, - "size": 0.003312, - "gross_pnl": 0.0079488000000003, - "fees": 0.0090328176, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0010840175999996988, - "duration": "0:18:45.478454", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 76, - "side": "SHORT", - "entry_time": "2025-05-29T05:01:00.746842+00:00", - "exit_time": "2025-05-29T05:03:19.086026+00:00", - "entry_price": 2728.5, - "exit_price": 2729.2, - "size": 0.003482, - "gross_pnl": -0.0024373999999993665, - "fees": 0.009501855699999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.011939255699999365, - "duration": "0:02:18.339184", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 77, - "side": "LONG", - "entry_time": "2025-05-29T05:03:19.086026+00:00", - "exit_time": "2025-05-29T05:06:51.740493+00:00", - "entry_price": 2729.2, - "exit_price": 2726.2, - "size": 0.003433, - "gross_pnl": -0.010298999999999999, - "fees": 0.0093641941, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.019663194099999996, - "duration": "0:03:32.654467", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 78, - "side": "SHORT", - "entry_time": "2025-05-29T05:06:51.740493+00:00", - "exit_time": "2025-05-29T05:10:54.685720+00:00", - "entry_price": 2726.2, - "exit_price": 2728.65, - "size": 0.003013, - "gross_pnl": -0.007381850000000822, - "fees": 0.008217731524999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.015599581525000822, - "duration": "0:04:02.945227", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 79, - "side": "LONG", - "entry_time": "2025-05-29T05:10:54.685720+00:00", - "exit_time": "2025-05-29T05:13:51.340242+00:00", - "entry_price": 2728.65, - "exit_price": 2726.39, - "size": 0.003149, - "gross_pnl": -0.007116740000000687, - "fees": 0.00858896048, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.015705700480000688, - "duration": "0:02:56.654522", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 80, - "side": "SHORT", - "entry_time": "2025-05-29T05:13:51.340242+00:00", - "exit_time": "2025-05-29T05:14:53.396489+00:00", - "entry_price": 2726.39, - "exit_price": 2726.63, - "size": 0.002456, - "gross_pnl": -0.0005894400000005807, - "fees": 0.006696308559999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.00728574856000058, - "duration": "0:01:02.056247", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 81, - "side": "LONG", - "entry_time": "2025-05-29T05:14:53.396489+00:00", - "exit_time": "2025-05-29T05:16:32.192291+00:00", - "entry_price": 2726.63, - "exit_price": 2729.1, - "size": 0.003303, - "gross_pnl": 0.008158409999999339, - "fees": 0.009010138095, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0008517280950006614, - "duration": "0:01:38.795802", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 82, - "side": "SHORT", - "entry_time": "2025-05-29T05:16:32.192291+00:00", - "exit_time": "2025-05-29T05:27:41.465062+00:00", - "entry_price": 2729.1, - "exit_price": 2725.7, - "size": 0.002968, - "gross_pnl": 0.01009120000000027, - "fees": 0.0080949232, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.00199627680000027, - "duration": "0:11:09.272771", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 83, - "side": "LONG", - "entry_time": "2025-05-29T05:27:41.465062+00:00", - "exit_time": "2025-05-29T05:45:56.882106+00:00", - "entry_price": 2725.7, - "exit_price": 2730.62, - "size": 0.003325, - "gross_pnl": 0.01635900000000024, - "fees": 0.009071131999999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.007287868000000241, - "duration": "0:18:15.417044", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 84, - "side": "SHORT", - "entry_time": "2025-05-29T05:45:56.882106+00:00", - "exit_time": "2025-05-29T05:46:54.739105+00:00", - "entry_price": 2730.62, - "exit_price": 2732.1, - "size": 0.002974, - "gross_pnl": -0.0044015200000000546, - "fees": 0.00812306464, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.012524584640000055, - "duration": "0:00:57.856999", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 85, - "side": "LONG", - "entry_time": "2025-05-29T05:46:54.739105+00:00", - "exit_time": "2025-05-29T05:47:36.530754+00:00", - "entry_price": 2732.1, - "exit_price": 2735.5, - "size": 0.003477, - "gross_pnl": 0.011821800000000316, - "fees": 0.0095054226, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.0023163774000003155, - "duration": "0:00:41.791649", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 86, - "side": "SHORT", - "entry_time": "2025-05-29T05:47:36.530754+00:00", - "exit_time": "2025-05-29T05:47:52.608193+00:00", - "entry_price": 2735.5, - "exit_price": 2735.2, - "size": 0.002866, - "gross_pnl": 0.0008598000000005214, - "fees": 0.0078395131, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0069797130999994786, - "duration": "0:00:16.077439", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 87, - "side": "LONG", - "entry_time": "2025-05-29T05:47:52.608193+00:00", - "exit_time": "2025-05-29T05:48:02.651202+00:00", - "entry_price": 2735.2, - "exit_price": 2733.85, - "size": 0.003428, - "gross_pnl": -0.004627799999999688, - "fees": 0.0093739517, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.01400175169999969, - "duration": "0:00:10.043009", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 88, - "side": "SHORT", - "entry_time": "2025-05-29T05:48:02.651202+00:00", - "exit_time": "2025-05-29T05:49:09.057335+00:00", - "entry_price": 2733.85, - "exit_price": 2732.0, - "size": 0.003475, - "gross_pnl": 0.006428749999999684, - "fees": 0.009496914374999998, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0030681643750003146, - "duration": "0:01:06.406133", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 89, - "side": "LONG", - "entry_time": "2025-05-29T05:49:09.057335+00:00", - "exit_time": "2025-05-29T05:50:07.289280+00:00", - "entry_price": 2732.0, - "exit_price": 2734.81, - "size": 0.0034, - "gross_pnl": 0.009553999999999814, - "fees": 0.009293577, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 0.00026042299999981446, - "duration": "0:00:58.231945", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 90, - "side": "SHORT", - "entry_time": "2025-05-29T05:50:07.289280+00:00", - "exit_time": "2025-05-29T05:50:12.693919+00:00", - "entry_price": 2734.81, - "exit_price": 2735.21, - "size": 0.003474, - "gross_pnl": -0.001389600000000316, - "fees": 0.00950142474, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.010891024740000317, - "duration": "0:00:05.404639", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 91, - "side": "LONG", - "entry_time": "2025-05-29T05:50:12.693919+00:00", - "exit_time": "2025-05-29T05:50:23.873753+00:00", - "entry_price": 2735.21, - "exit_price": 2734.69, - "size": 0.003208, - "gross_pnl": -0.0016681599999999416, - "fees": 0.0087737196, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.01044187959999994, - "duration": "0:00:11.179834", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 92, - "side": "SHORT", - "entry_time": "2025-05-29T05:50:23.873753+00:00", - "exit_time": "2025-05-29T05:50:33.603035+00:00", - "entry_price": 2734.69, - "exit_price": 2735.09, - "size": 0.002854, - "gross_pnl": -0.0011416000000002596, - "fees": 0.007805376060000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.00894697606000026, - "duration": "0:00:09.729282", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 93, - "side": "LONG", - "entry_time": "2025-05-29T05:50:33.603035+00:00", - "exit_time": "2025-05-29T05:50:43.597445+00:00", - "entry_price": 2735.09, - "exit_price": 2734.01, - "size": 0.003181, - "gross_pnl": -0.0034354799999997683, - "fees": 0.00869860355, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.012134083549999768, - "duration": "0:00:09.994410", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 94, - "side": "SHORT", - "entry_time": "2025-05-29T05:50:43.597445+00:00", - "exit_time": "2025-05-29T05:51:08.546105+00:00", - "entry_price": 2734.01, - "exit_price": 2734.92, - "size": 0.003475, - "gross_pnl": -0.003162249999999494, - "fees": 0.009502265875, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.012664515874999494, - "duration": "0:00:24.948660", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 95, - "side": "LONG", - "entry_time": "2025-05-29T05:51:08.546105+00:00", - "exit_time": "2025-05-29T05:51:14.225825+00:00", - "entry_price": 2734.92, - "exit_price": 2734.31, - "size": 0.002668, - "gross_pnl": -0.0016274800000003395, - "fees": 0.00729595282, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.008923432820000339, - "duration": "0:00:05.679720", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 96, - "side": "SHORT", - "entry_time": "2025-05-29T05:51:14.225825+00:00", - "exit_time": "2025-05-29T05:52:09.389338+00:00", - "entry_price": 2734.31, - "exit_price": 2732.7, - "size": 0.00319, - "gross_pnl": 0.0051359000000004065, - "fees": 0.00871988095, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0035839809499995934, - "duration": "0:00:55.163513", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 97, - "side": "LONG", - "entry_time": "2025-05-29T05:52:09.389338+00:00", - "exit_time": "2025-05-29T05:52:41.588099+00:00", - "entry_price": 2732.7, - "exit_price": 2731.3, - "size": 0.003476, - "gross_pnl": -0.004866399999998735, - "fees": 0.009496431999999999, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.014362831999998736, - "duration": "0:00:32.198761", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 98, - "side": "SHORT", - "entry_time": "2025-05-29T05:52:41.588099+00:00", - "exit_time": "2025-05-29T05:53:23.400068+00:00", - "entry_price": 2731.3, - "exit_price": 2728.88, - "size": 0.002838, - "gross_pnl": 0.006867960000000206, - "fees": 0.007747995420000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0008800354199997944, - "duration": "0:00:41.811969", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 99, - "side": "LONG", - "entry_time": "2025-05-29T05:53:23.400068+00:00", - "exit_time": "2025-05-29T05:53:33.781642+00:00", - "entry_price": 2728.88, - "exit_price": 2728.13, - "size": 0.003331, - "gross_pnl": -0.00249825, - "fees": 0.009088650155000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.011586900155000001, - "duration": "0:00:10.381574", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 100, - "side": "SHORT", - "entry_time": "2025-05-29T05:53:33.781642+00:00", - "exit_time": "2025-05-29T05:53:39.264465+00:00", - "entry_price": 2728.13, - "exit_price": 2728.62, - "size": 0.003482, - "gross_pnl": -0.0017061799999992398, - "fees": 0.00950020175, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.01120638174999924, - "duration": "0:00:05.482823", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 101, - "side": "LONG", - "entry_time": "2025-05-29T05:53:39.264465+00:00", - "exit_time": "2025-05-29T05:53:49.430854+00:00", - "entry_price": 2728.62, - "exit_price": 2726.5, - "size": 0.002915, - "gross_pnl": -0.006179799999999682, - "fees": 0.0079508374, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.014130637399999682, - "duration": "0:00:10.166389", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 102, - "side": "SHORT", - "entry_time": "2025-05-29T05:53:49.430854+00:00", - "exit_time": "2025-05-29T05:54:14.903319+00:00", - "entry_price": 2726.5, - "exit_price": 2725.11, - "size": 0.003484, - "gross_pnl": 0.0048427599999995566, - "fees": 0.009496704620000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.004653944620000444, - "duration": "0:00:25.472465", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 103, - "side": "LONG", - "entry_time": "2025-05-29T05:54:14.903319+00:00", - "exit_time": "2025-05-29T05:54:41.304208+00:00", - "entry_price": 2725.11, - "exit_price": 2726.1, - "size": 0.003406, - "gross_pnl": 0.0033719399999992566, - "fees": 0.00928341063, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0059114706300007445, - "duration": "0:00:26.400889", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 104, - "side": "SHORT", - "entry_time": "2025-05-29T05:54:41.304208+00:00", - "exit_time": "2025-05-29T05:55:02.154969+00:00", - "entry_price": 2726.1, - "exit_price": 2726.1, - "size": 0.003448, - "gross_pnl": 0.0, - "fees": 0.0093995928, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0093995928, - "duration": "0:00:20.850761", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 105, - "side": "LONG", - "entry_time": "2025-05-29T05:55:02.154969+00:00", - "exit_time": "2025-05-29T05:55:12.570755+00:00", - "entry_price": 2726.1, - "exit_price": 2720.91, - "size": 0.002752, - "gross_pnl": -0.01428288000000015, - "fees": 0.00749508576, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.021777965760000153, - "duration": "0:00:10.415786", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 106, - "side": "SHORT", - "entry_time": "2025-05-29T05:55:12.570755+00:00", - "exit_time": "2025-05-29T05:55:28.199734+00:00", - "entry_price": 2720.91, - "exit_price": 2722.8, - "size": 0.003301, - "gross_pnl": -0.006238890000001081, - "fees": 0.008984843355000001, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.015223733355001082, - "duration": "0:00:15.628979", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 107, - "side": "LONG", - "entry_time": "2025-05-29T05:55:28.199734+00:00", - "exit_time": "2025-05-29T05:55:48.581493+00:00", - "entry_price": 2722.8, - "exit_price": 2722.94, - "size": 0.003489, - "gross_pnl": 0.0004884599999995557, - "fees": 0.00950009343, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.009011633430000445, - "duration": "0:00:20.381759", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 108, - "side": "SHORT", - "entry_time": "2025-05-29T05:55:48.581493+00:00", - "exit_time": "2025-05-29T05:56:15.023454+00:00", - "entry_price": 2722.94, - "exit_price": 2720.19, - "size": 0.002969, - "gross_pnl": 0.00816475, - "fees": 0.008080326485, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": 8.442351500000025e-05, - "duration": "0:00:26.441961", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 109, - "side": "LONG", - "entry_time": "2025-05-29T05:56:15.023454+00:00", - "exit_time": "2025-05-29T05:56:50.858878+00:00", - "entry_price": 2720.19, - "exit_price": 2720.7, - "size": 0.003201, - "gross_pnl": 0.001632509999999243, - "fees": 0.008708144445, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.007075634445000757, - "duration": "0:00:35.835424", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 110, - "side": "SHORT", - "entry_time": "2025-05-29T05:56:50.858878+00:00", - "exit_time": "2025-05-29T05:57:06.428552+00:00", - "entry_price": 2720.7, - "exit_price": 2721.6, - "size": 0.003492, - "gross_pnl": -0.0031428000000003173, - "fees": 0.0095022558, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.012645055800000318, - "duration": "0:00:15.569674", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 111, - "side": "LONG", - "entry_time": "2025-05-29T05:57:06.428552+00:00", - "exit_time": "2025-05-29T05:57:37.820004+00:00", - "entry_price": 2721.6, - "exit_price": 2723.1, - "size": 0.003115, - "gross_pnl": 0.0046725000000000004, - "fees": 0.00848012025, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.0038076202499999993, - "duration": "0:00:31.391452", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 112, - "side": "SHORT", - "entry_time": "2025-05-29T05:57:37.820004+00:00", - "exit_time": "2025-05-29T05:57:42.906791+00:00", - "entry_price": 2723.1, - "exit_price": 2723.49, - "size": 0.003228, - "gross_pnl": -0.001258919999999589, - "fees": 0.00879079626, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.010049716259999588, - "duration": "0:00:05.086787", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 113, - "side": "LONG", - "entry_time": "2025-05-29T05:57:42.906791+00:00", - "exit_time": "2025-05-29T05:58:23.362696+00:00", - "entry_price": 2723.49, - "exit_price": 2723.8, - "size": 0.003238, - "gross_pnl": 0.0010037800000012957, - "fees": 0.00881916251, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.007815382509998706, - "duration": "0:00:40.455905", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 114, - "side": "SHORT", - "entry_time": "2025-05-29T05:58:23.362696+00:00", - "exit_time": "2025-05-29T05:58:44.158632+00:00", - "entry_price": 2723.8, - "exit_price": 2723.92, - "size": 0.003299, - "gross_pnl": -0.0003958799999996399, - "fees": 0.00898601414, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.009381894139999639, - "duration": "0:00:20.795936", - "symbol": "ETH/USDC", - "mexc_executed": false - }, - { - "trade_id": 115, - "side": "LONG", - "entry_time": "2025-05-29T05:58:44.158632+00:00", - "exit_time": "2025-05-29T05:58:49.623839+00:00", - "entry_price": 2723.92, - "exit_price": 2723.83, - "size": 0.003317, - "gross_pnl": -0.0002985300000004827, - "fees": 0.009035093375, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.009333623375000483, - "duration": "0:00:05.465207", - "symbol": "ETH/USDC", - "mexc_executed": true - }, - { - "trade_id": 116, - "side": "SHORT", - "entry_time": "2025-05-29T05:58:49.623839+00:00", - "exit_time": "2025-05-29T05:59:26.192661+00:00", - "entry_price": 2723.83, - "exit_price": 2722.74, - "size": 0.002869, - "gross_pnl": 0.0031272100000004177, - "fees": 0.007813104665, - "fee_type": "taker", - "fee_rate": 0.0005, - "net_pnl": -0.004685894664999583, - "duration": "0:00:36.568822", + "net_pnl": -0.008833607240000797, + "duration": "0:00:32.275301", "symbol": "ETH/USDC", "mexc_executed": true } diff --git a/docs/requirements.md b/docs/requirements.md index 8225230..a43e046 100644 --- a/docs/requirements.md +++ b/docs/requirements.md @@ -34,3 +34,15 @@ we will have 2 types of pivot points: theese pivot points will define the trend direction and the trend strength. level 2 pivot should not use different (bigger ) price timeframe, but should use the level1 pivot points as candles instead. so a level 2 low pivot is a when a level 1 pivot low is surrownded by higher level 1 pibot lows +---- +input should be multitiframe and multi symbol timeseries with the label of the "chart" included, so the model knows what th esecondary timeseries is. So +primary symbol (that we trade, now ETC): + - 5 min of raw ticks data + - 900 of 1s timeseries with common indicators + - 900 of 1m and 900 of 1h with indicators + - all the available pivot points (multiple levels) + - one additional reference symbol (BTC) - 5 min ot ticks +if there are no ticks, we bstitute them with 1s or lowest ohclv data. +this is my idea, but I am open to improvement suggestions. +output of the CNN model should be the next pibot point in each level +course, data must be normalized to the max and min of the highest timeframe, so the relations between different timeframes stay the same diff --git a/test_enhanced_williams_cnn.py b/test_enhanced_williams_cnn.py new file mode 100644 index 0000000..1da5152 --- /dev/null +++ b/test_enhanced_williams_cnn.py @@ -0,0 +1,346 @@ +#!/usr/bin/env python3 +""" +Test Enhanced Williams Market Structure with CNN Integration + +This script demonstrates the multi-timeframe, multi-symbol CNN-enabled +Williams Market Structure that predicts pivot points using TrainingDataPacket. + +Features tested: +- Multi-timeframe data integration (1s, 1m, 1h) +- Multi-symbol support (ETH, BTC) +- Tick data aggregation +- 1h-based normalization strategy +- Multi-level pivot prediction (5 levels, type + price) +""" + +import numpy as np +import logging +from datetime import datetime, timedelta +from typing import Dict, List, Optional, Any +from dataclasses import dataclass + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') +logger = logging.getLogger(__name__) + +# Mock TrainingDataPacket for testing +@dataclass +class MockTrainingDataPacket: + """Mock TrainingDataPacket for testing CNN integration""" + timestamp: datetime + symbol: str + tick_cache: List[Dict[str, Any]] + one_second_bars: List[Dict[str, Any]] + multi_timeframe_data: Dict[str, List[Dict[str, Any]]] + cnn_features: Optional[Dict[str, np.ndarray]] = None + cnn_predictions: Optional[Dict[str, np.ndarray]] = None + market_state: Optional[Any] = None + universal_stream: Optional[Any] = None + +class MockTrainingDataProvider: + """Mock provider that supplies TrainingDataPacket for testing""" + + def __init__(self): + self.training_data_buffer = [] + self._generate_mock_data() + + def _generate_mock_data(self): + """Generate comprehensive mock market data""" + current_time = datetime.now() + + # Generate ETH data for different timeframes + eth_1s_data = self._generate_ohlcv_data(2400.0, 900, '1s', current_time) # 15 min of 1s data + eth_1m_data = self._generate_ohlcv_data(2400.0, 900, '1m', current_time) # 15 hours of 1m data + eth_1h_data = self._generate_ohlcv_data(2400.0, 24, '1h', current_time) # 24 hours of 1h data + + # Generate BTC data + btc_1s_data = self._generate_ohlcv_data(45000.0, 300, '1s', current_time) # 5 min of 1s data + + # Generate tick data + tick_data = self._generate_tick_data(current_time) + + # Create comprehensive TrainingDataPacket + training_packet = MockTrainingDataPacket( + timestamp=current_time, + symbol='ETH/USDT', + tick_cache=tick_data, + one_second_bars=eth_1s_data[-300:], # Last 5 minutes + multi_timeframe_data={ + 'ETH/USDT': { + '1s': eth_1s_data, + '1m': eth_1m_data, + '1h': eth_1h_data + }, + 'BTC/USDT': { + '1s': btc_1s_data + } + } + ) + + self.training_data_buffer.append(training_packet) + logger.info(f"Generated mock training data: {len(eth_1s_data)} 1s bars, {len(eth_1m_data)} 1m bars, {len(eth_1h_data)} 1h bars") + logger.info(f"ETH 1h price range: {min(bar['low'] for bar in eth_1h_data):.2f} - {max(bar['high'] for bar in eth_1h_data):.2f}") + + def _generate_ohlcv_data(self, base_price: float, count: int, timeframe: str, end_time: datetime) -> List[Dict[str, Any]]: + """Generate realistic OHLCV data with indicators""" + data = [] + + # Calculate time delta based on timeframe + if timeframe == '1s': + delta = timedelta(seconds=1) + elif timeframe == '1m': + delta = timedelta(minutes=1) + elif timeframe == '1h': + delta = timedelta(hours=1) + else: + delta = timedelta(minutes=1) + + current_price = base_price + + for i in range(count): + timestamp = end_time - delta * (count - i - 1) + + # Generate realistic price movement + price_change = np.random.normal(0, base_price * 0.001) # 0.1% volatility + current_price = max(current_price + price_change, base_price * 0.8) # Floor at 80% of base + + # Generate OHLCV + open_price = current_price + high_price = open_price * (1 + abs(np.random.normal(0, 0.002))) + low_price = open_price * (1 - abs(np.random.normal(0, 0.002))) + close_price = low_price + (high_price - low_price) * np.random.random() + volume = np.random.exponential(1000) + + current_price = close_price + + # Calculate basic indicators (placeholders) + sma_20 = close_price * (1 + np.random.normal(0, 0.001)) + ema_20 = close_price * (1 + np.random.normal(0, 0.0005)) + rsi_14 = 30 + np.random.random() * 40 # RSI between 30-70 + macd = np.random.normal(0, 0.1) + bb_upper = high_price * 1.02 + + bar = { + 'timestamp': timestamp, + 'open': open_price, + 'high': high_price, + 'low': low_price, + 'close': close_price, + 'volume': volume, + 'sma_20': sma_20, + 'ema_20': ema_20, + 'rsi_14': rsi_14, + 'macd': macd, + 'bb_upper': bb_upper + } + data.append(bar) + + return data + + def _generate_tick_data(self, end_time: datetime) -> List[Dict[str, Any]]: + """Generate realistic tick data for last 5 minutes""" + ticks = [] + + # Generate ETH ticks + for i in range(300): # 5 minutes * 60 seconds + tick_time = end_time - timedelta(seconds=300 - i) + + # 2-5 ticks per second + ticks_per_second = np.random.randint(2, 6) + + for j in range(ticks_per_second): + tick = { + 'symbol': 'ETH/USDT', + 'timestamp': tick_time + timedelta(milliseconds=j * 200), + 'price': 2400.0 + np.random.normal(0, 5), + 'volume': np.random.exponential(0.5), + 'quantity': np.random.exponential(1.0), + 'side': 'buy' if np.random.random() > 0.5 else 'sell' + } + ticks.append(tick) + + # Generate BTC ticks + for i in range(300): # 5 minutes * 60 seconds + tick_time = end_time - timedelta(seconds=300 - i) + + ticks_per_second = np.random.randint(1, 4) + + for j in range(ticks_per_second): + tick = { + 'symbol': 'BTC/USDT', + 'timestamp': tick_time + timedelta(milliseconds=j * 300), + 'price': 45000.0 + np.random.normal(0, 100), + 'volume': np.random.exponential(0.1), + 'quantity': np.random.exponential(0.5), + 'side': 'buy' if np.random.random() > 0.5 else 'sell' + } + ticks.append(tick) + + return ticks + + def get_latest_training_data(self): + """Return the latest TrainingDataPacket""" + return self.training_data_buffer[-1] if self.training_data_buffer else None + + +def test_enhanced_williams_cnn(): + """Test the enhanced Williams Market Structure with CNN integration""" + try: + from training.williams_market_structure import WilliamsMarketStructure, SwingType + + logger.info("=" * 80) + logger.info("TESTING ENHANCED WILLIAMS MARKET STRUCTURE WITH CNN INTEGRATION") + logger.info("=" * 80) + + # Create mock data provider + data_provider = MockTrainingDataProvider() + + # Initialize Williams Market Structure with CNN + williams = WilliamsMarketStructure( + swing_strengths=[2, 3, 5], # Reduced for testing + cnn_input_shape=(900, 50), # 900 timesteps, 50 features + cnn_output_size=10, # 5 levels * 2 outputs (type + price) + enable_cnn_feature=True, # Enable CNN features + training_data_provider=data_provider + ) + + logger.info(f"CNN enabled: {williams.enable_cnn_feature}") + logger.info(f"Training data provider available: {williams.training_data_provider is not None}") + + # Generate test OHLCV data for Williams calculation + test_ohlcv = generate_test_ohlcv_data() + logger.info(f"Generated test OHLCV data: {len(test_ohlcv)} bars") + + # Test Williams calculation with CNN integration + logger.info("\n" + "=" * 60) + logger.info("RUNNING WILLIAMS PIVOT CALCULATION WITH CNN INTEGRATION") + logger.info("=" * 60) + + structure_levels = williams.calculate_recursive_pivot_points(test_ohlcv) + + # Display results + logger.info(f"\nWilliams Structure Analysis Results:") + logger.info(f"Calculated levels: {len(structure_levels)}") + + for level_key, level_data in structure_levels.items(): + swing_count = len(level_data.swing_points) + logger.info(f"{level_key}: {swing_count} swing points, " + f"trend: {level_data.trend_analysis.direction.value}, " + f"bias: {level_data.current_bias.value}") + + if swing_count > 0: + latest_swing = level_data.swing_points[-1] + logger.info(f" Latest swing: {latest_swing.swing_type.name} @ {latest_swing.price:.2f}") + + # Test CNN input preparation directly + logger.info("\n" + "=" * 60) + logger.info("TESTING CNN INPUT PREPARATION") + logger.info("=" * 60) + + if williams.enable_cnn_feature and structure_levels['level_0'].swing_points: + test_pivot = structure_levels['level_0'].swing_points[-1] + + logger.info(f"Testing CNN input for pivot: {test_pivot.swing_type.name} @ {test_pivot.price:.2f}") + + # Test input preparation + cnn_input = williams._prepare_cnn_input( + current_pivot=test_pivot, + ohlcv_data_context=test_ohlcv, + previous_pivot_details=None + ) + + logger.info(f"CNN input shape: {cnn_input.shape}") + logger.info(f"CNN input range: [{cnn_input.min():.4f}, {cnn_input.max():.4f}]") + logger.info(f"CNN input mean: {cnn_input.mean():.4f}, std: {cnn_input.std():.4f}") + + # Test ground truth preparation + if len(structure_levels['level_0'].swing_points) >= 2: + prev_pivot = structure_levels['level_0'].swing_points[-2] + current_pivot = structure_levels['level_0'].swing_points[-1] + + prev_details = {'pivot': prev_pivot} + ground_truth = williams._get_cnn_ground_truth(prev_details, current_pivot) + + logger.info(f"Ground truth shape: {ground_truth.shape}") + logger.info(f"Ground truth (first 4 values): {ground_truth[:4]}") + logger.info(f"Level 0 prediction: type={ground_truth[0]:.2f}, price={ground_truth[1]:.4f}") + + # Test normalization + logger.info("\n" + "=" * 60) + logger.info("TESTING 1H-BASED NORMALIZATION") + logger.info("=" * 60) + + training_packet = data_provider.get_latest_training_data() + if training_packet: + # Test normalization with sample data + sample_features = np.random.normal(2400, 50, (100, 10)) # ETH-like prices + + normalized = williams._normalize_features_by_1h_range(sample_features, training_packet) + + logger.info(f"Original features range: [{sample_features.min():.2f}, {sample_features.max():.2f}]") + logger.info(f"Normalized features range: [{normalized.min():.4f}, {normalized.max():.4f}]") + + # Check if 1h data is being used for normalization + eth_1h = training_packet.multi_timeframe_data.get('ETH/USDT', {}).get('1h', []) + if eth_1h: + h1_prices = [] + for bar in eth_1h[-24:]: + h1_prices.extend([bar['open'], bar['high'], bar['low'], bar['close']]) + h1_range = max(h1_prices) - min(h1_prices) + logger.info(f"1h price range used for normalization: {h1_range:.2f}") + + logger.info("\n" + "=" * 80) + logger.info("ENHANCED WILLIAMS CNN INTEGRATION TEST COMPLETED SUCCESSFULLY") + logger.info("=" * 80) + + return True + + except ImportError as e: + logger.error(f"Import error - some dependencies missing: {e}") + logger.info("This is expected if TensorFlow or other dependencies are not installed") + return False + except Exception as e: + logger.error(f"Test failed with error: {e}", exc_info=True) + return False + + +def generate_test_ohlcv_data(bars=200, base_price=2400.0): + """Generate test OHLCV data for Williams calculation""" + data = [] + current_price = base_price + current_time = datetime.now() + + for i in range(bars): + timestamp = current_time - timedelta(seconds=bars - i) + + # Generate price movement + price_change = np.random.normal(0, base_price * 0.002) + current_price = max(current_price + price_change, base_price * 0.9) + + open_price = current_price + high_price = open_price * (1 + abs(np.random.normal(0, 0.003))) + low_price = open_price * (1 - abs(np.random.normal(0, 0.003))) + close_price = low_price + (high_price - low_price) * np.random.random() + volume = np.random.exponential(1000) + + current_price = close_price + + bar = [ + timestamp.timestamp(), + open_price, + high_price, + low_price, + close_price, + volume + ] + data.append(bar) + + return np.array(data) + + +if __name__ == "__main__": + success = test_enhanced_williams_cnn() + if success: + print("\nāœ… All tests passed! Enhanced Williams CNN integration is working.") + else: + print("\nāŒ Some tests failed. Check logs for details.") \ No newline at end of file diff --git a/training/williams_market_structure.py b/training/williams_market_structure.py index b9a9926..01213a6 100644 --- a/training/williams_market_structure.py +++ b/training/williams_market_structure.py @@ -24,6 +24,18 @@ from typing import Dict, List, Optional, Tuple, Any from dataclasses import dataclass from enum import Enum +try: + from NN.models.cnn_model import CNNModel +except ImportError: + CNNModel = None # Allow running without TF/CNN if not installed or path issue + print("Warning: CNNModel could not be imported. CNN-based pivot prediction/training will be disabled.") + +try: + from core.unified_data_stream import TrainingDataPacket +except ImportError: + TrainingDataPacket = None + print("Warning: TrainingDataPacket could not be imported. Using fallback interface.") + logger = logging.getLogger(__name__) class TrendDirection(Enum): @@ -84,12 +96,25 @@ class WilliamsMarketStructure: - Structure break detection """ - def __init__(self, swing_strengths: List[int] = None): + def __init__(self, + swing_strengths: List[int] = None, + cnn_input_shape: Optional[Tuple[int, int]] = (900, 50), # Updated: 900 timesteps (1s), 50 features + cnn_output_size: Optional[int] = 10, # Updated: 5 levels * (type + price) = 10 outputs + cnn_model_config: Optional[Dict[str, Any]] = None, # For build_model params like filters, learning_rate + cnn_model_path: Optional[str] = None, + enable_cnn_feature: bool = True, # Master switch for this feature + training_data_provider: Optional[Any] = None): # Provider for TrainingDataPacket access """ Initialize Williams market structure analyzer Args: swing_strengths: List of swing detection strengths (bars on each side) + cnn_input_shape: Shape of input data for CNN (sequence_length, features) + cnn_output_size: Number of output classes for CNN (10 for 5 levels * 2 outputs each) + cnn_model_config: Dictionary with parameters for CNNModel.build_model() + cnn_model_path: Path to a pre-trained Keras CNN model (.h5 file) + enable_cnn_feature: If True, enables CNN prediction and training at pivots. + training_data_provider: Provider/stream for accessing TrainingDataPacket """ self.swing_strengths = swing_strengths or [2, 3, 5, 8, 13] # Fibonacci-based strengths self.max_levels = 5 @@ -99,6 +124,32 @@ class WilliamsMarketStructure: self.swing_cache = {} self.trend_cache = {} + self.enable_cnn_feature = enable_cnn_feature and CNNModel is not None + self.cnn_model: Optional[CNNModel] = None + self.previous_pivot_details_for_cnn: Optional[Dict[str, Any]] = None # Stores {'features': X, 'pivot': SwingPoint} + self.training_data_provider = training_data_provider # Access to TrainingDataPacket + + if self.enable_cnn_feature: + try: + logger.info(f"Initializing CNN for multi-timeframe pivot prediction. Input: {cnn_input_shape}, Output: {cnn_output_size}") + logger.info("CNN will predict next pivot (type + price) for all 5 Williams levels") + + self.cnn_model = CNNModel(input_shape=cnn_input_shape, output_size=cnn_output_size) + if cnn_model_path: + logger.info(f"Loading pre-trained CNN model from: {cnn_model_path}") + self.cnn_model.load(cnn_model_path) + else: + logger.info("Building new CNN model.") + # Use provided config or defaults for build_model + build_params = cnn_model_config or {} + self.cnn_model.build_model(**build_params) + logger.info("CNN Model initialized successfully.") + except Exception as e: + logger.error(f"Failed to initialize or load CNN model: {e}. Disabling CNN feature.", exc_info=True) + self.enable_cnn_feature = False + else: + logger.info("CNN feature for pivot prediction/training is disabled.") + logger.info(f"Williams Market Structure initialized with strengths: {self.swing_strengths}") def calculate_recursive_pivot_points(self, ohlcv_data: np.ndarray) -> Dict[str, MarketStructureLevel]: @@ -187,8 +238,8 @@ class WilliamsMarketStructure: all_swings = [] for strength in self.swing_strengths: - swings = self._find_swing_points_single_strength(ohlcv_data, strength) - for swing in swings: + swings_at_strength = self._find_swing_points_single_strength(ohlcv_data, strength) + for swing in swings_at_strength: # Avoid duplicates (swings at same index) if not any(existing.index == swing.index for existing in all_swings): all_swings.append(swing) @@ -201,10 +252,10 @@ class WilliamsMarketStructure: def _find_swing_points_single_strength(self, ohlcv_data: np.ndarray, strength: int) -> List[SwingPoint]: """Find swing points with specific strength requirement""" - swings = [] + identified_swings_in_this_call = [] # Temporary list for swings found in this specific call if len(ohlcv_data) < (strength * 2 + 1): - return swings + return identified_swings_in_this_call for i in range(strength, len(ohlcv_data) - strength): current_high = ohlcv_data[i, 2] # High price @@ -219,14 +270,16 @@ class WilliamsMarketStructure: break if is_swing_high: - swings.append(SwingPoint( + new_pivot = SwingPoint( timestamp=datetime.fromtimestamp(ohlcv_data[i, 0]) if ohlcv_data[i, 0] > 1e9 else datetime.now(), price=current_high, index=i, swing_type=SwingType.SWING_HIGH, strength=strength, volume=current_volume - )) + ) + identified_swings_in_this_call.append(new_pivot) + self._handle_cnn_at_pivot(new_pivot, ohlcv_data) # CNN logic call # Check for swing low (lower than surrounding bars) is_swing_low = True @@ -236,16 +289,18 @@ class WilliamsMarketStructure: break if is_swing_low: - swings.append(SwingPoint( + new_pivot = SwingPoint( timestamp=datetime.fromtimestamp(ohlcv_data[i, 0]) if ohlcv_data[i, 0] > 1e9 else datetime.now(), price=current_low, index=i, swing_type=SwingType.SWING_LOW, strength=strength, volume=current_volume - )) + ) + identified_swings_in_this_call.append(new_pivot) + self._handle_cnn_at_pivot(new_pivot, ohlcv_data) # CNN logic call - return swings + return identified_swings_in_this_call # Return swings found in this call def _filter_significant_swings(self, swings: List[SwingPoint]) -> List[SwingPoint]: """Filter to keep only the most significant swings""" @@ -511,10 +566,10 @@ class WilliamsMarketStructure: pivot_array: Array of pivot points as [timestamp, price, price, price, price, 0] format level: Current level being calculated """ - swings = [] - - if len(pivot_array) < 5: - return swings + identified_swings_in_this_call = [] # Temporary list + + if len(pivot_array) < 5: # Min bars for even smallest strength (e.g. strength 2 needs 2+1+2=5) + return identified_swings_in_this_call # Use configurable strength for higher levels (more conservative) strength = min(2 + level, 5) # Level 1: 3 bars, Level 2: 4 bars, Level 3+: 5 bars @@ -526,38 +581,42 @@ class WilliamsMarketStructure: # Check for swing high (pivot high surrounded by lower pivot highs) is_swing_high = True for j in range(i - strength, i + strength + 1): - if j != i and pivot_array[j, 1] >= current_price: + if j != i and pivot_array[j, 1] >= current_price: # Compare with price of other pivots is_swing_high = False break if is_swing_high: - swings.append(SwingPoint( + new_pivot = SwingPoint( timestamp=datetime.fromtimestamp(current_timestamp) if current_timestamp > 1e9 else datetime.now(), price=current_price, index=i, swing_type=SwingType.SWING_HIGH, - strength=strength, + strength=strength, # Strength here is derived from level, e.g., min(2 + level, 5) volume=0.0 # Pivot points don't have volume - )) - + ) + identified_swings_in_this_call.append(new_pivot) + self._handle_cnn_at_pivot(new_pivot, pivot_array) # CNN logic call + # Check for swing low (pivot low surrounded by higher pivot lows) is_swing_low = True for j in range(i - strength, i + strength + 1): - if j != i and pivot_array[j, 1] <= current_price: + if j != i and pivot_array[j, 1] <= current_price: # Compare with price of other pivots is_swing_low = False break if is_swing_low: - swings.append(SwingPoint( + new_pivot = SwingPoint( timestamp=datetime.fromtimestamp(current_timestamp) if current_timestamp > 1e9 else datetime.now(), price=current_price, index=i, swing_type=SwingType.SWING_LOW, - strength=strength, + strength=strength, # Strength here is derived from level volume=0.0 # Pivot points don't have volume - )) + ) + identified_swings_in_this_call.append(new_pivot) + self._handle_cnn_at_pivot(new_pivot, pivot_array) # CNN logic call - return swings + return identified_swings_in_this_call # Return swings found in this call def _convert_pivots_to_price_points(self, swing_points: List[SwingPoint]) -> np.ndarray: """ @@ -695,4 +754,479 @@ class WilliamsMarketStructure: features.extend([0.0, 0.0]) recent_breaks.append({}) - return features[:50] # Ensure exactly 50 features per level \ No newline at end of file + return features[:50] # Ensure exactly 50 features per level + + def _handle_cnn_at_pivot(self, + newly_identified_pivot: SwingPoint, + ohlcv_data_context: np.ndarray): + """ + Handles CNN training for the previous pivot and prediction for the next pivot. + Called when a new pivot point is identified. + + Args: + newly_identified_pivot: The SwingPoint object for the just-formed pivot. + ohlcv_data_context: The OHLCV data (or pivot array for higher levels) + relevant to this pivot's formation. + """ + if not self.enable_cnn_feature or self.cnn_model is None: + return + + # 1. Train model based on the *previous* pivot's prediction and the *current* actual outcome + if self.previous_pivot_details_for_cnn: + try: + logger.debug(f"CNN Training: Previous pivot at idx {self.previous_pivot_details_for_cnn['pivot'].index}, " + f"Current pivot (ground truth) at idx {newly_identified_pivot.index}") + + X_train = self.previous_pivot_details_for_cnn['features'] + # previous_pivot_info contains 'pivot' which is the SwingPoint object of N-1 + y_train = self._get_cnn_ground_truth(self.previous_pivot_details_for_cnn, newly_identified_pivot) + + if X_train is not None and X_train.size > 0 and y_train is not None and y_train.size > 0: + # Reshape X_train if it's a single sample and model expects batch + if len(X_train.shape) == len(self.cnn_model.input_shape) and X_train.shape == self.cnn_model.input_shape : + X_train_batch = np.expand_dims(X_train, axis=0) + else: # Should already be correctly shaped by _prepare_cnn_input + X_train_batch = X_train # Or handle error + + # Reshape y_train if needed + if self.cnn_model.output_size > 1 and len(y_train.shape) ==1: # e.g. [0.,1.] but model needs [[0.,1.]] + y_train_batch = np.expand_dims(y_train, axis=0) + elif self.cnn_model.output_size == 1 and not isinstance(y_train, (list, np.ndarray)): # e.g. plain 0 or 1 + y_train_batch = np.array([[y_train]], dtype=np.float32) + elif self.cnn_model.output_size == 1 and isinstance(y_train, np.ndarray) and y_train.ndim == 1: + y_train_batch = y_train.reshape(-1,1) # ensure [[0.]] for single binary output + else: + y_train_batch = y_train + + + logger.info(f"CNN Training with X_shape: {X_train_batch.shape}, y_shape: {y_train_batch.shape}") + # Perform a single step of training (online learning) + # Use minimal callbacks for online learning, or allow configuration + self.cnn_model.model.fit(X_train_batch, y_train_batch, batch_size=1, epochs=1, verbose=0, callbacks=[]) + logger.info(f"CNN online training step completed for pivot at index {self.previous_pivot_details_for_cnn['pivot'].index}.") + else: + logger.warning("CNN Training: Skipping due to invalid X_train or y_train.") + + except Exception as e: + logger.error(f"Error during CNN online training: {e}", exc_info=True) + + # 2. Predict for the *next* pivot based on the *current* newly_identified_pivot + try: + logger.debug(f"CNN Prediction: Preparing input for current pivot at idx {newly_identified_pivot.index}") + + # The 'previous_pivot_details' for _prepare_cnn_input here is the one active *before* this current call + # which means it refers to the pivot that just got its ground truth trained on. + # If this is the first pivot ever, self.previous_pivot_details_for_cnn would be None. + + # Correct context for _prepare_cnn_input: + # current_pivot = newly_identified_pivot + # previous_pivot_details = self.previous_pivot_details_for_cnn (this is N-1, which was used for training above) + + X_predict = self._prepare_cnn_input(newly_identified_pivot, + ohlcv_data_context, + self.previous_pivot_details_for_cnn) # Pass the N-1 pivot details + + if X_predict is not None and X_predict.size > 0: + # Reshape X_predict if it's a single sample and model expects batch + if len(X_predict.shape) == len(self.cnn_model.input_shape) and X_predict.shape == self.cnn_model.input_shape : + X_predict_batch = np.expand_dims(X_predict, axis=0) + else: + X_predict_batch = X_predict # Or handle error + + logger.info(f"CNN Predicting with X_shape: {X_predict_batch.shape}") + pred_class, pred_proba = self.cnn_model.predict(X_predict_batch) # predict expects batch + + # pred_class/pred_proba might be arrays if batch_size > 1, or if output is multi-dim + # For batch_size=1, take the first element + final_pred_class = pred_class[0] if isinstance(pred_class, np.ndarray) and pred_class.ndim > 0 else pred_class + final_pred_proba = pred_proba[0] if isinstance(pred_proba, np.ndarray) and pred_proba.ndim > 0 else pred_proba + + logger.info(f"CNN Prediction for pivot after index {newly_identified_pivot.index}: Class={final_pred_class}, Proba/Val={final_pred_proba}") + + # Store the features (X_predict) and the pivot (newly_identified_pivot) itself for the next training cycle + self.previous_pivot_details_for_cnn = {'features': X_predict, 'pivot': newly_identified_pivot} + else: + logger.warning("CNN Prediction: Skipping due to invalid X_predict.") + # If prediction can't be made, ensure we don't carry over stale 'previous_pivot_details_for_cnn' + # Or, decide if we should clear it or keep the N-2 details. + # For now, if X_predict is None, we clear it so no training happens next round unless a new pred is made. + self.previous_pivot_details_for_cnn = None + + except Exception as e: + logger.error(f"Error during CNN prediction: {e}", exc_info=True) + self.previous_pivot_details_for_cnn = None # Clear on error to prevent bad training + + def _prepare_cnn_input(self, + current_pivot: SwingPoint, + ohlcv_data_context: np.ndarray, + previous_pivot_details: Optional[Dict[str, Any]]) -> np.ndarray: + """ + Prepare multi-timeframe, multi-symbol input features for CNN using TrainingDataPacket. + + Features include: + - ETH: 5 min ticks → 300 x 1s bars with ticks features (4 features) + - ETH: 900 x 1s OHLCV + indicators (10 features) + - ETH: 900 x 1m OHLCV + indicators (10 features) + - ETH: 900 x 1h OHLCV + indicators (10 features) + - ETH: All pivot points from all levels (15 features) + - BTC: 5 min ticks → 300 x 1s reference (4 features) + - Chart labels for data identification (7 features) + + Total: ~50 features per timestep over 900 timesteps + Data normalized using 1h min/max to preserve cross-timeframe relationships. + + Args: + current_pivot: The newly identified SwingPoint + ohlcv_data_context: The OHLCV data from Williams calculation (may not be used directly) + previous_pivot_details: Previous pivot info for context + + Returns: + A numpy array of shape (900, 50) with normalized features + """ + if self.cnn_model is None or not self.training_data_provider: + logger.warning("CNN model or training data provider not available") + return np.zeros(self.cnn_model.input_shape if self.cnn_model else (900, 50), dtype=np.float32) + + sequence_length, num_features = self.cnn_model.input_shape + + try: + # Get latest TrainingDataPacket from provider + training_packet = self._get_latest_training_data() + if not training_packet: + logger.warning("No TrainingDataPacket available for CNN input") + return np.zeros((sequence_length, num_features), dtype=np.float32) + + logger.debug(f"CNN Input: Preparing features for pivot at {current_pivot.timestamp}") + + # Prepare feature components (in actual values) + eth_features = self._prepare_eth_features(training_packet, sequence_length) + btc_features = self._prepare_btc_reference_features(training_packet, sequence_length) + pivot_features = self._prepare_pivot_features(training_packet, current_pivot, sequence_length) + chart_labels = self._prepare_chart_labels(sequence_length) + + # Combine all features (still in actual values) + combined_features = np.concatenate([ + eth_features, # ~40 features + btc_features, # ~4 features + pivot_features, # ~3 features + chart_labels # ~3 features + ], axis=1) + + # Ensure we match expected feature count + if combined_features.shape[1] > num_features: + combined_features = combined_features[:, :num_features] + elif combined_features.shape[1] < num_features: + padding = np.zeros((sequence_length, num_features - combined_features.shape[1])) + combined_features = np.concatenate([combined_features, padding], axis=1) + + # NORMALIZATION: Apply 1h timeframe min/max to preserve relationships + normalized_features = self._normalize_features_by_1h_range(combined_features, training_packet) + + logger.debug(f"CNN Input prepared: shape {normalized_features.shape}, " + f"min: {normalized_features.min():.4f}, max: {normalized_features.max():.4f}") + + return normalized_features.astype(np.float32) + + except Exception as e: + logger.error(f"Error preparing CNN input: {e}", exc_info=True) + return np.zeros((sequence_length, num_features), dtype=np.float32) + + def _get_latest_training_data(self): + """Get latest TrainingDataPacket from provider""" + try: + if hasattr(self.training_data_provider, 'get_latest_training_data'): + return self.training_data_provider.get_latest_training_data() + elif hasattr(self.training_data_provider, 'training_data_buffer'): + return self.training_data_provider.training_data_buffer[-1] if self.training_data_provider.training_data_buffer else None + else: + logger.warning("Training data provider does not have expected interface") + return None + except Exception as e: + logger.error(f"Error getting training data: {e}") + return None + + def _prepare_eth_features(self, training_packet, sequence_length: int) -> np.ndarray: + """ + Prepare ETH multi-timeframe features (keep in actual values): + - 1s bars with indicators (10 features) + - 1m bars with indicators (10 features) + - 1h bars with indicators (10 features) + - Tick-derived 1s features (10 features) + Total: 40 features per timestep + """ + features = [] + + # ETH 1s data with indicators + eth_1s_features = self._extract_timeframe_features( + training_packet.multi_timeframe_data.get('ETH/USDT', {}).get('1s', []), + sequence_length, 'ETH_1s' + ) + features.append(eth_1s_features) + + # ETH 1m data with indicators + eth_1m_features = self._extract_timeframe_features( + training_packet.multi_timeframe_data.get('ETH/USDT', {}).get('1m', []), + sequence_length, 'ETH_1m' + ) + features.append(eth_1m_features) + + # ETH 1h data with indicators + eth_1h_features = self._extract_timeframe_features( + training_packet.multi_timeframe_data.get('ETH/USDT', {}).get('1h', []), + sequence_length, 'ETH_1h' + ) + features.append(eth_1h_features) + + # ETH tick-derived features (5 min of ticks → 300 x 1s aggregated to match sequence_length) + eth_tick_features = self._extract_tick_features( + training_packet.tick_cache, 'ETH/USDT', sequence_length + ) + features.append(eth_tick_features) + + return np.concatenate(features, axis=1) + + def _prepare_btc_reference_features(self, training_packet, sequence_length: int) -> np.ndarray: + """ + Prepare BTC reference features (keep in actual values): + - Tick-derived features for correlation analysis + Total: 4 features per timestep + """ + return self._extract_tick_features( + training_packet.tick_cache, 'BTC/USDT', sequence_length + ) + + def _prepare_pivot_features(self, training_packet, current_pivot: SwingPoint, sequence_length: int) -> np.ndarray: + """ + Prepare pivot point features from all Williams levels: + - Recent pivot characteristics + - Level-specific trend information + Total: 3 features per timestep (repeated for sequence) + """ + # Extract Williams pivot features using existing method if available + if hasattr(training_packet, 'universal_stream') and training_packet.universal_stream: + # Use existing pivot extraction logic + pivot_feature_vector = [ + current_pivot.price, + 1.0 if current_pivot.swing_type == SwingType.SWING_HIGH else 0.0, + float(current_pivot.strength) + ] + else: + pivot_feature_vector = [0.0, 0.0, 0.0] + + # Repeat pivot features for all timesteps in sequence + return np.tile(pivot_feature_vector, (sequence_length, 1)) + + def _prepare_chart_labels(self, sequence_length: int) -> np.ndarray: + """ + Prepare chart identification labels: + - Symbol identifiers + - Timeframe identifiers + Total: 3 features per timestep + """ + # Simple encoding: [is_eth, is_btc, timeframe_mix] + chart_labels = [1.0, 1.0, 1.0] # Mixed multi-timeframe ETH+BTC data + return np.tile(chart_labels, (sequence_length, 1)) + + def _extract_timeframe_features(self, ohlcv_data: List[Dict], sequence_length: int, timeframe_label: str) -> np.ndarray: + """ + Extract OHLCV + indicator features from timeframe data (keep actual values). + Returns 10 features: OHLCV + volume + 5 indicators + """ + if not ohlcv_data: + return np.zeros((sequence_length, 10)) + + # Take last sequence_length bars or pad if insufficient + data_to_use = ohlcv_data[-sequence_length:] if len(ohlcv_data) >= sequence_length else ohlcv_data + + features = [] + for bar in data_to_use: + bar_features = [ + bar.get('open', 0.0), + bar.get('high', 0.0), + bar.get('low', 0.0), + bar.get('close', 0.0), + bar.get('volume', 0.0), + # TODO: Add 5 calculated indicators (SMA, EMA, RSI, MACD, etc.) + bar.get('sma_20', bar.get('close', 0.0)), # Placeholder + bar.get('ema_20', bar.get('close', 0.0)), # Placeholder + bar.get('rsi_14', 50.0), # Placeholder + bar.get('macd', 0.0), # Placeholder + bar.get('bb_upper', bar.get('high', 0.0)) # Placeholder + ] + features.append(bar_features) + + # Pad if insufficient data + while len(features) < sequence_length: + features.insert(0, features[0] if features else [0.0] * 10) + + return np.array(features, dtype=np.float32) + + def _extract_tick_features(self, tick_cache: List[Dict], symbol: str, sequence_length: int) -> np.ndarray: + """ + Extract tick-derived features aggregated to 1s intervals (keep actual values). + Returns 4 features: tick_count, total_volume, vwap, price_volatility per second + """ + # Filter ticks for symbol and last 5 minutes + symbol_ticks = [t for t in tick_cache[-1500:] if t.get('symbol') == symbol] # Assume ~5 ticks/sec + + if not symbol_ticks: + return np.zeros((sequence_length, 4)) + + # Group ticks by second and calculate features + tick_features = [] + current_time = datetime.now() + + for i in range(sequence_length): + second_start = current_time - timedelta(seconds=sequence_length - i) + second_end = second_start + timedelta(seconds=1) + + second_ticks = [ + t for t in symbol_ticks + if second_start <= t.get('timestamp', datetime.min) < second_end + ] + + if second_ticks: + prices = [t.get('price', 0.0) for t in second_ticks] + volumes = [t.get('volume', 0.0) for t in second_ticks] + total_volume = sum(volumes) + + tick_count = len(second_ticks) + vwap = sum(p * v for p, v in zip(prices, volumes)) / total_volume if total_volume > 0 else 0.0 + price_volatility = np.std(prices) if len(prices) > 1 else 0.0 + + second_features = [tick_count, total_volume, vwap, price_volatility] + else: + second_features = [0.0, 0.0, 0.0, 0.0] + + tick_features.append(second_features) + + return np.array(tick_features, dtype=np.float32) + + def _normalize_features_by_1h_range(self, features: np.ndarray, training_packet) -> np.ndarray: + """ + Normalize all features using 1h timeframe min/max to preserve cross-timeframe relationships. + This is the final normalization step before feeding to CNN. + """ + try: + # Get 1h ETH data for normalization reference + eth_1h_data = training_packet.multi_timeframe_data.get('ETH/USDT', {}).get('1h', []) + + if not eth_1h_data: + logger.warning("No 1h data available for normalization, using feature-wise normalization") + # Fallback: normalize each feature independently + feature_min = np.min(features, axis=0, keepdims=True) + feature_max = np.max(features, axis=0, keepdims=True) + feature_range = feature_max - feature_min + feature_range[feature_range == 0] = 1.0 # Avoid division by zero + return (features - feature_min) / feature_range + + # Extract 1h price range for primary normalization + h1_prices = [] + for bar in eth_1h_data[-24:]: # Last 24 hours for robust range + h1_prices.extend([ + bar.get('open', 0.0), + bar.get('high', 0.0), + bar.get('low', 0.0), + bar.get('close', 0.0) + ]) + + if h1_prices: + h1_min = min(h1_prices) + h1_max = max(h1_prices) + h1_range = h1_max - h1_min + + if h1_range > 0: + logger.debug(f"Normalizing features using 1h range: {h1_min:.2f} - {h1_max:.2f}") + + # Apply 1h-based normalization to price-related features (first ~30 features) + normalized_features = features.copy() + price_feature_count = min(30, features.shape[1]) + + # Normalize price-related features with 1h range + normalized_features[:, :price_feature_count] = ( + (features[:, :price_feature_count] - h1_min) / h1_range + ) + + # For non-price features (indicators, counts, etc.), use feature-wise normalization + if features.shape[1] > price_feature_count: + remaining_features = features[:, price_feature_count:] + feature_min = np.min(remaining_features, axis=0, keepdims=True) + feature_max = np.max(remaining_features, axis=0, keepdims=True) + feature_range = feature_max - feature_min + feature_range[feature_range == 0] = 1.0 + + normalized_features[:, price_feature_count:] = ( + (remaining_features - feature_min) / feature_range + ) + + return normalized_features + + # Fallback normalization if 1h range calculation fails + logger.warning("1h range calculation failed, using min-max normalization") + feature_min = np.min(features, axis=0, keepdims=True) + feature_max = np.max(features, axis=0, keepdims=True) + feature_range = feature_max - feature_min + feature_range[feature_range == 0] = 1.0 + return (features - feature_min) / feature_range + + except Exception as e: + logger.error(f"Error in normalization: {e}", exc_info=True) + # Emergency fallback: return features as-is but scaled to [0,1] roughly + return np.clip(features / (np.max(np.abs(features)) + 1e-8), -1.0, 1.0) + + + def _get_cnn_ground_truth(self, + previous_pivot_info: Dict[str, Any], # Contains 'pivot': SwingPoint obj of N-1 + actual_current_pivot: SwingPoint # This is pivot N + ) -> np.ndarray: + """ + Determine the ground truth for CNN prediction made at previous_pivot. + + Updated to return prediction for next pivot in ALL 5 LEVELS: + - For each level: [type (0=LOW, 1=HIGH), normalized_price_target] + - Total output: 10 values (5 levels * 2 outputs each) + + Args: + previous_pivot_info: Dict with 'pivot' = SwingPoint of N-1 + actual_current_pivot: SwingPoint of pivot N (actual outcome) + + Returns: + A numpy array of shape (10,) with ground truth for all levels + """ + if self.cnn_model is None: + return np.array([]) + + # Initialize ground truth array for all 5 levels + ground_truth = np.zeros(10, dtype=np.float32) # 5 levels * 2 outputs + + try: + # For Level 0 (current pivot level), we have actual data + level_0_type = 1.0 if actual_current_pivot.swing_type == SwingType.SWING_HIGH else 0.0 + level_0_price = actual_current_pivot.price + + # Normalize price (this is a placeholder - proper normalization should use market context) + # In real implementation, use the same 1h range normalization as input features + normalized_price = level_0_price / 10000.0 # Rough normalization for ETH prices + + ground_truth[0] = level_0_type # Level 0 type + ground_truth[1] = normalized_price # Level 0 price + + # For higher levels (1-4), we would need to calculate what the next pivot would be + # This requires access to higher-level Williams calculations + # For now, use placeholder logic based on current pivot characteristics + + for level in range(1, 5): + # Placeholder: higher levels follow similar pattern but with reduced confidence + confidence_factor = 1.0 / (level + 1) + + ground_truth[level * 2] = level_0_type * confidence_factor # Level N type + ground_truth[level * 2 + 1] = normalized_price * confidence_factor # Level N price + + logger.debug(f"CNN Ground Truth: Level 0 = [{level_0_type}, {normalized_price:.4f}], " + f"Current pivot = {actual_current_pivot.swing_type.name} @ {actual_current_pivot.price}") + + return ground_truth + + except Exception as e: + logger.error(f"Error calculating CNN ground truth: {e}", exc_info=True) + return np.zeros(10, dtype=np.float32) \ No newline at end of file diff --git a/web/dashboard.py b/web/dashboard.py index 54bf25a..dc166d2 100644 --- a/web/dashboard.py +++ b/web/dashboard.py @@ -321,6 +321,19 @@ class TradingDashboard: logger.info("Trading Dashboard initialized with enhanced RL training integration") logger.info(f"Enhanced RL enabled: {self.enhanced_rl_training_enabled}") logger.info(f"Stream consumer ID: {self.stream_consumer_id}") + + # Initialize Williams Market Structure once + try: + from training.williams_market_structure import WilliamsMarketStructure + self.williams_structure = WilliamsMarketStructure( + swing_strengths=[2, 3, 5], # Simplified for better performance + enable_cnn_feature=False, # Disable CNN until TensorFlow available + training_data_provider=None + ) + logger.info("Williams Market Structure initialized for dashboard") + except ImportError: + self.williams_structure = None + logger.warning("Williams Market Structure not available") def _to_local_timezone(self, dt: datetime) -> datetime: """Convert datetime to configured local timezone""" @@ -4532,32 +4545,49 @@ class TradingDashboard: logger.error(f"Error stopping streaming: {e}") def _get_williams_pivot_features(self, df: pd.DataFrame) -> Optional[List[float]]: - """Calculate Williams Market Structure pivot points features""" + """Get Williams Market Structure pivot features for RL training""" try: - # Import Williams Market Structure - try: - from training.williams_market_structure import WilliamsMarketStructure - except ImportError: + # Use reused Williams instance + if not self.williams_structure: logger.warning("Williams Market Structure not available") return None # Convert DataFrame to numpy array for Williams calculation - if len(df) < 50: + if len(df) < 20: # Reduced from 50 to match Williams minimum requirement + logger.debug(f"[WILLIAMS] Insufficient data for pivot calculation: {len(df)} bars (need 20+)") return None - ohlcv_array = np.array([ - [self._to_local_timezone(df.index[i]).timestamp() if hasattr(df.index[i], 'timestamp') else time.time(), - df['open'].iloc[i], df['high'].iloc[i], df['low'].iloc[i], - df['close'].iloc[i], df['volume'].iloc[i]] - for i in range(len(df)) - ]) + try: + ohlcv_array = np.array([ + [self._to_local_timezone(df.index[i]).timestamp() if hasattr(df.index[i], 'timestamp') else time.time(), + df['open'].iloc[i], df['high'].iloc[i], df['low'].iloc[i], + df['close'].iloc[i], df['volume'].iloc[i]] + for i in range(len(df)) + ]) + + logger.debug(f"[WILLIAMS] Prepared OHLCV array: {ohlcv_array.shape}, price range: {ohlcv_array[:, 4].min():.2f} - {ohlcv_array[:, 4].max():.2f}") + + except Exception as e: + logger.warning(f"[WILLIAMS] Error preparing OHLCV array: {e}") + return None - # Calculate Williams pivot points - williams = WilliamsMarketStructure() - structure_levels = williams.calculate_recursive_pivot_points(ohlcv_array) + # Calculate Williams pivot points with reused instance + try: + structure_levels = self.williams_structure.calculate_recursive_pivot_points(ohlcv_array) + + # Add diagnostics for debugging + total_pivots = sum(len(level.swing_points) for level in structure_levels.values()) + if total_pivots == 0: + logger.debug(f"[WILLIAMS] No pivot points detected in {len(ohlcv_array)} bars") + else: + logger.debug(f"[WILLIAMS] Successfully detected {total_pivots} pivot points across {len([l for l in structure_levels.values() if len(l.swing_points) > 0])} levels") + + except Exception as e: + logger.warning(f"[WILLIAMS] Error in pivot calculation: {e}") + return None # Extract features (250 features total) - pivot_features = williams.extract_features_for_rl(structure_levels) + pivot_features = self.williams_structure.extract_features_for_rl(structure_levels) logger.debug(f"[PIVOT] Calculated {len(pivot_features)} Williams pivot features") return pivot_features @@ -4795,40 +4825,66 @@ class TradingDashboard: logger.warning("Williams Market Structure not available for chart") return None - # Need at least 50 bars for meaningful pivot calculation - if len(df) < 50: - logger.debug(f"[WILLIAMS] Insufficient data for pivot calculation: {len(df)} bars") + # Reduced requirement to match Williams minimum + if len(df) < 20: + logger.debug(f"[WILLIAMS_CHART] Insufficient data for pivot calculation: {len(df)} bars (need 20+)") return None # Ensure timezone consistency for the chart data df = self._ensure_timezone_consistency(df) # Convert DataFrame to numpy array for Williams calculation with proper timezone handling - ohlcv_array = [] - for i in range(len(df)): - timestamp = df.index[i] + try: + ohlcv_array = [] + for i in range(len(df)): + timestamp = df.index[i] + + # Convert timestamp to local timezone and then to Unix timestamp + if hasattr(timestamp, 'timestamp'): + local_time = self._to_local_timezone(timestamp) + unix_timestamp = local_time.timestamp() + else: + unix_timestamp = time.time() + + ohlcv_array.append([ + unix_timestamp, + df['open'].iloc[i], + df['high'].iloc[i], + df['low'].iloc[i], + df['close'].iloc[i], + df['volume'].iloc[i] + ]) - # Convert timestamp to local timezone and then to Unix timestamp - if hasattr(timestamp, 'timestamp'): - local_time = self._to_local_timezone(timestamp) - unix_timestamp = local_time.timestamp() + ohlcv_array = np.array(ohlcv_array) + logger.debug(f"[WILLIAMS_CHART] Prepared OHLCV array: {ohlcv_array.shape}, price range: {ohlcv_array[:, 4].min():.2f} - {ohlcv_array[:, 4].max():.2f}") + + except Exception as e: + logger.warning(f"[WILLIAMS_CHART] Error preparing OHLCV array: {e}") + return None + + # Calculate Williams pivot points with proper configuration + try: + williams = WilliamsMarketStructure( + swing_strengths=[2, 3, 5], # Start with simpler strengths + enable_cnn_feature=False, # Disable CNN for chart display + training_data_provider=None # No training data provider needed for chart + ) + + structure_levels = williams.calculate_recursive_pivot_points(ohlcv_array) + + # Add diagnostics for debugging + total_pivots_detected = sum(len(level.swing_points) for level in structure_levels.values()) + if total_pivots_detected == 0: + logger.warning(f"[WILLIAMS_CHART] No pivot points detected in {len(ohlcv_array)} bars for chart display") + price_volatility = np.std(ohlcv_array[:, 4]) / np.mean(ohlcv_array[:, 4]) if np.mean(ohlcv_array[:, 4]) > 0 else 0.0 + logger.debug(f"[WILLIAMS_CHART] Data diagnostics: volatility={price_volatility:.4f}, time_span={ohlcv_array[-1, 0] - ohlcv_array[0, 0]:.0f}s") + return None else: - unix_timestamp = time.time() - - ohlcv_array.append([ - unix_timestamp, - df['open'].iloc[i], - df['high'].iloc[i], - df['low'].iloc[i], - df['close'].iloc[i], - df['volume'].iloc[i] - ]) - - ohlcv_array = np.array(ohlcv_array) - - # Calculate Williams pivot points - williams = WilliamsMarketStructure() - structure_levels = williams.calculate_recursive_pivot_points(ohlcv_array) + logger.debug(f"[WILLIAMS_CHART] Successfully detected {total_pivots_detected} pivot points for chart") + + except Exception as e: + logger.warning(f"[WILLIAMS_CHART] Error in pivot calculation: {e}") + return None # Extract pivot points for chart display chart_pivots = {} @@ -4846,7 +4902,7 @@ class TradingDashboard: # Log swing point details for validation highs = [s for s in swing_points if s.swing_type.name == 'SWING_HIGH'] lows = [s for s in swing_points if s.swing_type.name == 'SWING_LOW'] - logger.debug(f"[WILLIAMS] Level {level}: {len(highs)} highs, {len(lows)} lows, total: {len(swing_points)}") + logger.debug(f"[WILLIAMS_CHART] Level {level}: {len(highs)} highs, {len(lows)} lows, total: {len(swing_points)}") # Convert swing points to chart format chart_pivots[f'level_{level}'] = { @@ -4858,11 +4914,11 @@ class TradingDashboard: } total_pivots += len(swing_points) - logger.info(f"[WILLIAMS] Calculated {total_pivots} total pivot points across {len(chart_pivots)} levels") + logger.info(f"[WILLIAMS_CHART] Calculated {total_pivots} total pivot points across {len(chart_pivots)} levels") return chart_pivots except Exception as e: - logger.warning(f"Error calculating Williams pivot points for chart: {e}") + logger.warning(f"Error calculating Williams pivot points: {e}") return None def _add_williams_pivot_points_to_chart(self, fig, pivot_points: Dict, row: int = 1):