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 @@
1
+ # ForexSmartBot Package
@@ -0,0 +1,4 @@
1
+ """Adapters for external systems."""
2
+
3
+ from .brokers import *
4
+ from .data import *
@@ -0,0 +1,8 @@
1
+ """Broker adapters."""
2
+
3
+ from .paper_broker import PaperBroker
4
+ from .mt4_broker import MT4Broker
5
+ from .rest_broker import RestBroker
6
+ from .ib_tws_broker import IBTWSBroker
7
+
8
+ __all__ = ['PaperBroker', 'MT4Broker', 'RestBroker', 'IBTWSBroker']
@@ -0,0 +1,244 @@
1
+ """Interactive Brokers TWS broker adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Dict, Optional, Tuple
6
+
7
+ from ...core.interfaces import IBroker, Position
8
+
9
+
10
+ class IBTWSBroker(IBroker):
11
+ """Interactive Brokers broker via TWS / IB Gateway using ib_insync."""
12
+
13
+ def __init__(
14
+ self,
15
+ host: str = "127.0.0.1",
16
+ port: int = 7497,
17
+ client_id: int = 1,
18
+ account_id: str = "",
19
+ ):
20
+ self._host = host
21
+ self._port = int(port)
22
+ self._client_id = int(client_id)
23
+ self._account_id = account_id
24
+ self._connected = False
25
+ self._contracts: Dict[str, object] = {}
26
+ self._orders: Dict[str, Tuple[str, float]] = {}
27
+ self._ib = None
28
+
29
+ try:
30
+ from ib_insync import IB
31
+
32
+ self._ib = IB()
33
+ except Exception:
34
+ self._ib = None
35
+
36
+ def connect(self) -> bool:
37
+ if self._ib is None:
38
+ print("IBTWSBroker: ib_insync is not installed. Install with: pip install ib_insync")
39
+ return False
40
+ try:
41
+ self._ib.connect(self._host, self._port, clientId=self._client_id, timeout=10)
42
+ self._connected = bool(self._ib.isConnected())
43
+ return self._connected
44
+ except Exception as e:
45
+ print(f"IBTWSBroker: Connection failed: {e}")
46
+ self._connected = False
47
+ return False
48
+
49
+ def disconnect(self) -> None:
50
+ try:
51
+ if self._ib is not None and self._ib.isConnected():
52
+ self._ib.disconnect()
53
+ finally:
54
+ self._connected = False
55
+
56
+ def is_connected(self) -> bool:
57
+ return self._connected and self._ib is not None and self._ib.isConnected()
58
+
59
+ def _get_contract(self, symbol: str):
60
+ if symbol in self._contracts:
61
+ return self._contracts[symbol]
62
+
63
+ from ib_insync import Forex
64
+
65
+ normalized = symbol.replace("=", "").replace("/", "").upper()
66
+ if normalized.endswith("X"):
67
+ normalized = normalized[:-1]
68
+ if len(normalized) != 6:
69
+ raise ValueError(f"Unsupported symbol format for IB Forex: {symbol}")
70
+
71
+ contract = Forex(normalized)
72
+ self._ib.qualifyContracts(contract)
73
+ self._contracts[symbol] = contract
74
+ return contract
75
+
76
+ def get_price(self, symbol: str) -> Optional[float]:
77
+ if not self.is_connected():
78
+ return None
79
+ try:
80
+ contract = self._get_contract(symbol)
81
+ ticker = self._ib.reqMktData(contract, "", False, False)
82
+ self._ib.sleep(0.5)
83
+
84
+ if ticker and ticker.last and ticker.last > 0:
85
+ return float(ticker.last)
86
+ if ticker and ticker.bid and ticker.ask and ticker.bid > 0 and ticker.ask > 0:
87
+ return float((ticker.bid + ticker.ask) / 2.0)
88
+ if ticker and ticker.close and ticker.close > 0:
89
+ return float(ticker.close)
90
+ return None
91
+ except Exception as e:
92
+ print(f"IBTWSBroker: get_price failed for {symbol}: {e}")
93
+ return None
94
+
95
+ def submit_order(
96
+ self,
97
+ symbol: str,
98
+ side: int,
99
+ quantity: float,
100
+ stop_loss: Optional[float] = None,
101
+ take_profit: Optional[float] = None,
102
+ ) -> Optional[str]:
103
+ if not self.is_connected():
104
+ return None
105
+ try:
106
+ from ib_insync import LimitOrder, MarketOrder, StopOrder
107
+
108
+ contract = self._get_contract(symbol)
109
+ action = "BUY" if side > 0 else "SELL"
110
+ exit_action = "SELL" if action == "BUY" else "BUY"
111
+ order_qty = float(abs(quantity))
112
+ if order_qty <= 0:
113
+ return None
114
+
115
+ order = MarketOrder(action, order_qty, transmit=True)
116
+ if self._account_id:
117
+ order.account = self._account_id
118
+
119
+ # If SL/TP are provided, place as linked child orders for risk control.
120
+ if stop_loss is not None or take_profit is not None:
121
+ order.transmit = False
122
+
123
+ trade = self._ib.placeOrder(contract, order)
124
+ self._ib.sleep(0.5)
125
+
126
+ order_id = str(trade.order.orderId)
127
+ self._orders[order_id] = (symbol, order_qty)
128
+
129
+ if stop_loss is not None:
130
+ sl_order = StopOrder(
131
+ action=exit_action,
132
+ totalQuantity=order_qty,
133
+ stopPrice=float(stop_loss),
134
+ parentId=trade.order.orderId,
135
+ transmit=(take_profit is None),
136
+ )
137
+ if self._account_id:
138
+ sl_order.account = self._account_id
139
+ self._ib.placeOrder(contract, sl_order)
140
+
141
+ if take_profit is not None:
142
+ tp_order = LimitOrder(
143
+ action=exit_action,
144
+ totalQuantity=order_qty,
145
+ lmtPrice=float(take_profit),
146
+ parentId=trade.order.orderId,
147
+ transmit=True,
148
+ )
149
+ if self._account_id:
150
+ tp_order.account = self._account_id
151
+ self._ib.placeOrder(contract, tp_order)
152
+
153
+ self._ib.sleep(0.3)
154
+ return order_id
155
+ except Exception as e:
156
+ print(f"IBTWSBroker: submit_order failed for {symbol}: {e}")
157
+ return None
158
+
159
+ def close_all(self, symbol: str) -> bool:
160
+ if not self.is_connected():
161
+ return False
162
+ try:
163
+ from ib_insync import MarketOrder
164
+
165
+ closed_any = False
166
+ target = symbol.replace("=", "").replace("/", "").upper()
167
+ if target.endswith("X"):
168
+ target = target[:-1]
169
+
170
+ for pos in self._ib.positions():
171
+ local_symbol = (pos.contract.localSymbol or "").replace(".", "").upper()
172
+ con_symbol = f"{pos.contract.symbol}{getattr(pos.contract, 'currency', '')}".upper()
173
+ if target not in (local_symbol, con_symbol):
174
+ continue
175
+
176
+ qty = float(pos.position)
177
+ if qty == 0:
178
+ continue
179
+ action = "SELL" if qty > 0 else "BUY"
180
+ order = MarketOrder(action, abs(qty))
181
+ if self._account_id:
182
+ order.account = self._account_id
183
+ self._ib.placeOrder(pos.contract, order)
184
+ closed_any = True
185
+
186
+ if closed_any:
187
+ self._ib.sleep(0.5)
188
+ return closed_any
189
+ except Exception as e:
190
+ print(f"IBTWSBroker: close_all failed for {symbol}: {e}")
191
+ return False
192
+
193
+ def get_positions(self) -> Dict[str, Position]:
194
+ positions: Dict[str, Position] = {}
195
+ if not self.is_connected():
196
+ return positions
197
+
198
+ try:
199
+ for pos in self._ib.positions():
200
+ qty = float(pos.position)
201
+ if qty == 0:
202
+ continue
203
+
204
+ symbol = f"{pos.contract.symbol}{getattr(pos.contract, 'currency', '')}"
205
+ market_price = self.get_price(symbol) or float(pos.avgCost)
206
+ side = 1 if qty > 0 else -1
207
+ unrealized = side * abs(qty) * (market_price - float(pos.avgCost))
208
+
209
+ positions[symbol] = Position(
210
+ symbol=symbol,
211
+ side=side,
212
+ quantity=abs(qty),
213
+ entry_price=float(pos.avgCost),
214
+ current_price=float(market_price),
215
+ unrealized_pnl=float(unrealized),
216
+ )
217
+ except Exception as e:
218
+ print(f"IBTWSBroker: get_positions failed: {e}")
219
+
220
+ return positions
221
+
222
+ def get_balance(self) -> float:
223
+ if not self.is_connected():
224
+ return 0.0
225
+ try:
226
+ values = self._ib.accountSummary()
227
+ for row in values:
228
+ if row.tag == "TotalCashValue" and (not self._account_id or row.account == self._account_id):
229
+ return float(row.value)
230
+ except Exception as e:
231
+ print(f"IBTWSBroker: get_balance failed: {e}")
232
+ return 0.0
233
+
234
+ def get_equity(self) -> float:
235
+ if not self.is_connected():
236
+ return 0.0
237
+ try:
238
+ values = self._ib.accountSummary()
239
+ for row in values:
240
+ if row.tag == "NetLiquidation" and (not self._account_id or row.account == self._account_id):
241
+ return float(row.value)
242
+ except Exception as e:
243
+ print(f"IBTWSBroker: get_equity failed: {e}")
244
+ return 0.0
@@ -0,0 +1,266 @@
1
+ """MetaTrader 4 broker implementation via file communication."""
2
+
3
+ import json
4
+ import time
5
+ import os
6
+ import subprocess
7
+ from typing import Dict, Optional
8
+ from datetime import datetime
9
+ from ...core.interfaces import IBroker, Position, Order
10
+
11
+
12
+ class MT4Broker(IBroker):
13
+ """MT4 broker implementation using file communication."""
14
+
15
+ def __init__(self, host: str = "127.0.0.1", port: int = 5555):
16
+ self._host = host
17
+ self._port = port
18
+ self._connected = False
19
+ self._files_dir = self._find_mt4_files_dir()
20
+
21
+ def _find_mt4_files_dir(self) -> str:
22
+ """Find MT4 Files directory."""
23
+ # First try to find the terminal-specific directory
24
+ terminal_dirs = []
25
+ base_path = os.path.join(os.getenv('APPDATA'), 'MetaQuotes', 'Terminal')
26
+ if os.path.exists(base_path):
27
+ for item in os.listdir(base_path):
28
+ if os.path.isdir(os.path.join(base_path, item)) and item != 'MQL4':
29
+ mql4_path = os.path.join(base_path, item, 'MQL4', 'Files')
30
+ if os.path.exists(mql4_path):
31
+ terminal_dirs.append(mql4_path)
32
+
33
+ # Use the exact terminal-specific directory that MT4 EA is looking in
34
+ terminal_specific_path = os.path.join(os.getenv('APPDATA'), 'MetaQuotes', 'Terminal', '0727F3F88B5F0FE006962B330B91FF37', 'MQL4', 'Files')
35
+
36
+ # Add other possible paths
37
+ possible_paths = [
38
+ terminal_specific_path,
39
+ os.path.join(os.getenv('APPDATA'), 'MetaQuotes', 'Terminal', 'MQL4', 'Files'),
40
+ ] + terminal_dirs + [
41
+ os.path.join(os.getenv('APPDATA'), 'MetaQuotes', 'Terminal', 'Files'),
42
+ os.path.join(os.getenv('USERPROFILE'), 'AppData', 'Roaming', 'MetaQuotes', 'Terminal', 'MQL4', 'Files')
43
+ ]
44
+
45
+ for path in possible_paths:
46
+ if os.path.exists(path):
47
+ print(f"MT4Broker: Found MT4 Files directory: {path}")
48
+ return path
49
+
50
+ # Create default directory if none exists
51
+ default_path = os.path.join(os.getenv('APPDATA'), 'MetaQuotes', 'Terminal', 'MQL4', 'Files')
52
+ os.makedirs(default_path, exist_ok=True)
53
+ print(f"MT4Broker: Created default MT4 Files directory: {default_path}")
54
+ return default_path
55
+
56
+ def connect(self) -> bool:
57
+ """Connect to MT4."""
58
+ try:
59
+ # Check if MT4 is running (check both terminal.exe and terminal64.exe)
60
+ # Use errors='ignore' to handle any encoding issues with subprocess output
61
+ result = subprocess.run(['tasklist', '/FI', 'IMAGENAME eq terminal.exe'],
62
+ capture_output=True, text=True, encoding='utf-8', errors='ignore')
63
+ result64 = subprocess.run(['tasklist', '/FI', 'IMAGENAME eq terminal64.exe'],
64
+ capture_output=True, text=True, encoding='utf-8', errors='ignore')
65
+
66
+ if 'terminal.exe' in result.stdout or 'terminal64.exe' in result64.stdout:
67
+ self._connected = True
68
+ print(f"MT4Broker: Connected to running MT4 instance")
69
+ print(f"MT4Broker: Using files directory: {self._files_dir}")
70
+ return True
71
+ else:
72
+ print("MT4Broker: MT4 not running - please start MT4 first")
73
+ self._connected = False
74
+ return False
75
+ except Exception as e:
76
+ print(f"MT4Broker: Connection error: {e}")
77
+ self._connected = False
78
+ return False
79
+
80
+ def disconnect(self) -> None:
81
+ """Disconnect from MT4."""
82
+ self._connected = False
83
+
84
+ def is_connected(self) -> bool:
85
+ """Check if connected to MT4."""
86
+ return self._connected
87
+
88
+ def _execute_command(self, command: str) -> bool:
89
+ """Execute command via file communication with MT4 EA."""
90
+ try:
91
+ command_file = os.path.join(self._files_dir, 'ForexSmartBot_Command.txt')
92
+
93
+ # Wait for EA to finish processing any previous command
94
+ max_wait = 10 # Wait up to 10 seconds
95
+ wait_count = 0
96
+ while os.path.exists(command_file) and wait_count < max_wait:
97
+ time.sleep(0.5)
98
+ wait_count += 1
99
+
100
+ with open(command_file, 'w') as f:
101
+ f.write(command)
102
+
103
+ # Wait for EA to process the command
104
+ time.sleep(2.0) # Give EA time to process
105
+
106
+ print(f"MT4Broker: Command written to file: {command}")
107
+ return True
108
+
109
+ except Exception as e:
110
+ print(f"MT4Broker: Error writing command file: {e}")
111
+ return False
112
+
113
+ def get_price(self, symbol: str) -> Optional[float]:
114
+ """Get current price from MT4."""
115
+ if not self._connected:
116
+ print("MT4Broker: Not connected to MT4")
117
+ return None
118
+
119
+ try:
120
+ # Request price from MT4
121
+ self._execute_command(f"GetPrice({symbol})")
122
+
123
+ # Wait for EA to process
124
+ time.sleep(1.0)
125
+
126
+ # Read price from response file
127
+ response_file = os.path.join(self._files_dir, 'ForexSmartBot_Price.txt')
128
+ if os.path.exists(response_file):
129
+ with open(response_file, 'r') as f:
130
+ content = f.read().strip()
131
+ if content:
132
+ price = float(content)
133
+ print(f"MT4Broker: Got real price from MT4 for {symbol}: {price}")
134
+ return price
135
+ except Exception as e:
136
+ print(f"MT4Broker: Error reading price: {e}")
137
+
138
+ print(f"MT4Broker: Using fallback price for {symbol}")
139
+ return 1.0700 if symbol == "EURUSD" else 1.2500
140
+
141
+ def submit_order(self, symbol: str, side: int, quantity: float,
142
+ stop_loss: Optional[float] = None,
143
+ take_profit: Optional[float] = None) -> Optional[str]:
144
+ """Submit order to MT4."""
145
+ if not self._connected:
146
+ print("MT4Broker: Not connected to MT4")
147
+ return None
148
+
149
+ try:
150
+ # Create order command for MT4
151
+ order_type = "BUY" if side > 0 else "SELL"
152
+ sl_str = f" SL={stop_loss}" if stop_loss else ""
153
+ tp_str = f" TP={take_profit}" if take_profit else ""
154
+
155
+ # Use a simpler command format that the EA can detect
156
+ command = f"OrderSend({symbol},{order_type},{quantity}{sl_str}{tp_str})"
157
+ print(f"MT4Broker: Command format: {command}")
158
+ print(f"MT4Broker: Command length: {len(command)}")
159
+
160
+ print(f"MT4Broker: Executing real MT4 order: {command}")
161
+
162
+ # Execute the order in MT4
163
+ if self._execute_command(command):
164
+ order_id = f"MT4_{int(time.time() * 1000)}"
165
+ print(f"MT4Broker: Order submitted successfully: {order_id}")
166
+ return order_id
167
+ else:
168
+ print("MT4Broker: Failed to submit order")
169
+ return None
170
+
171
+ except Exception as e:
172
+ print(f"MT4Broker: Error submitting order: {e}")
173
+ return None
174
+
175
+ def close_all(self, symbol: str) -> bool:
176
+ """Close all positions for symbol."""
177
+ if not self._connected:
178
+ print("MT4Broker: Not connected to MT4")
179
+ return False
180
+
181
+ try:
182
+ command = f"CloseAll({symbol})"
183
+ print(f"MT4Broker: Closing all positions for {symbol}")
184
+
185
+ if self._execute_command(command):
186
+ print(f"MT4Broker: Successfully closed all positions for {symbol}")
187
+ return True
188
+ else:
189
+ print(f"MT4Broker: Failed to close positions for {symbol}")
190
+ return False
191
+
192
+ except Exception as e:
193
+ print(f"MT4Broker: Error closing positions: {e}")
194
+ return False
195
+
196
+ def get_positions(self) -> Dict[str, Position]:
197
+ """Get positions from MT4."""
198
+ positions = {}
199
+
200
+ if not self._connected:
201
+ print("MT4Broker: Not connected to MT4")
202
+ return positions
203
+
204
+ try:
205
+ # Request positions from MT4
206
+ self._execute_command("GetPositions()")
207
+
208
+ # Wait for EA to process
209
+ time.sleep(1.0)
210
+
211
+ # Read positions from MT4 response file
212
+ response_file = os.path.join(self._files_dir, 'ForexSmartBot_Positions.txt')
213
+ if os.path.exists(response_file):
214
+ with open(response_file, 'r') as f:
215
+ content = f.read().strip()
216
+ if content:
217
+ import json
218
+ positions_data = json.loads(content)
219
+ for pos_data in positions_data:
220
+ symbol = pos_data['symbol']
221
+ positions[symbol] = Position(
222
+ symbol=symbol,
223
+ side=pos_data['side'],
224
+ quantity=pos_data['volume'],
225
+ entry_price=pos_data['entry_price'],
226
+ current_price=pos_data['current_price'],
227
+ stop_loss=pos_data.get('sl', 0),
228
+ take_profit=pos_data.get('tp', 0),
229
+ pnl=pos_data.get('profit', 0)
230
+ )
231
+ print(f"MT4Broker: Retrieved {len(positions)} positions from MT4")
232
+ except Exception as e:
233
+ print(f"MT4Broker: Error reading positions: {e}")
234
+
235
+ return positions
236
+
237
+ def get_balance(self) -> float:
238
+ """Get account balance from MT4."""
239
+ try:
240
+ # Request balance from MT4
241
+ self._execute_command("GetBalance()")
242
+
243
+ # Wait a moment for the EA to process
244
+ import time
245
+ time.sleep(0.5)
246
+
247
+ # Read balance from response file
248
+ response_file = os.path.join(self._files_dir, 'ForexSmartBot_Balance.txt')
249
+ if os.path.exists(response_file):
250
+ with open(response_file, 'r') as f:
251
+ content = f.read().strip()
252
+ if content:
253
+ balance = float(content)
254
+ print(f"MT4Broker: Got real balance from MT4: ${balance}")
255
+ return balance
256
+ except Exception as e:
257
+ print(f"MT4Broker: Error reading balance: {e}")
258
+
259
+ print("MT4Broker: Using fallback balance - EA not responding")
260
+ return 10000.0 # Fallback
261
+
262
+ def get_equity(self) -> float:
263
+ """Get account equity from MT4."""
264
+ # For now, return a simulated equity
265
+ # In production, this would read from MT4's account data
266
+ return 10000.0
@@ -0,0 +1,131 @@
1
+ """Paper trading broker implementation."""
2
+
3
+ from typing import Dict, Optional
4
+ from datetime import datetime
5
+ from ...core.interfaces import IBroker, Position, Order
6
+
7
+
8
+ class PaperBroker(IBroker):
9
+ """Paper trading broker for backtesting and simulation."""
10
+
11
+ def __init__(self, initial_balance: float = 10000.0):
12
+ self._balance = initial_balance
13
+ self._positions: Dict[str, Position] = {}
14
+ self._orders: Dict[str, Order] = {}
15
+ self._connected = False
16
+ self._order_counter = 0
17
+
18
+ def connect(self) -> bool:
19
+ """Connect to paper broker (always successful)."""
20
+ self._connected = True
21
+ return True
22
+
23
+ def disconnect(self) -> None:
24
+ """Disconnect from paper broker."""
25
+ self._connected = False
26
+
27
+ def is_connected(self) -> bool:
28
+ """Check if connected."""
29
+ return self._connected
30
+
31
+ def get_price(self, symbol: str) -> Optional[float]:
32
+ """Get current price with realistic simulation."""
33
+ # Get base price for symbol
34
+ base_prices = {
35
+ 'EURUSD': 1.1800,
36
+ 'GBPUSD': 1.2500,
37
+ 'USDJPY': 150.0,
38
+ 'AUDUSD': 0.7500,
39
+ 'USDCAD': 1.3500
40
+ }
41
+
42
+ base_price = base_prices.get(symbol, 1.0000)
43
+
44
+ # Add realistic price movement
45
+ import random
46
+ import time
47
+
48
+ # Use time-based seed for consistent but changing prices
49
+ random.seed(int(time.time() / 60)) # Change every minute
50
+ price_change = random.uniform(-0.001, 0.001) # Small random change
51
+
52
+ return base_price + price_change
53
+
54
+ def submit_order(self, symbol: str, side: int, quantity: float,
55
+ stop_loss: Optional[float] = None,
56
+ take_profit: Optional[float] = None) -> Optional[str]:
57
+ """Submit a paper order."""
58
+ if not self._connected:
59
+ return None
60
+
61
+ order_id = f"PAPER_{self._order_counter}"
62
+ self._order_counter += 1
63
+
64
+ # Create order
65
+ order = Order(
66
+ symbol=symbol,
67
+ side=side,
68
+ quantity=quantity,
69
+ entry_price=0.0, # Will be filled with current price
70
+ stop_loss=stop_loss,
71
+ take_profit=take_profit
72
+ )
73
+
74
+ self._orders[order_id] = order
75
+ return order_id
76
+
77
+ def close_all(self, symbol: str) -> bool:
78
+ """Close all positions for symbol."""
79
+ if symbol in self._positions:
80
+ del self._positions[symbol]
81
+ return True
82
+ return False
83
+
84
+ def get_positions(self) -> Dict[str, Position]:
85
+ """Get all open positions."""
86
+ return self._positions.copy()
87
+
88
+ def get_balance(self) -> float:
89
+ """Get account balance."""
90
+ return self._balance
91
+
92
+ def get_equity(self) -> float:
93
+ """Get account equity."""
94
+ unrealized_pnl = sum(pos.unrealized_pnl for pos in self._positions.values())
95
+ return self._balance + unrealized_pnl
96
+
97
+ def update_position(self, symbol: str, side: int, quantity: float,
98
+ entry_price: float, current_price: float) -> None:
99
+ """Update or create a position."""
100
+ unrealized_pnl = side * quantity * (current_price - entry_price)
101
+
102
+ position = Position(
103
+ symbol=symbol,
104
+ side=side,
105
+ quantity=quantity,
106
+ entry_price=entry_price,
107
+ current_price=current_price,
108
+ unrealized_pnl=unrealized_pnl
109
+ )
110
+
111
+ self._positions[symbol] = position
112
+
113
+ def close_position(self, symbol: str, exit_price: float) -> Optional[float]:
114
+ """Close a position and return realized PnL."""
115
+ if symbol not in self._positions:
116
+ return None
117
+
118
+ position = self._positions.pop(symbol)
119
+ realized_pnl = position.side * position.quantity * (exit_price - position.entry_price)
120
+ self._balance += realized_pnl
121
+
122
+ return realized_pnl
123
+
124
+ def update_prices(self, prices: Dict[str, float]) -> None:
125
+ """Update position prices and calculate unrealized PnL."""
126
+ for symbol, position in self._positions.items():
127
+ if symbol in prices:
128
+ position.current_price = prices[symbol]
129
+ position.unrealized_pnl = position.side * position.quantity * (
130
+ position.current_price - position.entry_price
131
+ )