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.
Files changed (146) hide show
  1. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/PKG-INFO +27 -3
  2. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/README.md +5 -0
  3. forexsmartbot-3.2.4/forexsmartbot/analytics/__init__.py +43 -0
  4. forexsmartbot-3.2.4/forexsmartbot/analytics/live_metrics.py +161 -0
  5. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/portfolio_analytics.py +20 -11
  6. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/risk_analytics.py +6 -1
  7. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/__init__.py +30 -17
  8. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/analytics_dialog.py +33 -19
  9. forexsmartbot-3.2.4/forexsmartbot/ui/analytics_widget.py +202 -0
  10. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/backtest_dialog.py +334 -134
  11. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/enhanced_main_window.py +7 -0
  12. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/tradingview_chart.py +4 -1
  13. forexsmartbot-3.2.4/forexsmartbot/utils/backtest_utils.py +12 -0
  14. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/utils/gpu_utils.py +45 -35
  15. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/PKG-INFO +27 -3
  16. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/SOURCES.txt +4 -0
  17. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/requires.txt +25 -2
  18. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/pyproject.toml +26 -3
  19. forexsmartbot-3.2.4/tests/test_backtest_dialog_ui.py +11 -0
  20. forexsmartbot-3.2.4/tests/test_live_metrics.py +70 -0
  21. forexsmartbot-3.2.1/forexsmartbot/analytics/__init__.py +0 -28
  22. forexsmartbot-3.2.1/forexsmartbot/ui/analytics_widget.py +0 -68
  23. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/LICENSE +0 -0
  24. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/__init__.py +0 -0
  25. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/__init__.py +0 -0
  26. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/__init__.py +0 -0
  27. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/ib_tws_broker.py +0 -0
  28. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/mt4_broker.py +0 -0
  29. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/paper_broker.py +0 -0
  30. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/brokers/rest_broker.py +0 -0
  31. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/__init__.py +0 -0
  32. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/alpha_vantage_provider.py +0 -0
  33. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/config.py +0 -0
  34. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/csv_provider.py +0 -0
  35. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/dummy_provider.py +0 -0
  36. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/mt4_provider.py +0 -0
  37. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/multi_provider.py +0 -0
  38. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/oanda_provider.py +0 -0
  39. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/stooq_provider.py +0 -0
  40. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/twelve_data_provider.py +0 -0
  41. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/adapters/data/yfinance_provider.py +0 -0
  42. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/chart_patterns.py +0 -0
  43. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/correlation_matrix.py +0 -0
  44. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/economic_calendar.py +0 -0
  45. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/enhanced_chart.py +0 -0
  46. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/market_depth.py +0 -0
  47. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/performance_attribution.py +0 -0
  48. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/strategy_comparison.py +0 -0
  49. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/analytics/trade_journal.py +0 -0
  50. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/brokers/mt4_bridge.py +0 -0
  51. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/builder/__init__.py +0 -0
  52. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/builder/code_generator.py +0 -0
  53. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/builder/strategy_builder.py +0 -0
  54. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/builder/strategy_template.py +0 -0
  55. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/__init__.py +0 -0
  56. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/api_client.py +0 -0
  57. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/api_server.py +0 -0
  58. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/cloud_sync.py +0 -0
  59. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/error_handler.py +0 -0
  60. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/mobile_api.py +0 -0
  61. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/remote_monitor.py +0 -0
  62. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/cloud/security.py +0 -0
  63. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/backtester.py +0 -0
  64. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/config.py +0 -0
  65. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/interfaces.py +0 -0
  66. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/paper_broker.py +0 -0
  67. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/portfolio.py +0 -0
  68. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/risk.py +0 -0
  69. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/risk_engine.py +0 -0
  70. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/strategy.py +0 -0
  71. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/core/trade_manager.py +0 -0
  72. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/gui/main_window.py +0 -0
  73. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/gui/theme.py +0 -0
  74. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/marketplace/__init__.py +0 -0
  75. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/marketplace/marketplace.py +0 -0
  76. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/marketplace/strategy_rating.py +0 -0
  77. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/monitoring/__init__.py +0 -0
  78. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/monitoring/health_check.py +0 -0
  79. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/monitoring/performance_tracker.py +0 -0
  80. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/monitoring/strategy_monitor.py +0 -0
  81. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/__init__.py +0 -0
  82. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/adaptive_parameters.py +0 -0
  83. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/genetic_optimizer.py +0 -0
  84. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/hyperparameter_optimizer.py +0 -0
  85. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/monte_carlo.py +0 -0
  86. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/multi_objective_optimizer.py +0 -0
  87. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/parameter_sensitivity.py +0 -0
  88. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/optimization/walk_forward.py +0 -0
  89. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/__init__.py +0 -0
  90. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/backtest.py +0 -0
  91. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/calculation_engine.py +0 -0
  92. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/controller.py +0 -0
  93. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/data_streaming.py +0 -0
  94. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/enhanced_backtest.py +0 -0
  95. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/fast_trading_controller.py +0 -0
  96. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/gpu_backtest.py +0 -0
  97. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/language_manager.py +0 -0
  98. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/notification_service.py +0 -0
  99. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/persistence.py +0 -0
  100. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/services/startup_manager.py +0 -0
  101. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/adaptive_trend_flow.py +0 -0
  102. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/breakout_atr.py +0 -0
  103. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/ensemble_ml_strategy.py +0 -0
  104. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/fear_index.py +0 -0
  105. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/lstm_strategy.py +0 -0
  106. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/mean_reversion.py +0 -0
  107. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/ml_adaptive_supertrend.py +0 -0
  108. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/momentum_breakout.py +0 -0
  109. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/multi_timeframe_strategy.py +0 -0
  110. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/news_trading.py +0 -0
  111. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/rl_strategy.py +0 -0
  112. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/rsi_reversion.py +0 -0
  113. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/scalping_ma.py +0 -0
  114. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/sma_crossover.py +0 -0
  115. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/svm_strategy.py +0 -0
  116. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/strategies/transformer_strategy.py +0 -0
  117. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/testing/__init__.py +0 -0
  118. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/testing/ci_cd_pipeline.py +0 -0
  119. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/testing/paper_account_tester.py +0 -0
  120. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/testing/strategy_sandbox.py +0 -0
  121. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/__init__.py +0 -0
  122. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/charts.py +0 -0
  123. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/cloud_dialog.py +0 -0
  124. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/enhanced_charts.py +0 -0
  125. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/export_dialog.py +0 -0
  126. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/main_window.py +0 -0
  127. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/marketplace_dialog.py +0 -0
  128. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/monitoring_dialog.py +0 -0
  129. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/monitoring_widget.py +0 -0
  130. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/optimization_dialog.py +0 -0
  131. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/settings_dialog.py +0 -0
  132. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/simple_chart.py +0 -0
  133. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/strategy_builder_dialog.py +0 -0
  134. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/ui/theme.py +0 -0
  135. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/utils/__init__.py +0 -0
  136. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/utils/gpu_numpy.py +0 -0
  137. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot/utils/native_indicators.py +0 -0
  138. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/dependency_links.txt +0 -0
  139. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/entry_points.txt +0 -0
  140. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/forexsmartbot.egg-info/top_level.txt +0 -0
  141. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/setup.cfg +0 -0
  142. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/tests/test_config.py +0 -0
  143. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/tests/test_multi_timeframe_strategy.py +0 -0
  144. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/tests/test_native_indicators.py +0 -0
  145. {forexsmartbot-3.2.1 → forexsmartbot-3.2.4}/tests/test_risk.py +0 -0
  146. {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.1
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.10
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 (currently unused, kept for compatibility)
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
- # If portfolio is provided and has trades, use them
293
- if portfolio and hasattr(portfolio, 'trades') and portfolio.trades:
294
- # Temporarily use portfolio's trades
295
- original_trades = self.trades
296
- self.trades = portfolio.trades
297
- metrics = self.get_performance_summary()
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
- return metrics
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
- try:
17
- from .lstm_strategy import LSTMStrategy
18
- LSTM_AVAILABLE = True
19
- except (ImportError, OSError, RuntimeError) as e:
20
- LSTM_AVAILABLE = False
21
- LSTMStrategy = None
22
- import warnings
23
- warnings.warn(f"LSTM strategy not available: {e}")
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
- try:
44
- from .transformer_strategy import TransformerStrategy
45
- TRANSFORMER_AVAILABLE = True
46
- except (ImportError, OSError, RuntimeError) as e:
47
- TRANSFORMER_AVAILABLE = False
48
- TransformerStrategy = None
49
- import warnings
50
- warnings.warn(f"Transformer strategy not available: {e}")
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
- metrics = self.analytics.calculate_metrics(portfolio)
90
-
91
- if not metrics:
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
- # Calculate VaR
140
- var_result = self.risk_analytics.calculate_var(confidence_level=0.95)
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
- 'VaR (95%)': var_result.get('var', 0.0),
148
- 'CVaR (95%)': cvar_result.get('cvar', 0.0),
149
- 'Max Drawdown': var_result.get('max_drawdown', 0.0),
150
- 'Volatility': var_result.get('volatility', 0.0)
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))