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.
- forexsmartbot/__init__.py +1 -0
- forexsmartbot/adapters/__init__.py +4 -0
- forexsmartbot/adapters/brokers/__init__.py +8 -0
- forexsmartbot/adapters/brokers/ib_tws_broker.py +244 -0
- forexsmartbot/adapters/brokers/mt4_broker.py +266 -0
- forexsmartbot/adapters/brokers/paper_broker.py +131 -0
- forexsmartbot/adapters/brokers/rest_broker.py +304 -0
- forexsmartbot/adapters/data/__init__.py +27 -0
- forexsmartbot/adapters/data/alpha_vantage_provider.py +261 -0
- forexsmartbot/adapters/data/config.py +101 -0
- forexsmartbot/adapters/data/csv_provider.py +137 -0
- forexsmartbot/adapters/data/dummy_provider.py +35 -0
- forexsmartbot/adapters/data/mt4_provider.py +217 -0
- forexsmartbot/adapters/data/multi_provider.py +191 -0
- forexsmartbot/adapters/data/oanda_provider.py +173 -0
- forexsmartbot/adapters/data/stooq_provider.py +114 -0
- forexsmartbot/adapters/data/twelve_data_provider.py +117 -0
- forexsmartbot/adapters/data/yfinance_provider.py +246 -0
- forexsmartbot/analytics/__init__.py +28 -0
- forexsmartbot/analytics/chart_patterns.py +322 -0
- forexsmartbot/analytics/correlation_matrix.py +186 -0
- forexsmartbot/analytics/economic_calendar.py +616 -0
- forexsmartbot/analytics/enhanced_chart.py +57 -0
- forexsmartbot/analytics/market_depth.py +134 -0
- forexsmartbot/analytics/performance_attribution.py +210 -0
- forexsmartbot/analytics/portfolio_analytics.py +303 -0
- forexsmartbot/analytics/risk_analytics.py +308 -0
- forexsmartbot/analytics/strategy_comparison.py +194 -0
- forexsmartbot/analytics/trade_journal.py +235 -0
- forexsmartbot/brokers/mt4_bridge.py +49 -0
- forexsmartbot/builder/__init__.py +12 -0
- forexsmartbot/builder/code_generator.py +173 -0
- forexsmartbot/builder/strategy_builder.py +119 -0
- forexsmartbot/builder/strategy_template.py +138 -0
- forexsmartbot/cloud/__init__.py +24 -0
- forexsmartbot/cloud/api_client.py +188 -0
- forexsmartbot/cloud/api_server.py +366 -0
- forexsmartbot/cloud/cloud_sync.py +279 -0
- forexsmartbot/cloud/error_handler.py +150 -0
- forexsmartbot/cloud/mobile_api.py +213 -0
- forexsmartbot/cloud/remote_monitor.py +310 -0
- forexsmartbot/cloud/security.py +208 -0
- forexsmartbot/core/backtester.py +57 -0
- forexsmartbot/core/config.py +38 -0
- forexsmartbot/core/interfaces.py +253 -0
- forexsmartbot/core/paper_broker.py +28 -0
- forexsmartbot/core/portfolio.py +227 -0
- forexsmartbot/core/risk.py +24 -0
- forexsmartbot/core/risk_engine.py +203 -0
- forexsmartbot/core/strategy.py +44 -0
- forexsmartbot/core/trade_manager.py +261 -0
- forexsmartbot/gui/main_window.py +168 -0
- forexsmartbot/gui/theme.py +32 -0
- forexsmartbot/marketplace/__init__.py +10 -0
- forexsmartbot/marketplace/marketplace.py +178 -0
- forexsmartbot/marketplace/strategy_rating.py +64 -0
- forexsmartbot/monitoring/__init__.py +12 -0
- forexsmartbot/monitoring/health_check.py +109 -0
- forexsmartbot/monitoring/performance_tracker.py +213 -0
- forexsmartbot/monitoring/strategy_monitor.py +175 -0
- forexsmartbot/optimization/__init__.py +26 -0
- forexsmartbot/optimization/adaptive_parameters.py +224 -0
- forexsmartbot/optimization/genetic_optimizer.py +116 -0
- forexsmartbot/optimization/hyperparameter_optimizer.py +77 -0
- forexsmartbot/optimization/monte_carlo.py +114 -0
- forexsmartbot/optimization/multi_objective_optimizer.py +299 -0
- forexsmartbot/optimization/parameter_sensitivity.py +187 -0
- forexsmartbot/optimization/walk_forward.py +131 -0
- forexsmartbot/services/__init__.py +7 -0
- forexsmartbot/services/backtest.py +354 -0
- forexsmartbot/services/calculation_engine.py +99 -0
- forexsmartbot/services/controller.py +419 -0
- forexsmartbot/services/data_streaming.py +116 -0
- forexsmartbot/services/enhanced_backtest.py +390 -0
- forexsmartbot/services/fast_trading_controller.py +317 -0
- forexsmartbot/services/gpu_backtest.py +303 -0
- forexsmartbot/services/language_manager.py +621 -0
- forexsmartbot/services/notification_service.py +283 -0
- forexsmartbot/services/persistence.py +299 -0
- forexsmartbot/services/startup_manager.py +200 -0
- forexsmartbot/strategies/__init__.py +120 -0
- forexsmartbot/strategies/adaptive_trend_flow.py +311 -0
- forexsmartbot/strategies/breakout_atr.py +136 -0
- forexsmartbot/strategies/ensemble_ml_strategy.py +209 -0
- forexsmartbot/strategies/fear_index.py +174 -0
- forexsmartbot/strategies/lstm_strategy.py +269 -0
- forexsmartbot/strategies/mean_reversion.py +158 -0
- forexsmartbot/strategies/ml_adaptive_supertrend.py +272 -0
- forexsmartbot/strategies/momentum_breakout.py +183 -0
- forexsmartbot/strategies/multi_timeframe_strategy.py +186 -0
- forexsmartbot/strategies/news_trading.py +187 -0
- forexsmartbot/strategies/rl_strategy.py +277 -0
- forexsmartbot/strategies/rsi_reversion.py +162 -0
- forexsmartbot/strategies/scalping_ma.py +195 -0
- forexsmartbot/strategies/sma_crossover.py +118 -0
- forexsmartbot/strategies/svm_strategy.py +199 -0
- forexsmartbot/strategies/transformer_strategy.py +233 -0
- forexsmartbot/testing/__init__.py +14 -0
- forexsmartbot/testing/ci_cd_pipeline.py +286 -0
- forexsmartbot/testing/paper_account_tester.py +326 -0
- forexsmartbot/testing/strategy_sandbox.py +284 -0
- forexsmartbot/ui/__init__.py +8 -0
- forexsmartbot/ui/analytics_dialog.py +262 -0
- forexsmartbot/ui/analytics_widget.py +68 -0
- forexsmartbot/ui/backtest_dialog.py +908 -0
- forexsmartbot/ui/charts.py +172 -0
- forexsmartbot/ui/cloud_dialog.py +371 -0
- forexsmartbot/ui/enhanced_charts.py +491 -0
- forexsmartbot/ui/enhanced_main_window.py +2800 -0
- forexsmartbot/ui/export_dialog.py +333 -0
- forexsmartbot/ui/main_window.py +648 -0
- forexsmartbot/ui/marketplace_dialog.py +438 -0
- forexsmartbot/ui/monitoring_dialog.py +215 -0
- forexsmartbot/ui/monitoring_widget.py +80 -0
- forexsmartbot/ui/optimization_dialog.py +603 -0
- forexsmartbot/ui/settings_dialog.py +671 -0
- forexsmartbot/ui/simple_chart.py +309 -0
- forexsmartbot/ui/strategy_builder_dialog.py +595 -0
- forexsmartbot/ui/theme.py +109 -0
- forexsmartbot/ui/tradingview_chart.py +736 -0
- forexsmartbot/utils/__init__.py +42 -0
- forexsmartbot/utils/gpu_numpy.py +194 -0
- forexsmartbot/utils/gpu_utils.py +260 -0
- forexsmartbot-3.2.0.dist-info/METADATA +177 -0
- forexsmartbot-3.2.0.dist-info/RECORD +129 -0
- forexsmartbot-3.2.0.dist-info/WHEEL +5 -0
- forexsmartbot-3.2.0.dist-info/entry_points.txt +2 -0
- forexsmartbot-3.2.0.dist-info/licenses/LICENSE +150 -0
- 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()
|