forexsmartbot 3.2.0__py3-none-any.whl

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 (129) hide show
  1. forexsmartbot/__init__.py +1 -0
  2. forexsmartbot/adapters/__init__.py +4 -0
  3. forexsmartbot/adapters/brokers/__init__.py +8 -0
  4. forexsmartbot/adapters/brokers/ib_tws_broker.py +244 -0
  5. forexsmartbot/adapters/brokers/mt4_broker.py +266 -0
  6. forexsmartbot/adapters/brokers/paper_broker.py +131 -0
  7. forexsmartbot/adapters/brokers/rest_broker.py +304 -0
  8. forexsmartbot/adapters/data/__init__.py +27 -0
  9. forexsmartbot/adapters/data/alpha_vantage_provider.py +261 -0
  10. forexsmartbot/adapters/data/config.py +101 -0
  11. forexsmartbot/adapters/data/csv_provider.py +137 -0
  12. forexsmartbot/adapters/data/dummy_provider.py +35 -0
  13. forexsmartbot/adapters/data/mt4_provider.py +217 -0
  14. forexsmartbot/adapters/data/multi_provider.py +191 -0
  15. forexsmartbot/adapters/data/oanda_provider.py +173 -0
  16. forexsmartbot/adapters/data/stooq_provider.py +114 -0
  17. forexsmartbot/adapters/data/twelve_data_provider.py +117 -0
  18. forexsmartbot/adapters/data/yfinance_provider.py +246 -0
  19. forexsmartbot/analytics/__init__.py +28 -0
  20. forexsmartbot/analytics/chart_patterns.py +322 -0
  21. forexsmartbot/analytics/correlation_matrix.py +186 -0
  22. forexsmartbot/analytics/economic_calendar.py +616 -0
  23. forexsmartbot/analytics/enhanced_chart.py +57 -0
  24. forexsmartbot/analytics/market_depth.py +134 -0
  25. forexsmartbot/analytics/performance_attribution.py +210 -0
  26. forexsmartbot/analytics/portfolio_analytics.py +303 -0
  27. forexsmartbot/analytics/risk_analytics.py +308 -0
  28. forexsmartbot/analytics/strategy_comparison.py +194 -0
  29. forexsmartbot/analytics/trade_journal.py +235 -0
  30. forexsmartbot/brokers/mt4_bridge.py +49 -0
  31. forexsmartbot/builder/__init__.py +12 -0
  32. forexsmartbot/builder/code_generator.py +173 -0
  33. forexsmartbot/builder/strategy_builder.py +119 -0
  34. forexsmartbot/builder/strategy_template.py +138 -0
  35. forexsmartbot/cloud/__init__.py +24 -0
  36. forexsmartbot/cloud/api_client.py +188 -0
  37. forexsmartbot/cloud/api_server.py +366 -0
  38. forexsmartbot/cloud/cloud_sync.py +279 -0
  39. forexsmartbot/cloud/error_handler.py +150 -0
  40. forexsmartbot/cloud/mobile_api.py +213 -0
  41. forexsmartbot/cloud/remote_monitor.py +310 -0
  42. forexsmartbot/cloud/security.py +208 -0
  43. forexsmartbot/core/backtester.py +57 -0
  44. forexsmartbot/core/config.py +38 -0
  45. forexsmartbot/core/interfaces.py +253 -0
  46. forexsmartbot/core/paper_broker.py +28 -0
  47. forexsmartbot/core/portfolio.py +227 -0
  48. forexsmartbot/core/risk.py +24 -0
  49. forexsmartbot/core/risk_engine.py +203 -0
  50. forexsmartbot/core/strategy.py +44 -0
  51. forexsmartbot/core/trade_manager.py +261 -0
  52. forexsmartbot/gui/main_window.py +168 -0
  53. forexsmartbot/gui/theme.py +32 -0
  54. forexsmartbot/marketplace/__init__.py +10 -0
  55. forexsmartbot/marketplace/marketplace.py +178 -0
  56. forexsmartbot/marketplace/strategy_rating.py +64 -0
  57. forexsmartbot/monitoring/__init__.py +12 -0
  58. forexsmartbot/monitoring/health_check.py +109 -0
  59. forexsmartbot/monitoring/performance_tracker.py +213 -0
  60. forexsmartbot/monitoring/strategy_monitor.py +175 -0
  61. forexsmartbot/optimization/__init__.py +26 -0
  62. forexsmartbot/optimization/adaptive_parameters.py +224 -0
  63. forexsmartbot/optimization/genetic_optimizer.py +116 -0
  64. forexsmartbot/optimization/hyperparameter_optimizer.py +77 -0
  65. forexsmartbot/optimization/monte_carlo.py +114 -0
  66. forexsmartbot/optimization/multi_objective_optimizer.py +299 -0
  67. forexsmartbot/optimization/parameter_sensitivity.py +187 -0
  68. forexsmartbot/optimization/walk_forward.py +131 -0
  69. forexsmartbot/services/__init__.py +7 -0
  70. forexsmartbot/services/backtest.py +354 -0
  71. forexsmartbot/services/calculation_engine.py +99 -0
  72. forexsmartbot/services/controller.py +419 -0
  73. forexsmartbot/services/data_streaming.py +116 -0
  74. forexsmartbot/services/enhanced_backtest.py +390 -0
  75. forexsmartbot/services/fast_trading_controller.py +317 -0
  76. forexsmartbot/services/gpu_backtest.py +303 -0
  77. forexsmartbot/services/language_manager.py +621 -0
  78. forexsmartbot/services/notification_service.py +283 -0
  79. forexsmartbot/services/persistence.py +299 -0
  80. forexsmartbot/services/startup_manager.py +200 -0
  81. forexsmartbot/strategies/__init__.py +120 -0
  82. forexsmartbot/strategies/adaptive_trend_flow.py +311 -0
  83. forexsmartbot/strategies/breakout_atr.py +136 -0
  84. forexsmartbot/strategies/ensemble_ml_strategy.py +209 -0
  85. forexsmartbot/strategies/fear_index.py +174 -0
  86. forexsmartbot/strategies/lstm_strategy.py +269 -0
  87. forexsmartbot/strategies/mean_reversion.py +158 -0
  88. forexsmartbot/strategies/ml_adaptive_supertrend.py +272 -0
  89. forexsmartbot/strategies/momentum_breakout.py +183 -0
  90. forexsmartbot/strategies/multi_timeframe_strategy.py +186 -0
  91. forexsmartbot/strategies/news_trading.py +187 -0
  92. forexsmartbot/strategies/rl_strategy.py +277 -0
  93. forexsmartbot/strategies/rsi_reversion.py +162 -0
  94. forexsmartbot/strategies/scalping_ma.py +195 -0
  95. forexsmartbot/strategies/sma_crossover.py +118 -0
  96. forexsmartbot/strategies/svm_strategy.py +199 -0
  97. forexsmartbot/strategies/transformer_strategy.py +233 -0
  98. forexsmartbot/testing/__init__.py +14 -0
  99. forexsmartbot/testing/ci_cd_pipeline.py +286 -0
  100. forexsmartbot/testing/paper_account_tester.py +326 -0
  101. forexsmartbot/testing/strategy_sandbox.py +284 -0
  102. forexsmartbot/ui/__init__.py +8 -0
  103. forexsmartbot/ui/analytics_dialog.py +262 -0
  104. forexsmartbot/ui/analytics_widget.py +68 -0
  105. forexsmartbot/ui/backtest_dialog.py +908 -0
  106. forexsmartbot/ui/charts.py +172 -0
  107. forexsmartbot/ui/cloud_dialog.py +371 -0
  108. forexsmartbot/ui/enhanced_charts.py +491 -0
  109. forexsmartbot/ui/enhanced_main_window.py +2800 -0
  110. forexsmartbot/ui/export_dialog.py +333 -0
  111. forexsmartbot/ui/main_window.py +648 -0
  112. forexsmartbot/ui/marketplace_dialog.py +438 -0
  113. forexsmartbot/ui/monitoring_dialog.py +215 -0
  114. forexsmartbot/ui/monitoring_widget.py +80 -0
  115. forexsmartbot/ui/optimization_dialog.py +603 -0
  116. forexsmartbot/ui/settings_dialog.py +671 -0
  117. forexsmartbot/ui/simple_chart.py +309 -0
  118. forexsmartbot/ui/strategy_builder_dialog.py +595 -0
  119. forexsmartbot/ui/theme.py +109 -0
  120. forexsmartbot/ui/tradingview_chart.py +736 -0
  121. forexsmartbot/utils/__init__.py +42 -0
  122. forexsmartbot/utils/gpu_numpy.py +194 -0
  123. forexsmartbot/utils/gpu_utils.py +260 -0
  124. forexsmartbot-3.2.0.dist-info/METADATA +177 -0
  125. forexsmartbot-3.2.0.dist-info/RECORD +129 -0
  126. forexsmartbot-3.2.0.dist-info/WHEEL +5 -0
  127. forexsmartbot-3.2.0.dist-info/entry_points.txt +2 -0
  128. forexsmartbot-3.2.0.dist-info/licenses/LICENSE +150 -0
  129. forexsmartbot-3.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,648 @@
1
+ """Enhanced main window for ForexSmartBot."""
2
+
3
+ from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
4
+ QLabel, QLineEdit, QPushButton, QComboBox,
5
+ QDoubleSpinBox, QSpinBox, QTextEdit, QTabWidget,
6
+ QTableWidget, QTableWidgetItem, QHeaderView,
7
+ QGroupBox, QFormLayout, QCheckBox, QMessageBox,
8
+ QStatusBar, QProgressBar, QSplitter, QDialog)
9
+ from PyQt6.QtCore import QTimer, Qt, pyqtSignal
10
+ from PyQt6.QtGui import QFont
11
+ import pandas as pd
12
+ from datetime import datetime
13
+ from typing import Dict, List, Optional
14
+
15
+ from ..core.interfaces import IBroker, IStrategy, IDataProvider
16
+ from ..core.portfolio import Portfolio
17
+ from ..core.risk_engine import RiskEngine, RiskConfig
18
+ from ..services.controller import TradingController
19
+ from ..services.persistence import SettingsManager, DatabaseManager, LogManager
20
+ from ..strategies import get_strategy, list_strategies
21
+ from ..adapters.brokers import PaperBroker, MT4Broker, RestBroker
22
+ from ..adapters.data import YFinanceProvider, CSVProvider
23
+ from .settings_dialog import SettingsDialog
24
+ from .charts import ChartWidget
25
+ from .theme import ThemeManager
26
+
27
+
28
+ class MainWindow(QMainWindow):
29
+ """Enhanced main window with portfolio mode and advanced features."""
30
+
31
+ def __init__(self):
32
+ super().__init__()
33
+ self.setWindowTitle("ForexSmartBot - Professional Trading System")
34
+ self.setGeometry(100, 100, 1400, 900)
35
+
36
+ # Initialize services
37
+ self.settings_manager = SettingsManager()
38
+ self.db_manager = DatabaseManager()
39
+ self.log_manager = LogManager()
40
+ self.logger = self.log_manager.get_logger(__name__)
41
+
42
+ # Initialize components
43
+ self.portfolio = Portfolio(self.settings_manager.get('initial_balance', 10000.0))
44
+ self.risk_config = self._create_risk_config()
45
+ self.risk_engine = RiskEngine(self.risk_config)
46
+
47
+ # Initialize brokers and data providers
48
+ self.broker = None
49
+ self.data_provider = None
50
+ self.controller = None
51
+
52
+ # UI state
53
+ self.is_running = False
54
+ self.selected_symbols = []
55
+ self.strategies = {}
56
+
57
+ # Initialize UI
58
+ self.setup_ui()
59
+ self.setup_theme()
60
+ self.setup_connections()
61
+ self.load_settings()
62
+
63
+ # Initialize timer
64
+ self.timer = QTimer()
65
+ self.timer.timeout.connect(self.update_display)
66
+ self.timer.start(5000) # Update every 5 seconds
67
+
68
+ # Load initial chart data
69
+ self.load_chart_data()
70
+
71
+ def setup_ui(self):
72
+ """Setup the main UI components."""
73
+ central_widget = QWidget()
74
+ self.setCentralWidget(central_widget)
75
+
76
+ # Main layout
77
+ main_layout = QVBoxLayout(central_widget)
78
+
79
+ # Top controls
80
+ self.create_controls_panel(main_layout)
81
+
82
+ # Status bar
83
+ self.create_status_bar()
84
+
85
+ # Main content area
86
+ splitter = QSplitter(Qt.Orientation.Horizontal)
87
+ main_layout.addWidget(splitter)
88
+
89
+ # Left panel - controls and log
90
+ left_panel = self.create_left_panel()
91
+ splitter.addWidget(left_panel)
92
+
93
+ # Right panel - charts and positions
94
+ right_panel = self.create_right_panel()
95
+ splitter.addWidget(right_panel)
96
+
97
+ # Set splitter proportions
98
+ splitter.setSizes([400, 1000])
99
+
100
+ def create_controls_panel(self, parent_layout):
101
+ """Create the top controls panel."""
102
+ controls_group = QGroupBox("Trading Controls")
103
+ controls_layout = QHBoxLayout(controls_group)
104
+
105
+ # Broker selection
106
+ controls_layout.addWidget(QLabel("Broker:"))
107
+ self.broker_combo = QComboBox()
108
+ self.broker_combo.addItems(["PAPER", "MT4", "REST"])
109
+ controls_layout.addWidget(self.broker_combo)
110
+
111
+ # Strategy selection
112
+ controls_layout.addWidget(QLabel("Strategy:"))
113
+ self.strategy_combo = QComboBox()
114
+ self.strategy_combo.addItems(list_strategies())
115
+ controls_layout.addWidget(self.strategy_combo)
116
+
117
+ # Symbols selection
118
+ controls_layout.addWidget(QLabel("Symbols:"))
119
+ self.symbols_edit = QLineEdit()
120
+ self.symbols_edit.setPlaceholderText("EURUSD,USDJPY,GBPUSD")
121
+ controls_layout.addWidget(self.symbols_edit)
122
+
123
+ # Risk percentage
124
+ controls_layout.addWidget(QLabel("Risk %:"))
125
+ self.risk_pct_spin = QDoubleSpinBox()
126
+ self.risk_pct_spin.setRange(0.001, 0.1)
127
+ self.risk_pct_spin.setDecimals(3)
128
+ self.risk_pct_spin.setSingleStep(0.005)
129
+ self.risk_pct_spin.setValue(0.02)
130
+ controls_layout.addWidget(self.risk_pct_spin)
131
+
132
+ # Theme selector
133
+ controls_layout.addWidget(QLabel("Theme:"))
134
+ self.theme_combo = QComboBox()
135
+ self.theme_combo.addItems(["Auto", "Light", "Dark"])
136
+ controls_layout.addWidget(self.theme_combo)
137
+
138
+ # Buttons
139
+ self.connect_button = QPushButton("Connect")
140
+ self.connect_button.clicked.connect(self.on_connect)
141
+ controls_layout.addWidget(self.connect_button)
142
+
143
+ self.start_button = QPushButton("Start Bot")
144
+ self.start_button.clicked.connect(self.on_start)
145
+ controls_layout.addWidget(self.start_button)
146
+
147
+ self.stop_button = QPushButton("Stop Bot")
148
+ self.stop_button.clicked.connect(self.on_stop)
149
+ controls_layout.addWidget(self.stop_button)
150
+
151
+ self.settings_button = QPushButton("Settings")
152
+ self.settings_button.clicked.connect(self.show_settings)
153
+ controls_layout.addWidget(self.settings_button)
154
+
155
+ self.export_button = QPushButton("Export Trades")
156
+ self.export_button.clicked.connect(self.export_trades)
157
+ controls_layout.addWidget(self.export_button)
158
+
159
+ parent_layout.addWidget(controls_group)
160
+
161
+ def create_status_bar(self):
162
+ """Create the status bar."""
163
+ self.status_bar = QStatusBar()
164
+ self.setStatusBar(self.status_bar)
165
+
166
+ # Status labels
167
+ self.status_label = QLabel("Status: Idle")
168
+ self.balance_label = QLabel("Balance: $0.00")
169
+ self.equity_label = QLabel("Equity: $0.00")
170
+ self.drawdown_label = QLabel("DD: 0.0%")
171
+
172
+ self.status_bar.addWidget(self.status_label)
173
+ self.status_bar.addWidget(self.balance_label)
174
+ self.status_bar.addWidget(self.equity_label)
175
+ self.status_bar.addWidget(self.drawdown_label)
176
+
177
+ # Progress bar
178
+ self.progress_bar = QProgressBar()
179
+ self.progress_bar.setVisible(False)
180
+ self.status_bar.addPermanentWidget(self.progress_bar)
181
+
182
+ def create_left_panel(self):
183
+ """Create the left panel with log and metrics."""
184
+ widget = QWidget()
185
+ layout = QVBoxLayout(widget)
186
+
187
+ # Log tab widget
188
+ self.log_tabs = QTabWidget()
189
+ layout.addWidget(self.log_tabs)
190
+
191
+ # Log tab
192
+ log_widget = QWidget()
193
+ log_layout = QVBoxLayout(log_widget)
194
+
195
+ self.log_text = QTextEdit()
196
+ self.log_text.setReadOnly(True)
197
+ self.log_text.setMaximumHeight(200)
198
+ log_layout.addWidget(self.log_text)
199
+
200
+ self.log_tabs.addTab(log_widget, "Log")
201
+
202
+ # Metrics tab
203
+ metrics_widget = self.create_metrics_tab()
204
+ self.log_tabs.addTab(metrics_widget, "Metrics")
205
+
206
+ # Open positions tab
207
+ positions_widget = self.create_positions_tab()
208
+ self.log_tabs.addTab(positions_widget, "Positions")
209
+
210
+ return widget
211
+
212
+ def create_right_panel(self):
213
+ """Create the right panel with charts."""
214
+ widget = QWidget()
215
+ layout = QVBoxLayout(widget)
216
+
217
+ # Chart widget
218
+ self.chart_widget = ChartWidget()
219
+ layout.addWidget(self.chart_widget)
220
+
221
+ return widget
222
+
223
+ def create_metrics_tab(self):
224
+ """Create the metrics tab."""
225
+ widget = QWidget()
226
+ layout = QVBoxLayout(widget)
227
+
228
+ # Metrics table
229
+ self.metrics_table = QTableWidget()
230
+ self.metrics_table.setColumnCount(2)
231
+ self.metrics_table.setHorizontalHeaderLabels(["Metric", "Value"])
232
+ self.metrics_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
233
+
234
+ layout.addWidget(self.metrics_table)
235
+
236
+ return widget
237
+
238
+ def create_positions_tab(self):
239
+ """Create the positions tab."""
240
+ widget = QWidget()
241
+ layout = QVBoxLayout(widget)
242
+
243
+ # Positions table
244
+ self.positions_table = QTableWidget()
245
+ self.positions_table.setColumnCount(7)
246
+ self.positions_table.setHorizontalHeaderLabels([
247
+ "Symbol", "Side", "Quantity", "Entry Price", "Current Price", "PnL", "Strategy"
248
+ ])
249
+ self.positions_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
250
+
251
+ layout.addWidget(self.positions_table)
252
+
253
+ return widget
254
+
255
+ def setup_theme(self):
256
+ """Setup theme management."""
257
+ self.theme_manager = ThemeManager(self.settings_manager)
258
+ self.theme_manager.apply_theme(self.app)
259
+
260
+ def setup_connections(self):
261
+ """Setup signal connections."""
262
+ # Theme change
263
+ self.theme_combo.currentIndexChanged.connect(self.on_theme_changed)
264
+
265
+ # Strategy change
266
+ self.strategy_combo.currentTextChanged.connect(self.on_strategy_changed)
267
+
268
+ # Symbol change for chart
269
+ self.symbols_edit.textChanged.connect(self.on_symbols_changed)
270
+
271
+ def load_settings(self):
272
+ """Load settings from settings manager."""
273
+ # Load theme
274
+ theme = self.settings_manager.get('theme', 'auto')
275
+ theme_map = {'auto': 0, 'light': 1, 'dark': 2}
276
+ self.theme_combo.setCurrentIndex(theme_map.get(theme, 0))
277
+
278
+ # Load broker
279
+ broker = self.settings_manager.get('broker_mode', 'PAPER')
280
+ self.broker_combo.setCurrentText(broker)
281
+
282
+ # Load strategy
283
+ strategy = self.settings_manager.get('strategy', 'SMA_Crossover')
284
+ self.strategy_combo.setCurrentText(strategy)
285
+
286
+ # Load symbols
287
+ symbols = self.settings_manager.get('selected_symbols', ['EURUSD'])
288
+ self.symbols_edit.setText(','.join(symbols))
289
+
290
+ # Load risk settings
291
+ self.risk_pct_spin.setValue(self.settings_manager.get('risk_pct', 0.02))
292
+
293
+ def _create_risk_config(self) -> RiskConfig:
294
+ """Create risk configuration from settings."""
295
+ return RiskConfig(
296
+ base_risk_pct=self.settings_manager.get('risk_pct', 0.02),
297
+ max_risk_pct=self.settings_manager.get('max_risk_pct', 0.05),
298
+ daily_risk_cap=self.settings_manager.get('daily_risk_cap', 0.05),
299
+ max_drawdown_pct=self.settings_manager.get('max_drawdown_pct', 0.25),
300
+ min_trade_amount=self.settings_manager.get('trade_amount_min', 10.0),
301
+ max_trade_amount=self.settings_manager.get('trade_amount_max', 100.0)
302
+ )
303
+
304
+ def on_connect(self):
305
+ """Handle broker connection."""
306
+ broker_type = self.broker_combo.currentText()
307
+
308
+ try:
309
+ if broker_type == "PAPER":
310
+ self.broker = PaperBroker(self.settings_manager.get('initial_balance', 10000.0))
311
+ elif broker_type == "MT4":
312
+ host = self.settings_manager.get('mt4_host', '127.0.0.1')
313
+ port = self.settings_manager.get('mt4_port', 5555)
314
+ self.broker = MT4Broker(host, port)
315
+ elif broker_type == "REST":
316
+ api_key = self.settings_manager.get('rest_api_key', '')
317
+ api_secret = self.settings_manager.get('rest_api_secret', '')
318
+ base_url = self.settings_manager.get('rest_base_url', 'https://api.example.com')
319
+ self.broker = RestBroker(api_key, api_secret, base_url)
320
+
321
+ if self.broker.connect():
322
+ self.append_log(f"Connected to {broker_type} broker")
323
+ self.status_label.setText(f"Status: Connected to {broker_type}")
324
+ else:
325
+ self.append_log(f"Failed to connect to {broker_type} broker")
326
+ self.status_label.setText("Status: Connection Failed")
327
+
328
+ except Exception as e:
329
+ self.append_log(f"Connection error: {str(e)}")
330
+ self.status_label.setText("Status: Error")
331
+
332
+ def on_start(self):
333
+ """Start the trading bot."""
334
+ if not self.broker or not self.broker.is_connected():
335
+ QMessageBox.warning(self, "Start Bot", "Please connect to a broker first")
336
+ return
337
+
338
+ # Parse symbols
339
+ symbols_text = self.symbols_edit.text().strip()
340
+ if not symbols_text:
341
+ QMessageBox.warning(self, "Start Bot", "Please enter at least one symbol")
342
+ return
343
+
344
+ self.selected_symbols = [s.strip().upper() for s in symbols_text.split(',') if s.strip()]
345
+
346
+ # Initialize data provider
347
+ data_provider_type = self.settings_manager.get('data_provider', 'yfinance')
348
+ if data_provider_type == 'yfinance':
349
+ self.data_provider = YFinanceProvider()
350
+ elif data_provider_type == 'csv':
351
+ self.data_provider = CSVProvider()
352
+
353
+ # Initialize controller
354
+ self.controller = TradingController(
355
+ self.broker, self.data_provider, self.risk_engine, self.portfolio
356
+ )
357
+
358
+ # Connect controller signals
359
+ self.controller.signal_trade_executed.connect(self.on_trade_executed)
360
+ self.controller.signal_position_updated.connect(self.on_position_updated)
361
+ self.controller.signal_equity_updated.connect(self.on_equity_updated)
362
+ self.controller.signal_error_occurred.connect(self.on_error_occurred)
363
+ self.controller.signal_log_message.connect(self.append_log)
364
+
365
+ # Add strategies for each symbol
366
+ strategy_name = self.strategy_combo.currentText()
367
+ for symbol in self.selected_symbols:
368
+ strategy = get_strategy(strategy_name)
369
+ self.controller.add_strategy(symbol, strategy)
370
+
371
+ # Start trading
372
+ if self.controller.start_trading():
373
+ self.is_running = True
374
+ self.start_button.setEnabled(False)
375
+ self.stop_button.setEnabled(True)
376
+ self.status_label.setText("Status: Running")
377
+ self.append_log("Trading bot started")
378
+ else:
379
+ QMessageBox.critical(self, "Start Bot", "Failed to start trading bot")
380
+
381
+ def on_stop(self):
382
+ """Stop the trading bot."""
383
+ if self.controller:
384
+ self.controller.stop_trading()
385
+
386
+ self.is_running = False
387
+ self.start_button.setEnabled(True)
388
+ self.stop_button.setEnabled(False)
389
+ self.status_label.setText("Status: Stopped")
390
+ self.append_log("Trading bot stopped")
391
+
392
+ def on_theme_changed(self, index):
393
+ """Handle theme change."""
394
+ theme_map = {0: 'auto', 1: 'light', 2: 'dark'}
395
+ theme = theme_map[index]
396
+ self.theme_manager.set_theme(theme)
397
+ self.theme_manager.apply_theme(self.app)
398
+
399
+ def on_strategy_changed(self, strategy_name):
400
+ """Handle strategy change."""
401
+ # Update strategy parameters if needed
402
+ pass
403
+
404
+ def on_symbols_changed(self):
405
+ """Handle symbol change for chart update."""
406
+ try:
407
+ symbols_text = self.symbols_edit.text().strip()
408
+ if symbols_text and len(symbols_text) >= 3: # Only process complete symbols
409
+ # Get the first symbol for chart display
410
+ first_symbol = symbols_text.split(',')[0].strip().upper()
411
+ # Only update if it's a valid forex pair (6 characters)
412
+ if len(first_symbol) == 6:
413
+ self.update_chart_data(first_symbol)
414
+ except Exception as e:
415
+ self.append_log(f"Error updating chart for symbol: {str(e)}")
416
+
417
+ def on_trade_executed(self, trade_data):
418
+ """Handle trade execution signal."""
419
+ self.append_log(f"Trade executed: {trade_data}")
420
+ self.update_positions_table()
421
+ self.update_metrics_table()
422
+
423
+ def on_position_updated(self, position_data):
424
+ """Handle position update signal."""
425
+ self.update_positions_table()
426
+
427
+ def on_equity_updated(self, equity):
428
+ """Handle equity update signal."""
429
+ self.equity_label.setText(f"Equity: ${equity:.2f}")
430
+
431
+ def on_error_occurred(self, error_message):
432
+ """Handle error signal."""
433
+ self.append_log(f"ERROR: {error_message}")
434
+ self.status_label.setText("Status: Error")
435
+
436
+ def append_log(self, message):
437
+ """Append message to log."""
438
+ timestamp = datetime.now().strftime("%H:%M:%S")
439
+ self.log_text.append(f"[{timestamp}] {message}")
440
+ self.log_text.moveCursor(self.log_text.textCursor().MoveOperation.End)
441
+
442
+ def update_display(self):
443
+ """Update the display with current data."""
444
+ if not self.is_running or not self.broker:
445
+ return
446
+
447
+ try:
448
+ # Update balance and equity
449
+ balance = self.broker.get_balance()
450
+ equity = self.broker.get_equity()
451
+
452
+ self.balance_label.setText(f"Balance: ${balance:.2f}")
453
+ self.equity_label.setText(f"Equity: ${equity:.2f}")
454
+
455
+ # Update drawdown
456
+ if hasattr(self.portfolio, 'get_current_drawdown'):
457
+ drawdown = self.portfolio.get_current_drawdown()
458
+ self.drawdown_label.setText(f"DD: {drawdown:.1f}%")
459
+
460
+ # Update tables
461
+ self.update_positions_table()
462
+ self.update_metrics_table()
463
+
464
+ except Exception as e:
465
+ self.append_log(f"Display update error: {str(e)}")
466
+
467
+ def update_positions_table(self):
468
+ """Update the positions table."""
469
+ if not self.broker:
470
+ return
471
+
472
+ positions = self.broker.get_positions()
473
+
474
+ self.positions_table.setRowCount(len(positions))
475
+
476
+ for row, (symbol, position) in enumerate(positions.items()):
477
+ self.positions_table.setItem(row, 0, QTableWidgetItem(symbol))
478
+ self.positions_table.setItem(row, 1, QTableWidgetItem("LONG" if position.side > 0 else "SHORT"))
479
+ self.positions_table.setItem(row, 2, QTableWidgetItem(f"{position.quantity:.6f}"))
480
+ self.positions_table.setItem(row, 3, QTableWidgetItem(f"{position.entry_price:.5f}"))
481
+ self.positions_table.setItem(row, 4, QTableWidgetItem(f"{position.current_price:.5f}"))
482
+ self.positions_table.setItem(row, 5, QTableWidgetItem(f"{position.unrealized_pnl:.2f}"))
483
+ self.positions_table.setItem(row, 6, QTableWidgetItem("Unknown"))
484
+
485
+ def update_metrics_table(self):
486
+ """Update the metrics table."""
487
+ if not self.portfolio:
488
+ return
489
+
490
+ metrics = self.portfolio.calculate_metrics()
491
+
492
+ metrics_data = [
493
+ ("Total Balance", f"${metrics.total_balance:.2f}"),
494
+ ("Total Equity", f"${metrics.total_equity:.2f}"),
495
+ ("Unrealized PnL", f"${metrics.unrealized_pnl:.2f}"),
496
+ ("Realized PnL", f"${metrics.realized_pnl:.2f}"),
497
+ ("Max Drawdown", f"{metrics.max_drawdown:.2f}%"),
498
+ ("Current Drawdown", f"{metrics.current_drawdown:.2f}%"),
499
+ ("Win Rate", f"{metrics.win_rate:.2f}%"),
500
+ ("Profit Factor", f"{metrics.profit_factor:.2f}"),
501
+ ("Total Trades", str(metrics.total_trades)),
502
+ ("Winning Trades", str(metrics.winning_trades)),
503
+ ("Losing Trades", str(metrics.losing_trades)),
504
+ ("Avg Win", f"${metrics.avg_win:.2f}"),
505
+ ("Avg Loss", f"${metrics.avg_loss:.2f}"),
506
+ ]
507
+
508
+ self.metrics_table.setRowCount(len(metrics_data))
509
+
510
+ for row, (metric, value) in enumerate(metrics_data):
511
+ self.metrics_table.setItem(row, 0, QTableWidgetItem(metric))
512
+ self.metrics_table.setItem(row, 1, QTableWidgetItem(value))
513
+
514
+ def show_settings(self):
515
+ """Show settings dialog."""
516
+ dialog = SettingsDialog(self.settings_manager, self)
517
+ if dialog.exec() == QDialog.DialogCode.Accepted:
518
+ self.load_settings()
519
+ # Recreate risk config with new settings
520
+ self.risk_config = self._create_risk_config()
521
+ self.risk_engine = RiskEngine(self.risk_config)
522
+
523
+ def export_trades(self):
524
+ """Export trades to CSV."""
525
+ try:
526
+ trades = self.db_manager.get_trades()
527
+ if not trades:
528
+ QMessageBox.information(self, "Export Trades", "No trades to export")
529
+ return
530
+
531
+ # Create CSV content
532
+ import csv
533
+ import io
534
+
535
+ output = io.StringIO()
536
+ writer = csv.writer(output)
537
+
538
+ # Write header
539
+ if trades:
540
+ writer.writerow(trades[0].keys())
541
+ for trade in trades:
542
+ writer.writerow(trade.values())
543
+
544
+ # Save to file
545
+ from PyQt6.QtWidgets import QFileDialog
546
+ filename, _ = QFileDialog.getSaveFileName(
547
+ self, "Export Trades", "trades.csv", "CSV Files (*.csv)"
548
+ )
549
+
550
+ if filename:
551
+ with open(filename, 'w', newline='') as f:
552
+ f.write(output.getvalue())
553
+ QMessageBox.information(self, "Export Trades", f"Trades exported to {filename}")
554
+
555
+ except Exception as e:
556
+ QMessageBox.critical(self, "Export Trades", f"Error exporting trades: {str(e)}")
557
+
558
+ def load_chart_data(self):
559
+ """Load initial chart data for display."""
560
+ try:
561
+ # Create data provider
562
+ data_provider = YFinanceProvider()
563
+
564
+ if data_provider.is_available():
565
+ # Get data for EURUSD
566
+ from datetime import timedelta
567
+ end_date = datetime.now().strftime('%Y-%m-%d')
568
+ start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
569
+
570
+ df = data_provider.get_data('EURUSD=X', start_date, end_date, '1h')
571
+
572
+ if not df.empty:
573
+ # Add to chart widget
574
+ self.chart_widget.set_data('EURUSD', df)
575
+ self.append_log(f"Chart data loaded: {df.shape[0]} rows for EURUSD")
576
+ else:
577
+ # Create sample data if no real data
578
+ self.create_sample_chart_data()
579
+ else:
580
+ # Create sample data if no internet
581
+ self.create_sample_chart_data()
582
+
583
+ except Exception as e:
584
+ self.append_log(f"Error loading chart data: {str(e)}")
585
+ self.create_sample_chart_data()
586
+
587
+ def create_sample_chart_data(self):
588
+ """Create sample chart data for demonstration."""
589
+ try:
590
+ import pandas as pd
591
+ import numpy as np
592
+
593
+ # Generate sample data
594
+ dates = pd.date_range(start='2024-01-01', end='2024-01-31', freq='1h')
595
+ np.random.seed(42)
596
+ base_price = 1.1800
597
+ price_changes = np.random.randn(len(dates)) * 0.001
598
+ prices = base_price + np.cumsum(price_changes)
599
+
600
+ sample_data = pd.DataFrame({
601
+ 'Open': prices,
602
+ 'High': prices + np.random.rand(len(dates)) * 0.0005,
603
+ 'Low': prices - np.random.rand(len(dates)) * 0.0005,
604
+ 'Close': prices + np.random.randn(len(dates)) * 0.0001,
605
+ 'Volume': np.random.randint(1000, 10000, len(dates))
606
+ }, index=dates)
607
+
608
+ # Add SMA indicators
609
+ sample_data['SMA_fast'] = sample_data['Close'].rolling(window=10).mean()
610
+ sample_data['SMA_slow'] = sample_data['Close'].rolling(window=20).mean()
611
+ sample_data['ATR'] = sample_data['High'].rolling(window=14).max() - sample_data['Low'].rolling(window=14).min()
612
+
613
+ # Add to chart widget
614
+ self.chart_widget.set_data('EURUSD', sample_data)
615
+ self.append_log(f"Sample chart data created: {sample_data.shape[0]} rows for EURUSD")
616
+
617
+ except Exception as e:
618
+ self.append_log(f"Error creating sample data: {str(e)}")
619
+
620
+ def update_chart_data(self, symbol: str):
621
+ """Update chart data for a specific symbol."""
622
+ try:
623
+ if self.data_provider and self.data_provider.is_available():
624
+ from datetime import timedelta
625
+ end_date = datetime.now().strftime('%Y-%m-%d')
626
+ start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
627
+
628
+ # Convert symbol format for Yahoo Finance
629
+ yahoo_symbol = f"{symbol}=X" if not symbol.endswith('=X') else symbol
630
+
631
+ df = self.data_provider.get_data(yahoo_symbol, start_date, end_date, '1h')
632
+
633
+ if not df.empty:
634
+ self.chart_widget.set_data(symbol, df)
635
+ self.append_log(f"Chart data updated: {df.shape[0]} rows for {symbol}")
636
+ else:
637
+ self.append_log(f"No data available for {symbol}")
638
+ else:
639
+ self.append_log("Data provider not available")
640
+
641
+ except Exception as e:
642
+ self.append_log(f"Error updating chart data: {str(e)}")
643
+
644
+ @property
645
+ def app(self):
646
+ """Get the QApplication instance."""
647
+ from PyQt6.QtWidgets import QApplication
648
+ return QApplication.instance()