forexsmartbot 3.2.1__tar.gz → 3.2.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/PKG-INFO +27 -3
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/README.md +5 -0
- forexsmartbot-3.2.4/forexsmartbot/analytics/__init__.py +43 -0
- forexsmartbot-3.2.4/forexsmartbot/analytics/live_metrics.py +161 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/portfolio_analytics.py +20 -11
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/risk_analytics.py +6 -1
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/__init__.py +30 -17
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/analytics_dialog.py +33 -19
- forexsmartbot-3.2.4/forexsmartbot/ui/analytics_widget.py +202 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/backtest_dialog.py +334 -134
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/enhanced_main_window.py +7 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/tradingview_chart.py +4 -1
- forexsmartbot-3.2.4/forexsmartbot/utils/backtest_utils.py +12 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/utils/gpu_utils.py +45 -35
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/PKG-INFO +27 -3
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/SOURCES.txt +4 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/requires.txt +25 -2
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/pyproject.toml +26 -3
- forexsmartbot-3.2.4/tests/test_backtest_dialog_ui.py +11 -0
- forexsmartbot-3.2.4/tests/test_live_metrics.py +70 -0
- forexsmartbot-3.2.1/forexsmartbot/analytics/__init__.py +0 -28
- forexsmartbot-3.2.1/forexsmartbot/ui/analytics_widget.py +0 -68
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/LICENSE +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/ib_tws_broker.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/mt4_broker.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/paper_broker.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/rest_broker.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/alpha_vantage_provider.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/config.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/csv_provider.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/dummy_provider.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/mt4_provider.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/multi_provider.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/oanda_provider.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/stooq_provider.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/twelve_data_provider.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/yfinance_provider.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/chart_patterns.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/correlation_matrix.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/economic_calendar.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/enhanced_chart.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/market_depth.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/performance_attribution.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/strategy_comparison.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/trade_journal.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/brokers/mt4_bridge.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/builder/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/builder/code_generator.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/builder/strategy_builder.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/builder/strategy_template.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/api_client.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/api_server.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/cloud_sync.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/error_handler.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/mobile_api.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/remote_monitor.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/security.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/backtester.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/config.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/interfaces.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/paper_broker.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/portfolio.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/risk.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/risk_engine.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/strategy.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/trade_manager.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/gui/main_window.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/gui/theme.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/marketplace/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/marketplace/marketplace.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/marketplace/strategy_rating.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/monitoring/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/monitoring/health_check.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/monitoring/performance_tracker.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/monitoring/strategy_monitor.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/adaptive_parameters.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/genetic_optimizer.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/hyperparameter_optimizer.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/monte_carlo.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/multi_objective_optimizer.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/parameter_sensitivity.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/walk_forward.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/backtest.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/calculation_engine.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/controller.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/data_streaming.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/enhanced_backtest.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/fast_trading_controller.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/gpu_backtest.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/language_manager.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/notification_service.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/persistence.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/startup_manager.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/adaptive_trend_flow.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/breakout_atr.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/ensemble_ml_strategy.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/fear_index.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/lstm_strategy.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/mean_reversion.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/ml_adaptive_supertrend.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/momentum_breakout.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/multi_timeframe_strategy.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/news_trading.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/rl_strategy.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/rsi_reversion.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/scalping_ma.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/sma_crossover.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/svm_strategy.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/transformer_strategy.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/testing/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/testing/ci_cd_pipeline.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/testing/paper_account_tester.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/testing/strategy_sandbox.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/charts.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/cloud_dialog.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/enhanced_charts.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/export_dialog.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/main_window.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/marketplace_dialog.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/monitoring_dialog.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/monitoring_widget.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/optimization_dialog.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/settings_dialog.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/simple_chart.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/strategy_builder_dialog.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/theme.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/utils/__init__.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/utils/gpu_numpy.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/utils/native_indicators.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/dependency_links.txt +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/entry_points.txt +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/top_level.txt +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/setup.cfg +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/tests/test_config.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/tests/test_multi_timeframe_strategy.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/tests/test_native_indicators.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/tests/test_risk.py +0 -0
- {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/tests/test_strategies.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: forexsmartbot
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.4
|
|
4
4
|
Summary: Professional forex trading bot with advanced risk management and strategy plugins
|
|
5
5
|
Author-email: VoxHash <contact@voxhash.dev>
|
|
6
6
|
License: MIT
|
|
@@ -26,11 +26,30 @@ Requires-Dist: pyzmq>=26.0.0
|
|
|
26
26
|
Requires-Dist: matplotlib>=3.9.0
|
|
27
27
|
Requires-Dist: darkdetect>=0.8.0
|
|
28
28
|
Requires-Dist: scikit-learn>=1.3.0
|
|
29
|
-
Requires-Dist: mplfinance>=0.12.
|
|
30
|
-
Requires-Dist: TA-Lib>=0.6.7
|
|
29
|
+
Requires-Dist: mplfinance>=0.12.10b0
|
|
31
30
|
Requires-Dist: requests>=2.31.0
|
|
32
31
|
Requires-Dist: ib-insync>=0.9.86
|
|
32
|
+
Requires-Dist: scipy>=1.11.0
|
|
33
|
+
Requires-Dist: optuna>=3.4.0
|
|
34
|
+
Requires-Dist: deap>=1.4.1
|
|
33
35
|
Requires-Dist: win10toast>=0.9; sys_platform == "win32"
|
|
36
|
+
Provides-Extra: talib
|
|
37
|
+
Requires-Dist: TA-Lib>=0.6.7; extra == "talib"
|
|
38
|
+
Provides-Extra: ml
|
|
39
|
+
Requires-Dist: tensorflow>=2.15.0; extra == "ml"
|
|
40
|
+
Requires-Dist: torch>=2.1.0; extra == "ml"
|
|
41
|
+
Requires-Dist: transformers>=4.35.0; extra == "ml"
|
|
42
|
+
Requires-Dist: gymnasium>=0.29.0; extra == "ml"
|
|
43
|
+
Requires-Dist: stable-baselines3>=2.2.0; extra == "ml"
|
|
44
|
+
Requires-Dist: numba>=0.59.0; extra == "ml"
|
|
45
|
+
Provides-Extra: remote
|
|
46
|
+
Requires-Dist: flask>=3.0.0; extra == "remote"
|
|
47
|
+
Requires-Dist: flask-cors>=4.0.0; extra == "remote"
|
|
48
|
+
Requires-Dist: flask-limiter>=3.5.0; extra == "remote"
|
|
49
|
+
Requires-Dist: websockets>=12.0; extra == "remote"
|
|
50
|
+
Requires-Dist: pyjwt>=2.8.0; extra == "remote"
|
|
51
|
+
Provides-Extra: full
|
|
52
|
+
Requires-Dist: forexsmartbot[ml,remote,talib]; extra == "full"
|
|
34
53
|
Provides-Extra: dev
|
|
35
54
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
36
55
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
@@ -111,7 +130,12 @@ pip install -r requirements.txt
|
|
|
111
130
|
python app.py
|
|
112
131
|
```
|
|
113
132
|
|
|
133
|
+
Optional TA-Lib / deep-learning / remote API stack:
|
|
114
134
|
|
|
135
|
+
```bash
|
|
136
|
+
pip install -r requirements-optional.txt
|
|
137
|
+
# or: pip install "forexsmartbot[talib,ml,remote]"
|
|
138
|
+
```
|
|
115
139
|
|
|
116
140
|
### With GPU Acceleration (Optional)
|
|
117
141
|
|
|
@@ -69,7 +69,12 @@ pip install -r requirements.txt
|
|
|
69
69
|
python app.py
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
+
Optional TA-Lib / deep-learning / remote API stack:
|
|
72
73
|
|
|
74
|
+
```bash
|
|
75
|
+
pip install -r requirements-optional.txt
|
|
76
|
+
# or: pip install "forexsmartbot[talib,ml,remote]"
|
|
77
|
+
```
|
|
73
78
|
|
|
74
79
|
### With GPU Acceleration (Optional)
|
|
75
80
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Analytics module for ForexSmartBot v3.2.0
|
|
3
|
+
Provides portfolio analytics, risk analytics, and advanced charting features.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .portfolio_analytics import PortfolioAnalytics
|
|
7
|
+
from .risk_analytics import RiskAnalytics
|
|
8
|
+
from .chart_patterns import ChartPatternRecognizer
|
|
9
|
+
from .performance_attribution import PerformanceAttribution
|
|
10
|
+
from .live_metrics import LiveMetricsSnapshot, build_live_metrics
|
|
11
|
+
|
|
12
|
+
# Qt-backed widgets are optional (headless / test environments may lack PyQt6)
|
|
13
|
+
try:
|
|
14
|
+
from .market_depth import MarketDepthWidget, MarketDepthProvider
|
|
15
|
+
from .correlation_matrix import CorrelationMatrixWidget, CorrelationAnalyzer
|
|
16
|
+
from .economic_calendar import EconomicCalendarWidget, EconomicCalendarProvider
|
|
17
|
+
from .trade_journal import TradeJournalWidget, TradeJournalManager
|
|
18
|
+
except ImportError: # pragma: no cover - UI optional
|
|
19
|
+
MarketDepthWidget = None # type: ignore[misc, assignment]
|
|
20
|
+
MarketDepthProvider = None # type: ignore[misc, assignment]
|
|
21
|
+
CorrelationMatrixWidget = None # type: ignore[misc, assignment]
|
|
22
|
+
CorrelationAnalyzer = None # type: ignore[misc, assignment]
|
|
23
|
+
EconomicCalendarWidget = None # type: ignore[misc, assignment]
|
|
24
|
+
EconomicCalendarProvider = None # type: ignore[misc, assignment]
|
|
25
|
+
TradeJournalWidget = None # type: ignore[misc, assignment]
|
|
26
|
+
TradeJournalManager = None # type: ignore[misc, assignment]
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
'PortfolioAnalytics',
|
|
30
|
+
'RiskAnalytics',
|
|
31
|
+
'ChartPatternRecognizer',
|
|
32
|
+
'PerformanceAttribution',
|
|
33
|
+
'MarketDepthWidget',
|
|
34
|
+
'MarketDepthProvider',
|
|
35
|
+
'CorrelationMatrixWidget',
|
|
36
|
+
'CorrelationAnalyzer',
|
|
37
|
+
'EconomicCalendarWidget',
|
|
38
|
+
'EconomicCalendarProvider',
|
|
39
|
+
'TradeJournalWidget',
|
|
40
|
+
'TradeJournalManager',
|
|
41
|
+
'LiveMetricsSnapshot',
|
|
42
|
+
'build_live_metrics',
|
|
43
|
+
]
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Live portfolio and risk metrics for the analytics dashboard.
|
|
3
|
+
|
|
4
|
+
Pure computation (no Qt) so the snapshot can be unit-tested and reused by
|
|
5
|
+
dialogs, widgets, and remote monitors.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import asdict, dataclass
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from typing import Any, Dict, List, Optional, Sequence
|
|
13
|
+
|
|
14
|
+
from ..core.interfaces import Trade
|
|
15
|
+
from .portfolio_analytics import PortfolioAnalytics
|
|
16
|
+
from .risk_analytics import RiskAnalytics
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class LiveMetricsSnapshot:
|
|
21
|
+
"""Point-in-time analytics snapshot for UI and monitoring."""
|
|
22
|
+
|
|
23
|
+
timestamp: datetime
|
|
24
|
+
equity: float
|
|
25
|
+
balance: float
|
|
26
|
+
realized_pnl: float
|
|
27
|
+
unrealized_pnl: float
|
|
28
|
+
daily_pnl: float
|
|
29
|
+
open_positions: int
|
|
30
|
+
total_trades: int
|
|
31
|
+
win_rate: float
|
|
32
|
+
profit_factor: float
|
|
33
|
+
sharpe_ratio: float
|
|
34
|
+
sortino_ratio: float
|
|
35
|
+
max_drawdown: float
|
|
36
|
+
max_drawdown_pct: float
|
|
37
|
+
current_drawdown: float
|
|
38
|
+
var_95: float
|
|
39
|
+
cvar_95: float
|
|
40
|
+
volatility: float
|
|
41
|
+
average_win: float
|
|
42
|
+
average_loss: float
|
|
43
|
+
|
|
44
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
45
|
+
"""Serialize for tables / JSON."""
|
|
46
|
+
data = asdict(self)
|
|
47
|
+
data["timestamp"] = self.timestamp.isoformat()
|
|
48
|
+
return data
|
|
49
|
+
|
|
50
|
+
def card_metrics(self) -> List[tuple]:
|
|
51
|
+
"""Primary KPI cards: (label, formatted value, tone)."""
|
|
52
|
+
tone_pnl = "positive" if self.daily_pnl >= 0 else "negative"
|
|
53
|
+
tone_eq = "positive" if self.unrealized_pnl + self.realized_pnl >= 0 else "negative"
|
|
54
|
+
return [
|
|
55
|
+
("Equity", f"${self.equity:,.2f}", tone_eq),
|
|
56
|
+
("Daily P&L", f"${self.daily_pnl:,.2f}", tone_pnl),
|
|
57
|
+
("Win Rate", f"{self.win_rate:.1f}%", "neutral"),
|
|
58
|
+
("Sharpe", f"{self.sharpe_ratio:.3f}", "neutral"),
|
|
59
|
+
("Max DD", f"{self.max_drawdown_pct:.2f}%", "negative" if self.max_drawdown_pct else "neutral"),
|
|
60
|
+
("VaR 95%", f"{self.var_95:.4f}", "neutral"),
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _portfolio_trades(portfolio) -> List[Trade]:
|
|
65
|
+
"""Extract Trade list from Portfolio or trade sequence."""
|
|
66
|
+
if portfolio is None:
|
|
67
|
+
return []
|
|
68
|
+
if hasattr(portfolio, "trades"):
|
|
69
|
+
return list(portfolio.trades)
|
|
70
|
+
if isinstance(portfolio, Sequence):
|
|
71
|
+
return list(portfolio)
|
|
72
|
+
return []
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _sync_portfolio_analytics(analytics: PortfolioAnalytics, portfolio) -> None:
|
|
76
|
+
"""Load trades and equity curve from a live Portfolio."""
|
|
77
|
+
analytics.trades = _portfolio_trades(portfolio)
|
|
78
|
+
analytics.equity_curve = []
|
|
79
|
+
if portfolio is None:
|
|
80
|
+
return
|
|
81
|
+
timestamps = getattr(portfolio, "timestamps", None)
|
|
82
|
+
equity_history = getattr(portfolio, "equity_history", None)
|
|
83
|
+
if timestamps is not None and equity_history is not None:
|
|
84
|
+
ts = list(timestamps)
|
|
85
|
+
eq = list(equity_history)
|
|
86
|
+
n = min(len(ts), len(eq))
|
|
87
|
+
analytics.equity_curve = list(zip(ts[:n], eq[:n]))
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _sync_risk_analytics(risk: RiskAnalytics, portfolio) -> None:
|
|
91
|
+
"""Load trades into RiskAnalytics from a live Portfolio."""
|
|
92
|
+
risk.trades = _portfolio_trades(portfolio)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def build_live_metrics(portfolio, *, now: Optional[datetime] = None) -> LiveMetricsSnapshot:
|
|
96
|
+
"""
|
|
97
|
+
Build a live metrics snapshot from a Portfolio (or trade container).
|
|
98
|
+
|
|
99
|
+
Uses Portfolio balance/equity when available; otherwise derives from trades.
|
|
100
|
+
"""
|
|
101
|
+
now = now or datetime.now()
|
|
102
|
+
portfolio_analytics = PortfolioAnalytics()
|
|
103
|
+
risk_analytics = RiskAnalytics()
|
|
104
|
+
_sync_portfolio_analytics(portfolio_analytics, portfolio)
|
|
105
|
+
_sync_risk_analytics(risk_analytics, portfolio)
|
|
106
|
+
|
|
107
|
+
summary = portfolio_analytics.get_performance_summary()
|
|
108
|
+
max_dd = portfolio_analytics.calculate_max_drawdown()
|
|
109
|
+
var_result = risk_analytics.calculate_var(confidence_level=0.95)
|
|
110
|
+
cvar_result = risk_analytics.calculate_cvar(confidence_level=0.95)
|
|
111
|
+
|
|
112
|
+
returns = risk_analytics._get_returns()
|
|
113
|
+
volatility = float(returns.std()) if len(returns) >= 2 else 0.0
|
|
114
|
+
|
|
115
|
+
if portfolio is not None and hasattr(portfolio, "get_total_equity"):
|
|
116
|
+
equity = float(portfolio.get_total_equity())
|
|
117
|
+
balance = float(portfolio.get_total_balance())
|
|
118
|
+
realized = float(portfolio.get_realized_pnl())
|
|
119
|
+
unrealized = float(portfolio.get_unrealized_pnl())
|
|
120
|
+
daily = float(portfolio.get_daily_pnl())
|
|
121
|
+
open_positions = len(getattr(portfolio, "positions", {}) or {})
|
|
122
|
+
current_dd = float(portfolio.get_current_drawdown()) * 100.0
|
|
123
|
+
else:
|
|
124
|
+
equity = 10000.0 + float(summary.get("total_pnl", 0.0))
|
|
125
|
+
balance = equity
|
|
126
|
+
realized = float(summary.get("total_pnl", 0.0))
|
|
127
|
+
unrealized = 0.0
|
|
128
|
+
daily = realized
|
|
129
|
+
open_positions = 0
|
|
130
|
+
current_dd = float(max_dd.get("max_drawdown_pct", 0.0))
|
|
131
|
+
|
|
132
|
+
profit_factor = summary.get("profit_factor", 0.0)
|
|
133
|
+
if profit_factor == float("inf"):
|
|
134
|
+
profit_factor = 0.0
|
|
135
|
+
|
|
136
|
+
sortino = summary.get("sortino_ratio", 0.0)
|
|
137
|
+
if sortino == float("inf"):
|
|
138
|
+
sortino = 0.0
|
|
139
|
+
|
|
140
|
+
return LiveMetricsSnapshot(
|
|
141
|
+
timestamp=now,
|
|
142
|
+
equity=equity,
|
|
143
|
+
balance=balance,
|
|
144
|
+
realized_pnl=realized,
|
|
145
|
+
unrealized_pnl=unrealized,
|
|
146
|
+
daily_pnl=daily,
|
|
147
|
+
open_positions=open_positions,
|
|
148
|
+
total_trades=int(summary.get("total_trades", 0)),
|
|
149
|
+
win_rate=float(summary.get("win_rate", 0.0)),
|
|
150
|
+
profit_factor=float(profit_factor),
|
|
151
|
+
sharpe_ratio=float(summary.get("sharpe_ratio", 0.0)),
|
|
152
|
+
sortino_ratio=float(sortino),
|
|
153
|
+
max_drawdown=float(max_dd.get("max_drawdown", 0.0)),
|
|
154
|
+
max_drawdown_pct=float(max_dd.get("max_drawdown_pct", 0.0)),
|
|
155
|
+
current_drawdown=current_dd,
|
|
156
|
+
var_95=float(var_result.get("var", 0.0)),
|
|
157
|
+
cvar_95=float(cvar_result.get("cvar", 0.0)),
|
|
158
|
+
volatility=volatility,
|
|
159
|
+
average_win=float(summary.get("average_win", 0.0)),
|
|
160
|
+
average_loss=float(summary.get("average_loss", 0.0)),
|
|
161
|
+
)
|
|
@@ -284,20 +284,29 @@ class PortfolioAnalytics:
|
|
|
284
284
|
Calculate portfolio metrics (alias for get_performance_summary for compatibility).
|
|
285
285
|
|
|
286
286
|
Args:
|
|
287
|
-
portfolio: Optional portfolio object
|
|
287
|
+
portfolio: Optional portfolio object; when provided, trades and equity
|
|
288
|
+
history are loaded for the calculation.
|
|
288
289
|
|
|
289
290
|
Returns:
|
|
290
291
|
Dictionary of performance metrics
|
|
291
292
|
"""
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
293
|
+
if portfolio is None:
|
|
294
|
+
return self.get_performance_summary()
|
|
295
|
+
|
|
296
|
+
original_trades = self.trades
|
|
297
|
+
original_equity = self.equity_curve
|
|
298
|
+
try:
|
|
299
|
+
if hasattr(portfolio, "trades"):
|
|
300
|
+
self.trades = list(portfolio.trades)
|
|
301
|
+
timestamps = getattr(portfolio, "timestamps", None)
|
|
302
|
+
equity_history = getattr(portfolio, "equity_history", None)
|
|
303
|
+
if timestamps is not None and equity_history is not None:
|
|
304
|
+
ts = list(timestamps)
|
|
305
|
+
eq = list(equity_history)
|
|
306
|
+
n = min(len(ts), len(eq))
|
|
307
|
+
self.equity_curve = list(zip(ts[:n], eq[:n]))
|
|
308
|
+
return self.get_performance_summary()
|
|
309
|
+
finally:
|
|
298
310
|
self.trades = original_trades
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
# Otherwise use internal trades
|
|
302
|
-
return self.get_performance_summary()
|
|
311
|
+
self.equity_curve = original_equity
|
|
303
312
|
|
|
@@ -7,7 +7,6 @@ import numpy as np
|
|
|
7
7
|
import pandas as pd
|
|
8
8
|
from typing import Dict, List, Optional, Tuple
|
|
9
9
|
from datetime import datetime, timedelta
|
|
10
|
-
from scipy import stats
|
|
11
10
|
from ..core.interfaces import Trade, Position
|
|
12
11
|
|
|
13
12
|
|
|
@@ -245,6 +244,12 @@ class RiskAnalytics:
|
|
|
245
244
|
def _calculate_parametric_var(self, returns: np.ndarray,
|
|
246
245
|
confidence_level: float) -> float:
|
|
247
246
|
"""Calculate VaR using parametric (variance-covariance) method."""
|
|
247
|
+
try:
|
|
248
|
+
from scipy import stats
|
|
249
|
+
except ImportError as exc:
|
|
250
|
+
raise ImportError(
|
|
251
|
+
"scipy is required for parametric VaR; use method='historical'"
|
|
252
|
+
) from exc
|
|
248
253
|
mean_return = np.mean(returns)
|
|
249
254
|
std_return = np.std(returns)
|
|
250
255
|
|
|
@@ -12,15 +12,26 @@ from .news_trading import NewsTrading
|
|
|
12
12
|
from .fear_index import FearIndexStrategy
|
|
13
13
|
|
|
14
14
|
# New ML Strategies
|
|
15
|
-
# Use broad exception handling to catch DLL loading errors on Windows
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
# Use broad exception handling to catch DLL loading errors on Windows.
|
|
16
|
+
# FSB_SKIP_HEAVY_ML skips TensorFlow/torch-backed strategies (CI/CPU runners).
|
|
17
|
+
import os as _os
|
|
18
|
+
|
|
19
|
+
_SKIP_HEAVY_ML = _os.environ.get("FSB_SKIP_HEAVY_ML", "").strip().lower() in (
|
|
20
|
+
"1",
|
|
21
|
+
"true",
|
|
22
|
+
"yes",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
LSTM_AVAILABLE = False
|
|
26
|
+
LSTMStrategy = None
|
|
27
|
+
if not _SKIP_HEAVY_ML:
|
|
28
|
+
try:
|
|
29
|
+
from .lstm_strategy import LSTMStrategy
|
|
30
|
+
LSTM_AVAILABLE = True
|
|
31
|
+
except (ImportError, OSError, RuntimeError) as e:
|
|
32
|
+
LSTMStrategy = None
|
|
33
|
+
import warnings
|
|
34
|
+
warnings.warn(f"LSTM strategy not available: {e}")
|
|
24
35
|
|
|
25
36
|
try:
|
|
26
37
|
from .svm_strategy import SVMStrategy
|
|
@@ -40,14 +51,16 @@ except (ImportError, OSError, RuntimeError) as e:
|
|
|
40
51
|
import warnings
|
|
41
52
|
warnings.warn(f"Ensemble ML strategy not available: {e}")
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
54
|
+
TRANSFORMER_AVAILABLE = False
|
|
55
|
+
TransformerStrategy = None
|
|
56
|
+
if not _SKIP_HEAVY_ML:
|
|
57
|
+
try:
|
|
58
|
+
from .transformer_strategy import TransformerStrategy
|
|
59
|
+
TRANSFORMER_AVAILABLE = True
|
|
60
|
+
except (ImportError, OSError, RuntimeError) as e:
|
|
61
|
+
TransformerStrategy = None
|
|
62
|
+
import warnings
|
|
63
|
+
warnings.warn(f"Transformer strategy not available: {e}")
|
|
51
64
|
|
|
52
65
|
try:
|
|
53
66
|
from .rl_strategy import RLStrategy
|
|
@@ -86,16 +86,32 @@ class AnalyticsDialog(QDialog):
|
|
|
86
86
|
def update_portfolio_metrics(self, portfolio=None):
|
|
87
87
|
"""Update portfolio metrics table."""
|
|
88
88
|
try:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
from ..analytics.live_metrics import build_live_metrics
|
|
90
|
+
|
|
91
|
+
snapshot = build_live_metrics(portfolio)
|
|
92
|
+
metrics = {
|
|
93
|
+
"equity": snapshot.equity,
|
|
94
|
+
"balance": snapshot.balance,
|
|
95
|
+
"realized_pnl": snapshot.realized_pnl,
|
|
96
|
+
"unrealized_pnl": snapshot.unrealized_pnl,
|
|
97
|
+
"daily_pnl": snapshot.daily_pnl,
|
|
98
|
+
"total_trades": snapshot.total_trades,
|
|
99
|
+
"win_rate": snapshot.win_rate,
|
|
100
|
+
"profit_factor": snapshot.profit_factor,
|
|
101
|
+
"sharpe_ratio": snapshot.sharpe_ratio,
|
|
102
|
+
"sortino_ratio": snapshot.sortino_ratio,
|
|
103
|
+
"max_drawdown": snapshot.max_drawdown,
|
|
104
|
+
"max_drawdown_pct": snapshot.max_drawdown_pct,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if snapshot.total_trades == 0 and snapshot.open_positions == 0:
|
|
92
108
|
self.metrics_table.setRowCount(1)
|
|
93
109
|
self.metrics_table.setItem(0, 0, QTableWidgetItem("No data"))
|
|
94
110
|
self.metrics_table.setItem(0, 1, QTableWidgetItem("No trades available"))
|
|
95
111
|
return
|
|
96
|
-
|
|
112
|
+
|
|
97
113
|
self.metrics_table.setRowCount(len(metrics))
|
|
98
|
-
|
|
114
|
+
|
|
99
115
|
for i, (key, value) in enumerate(metrics.items()):
|
|
100
116
|
self.metrics_table.setItem(i, 0, QTableWidgetItem(str(key).replace('_', ' ').title()))
|
|
101
117
|
if isinstance(value, float):
|
|
@@ -134,24 +150,22 @@ class AnalyticsDialog(QDialog):
|
|
|
134
150
|
self.update_risk_metrics(portfolio)
|
|
135
151
|
|
|
136
152
|
def update_risk_metrics(self, portfolio=None):
|
|
137
|
-
"""Update risk metrics table."""
|
|
153
|
+
"""Update risk metrics table from portfolio trades when available."""
|
|
138
154
|
try:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
# Calculate CVaR
|
|
143
|
-
cvar_result = self.risk_analytics.calculate_cvar(confidence_level=0.95)
|
|
144
|
-
|
|
145
|
-
# Combine metrics
|
|
155
|
+
from ..analytics.live_metrics import build_live_metrics
|
|
156
|
+
|
|
157
|
+
snapshot = build_live_metrics(portfolio)
|
|
146
158
|
metrics = {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
159
|
+
"VaR (95%)": snapshot.var_95,
|
|
160
|
+
"CVaR (95%)": snapshot.cvar_95,
|
|
161
|
+
"Max Drawdown": snapshot.max_drawdown,
|
|
162
|
+
"Max Drawdown %": snapshot.max_drawdown_pct,
|
|
163
|
+
"Current Drawdown %": snapshot.current_drawdown,
|
|
164
|
+
"Volatility": snapshot.volatility,
|
|
151
165
|
}
|
|
152
|
-
|
|
166
|
+
|
|
153
167
|
self.risk_table.setRowCount(len(metrics))
|
|
154
|
-
|
|
168
|
+
|
|
155
169
|
for i, (key, value) in enumerate(metrics.items()):
|
|
156
170
|
self.risk_table.setItem(i, 0, QTableWidgetItem(key))
|
|
157
171
|
if isinstance(value, float):
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Live Analytics Dashboard widget for main window integration.
|
|
3
|
+
|
|
4
|
+
Shows KPI cards plus portfolio/risk tables, refreshed on a timer from the
|
|
5
|
+
live Portfolio.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from PyQt6.QtWidgets import (
|
|
9
|
+
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QTableWidget,
|
|
10
|
+
QTableWidgetItem, QGroupBox, QGridLayout, QFrame, QSizePolicy,
|
|
11
|
+
)
|
|
12
|
+
from PyQt6.QtCore import Qt, QTimer
|
|
13
|
+
from typing import Optional
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_CARD_STYLE = """
|
|
17
|
+
QFrame#metricCard {{
|
|
18
|
+
background-color: palette(base);
|
|
19
|
+
border: 1px solid palette(mid);
|
|
20
|
+
border-radius: 6px;
|
|
21
|
+
padding: 8px;
|
|
22
|
+
}}
|
|
23
|
+
QLabel#cardValue {{
|
|
24
|
+
font-size: 18px;
|
|
25
|
+
font-weight: bold;
|
|
26
|
+
}}
|
|
27
|
+
QLabel#cardLabel {{
|
|
28
|
+
font-size: 11px;
|
|
29
|
+
color: palette(mid);
|
|
30
|
+
}}
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
_TONE_COLORS = {
|
|
34
|
+
"positive": "#2e7d32",
|
|
35
|
+
"negative": "#c62828",
|
|
36
|
+
"neutral": "",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PortfolioAnalyticsWidget(QWidget):
|
|
41
|
+
"""Live analytics dashboard: KPI cards + portfolio/risk metric tables."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, portfolio, parent=None, refresh_ms: int = 5000):
|
|
44
|
+
super().__init__(parent)
|
|
45
|
+
self.portfolio = portfolio
|
|
46
|
+
self.refresh_ms = refresh_ms
|
|
47
|
+
self._card_value_labels = []
|
|
48
|
+
self.setup_ui()
|
|
49
|
+
self.setup_timer()
|
|
50
|
+
self.update_metrics()
|
|
51
|
+
|
|
52
|
+
def setup_ui(self):
|
|
53
|
+
"""Setup UI."""
|
|
54
|
+
layout = QVBoxLayout(self)
|
|
55
|
+
layout.setContentsMargins(8, 8, 8, 8)
|
|
56
|
+
layout.setSpacing(10)
|
|
57
|
+
|
|
58
|
+
header = QHBoxLayout()
|
|
59
|
+
title = QLabel("Live Analytics")
|
|
60
|
+
title.setStyleSheet("font-size: 16px; font-weight: bold;")
|
|
61
|
+
header.addWidget(title)
|
|
62
|
+
header.addStretch()
|
|
63
|
+
self.updated_label = QLabel("Updated: —")
|
|
64
|
+
self.updated_label.setStyleSheet("font-size: 11px; color: palette(mid);")
|
|
65
|
+
header.addWidget(self.updated_label)
|
|
66
|
+
layout.addLayout(header)
|
|
67
|
+
|
|
68
|
+
self.cards_frame = QFrame()
|
|
69
|
+
self.cards_frame.setObjectName("metricCards")
|
|
70
|
+
cards_layout = QHBoxLayout(self.cards_frame)
|
|
71
|
+
cards_layout.setContentsMargins(0, 0, 0, 0)
|
|
72
|
+
cards_layout.setSpacing(8)
|
|
73
|
+
self._card_value_labels = []
|
|
74
|
+
for _ in range(6):
|
|
75
|
+
card, value_label = self._make_card("—", "—")
|
|
76
|
+
cards_layout.addWidget(card)
|
|
77
|
+
self._card_value_labels.append(value_label)
|
|
78
|
+
layout.addWidget(self.cards_frame)
|
|
79
|
+
|
|
80
|
+
tables = QHBoxLayout()
|
|
81
|
+
tables.setSpacing(10)
|
|
82
|
+
|
|
83
|
+
portfolio_group = QGroupBox("Portfolio")
|
|
84
|
+
portfolio_layout = QVBoxLayout(portfolio_group)
|
|
85
|
+
self.metrics_table = QTableWidget()
|
|
86
|
+
self.metrics_table.setColumnCount(2)
|
|
87
|
+
self.metrics_table.setHorizontalHeaderLabels(["Metric", "Value"])
|
|
88
|
+
self.metrics_table.horizontalHeader().setStretchLastSection(True)
|
|
89
|
+
self.metrics_table.setAlternatingRowColors(True)
|
|
90
|
+
self.metrics_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
|
91
|
+
self.metrics_table.setSelectionMode(QTableWidget.SelectionMode.NoSelection)
|
|
92
|
+
self.metrics_table.verticalHeader().setVisible(False)
|
|
93
|
+
portfolio_layout.addWidget(self.metrics_table)
|
|
94
|
+
tables.addWidget(portfolio_group)
|
|
95
|
+
|
|
96
|
+
risk_group = QGroupBox("Risk")
|
|
97
|
+
risk_layout = QVBoxLayout(risk_group)
|
|
98
|
+
self.risk_table = QTableWidget()
|
|
99
|
+
self.risk_table.setColumnCount(2)
|
|
100
|
+
self.risk_table.setHorizontalHeaderLabels(["Metric", "Value"])
|
|
101
|
+
self.risk_table.horizontalHeader().setStretchLastSection(True)
|
|
102
|
+
self.risk_table.setAlternatingRowColors(True)
|
|
103
|
+
self.risk_table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
|
|
104
|
+
self.risk_table.setSelectionMode(QTableWidget.SelectionMode.NoSelection)
|
|
105
|
+
self.risk_table.verticalHeader().setVisible(False)
|
|
106
|
+
risk_layout.addWidget(self.risk_table)
|
|
107
|
+
tables.addWidget(risk_group)
|
|
108
|
+
|
|
109
|
+
layout.addLayout(tables)
|
|
110
|
+
self.setStyleSheet(_CARD_STYLE)
|
|
111
|
+
|
|
112
|
+
def _make_card(self, label: str, value: str):
|
|
113
|
+
"""Create a KPI card frame; returns (frame, value_label)."""
|
|
114
|
+
frame = QFrame()
|
|
115
|
+
frame.setObjectName("metricCard")
|
|
116
|
+
frame.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
|
117
|
+
grid = QGridLayout(frame)
|
|
118
|
+
grid.setContentsMargins(10, 8, 10, 8)
|
|
119
|
+
value_label = QLabel(value)
|
|
120
|
+
value_label.setObjectName("cardValue")
|
|
121
|
+
value_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
|
122
|
+
name_label = QLabel(label)
|
|
123
|
+
name_label.setObjectName("cardLabel")
|
|
124
|
+
name_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
|
125
|
+
grid.addWidget(value_label, 0, 0)
|
|
126
|
+
grid.addWidget(name_label, 1, 0)
|
|
127
|
+
frame._name_label = name_label # noqa: SLF001 — local card wiring
|
|
128
|
+
return frame, value_label
|
|
129
|
+
|
|
130
|
+
def setup_timer(self):
|
|
131
|
+
"""Setup live refresh timer."""
|
|
132
|
+
self.update_timer = QTimer(self)
|
|
133
|
+
self.update_timer.timeout.connect(self.update_metrics)
|
|
134
|
+
self.update_timer.start(self.refresh_ms)
|
|
135
|
+
|
|
136
|
+
def set_portfolio(self, portfolio) -> None:
|
|
137
|
+
"""Replace the portfolio source and refresh immediately."""
|
|
138
|
+
self.portfolio = portfolio
|
|
139
|
+
self.update_metrics()
|
|
140
|
+
|
|
141
|
+
def update_metrics(self):
|
|
142
|
+
"""Refresh KPI cards and portfolio/risk tables from live portfolio."""
|
|
143
|
+
try:
|
|
144
|
+
from ..analytics.live_metrics import build_live_metrics
|
|
145
|
+
|
|
146
|
+
snapshot = build_live_metrics(self.portfolio)
|
|
147
|
+
self.updated_label.setText(
|
|
148
|
+
f"Updated: {snapshot.timestamp.strftime('%H:%M:%S')}"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
for i, (label, value, tone) in enumerate(snapshot.card_metrics()):
|
|
152
|
+
if i >= len(self._card_value_labels):
|
|
153
|
+
break
|
|
154
|
+
value_label = self._card_value_labels[i]
|
|
155
|
+
value_label.setText(value)
|
|
156
|
+
color = _TONE_COLORS.get(tone, "")
|
|
157
|
+
if color:
|
|
158
|
+
value_label.setStyleSheet(f"font-size: 18px; font-weight: bold; color: {color};")
|
|
159
|
+
else:
|
|
160
|
+
value_label.setStyleSheet("font-size: 18px; font-weight: bold;")
|
|
161
|
+
card = value_label.parent()
|
|
162
|
+
if card is not None and hasattr(card, "_name_label"):
|
|
163
|
+
card._name_label.setText(label)
|
|
164
|
+
|
|
165
|
+
portfolio_rows = {
|
|
166
|
+
"Equity": f"${snapshot.equity:,.2f}",
|
|
167
|
+
"Balance": f"${snapshot.balance:,.2f}",
|
|
168
|
+
"Realized P&L": f"${snapshot.realized_pnl:,.2f}",
|
|
169
|
+
"Unrealized P&L": f"${snapshot.unrealized_pnl:,.2f}",
|
|
170
|
+
"Daily P&L": f"${snapshot.daily_pnl:,.2f}",
|
|
171
|
+
"Open Positions": str(snapshot.open_positions),
|
|
172
|
+
"Total Trades": str(snapshot.total_trades),
|
|
173
|
+
"Win Rate": f"{snapshot.win_rate:.1f}%",
|
|
174
|
+
"Profit Factor": f"{snapshot.profit_factor:.3f}",
|
|
175
|
+
"Sharpe Ratio": f"{snapshot.sharpe_ratio:.4f}",
|
|
176
|
+
"Sortino Ratio": f"{snapshot.sortino_ratio:.4f}",
|
|
177
|
+
"Avg Win": f"${snapshot.average_win:,.2f}",
|
|
178
|
+
"Avg Loss": f"${snapshot.average_loss:,.2f}",
|
|
179
|
+
}
|
|
180
|
+
self._fill_table(self.metrics_table, portfolio_rows)
|
|
181
|
+
|
|
182
|
+
risk_rows = {
|
|
183
|
+
"Max Drawdown": f"${snapshot.max_drawdown:,.2f}",
|
|
184
|
+
"Max Drawdown %": f"{snapshot.max_drawdown_pct:.2f}%",
|
|
185
|
+
"Current Drawdown %": f"{snapshot.current_drawdown:.2f}%",
|
|
186
|
+
"VaR (95%)": f"{snapshot.var_95:.4f}",
|
|
187
|
+
"CVaR (95%)": f"{snapshot.cvar_95:.4f}",
|
|
188
|
+
"Volatility": f"{snapshot.volatility:.4f}",
|
|
189
|
+
}
|
|
190
|
+
self._fill_table(self.risk_table, risk_rows)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
self.metrics_table.setRowCount(1)
|
|
193
|
+
self.metrics_table.setItem(0, 0, QTableWidgetItem("Error"))
|
|
194
|
+
self.metrics_table.setItem(0, 1, QTableWidgetItem(str(e)))
|
|
195
|
+
self.risk_table.setRowCount(0)
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def _fill_table(table: QTableWidget, rows: dict) -> None:
|
|
199
|
+
table.setRowCount(len(rows))
|
|
200
|
+
for i, (key, value) in enumerate(rows.items()):
|
|
201
|
+
table.setItem(i, 0, QTableWidgetItem(key))
|
|
202
|
+
table.setItem(i, 1, QTableWidgetItem(value))
|