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,326 @@
1
+ """Real-time strategy testing on paper account."""
2
+
3
+ import pandas as pd
4
+ import numpy as np
5
+ from typing import Dict, Any, Optional, Callable, List
6
+ from datetime import datetime, timedelta
7
+ from dataclasses import dataclass
8
+
9
+ from ..core.interfaces import IStrategy, IDataProvider
10
+ from ..adapters.brokers.paper_broker import PaperBroker
11
+ from ..core.portfolio import Portfolio
12
+ from ..core.risk_engine import RiskEngine, RiskConfig
13
+
14
+
15
+ @dataclass
16
+ class PaperTestResult:
17
+ """Result of paper account test."""
18
+ strategy_name: str
19
+ test_duration: timedelta
20
+ total_trades: int
21
+ winning_trades: int
22
+ losing_trades: int
23
+ total_return: float
24
+ sharpe_ratio: float
25
+ max_drawdown: float
26
+ win_rate: float
27
+ final_balance: float
28
+ trades: List[Dict[str, Any]]
29
+
30
+
31
+ class PaperAccountTester:
32
+ """Test strategies in real-time on paper account."""
33
+
34
+ def __init__(self, data_provider: IDataProvider,
35
+ initial_balance: float = 10000.0,
36
+ risk_config: Optional[RiskConfig] = None):
37
+ """
38
+ Initialize paper account tester.
39
+
40
+ Args:
41
+ data_provider: Data provider for market data
42
+ initial_balance: Initial paper account balance
43
+ risk_config: Risk management configuration
44
+ """
45
+ self.data_provider = data_provider
46
+ self.initial_balance = initial_balance
47
+ self.risk_config = risk_config or RiskConfig()
48
+ self.broker = PaperBroker(initial_balance)
49
+ self.portfolio = Portfolio(initial_balance)
50
+ self.risk_engine = RiskEngine(self.risk_config)
51
+ self.active_tests: Dict[str, Dict[str, Any]] = {}
52
+
53
+ def start_test(self, strategy_name: str, strategy: IStrategy,
54
+ symbol: str, update_interval: int = 60) -> str:
55
+ """
56
+ Start a paper account test for a strategy.
57
+
58
+ Args:
59
+ strategy_name: Name of the strategy
60
+ strategy: Strategy instance
61
+ symbol: Trading symbol
62
+ update_interval: Update interval in seconds
63
+
64
+ Returns:
65
+ Test ID
66
+ """
67
+ import uuid
68
+ test_id = str(uuid.uuid4())
69
+
70
+ self.active_tests[test_id] = {
71
+ 'strategy_name': strategy_name,
72
+ 'strategy': strategy,
73
+ 'symbol': symbol,
74
+ 'start_time': datetime.now(),
75
+ 'update_interval': update_interval,
76
+ 'trades': [],
77
+ 'equity_history': [],
78
+ 'last_update': datetime.now()
79
+ }
80
+
81
+ return test_id
82
+
83
+ def update_test(self, test_id: str) -> Dict[str, Any]:
84
+ """
85
+ Update a paper account test with latest data.
86
+
87
+ Args:
88
+ test_id: Test ID
89
+
90
+ Returns:
91
+ Update result
92
+ """
93
+ if test_id not in self.active_tests:
94
+ return {'error': 'Test not found'}
95
+
96
+ test = self.active_tests[test_id]
97
+ strategy = test['strategy']
98
+ symbol = test['symbol']
99
+
100
+ # Get latest data
101
+ end_date = datetime.now().strftime('%Y-%m-%d')
102
+ start_date = (datetime.now() - timedelta(days=30)).strftime('%Y-%m-%d')
103
+
104
+ try:
105
+ df = self.data_provider.get_data(symbol, start_date, end_date, '1h')
106
+ if df.empty:
107
+ return {'error': 'No data available'}
108
+
109
+ # Calculate indicators
110
+ df = strategy.indicators(df)
111
+
112
+ # Generate signal
113
+ signal = strategy.signal(df)
114
+ volatility = strategy.volatility(df)
115
+ current_price = float(df['Close'].iloc[-1])
116
+
117
+ # Process trading logic
118
+ if signal != 0:
119
+ self._process_signal(test_id, strategy, symbol, signal, current_price, volatility, df)
120
+ else:
121
+ self._check_exit_conditions(test_id, symbol, current_price, strategy, df)
122
+
123
+ # Update portfolio
124
+ self.portfolio.update_equity(self.broker.get_balance())
125
+
126
+ # Record equity
127
+ test['equity_history'].append({
128
+ 'timestamp': datetime.now(),
129
+ 'equity': self.portfolio.get_total_equity()
130
+ })
131
+
132
+ test['last_update'] = datetime.now()
133
+
134
+ return {
135
+ 'test_id': test_id,
136
+ 'current_equity': self.portfolio.get_total_equity(),
137
+ 'total_return': (self.portfolio.get_total_equity() / self.initial_balance) - 1,
138
+ 'total_trades': len(test['trades']),
139
+ 'signal': signal
140
+ }
141
+
142
+ except Exception as e:
143
+ return {'error': str(e)}
144
+
145
+ def _process_signal(self, test_id: str, strategy: IStrategy, symbol: str,
146
+ signal: int, current_price: float, volatility: Optional[float],
147
+ df: pd.DataFrame) -> None:
148
+ """Process trading signal."""
149
+ test = self.active_tests[test_id]
150
+ positions = test.get('positions', {})
151
+
152
+ # Close opposite position
153
+ if symbol in positions:
154
+ pos = positions[symbol]
155
+ if (signal > 0 and pos['side'] < 0) or (signal < 0 and pos['side'] > 0):
156
+ self.broker.close_position(symbol, current_price)
157
+ profit = (current_price - pos['entry_price']) * pos['side'] * pos['size']
158
+ test['trades'].append({
159
+ 'symbol': symbol,
160
+ 'side': pos['side'],
161
+ 'entry_price': pos['entry_price'],
162
+ 'exit_price': current_price,
163
+ 'size': pos['size'],
164
+ 'profit': profit,
165
+ 'entry_time': pos['entry_time'],
166
+ 'exit_time': datetime.now()
167
+ })
168
+ del positions[symbol]
169
+
170
+ # Open new position
171
+ if symbol not in positions:
172
+ size = self.risk_engine.calculate_position_size(
173
+ self.portfolio.get_total_balance(), volatility or 0.01, signal
174
+ )
175
+
176
+ if size > 0:
177
+ sl = strategy.stop_loss(df, current_price, signal)
178
+ tp = strategy.take_profit(df, current_price, signal)
179
+
180
+ self.broker.open_position(symbol, signal, size, current_price, sl, tp)
181
+ positions[symbol] = {
182
+ 'side': signal,
183
+ 'entry_price': current_price,
184
+ 'size': size,
185
+ 'entry_time': datetime.now(),
186
+ 'stop_loss': sl,
187
+ 'take_profit': tp
188
+ }
189
+
190
+ test['positions'] = positions
191
+
192
+ def _check_exit_conditions(self, test_id: str, symbol: str, current_price: float,
193
+ strategy: IStrategy, df: pd.DataFrame) -> None:
194
+ """Check exit conditions for open positions."""
195
+ test = self.active_tests[test_id]
196
+ positions = test.get('positions', {})
197
+
198
+ if symbol not in positions:
199
+ return
200
+
201
+ pos = positions[symbol]
202
+
203
+ # Check stop loss
204
+ if pos.get('stop_loss') and (
205
+ (pos['side'] > 0 and current_price <= pos['stop_loss']) or
206
+ (pos['side'] < 0 and current_price >= pos['stop_loss'])
207
+ ):
208
+ self.broker.close_position(symbol, current_price)
209
+ profit = (current_price - pos['entry_price']) * pos['side'] * pos['size']
210
+ test['trades'].append({
211
+ 'symbol': symbol,
212
+ 'side': pos['side'],
213
+ 'entry_price': pos['entry_price'],
214
+ 'exit_price': current_price,
215
+ 'size': pos['size'],
216
+ 'profit': profit,
217
+ 'entry_time': pos['entry_time'],
218
+ 'exit_time': datetime.now(),
219
+ 'exit_reason': 'stop_loss'
220
+ })
221
+ del positions[symbol]
222
+
223
+ # Check take profit
224
+ if pos.get('take_profit') and (
225
+ (pos['side'] > 0 and current_price >= pos['take_profit']) or
226
+ (pos['side'] < 0 and current_price <= pos['take_profit'])
227
+ ):
228
+ self.broker.close_position(symbol, current_price)
229
+ profit = (current_price - pos['entry_price']) * pos['side'] * pos['size']
230
+ test['trades'].append({
231
+ 'symbol': symbol,
232
+ 'side': pos['side'],
233
+ 'entry_price': pos['entry_price'],
234
+ 'exit_price': current_price,
235
+ 'size': pos['size'],
236
+ 'profit': profit,
237
+ 'entry_time': pos['entry_time'],
238
+ 'exit_time': datetime.now(),
239
+ 'exit_reason': 'take_profit'
240
+ })
241
+ del positions[symbol]
242
+
243
+ test['positions'] = positions
244
+
245
+ def stop_test(self, test_id: str) -> PaperTestResult:
246
+ """
247
+ Stop a paper account test and get results.
248
+
249
+ Args:
250
+ test_id: Test ID
251
+
252
+ Returns:
253
+ PaperTestResult
254
+ """
255
+ if test_id not in self.active_tests:
256
+ raise ValueError(f"Test {test_id} not found")
257
+
258
+ test = self.active_tests[test_id]
259
+ test_duration = datetime.now() - test['start_time']
260
+
261
+ trades = test['trades']
262
+ winning_trades = [t for t in trades if t.get('profit', 0) > 0]
263
+ losing_trades = [t for t in trades if t.get('profit', 0) < 0]
264
+
265
+ # Calculate metrics
266
+ returns = [t.get('profit', 0) / self.initial_balance for t in trades]
267
+ total_return = sum(returns)
268
+
269
+ if len(returns) > 1:
270
+ sharpe_ratio = np.sqrt(252) * np.mean(returns) / np.std(returns) if np.std(returns) > 0 else 0
271
+ else:
272
+ sharpe_ratio = 0
273
+
274
+ # Calculate max drawdown from equity history
275
+ equity_values = [e['equity'] for e in test['equity_history']]
276
+ if equity_values:
277
+ equity_series = pd.Series(equity_values)
278
+ running_max = equity_series.expanding().max()
279
+ drawdown = (equity_series - running_max) / running_max
280
+ max_drawdown = abs(drawdown.min())
281
+ else:
282
+ max_drawdown = 0.0
283
+
284
+ win_rate = len(winning_trades) / len(trades) if trades else 0
285
+
286
+ result = PaperTestResult(
287
+ strategy_name=test['strategy_name'],
288
+ test_duration=test_duration,
289
+ total_trades=len(trades),
290
+ winning_trades=len(winning_trades),
291
+ losing_trades=len(losing_trades),
292
+ total_return=total_return,
293
+ sharpe_ratio=sharpe_ratio,
294
+ max_drawdown=max_drawdown,
295
+ win_rate=win_rate,
296
+ final_balance=self.portfolio.get_total_equity(),
297
+ trades=trades
298
+ )
299
+
300
+ # Remove test
301
+ del self.active_tests[test_id]
302
+
303
+ return result
304
+
305
+ def get_active_tests(self) -> List[str]:
306
+ """Get list of active test IDs."""
307
+ return list(self.active_tests.keys())
308
+
309
+ def get_test_status(self, test_id: str) -> Dict[str, Any]:
310
+ """Get current status of a test."""
311
+ if test_id not in self.active_tests:
312
+ return {'error': 'Test not found'}
313
+
314
+ test = self.active_tests[test_id]
315
+ return {
316
+ 'test_id': test_id,
317
+ 'strategy_name': test['strategy_name'],
318
+ 'symbol': test['symbol'],
319
+ 'start_time': test['start_time'],
320
+ 'duration': datetime.now() - test['start_time'],
321
+ 'total_trades': len(test['trades']),
322
+ 'current_equity': self.portfolio.get_total_equity(),
323
+ 'total_return': (self.portfolio.get_total_equity() / self.initial_balance) - 1,
324
+ 'last_update': test['last_update']
325
+ }
326
+
@@ -0,0 +1,284 @@
1
+ """Strategy sandbox environment for safe testing."""
2
+
3
+ import pandas as pd
4
+ import numpy as np
5
+ from typing import Dict, Any, Optional, List, Callable
6
+ from datetime import datetime
7
+ import warnings
8
+
9
+ from ..core.interfaces import IStrategy
10
+ from ..adapters.brokers.paper_broker import PaperBroker
11
+ from ..core.portfolio import Portfolio
12
+ from ..core.risk_engine import RiskEngine, RiskConfig
13
+
14
+
15
+ class StrategySandbox:
16
+ """Sandbox environment for safe strategy testing."""
17
+
18
+ def __init__(self, initial_balance: float = 10000.0,
19
+ risk_config: Optional[RiskConfig] = None,
20
+ max_drawdown_limit: float = 0.5,
21
+ max_loss_per_trade: float = 0.1):
22
+ """
23
+ Initialize strategy sandbox.
24
+
25
+ Args:
26
+ initial_balance: Initial balance for testing
27
+ risk_config: Risk management configuration
28
+ max_drawdown_limit: Maximum allowed drawdown (0.5 = 50%)
29
+ max_loss_per_trade: Maximum loss per trade (0.1 = 10%)
30
+ """
31
+ self.initial_balance = initial_balance
32
+ self.risk_config = risk_config or RiskConfig()
33
+ self.max_drawdown_limit = max_drawdown_limit
34
+ self.max_loss_per_trade = max_loss_per_trade
35
+ self.broker = PaperBroker(initial_balance)
36
+ self.portfolio = Portfolio(initial_balance)
37
+ self.risk_engine = RiskEngine(self.risk_config)
38
+ self.safety_checks_enabled = True
39
+
40
+ def test_strategy(self, strategy: IStrategy, df: pd.DataFrame,
41
+ symbol: str = "TEST") -> Dict[str, Any]:
42
+ """
43
+ Test strategy in sandbox environment.
44
+
45
+ Args:
46
+ strategy: Strategy to test
47
+ df: Historical data
48
+ symbol: Trading symbol
49
+
50
+ Returns:
51
+ Test results with safety checks
52
+ """
53
+ # Reset sandbox
54
+ self.broker = PaperBroker(self.initial_balance)
55
+ self.portfolio = Portfolio(self.initial_balance)
56
+
57
+ positions = {}
58
+ trades = []
59
+ equity_history = []
60
+ safety_violations = []
61
+
62
+ for i in range(len(df)):
63
+ try:
64
+ current_data = df.iloc[:i+1]
65
+
66
+ if len(current_data) < 20:
67
+ continue
68
+
69
+ # Calculate indicators
70
+ current_data = strategy.indicators(current_data)
71
+
72
+ # Get current price
73
+ current_price = float(current_data['Close'].iloc[-1])
74
+
75
+ # Generate signal
76
+ signal = strategy.signal(current_data)
77
+ volatility = strategy.volatility(current_data)
78
+
79
+ # Safety check: Validate signal
80
+ if signal not in [-1, 0, 1]:
81
+ safety_violations.append({
82
+ 'step': i,
83
+ 'type': 'invalid_signal',
84
+ 'message': f'Invalid signal value: {signal}'
85
+ })
86
+ signal = 0
87
+
88
+ # Process trading logic
89
+ if signal != 0:
90
+ # Safety check: Position size
91
+ size = self.risk_engine.calculate_position_size(
92
+ self.portfolio.get_total_balance(), volatility or 0.01, signal
93
+ )
94
+
95
+ # Check max loss per trade
96
+ potential_loss = abs(size * current_price * self.max_loss_per_trade)
97
+ if potential_loss > self.portfolio.get_total_balance() * self.max_loss_per_trade:
98
+ safety_violations.append({
99
+ 'step': i,
100
+ 'type': 'max_loss_exceeded',
101
+ 'message': f'Potential loss {potential_loss:.2f} exceeds limit'
102
+ })
103
+ size = self.portfolio.get_total_balance() * self.max_loss_per_trade / current_price
104
+
105
+ if size > 0:
106
+ sl = strategy.stop_loss(current_data, current_price, signal)
107
+ tp = strategy.take_profit(current_data, current_price, signal)
108
+
109
+ # Safety check: SL/TP validation
110
+ if sl and tp:
111
+ if signal > 0: # Long
112
+ if sl >= current_price:
113
+ safety_violations.append({
114
+ 'step': i,
115
+ 'type': 'invalid_sl',
116
+ 'message': 'Stop loss above entry for long'
117
+ })
118
+ sl = current_price * 0.98 # Default 2% SL
119
+ if tp <= current_price:
120
+ safety_violations.append({
121
+ 'step': i,
122
+ 'type': 'invalid_tp',
123
+ 'message': 'Take profit below entry for long'
124
+ })
125
+ tp = current_price * 1.04 # Default 4% TP
126
+
127
+ # Open position
128
+ self.broker.open_position(symbol, signal, size, current_price, sl, tp)
129
+ positions[symbol] = {
130
+ 'side': signal,
131
+ 'entry_price': current_price,
132
+ 'size': size,
133
+ 'entry_time': datetime.now()
134
+ }
135
+
136
+ # Check exit conditions
137
+ if symbol in positions:
138
+ pos = positions[symbol]
139
+
140
+ # Check stop loss
141
+ if pos.get('stop_loss') and (
142
+ (pos['side'] > 0 and current_price <= pos['stop_loss']) or
143
+ (pos['side'] < 0 and current_price >= pos['stop_loss'])
144
+ ):
145
+ self.broker.close_position(symbol, current_price)
146
+ profit = (current_price - pos['entry_price']) * pos['side'] * pos['size']
147
+ trades.append({
148
+ 'profit': profit,
149
+ 'entry_price': pos['entry_price'],
150
+ 'exit_price': current_price,
151
+ 'exit_reason': 'stop_loss'
152
+ })
153
+ del positions[symbol]
154
+
155
+ # Check take profit
156
+ if pos.get('take_profit') and (
157
+ (pos['side'] > 0 and current_price >= pos['take_profit']) or
158
+ (pos['side'] < 0 and current_price <= pos['take_profit'])
159
+ ):
160
+ self.broker.close_position(symbol, current_price)
161
+ profit = (current_price - pos['entry_price']) * pos['side'] * pos['size']
162
+ trades.append({
163
+ 'profit': profit,
164
+ 'entry_price': pos['entry_price'],
165
+ 'exit_price': current_price,
166
+ 'exit_reason': 'take_profit'
167
+ })
168
+ del positions[symbol]
169
+
170
+ # Update portfolio
171
+ self.portfolio.update_equity(self.broker.get_balance())
172
+ equity_history.append(self.portfolio.get_total_equity())
173
+
174
+ # Safety check: Max drawdown
175
+ if equity_history:
176
+ equity_series = pd.Series(equity_history)
177
+ running_max = equity_series.expanding().max()
178
+ drawdown = (equity_series - running_max) / running_max
179
+ current_drawdown = abs(drawdown.iloc[-1])
180
+
181
+ if current_drawdown > self.max_drawdown_limit:
182
+ safety_violations.append({
183
+ 'step': i,
184
+ 'type': 'max_drawdown_exceeded',
185
+ 'message': f'Drawdown {current_drawdown:.2%} exceeds limit {self.max_drawdown_limit:.2%}'
186
+ })
187
+
188
+ # Auto-stop if drawdown too high
189
+ if self.safety_checks_enabled:
190
+ break
191
+
192
+ except Exception as e:
193
+ safety_violations.append({
194
+ 'step': i,
195
+ 'type': 'exception',
196
+ 'message': str(e)
197
+ })
198
+ continue
199
+
200
+ # Calculate final metrics
201
+ total_return = (self.portfolio.get_total_equity() / self.initial_balance) - 1
202
+
203
+ # Calculate max drawdown
204
+ if equity_history:
205
+ equity_series = pd.Series(equity_history)
206
+ running_max = equity_series.expanding().max()
207
+ drawdown = (equity_series - running_max) / running_max
208
+ max_drawdown = abs(drawdown.min())
209
+ else:
210
+ max_drawdown = 0.0
211
+
212
+ winning_trades = [t for t in trades if t.get('profit', 0) > 0]
213
+ win_rate = len(winning_trades) / len(trades) if trades else 0
214
+
215
+ # Safety assessment
216
+ is_safe = len(safety_violations) == 0 and max_drawdown <= self.max_drawdown_limit
217
+
218
+ return {
219
+ 'strategy_name': strategy.name,
220
+ 'total_return': total_return,
221
+ 'max_drawdown': max_drawdown,
222
+ 'total_trades': len(trades),
223
+ 'winning_trades': len(winning_trades),
224
+ 'win_rate': win_rate,
225
+ 'final_balance': self.portfolio.get_total_equity(),
226
+ 'safety_violations': safety_violations,
227
+ 'is_safe': is_safe,
228
+ 'trades': trades,
229
+ 'equity_history': equity_history
230
+ }
231
+
232
+ def validate_strategy(self, strategy: IStrategy, df: pd.DataFrame) -> Dict[str, Any]:
233
+ """
234
+ Validate strategy safety without full backtest.
235
+
236
+ Args:
237
+ strategy: Strategy to validate
238
+ df: Sample data
239
+
240
+ Returns:
241
+ Validation results
242
+ """
243
+ validation_results = {
244
+ 'strategy_name': strategy.name,
245
+ 'is_valid': True,
246
+ 'warnings': [],
247
+ 'errors': []
248
+ }
249
+
250
+ # Check strategy interface
251
+ required_methods = ['indicators', 'signal', 'volatility', 'stop_loss', 'take_profit']
252
+ for method in required_methods:
253
+ if not hasattr(strategy, method):
254
+ validation_results['errors'].append(f'Missing method: {method}')
255
+ validation_results['is_valid'] = False
256
+
257
+ # Test indicator calculation
258
+ try:
259
+ df_with_indicators = strategy.indicators(df)
260
+ if df_with_indicators.empty:
261
+ validation_results['warnings'].append('Indicators return empty DataFrame')
262
+ except Exception as e:
263
+ validation_results['errors'].append(f'Indicator calculation error: {e}')
264
+ validation_results['is_valid'] = False
265
+
266
+ # Test signal generation
267
+ try:
268
+ signal = strategy.signal(df_with_indicators if 'df_with_indicators' in locals() else df)
269
+ if signal not in [-1, 0, 1]:
270
+ validation_results['warnings'].append(f'Signal value {signal} not in [-1, 0, 1]')
271
+ except Exception as e:
272
+ validation_results['errors'].append(f'Signal generation error: {e}')
273
+ validation_results['is_valid'] = False
274
+
275
+ # Test volatility calculation
276
+ try:
277
+ volatility = strategy.volatility(df_with_indicators if 'df_with_indicators' in locals() else df)
278
+ if volatility is not None and (volatility < 0 or volatility > 1):
279
+ validation_results['warnings'].append(f'Volatility value {volatility} outside [0, 1]')
280
+ except Exception as e:
281
+ validation_results['warnings'].append(f'Volatility calculation error: {e}')
282
+
283
+ return validation_results
284
+
@@ -0,0 +1,8 @@
1
+ """UI components for ForexSmartBot."""
2
+
3
+ from .main_window import MainWindow
4
+ from .settings_dialog import SettingsDialog
5
+ from .charts import ChartWidget
6
+ from .theme import ThemeManager
7
+
8
+ __all__ = ['MainWindow', 'SettingsDialog', 'ChartWidget', 'ThemeManager']