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,304 @@
|
|
|
1
|
+
"""REST API broker implementation for live trading."""
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
import hashlib
|
|
5
|
+
import hmac
|
|
6
|
+
import time
|
|
7
|
+
import json
|
|
8
|
+
from typing import Dict, Optional, List
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from ...core.interfaces import IBroker, Position, Order
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class RestBroker(IBroker):
|
|
14
|
+
"""REST API broker for live trading with real brokers."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, api_key: str = "", api_secret: str = "",
|
|
17
|
+
base_url: str = "https://api.example.com", account_id: str = None,
|
|
18
|
+
sandbox: bool = True):
|
|
19
|
+
self._api_key = api_key
|
|
20
|
+
self._api_secret = api_secret
|
|
21
|
+
self._base_url = base_url.rstrip('/')
|
|
22
|
+
self._account_id = account_id
|
|
23
|
+
self._sandbox = sandbox
|
|
24
|
+
self._connected = False
|
|
25
|
+
self._session = requests.Session()
|
|
26
|
+
self._session.headers.update({
|
|
27
|
+
'Content-Type': 'application/json',
|
|
28
|
+
'User-Agent': 'ForexSmartBot/2.0'
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
def connect(self) -> bool:
|
|
32
|
+
"""Connect to the REST API broker."""
|
|
33
|
+
try:
|
|
34
|
+
if not self._api_key or not self._api_secret:
|
|
35
|
+
print("REST broker: API key or secret not provided")
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
# Test connection with account info
|
|
39
|
+
response = self._make_request('GET', '/v1/accounts')
|
|
40
|
+
if response and response.get('status') == 'success':
|
|
41
|
+
self._connected = True
|
|
42
|
+
print("REST broker: Connected successfully")
|
|
43
|
+
return True
|
|
44
|
+
else:
|
|
45
|
+
# Fallback: try to get balance as connection test
|
|
46
|
+
balance = self.get_balance()
|
|
47
|
+
if balance is not None:
|
|
48
|
+
self._connected = True
|
|
49
|
+
print("REST broker: Connected successfully (fallback)")
|
|
50
|
+
return True
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f"REST broker connection failed: {e}")
|
|
53
|
+
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
def disconnect(self) -> None:
|
|
57
|
+
"""Disconnect from the REST API broker."""
|
|
58
|
+
self._connected = False
|
|
59
|
+
if hasattr(self, '_session'):
|
|
60
|
+
self._session.close()
|
|
61
|
+
print("REST broker: Disconnected")
|
|
62
|
+
|
|
63
|
+
def is_connected(self) -> bool:
|
|
64
|
+
"""Check if connected to the broker."""
|
|
65
|
+
return self._connected
|
|
66
|
+
|
|
67
|
+
def get_price(self, symbol: str) -> Optional[float]:
|
|
68
|
+
"""Get current price for a symbol."""
|
|
69
|
+
try:
|
|
70
|
+
response = self._make_request('GET', f'/v1/prices/{symbol}')
|
|
71
|
+
if response and 'price' in response:
|
|
72
|
+
return float(response['price'])
|
|
73
|
+
except Exception as e:
|
|
74
|
+
print(f"Error getting price for {symbol}: {e}")
|
|
75
|
+
return None
|
|
76
|
+
|
|
77
|
+
def submit_order(self, symbol: str, side: int, quantity: float,
|
|
78
|
+
stop_loss: Optional[float] = None,
|
|
79
|
+
take_profit: Optional[float] = None) -> Optional[str]:
|
|
80
|
+
"""Submit an order to the broker."""
|
|
81
|
+
try:
|
|
82
|
+
order_data = {
|
|
83
|
+
'symbol': symbol,
|
|
84
|
+
'side': 'buy' if side > 0 else 'sell',
|
|
85
|
+
'quantity': quantity,
|
|
86
|
+
'type': 'market'
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if stop_loss:
|
|
90
|
+
order_data['stop_loss'] = stop_loss
|
|
91
|
+
if take_profit:
|
|
92
|
+
order_data['take_profit'] = take_profit
|
|
93
|
+
|
|
94
|
+
response = self._make_request('POST', '/v1/orders', data=order_data)
|
|
95
|
+
if response and 'order_id' in response:
|
|
96
|
+
print(f"REST broker: Order submitted for {symbol}")
|
|
97
|
+
return response['order_id']
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Error submitting order: {e}")
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
def close_all(self, symbol: str) -> bool:
|
|
103
|
+
"""Close all positions for a symbol."""
|
|
104
|
+
try:
|
|
105
|
+
response = self._make_request('DELETE', f'/v1/positions/{symbol}')
|
|
106
|
+
success = response and response.get('status') == 'success'
|
|
107
|
+
if success:
|
|
108
|
+
print(f"REST broker: Closed all positions for {symbol}")
|
|
109
|
+
return success
|
|
110
|
+
except Exception as e:
|
|
111
|
+
print(f"Error closing positions for {symbol}: {e}")
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
def get_positions(self) -> Dict[str, Position]:
|
|
115
|
+
"""Get all open positions."""
|
|
116
|
+
try:
|
|
117
|
+
response = self._make_request('GET', '/v1/positions')
|
|
118
|
+
positions = {}
|
|
119
|
+
|
|
120
|
+
if response and 'positions' in response:
|
|
121
|
+
for pos_data in response['positions']:
|
|
122
|
+
position = Position(
|
|
123
|
+
symbol=pos_data['symbol'],
|
|
124
|
+
side=1 if pos_data['side'] == 'long' else -1,
|
|
125
|
+
quantity=pos_data['quantity'],
|
|
126
|
+
entry_price=pos_data['entry_price'],
|
|
127
|
+
current_price=pos_data['current_price'],
|
|
128
|
+
unrealized_pnl=pos_data.get('unrealized_pnl', 0.0)
|
|
129
|
+
)
|
|
130
|
+
positions[position.symbol] = position
|
|
131
|
+
|
|
132
|
+
return positions
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print(f"Error getting positions: {e}")
|
|
135
|
+
return {}
|
|
136
|
+
|
|
137
|
+
def get_balance(self) -> float:
|
|
138
|
+
"""Get account balance."""
|
|
139
|
+
try:
|
|
140
|
+
response = self._make_request('GET', '/v1/accounts/balance')
|
|
141
|
+
if response and 'balance' in response:
|
|
142
|
+
return float(response['balance'])
|
|
143
|
+
except Exception as e:
|
|
144
|
+
print(f"Error getting balance: {e}")
|
|
145
|
+
return 0.0
|
|
146
|
+
|
|
147
|
+
def get_equity(self) -> float:
|
|
148
|
+
"""Get account equity."""
|
|
149
|
+
try:
|
|
150
|
+
response = self._make_request('GET', '/v1/accounts/equity')
|
|
151
|
+
if response and 'equity' in response:
|
|
152
|
+
return float(response['equity'])
|
|
153
|
+
except Exception as e:
|
|
154
|
+
print(f"Error getting equity: {e}")
|
|
155
|
+
return 0.0
|
|
156
|
+
|
|
157
|
+
def get_orders(self) -> List[Dict]:
|
|
158
|
+
"""Get all orders."""
|
|
159
|
+
try:
|
|
160
|
+
response = self._make_request('GET', '/v1/orders')
|
|
161
|
+
if response and 'orders' in response:
|
|
162
|
+
return response['orders']
|
|
163
|
+
except Exception as e:
|
|
164
|
+
print(f"Error getting orders: {e}")
|
|
165
|
+
return []
|
|
166
|
+
|
|
167
|
+
def cancel_order(self, order_id: str) -> bool:
|
|
168
|
+
"""Cancel an order."""
|
|
169
|
+
try:
|
|
170
|
+
response = self._make_request('DELETE', f'/v1/orders/{order_id}')
|
|
171
|
+
success = response and response.get('status') == 'success'
|
|
172
|
+
if success:
|
|
173
|
+
print(f"REST broker: Canceled order {order_id}")
|
|
174
|
+
return success
|
|
175
|
+
except Exception as e:
|
|
176
|
+
print(f"Error canceling order {order_id}: {e}")
|
|
177
|
+
return False
|
|
178
|
+
|
|
179
|
+
def get_account_info(self) -> Dict:
|
|
180
|
+
"""Get account information."""
|
|
181
|
+
try:
|
|
182
|
+
response = self._make_request('GET', '/v1/accounts/info')
|
|
183
|
+
return response or {}
|
|
184
|
+
except Exception as e:
|
|
185
|
+
print(f"Error getting account info: {e}")
|
|
186
|
+
return {}
|
|
187
|
+
|
|
188
|
+
def _make_request(self, method: str, endpoint: str, data: Dict = None) -> Optional[Dict]:
|
|
189
|
+
"""Make an authenticated HTTP request to the broker API."""
|
|
190
|
+
try:
|
|
191
|
+
url = f"{self._base_url}{endpoint}"
|
|
192
|
+
|
|
193
|
+
# Add authentication headers
|
|
194
|
+
timestamp = str(int(time.time() * 1000))
|
|
195
|
+
signature = self._create_signature(method, endpoint, data, timestamp)
|
|
196
|
+
|
|
197
|
+
headers = {
|
|
198
|
+
'X-API-Key': self._api_key,
|
|
199
|
+
'X-Timestamp': timestamp,
|
|
200
|
+
'X-Signature': signature
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if method == 'GET':
|
|
204
|
+
response = self._session.get(url, headers=headers, timeout=10)
|
|
205
|
+
elif method == 'POST':
|
|
206
|
+
response = self._session.post(url, headers=headers, json=data, timeout=10)
|
|
207
|
+
elif method == 'PUT':
|
|
208
|
+
response = self._session.put(url, headers=headers, json=data, timeout=10)
|
|
209
|
+
elif method == 'DELETE':
|
|
210
|
+
response = self._session.delete(url, headers=headers, timeout=10)
|
|
211
|
+
else:
|
|
212
|
+
raise ValueError(f"Unsupported HTTP method: {method}")
|
|
213
|
+
|
|
214
|
+
response.raise_for_status()
|
|
215
|
+
return response.json()
|
|
216
|
+
|
|
217
|
+
except requests.exceptions.RequestException as e:
|
|
218
|
+
print(f"HTTP request failed: {e}")
|
|
219
|
+
except json.JSONDecodeError as e:
|
|
220
|
+
print(f"JSON decode error: {e}")
|
|
221
|
+
except Exception as e:
|
|
222
|
+
print(f"Request error: {e}")
|
|
223
|
+
|
|
224
|
+
return None
|
|
225
|
+
|
|
226
|
+
def _create_signature(self, method: str, endpoint: str, data: Dict, timestamp: str) -> str:
|
|
227
|
+
"""Create HMAC signature for API authentication."""
|
|
228
|
+
try:
|
|
229
|
+
# Create message to sign
|
|
230
|
+
message = f"{method}{endpoint}{timestamp}"
|
|
231
|
+
if data:
|
|
232
|
+
message += json.dumps(data, sort_keys=True)
|
|
233
|
+
|
|
234
|
+
# Create HMAC signature
|
|
235
|
+
signature = hmac.new(
|
|
236
|
+
self._api_secret.encode('utf-8'),
|
|
237
|
+
message.encode('utf-8'),
|
|
238
|
+
hashlib.sha256
|
|
239
|
+
).hexdigest()
|
|
240
|
+
|
|
241
|
+
return signature
|
|
242
|
+
except Exception as e:
|
|
243
|
+
print(f"Error creating signature: {e}")
|
|
244
|
+
return ""
|
|
245
|
+
|
|
246
|
+
def get_market_data(self, symbol: str, timeframe: str = '1h', limit: int = 100) -> List[Dict]:
|
|
247
|
+
"""Get historical market data."""
|
|
248
|
+
try:
|
|
249
|
+
params = {
|
|
250
|
+
'symbol': symbol,
|
|
251
|
+
'timeframe': timeframe,
|
|
252
|
+
'limit': limit
|
|
253
|
+
}
|
|
254
|
+
response = self._make_request('GET', '/v1/market_data', params)
|
|
255
|
+
if response and 'data' in response:
|
|
256
|
+
return response['data']
|
|
257
|
+
except Exception as e:
|
|
258
|
+
print(f"Error getting market data: {e}")
|
|
259
|
+
return []
|
|
260
|
+
|
|
261
|
+
def get_trading_hours(self, symbol: str) -> Dict:
|
|
262
|
+
"""Get trading hours for a symbol."""
|
|
263
|
+
try:
|
|
264
|
+
response = self._make_request('GET', f'/v1/trading_hours/{symbol}')
|
|
265
|
+
return response or {}
|
|
266
|
+
except Exception as e:
|
|
267
|
+
print(f"Error getting trading hours: {e}")
|
|
268
|
+
return {}
|
|
269
|
+
|
|
270
|
+
def get_margin_requirements(self, symbol: str) -> Dict:
|
|
271
|
+
"""Get margin requirements for a symbol."""
|
|
272
|
+
try:
|
|
273
|
+
response = self._make_request('GET', f'/v1/margin/{symbol}')
|
|
274
|
+
return response or {}
|
|
275
|
+
except Exception as e:
|
|
276
|
+
print(f"Error getting margin requirements: {e}")
|
|
277
|
+
return {}
|
|
278
|
+
|
|
279
|
+
def get_instruments(self) -> List[Dict]:
|
|
280
|
+
"""Get available trading instruments."""
|
|
281
|
+
try:
|
|
282
|
+
response = self._make_request('GET', '/v1/instruments')
|
|
283
|
+
if response and 'instruments' in response:
|
|
284
|
+
return response['instruments']
|
|
285
|
+
except Exception as e:
|
|
286
|
+
print(f"Error getting instruments: {e}")
|
|
287
|
+
return []
|
|
288
|
+
|
|
289
|
+
def get_historical_prices(self, symbol: str, start_date: str, end_date: str,
|
|
290
|
+
timeframe: str = '1h') -> List[Dict]:
|
|
291
|
+
"""Get historical price data."""
|
|
292
|
+
try:
|
|
293
|
+
params = {
|
|
294
|
+
'symbol': symbol,
|
|
295
|
+
'start_date': start_date,
|
|
296
|
+
'end_date': end_date,
|
|
297
|
+
'timeframe': timeframe
|
|
298
|
+
}
|
|
299
|
+
response = self._make_request('GET', '/v1/historical_prices', params)
|
|
300
|
+
if response and 'prices' in response:
|
|
301
|
+
return response['prices']
|
|
302
|
+
except Exception as e:
|
|
303
|
+
print(f"Error getting historical prices: {e}")
|
|
304
|
+
return []
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Data provider adapters."""
|
|
2
|
+
|
|
3
|
+
from .yfinance_provider import YFinanceProvider
|
|
4
|
+
from .csv_provider import CSVProvider
|
|
5
|
+
from .alpha_vantage_provider import AlphaVantageProvider
|
|
6
|
+
from .oanda_provider import OANDAProvider
|
|
7
|
+
from .mt4_provider import MT4Provider
|
|
8
|
+
from .multi_provider import MultiProvider
|
|
9
|
+
from .dummy_provider import DummyProvider
|
|
10
|
+
from .twelve_data_provider import TwelveDataProvider
|
|
11
|
+
from .stooq_provider import StooqProvider
|
|
12
|
+
from .config import DataProviderConfig
|
|
13
|
+
from ...core.interfaces import IDataProvider
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
'YFinanceProvider',
|
|
17
|
+
'CSVProvider',
|
|
18
|
+
'AlphaVantageProvider',
|
|
19
|
+
'OANDAProvider',
|
|
20
|
+
'MT4Provider',
|
|
21
|
+
'MultiProvider',
|
|
22
|
+
'DummyProvider',
|
|
23
|
+
'TwelveDataProvider',
|
|
24
|
+
'StooqProvider',
|
|
25
|
+
'DataProviderConfig',
|
|
26
|
+
'IDataProvider',
|
|
27
|
+
]
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
"""Alpha Vantage data provider implementation."""
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
import pandas as pd
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
import time
|
|
8
|
+
from ...core.interfaces import IDataProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AlphaVantageProvider(IDataProvider):
|
|
12
|
+
"""Alpha Vantage data provider for forex data."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, api_key: str = None):
|
|
15
|
+
self.api_key = api_key or "demo" # Use demo key if none provided
|
|
16
|
+
self.base_url = "https://www.alphavantage.co/query"
|
|
17
|
+
self._available = True
|
|
18
|
+
self._rate_limit_delay = 12 # Alpha Vantage free tier: 5 calls per minute
|
|
19
|
+
|
|
20
|
+
def get_data(self, symbol: str, start: str, end: str,
|
|
21
|
+
interval: str = "1h") -> pd.DataFrame:
|
|
22
|
+
"""Get historical data from Alpha Vantage."""
|
|
23
|
+
try:
|
|
24
|
+
# Convert forex symbol to Alpha Vantage format
|
|
25
|
+
if not symbol.endswith('=X'):
|
|
26
|
+
symbol = symbol.upper() + "=X"
|
|
27
|
+
|
|
28
|
+
# Remove =X suffix for Alpha Vantage
|
|
29
|
+
av_symbol = symbol.replace('=X', '')
|
|
30
|
+
|
|
31
|
+
# Map interval to Alpha Vantage format
|
|
32
|
+
av_interval = self._map_interval(interval)
|
|
33
|
+
|
|
34
|
+
params = {
|
|
35
|
+
'function': 'FX_INTRADAY' if av_interval == '1min' else 'FX_DAILY',
|
|
36
|
+
'from_symbol': av_symbol[:3],
|
|
37
|
+
'to_symbol': av_symbol[3:],
|
|
38
|
+
'apikey': self.api_key,
|
|
39
|
+
'outputsize': 'full'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if av_interval == '1min':
|
|
43
|
+
params['interval'] = '1min'
|
|
44
|
+
|
|
45
|
+
response = requests.get(self.base_url, params=params, timeout=5)
|
|
46
|
+
data = response.json()
|
|
47
|
+
|
|
48
|
+
# Debug: Print the response structure
|
|
49
|
+
print(f"Alpha Vantage response for {symbol}: {list(data.keys())}")
|
|
50
|
+
|
|
51
|
+
if 'Error Message' in data:
|
|
52
|
+
print(f"Alpha Vantage error for {symbol}: {data['Error Message']}")
|
|
53
|
+
return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
|
|
54
|
+
|
|
55
|
+
if 'Note' in data:
|
|
56
|
+
print(f"Alpha Vantage rate limit for {symbol}: {data['Note']}")
|
|
57
|
+
time.sleep(self._rate_limit_delay)
|
|
58
|
+
return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
|
|
59
|
+
|
|
60
|
+
if 'Information' in data:
|
|
61
|
+
print(f"Alpha Vantage information for {symbol}: {data['Information']}")
|
|
62
|
+
# Alpha Vantage free tier doesn't support forex - generate sample data
|
|
63
|
+
print(f"Generating sample forex data for {symbol} (Alpha Vantage free tier doesn't support forex)")
|
|
64
|
+
return self._generate_sample_forex_data(symbol, start, end, interval)
|
|
65
|
+
|
|
66
|
+
# Extract time series data - check for different possible keys
|
|
67
|
+
time_series_key = None
|
|
68
|
+
possible_keys = ['Time Series (FX)', 'Time Series (1min)', 'Time Series (5min)', 'Time Series (15min)', 'Time Series (30min)', 'Time Series (60min)']
|
|
69
|
+
|
|
70
|
+
for key in possible_keys:
|
|
71
|
+
if key in data:
|
|
72
|
+
time_series_key = key
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
if time_series_key is None:
|
|
76
|
+
print(f"No time series data for {symbol}. Available keys: {list(data.keys())}")
|
|
77
|
+
return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
|
|
78
|
+
|
|
79
|
+
time_series = data[time_series_key]
|
|
80
|
+
|
|
81
|
+
# Convert to DataFrame
|
|
82
|
+
df = pd.DataFrame.from_dict(time_series, orient='index')
|
|
83
|
+
df.index = pd.to_datetime(df.index)
|
|
84
|
+
df.columns = ['Open', 'High', 'Low', 'Close']
|
|
85
|
+
|
|
86
|
+
# Add volume (synthetic for forex)
|
|
87
|
+
df['Volume'] = 1000000
|
|
88
|
+
|
|
89
|
+
# Filter by date range
|
|
90
|
+
start_date = pd.to_datetime(start)
|
|
91
|
+
end_date = pd.to_datetime(end)
|
|
92
|
+
df = df[(df.index >= start_date) & (df.index <= end_date)]
|
|
93
|
+
|
|
94
|
+
# Resample if needed
|
|
95
|
+
if interval != '1min' and interval != '1d':
|
|
96
|
+
df = self._resample_data(df, interval)
|
|
97
|
+
|
|
98
|
+
return df.sort_index()
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"Error getting Alpha Vantage data for {symbol}: {e}")
|
|
102
|
+
return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
|
|
103
|
+
|
|
104
|
+
def get_latest_price(self, symbol: str) -> Optional[float]:
|
|
105
|
+
"""Get latest price from Alpha Vantage."""
|
|
106
|
+
try:
|
|
107
|
+
# Convert forex symbol to Alpha Vantage format
|
|
108
|
+
if not symbol.endswith('=X'):
|
|
109
|
+
symbol = symbol.upper() + "=X"
|
|
110
|
+
|
|
111
|
+
av_symbol = symbol.replace('=X', '')
|
|
112
|
+
|
|
113
|
+
params = {
|
|
114
|
+
'function': 'CURRENCY_EXCHANGE_RATE',
|
|
115
|
+
'from_currency': av_symbol[:3],
|
|
116
|
+
'to_currency': av_symbol[3:],
|
|
117
|
+
'apikey': self.api_key
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
response = requests.get(self.base_url, params=params, timeout=5)
|
|
121
|
+
data = response.json()
|
|
122
|
+
|
|
123
|
+
if 'Realtime Currency Exchange Rate' in data:
|
|
124
|
+
rate = data['Realtime Currency Exchange Rate']
|
|
125
|
+
return float(rate['5. Exchange Rate'])
|
|
126
|
+
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
except Exception as e:
|
|
130
|
+
print(f"Error getting Alpha Vantage latest price for {symbol}: {e}")
|
|
131
|
+
return None
|
|
132
|
+
|
|
133
|
+
def get_historical_data(self, symbol: str, period: str = '1d', interval: str = '1h') -> pd.DataFrame:
|
|
134
|
+
"""Get historical data for a symbol with specified period and interval."""
|
|
135
|
+
try:
|
|
136
|
+
# Convert period to start/end dates
|
|
137
|
+
now = datetime.now()
|
|
138
|
+
|
|
139
|
+
if period == '1d':
|
|
140
|
+
end_date = now
|
|
141
|
+
start_date = now - timedelta(days=1)
|
|
142
|
+
elif period == '5d':
|
|
143
|
+
end_date = now
|
|
144
|
+
start_date = now - timedelta(days=5)
|
|
145
|
+
elif period == '1mo':
|
|
146
|
+
end_date = now
|
|
147
|
+
start_date = now - timedelta(days=30)
|
|
148
|
+
elif period == '3mo':
|
|
149
|
+
end_date = now
|
|
150
|
+
start_date = now - timedelta(days=90)
|
|
151
|
+
elif period == '6mo':
|
|
152
|
+
end_date = now
|
|
153
|
+
start_date = now - timedelta(days=180)
|
|
154
|
+
elif period == '1y':
|
|
155
|
+
end_date = now
|
|
156
|
+
start_date = now - timedelta(days=365)
|
|
157
|
+
else:
|
|
158
|
+
# Default to 1 day
|
|
159
|
+
end_date = now
|
|
160
|
+
start_date = now - timedelta(days=1)
|
|
161
|
+
|
|
162
|
+
return self.get_data(symbol, start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'), interval)
|
|
163
|
+
|
|
164
|
+
except Exception as e:
|
|
165
|
+
print(f"Error getting Alpha Vantage historical data for {symbol}: {e}")
|
|
166
|
+
return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
|
|
167
|
+
|
|
168
|
+
def _map_interval(self, interval: str) -> str:
|
|
169
|
+
"""Map interval to Alpha Vantage format."""
|
|
170
|
+
mapping = {
|
|
171
|
+
'1m': '1min',
|
|
172
|
+
'5m': '5min',
|
|
173
|
+
'15m': '15min',
|
|
174
|
+
'30m': '30min',
|
|
175
|
+
'1h': '1min', # Alpha Vantage doesn't have 1h, use 1min
|
|
176
|
+
'4h': '1min', # Alpha Vantage doesn't have 4h, use 1min
|
|
177
|
+
'1d': '1d'
|
|
178
|
+
}
|
|
179
|
+
return mapping.get(interval, '1min')
|
|
180
|
+
|
|
181
|
+
def _resample_data(self, df: pd.DataFrame, interval: str) -> pd.DataFrame:
|
|
182
|
+
"""Resample data to desired interval."""
|
|
183
|
+
try:
|
|
184
|
+
if interval == '1h':
|
|
185
|
+
return df.resample('1H').agg({
|
|
186
|
+
'Open': 'first',
|
|
187
|
+
'High': 'max',
|
|
188
|
+
'Low': 'min',
|
|
189
|
+
'Close': 'last',
|
|
190
|
+
'Volume': 'sum'
|
|
191
|
+
}).dropna()
|
|
192
|
+
elif interval == '4h':
|
|
193
|
+
return df.resample('4H').agg({
|
|
194
|
+
'Open': 'first',
|
|
195
|
+
'High': 'max',
|
|
196
|
+
'Low': 'min',
|
|
197
|
+
'Close': 'last',
|
|
198
|
+
'Volume': 'sum'
|
|
199
|
+
}).dropna()
|
|
200
|
+
else:
|
|
201
|
+
return df
|
|
202
|
+
except Exception:
|
|
203
|
+
return df
|
|
204
|
+
|
|
205
|
+
def _generate_sample_forex_data(self, symbol: str, start: str, end: str, interval: str) -> pd.DataFrame:
|
|
206
|
+
"""Generate sample forex data when real data is not available."""
|
|
207
|
+
import numpy as np
|
|
208
|
+
|
|
209
|
+
# Parse dates
|
|
210
|
+
start_date = pd.to_datetime(start)
|
|
211
|
+
end_date = pd.to_datetime(end)
|
|
212
|
+
|
|
213
|
+
# Generate date range based on interval
|
|
214
|
+
if interval == '1h':
|
|
215
|
+
freq = '1H'
|
|
216
|
+
elif interval == '4h':
|
|
217
|
+
freq = '4H'
|
|
218
|
+
elif interval == '1d':
|
|
219
|
+
freq = '1D'
|
|
220
|
+
else:
|
|
221
|
+
freq = '1H'
|
|
222
|
+
|
|
223
|
+
dates = pd.date_range(start=start_date, end=end_date, freq=freq)
|
|
224
|
+
|
|
225
|
+
# Set base price based on symbol
|
|
226
|
+
if 'EUR' in symbol:
|
|
227
|
+
base_price = 1.1800
|
|
228
|
+
elif 'GBP' in symbol:
|
|
229
|
+
base_price = 1.2500
|
|
230
|
+
elif 'JPY' in symbol:
|
|
231
|
+
base_price = 150.0
|
|
232
|
+
elif 'AUD' in symbol:
|
|
233
|
+
base_price = 0.7500
|
|
234
|
+
elif 'CAD' in symbol:
|
|
235
|
+
base_price = 1.3500
|
|
236
|
+
elif 'CHF' in symbol:
|
|
237
|
+
base_price = 0.9200
|
|
238
|
+
elif 'NZD' in symbol:
|
|
239
|
+
base_price = 0.7000
|
|
240
|
+
else:
|
|
241
|
+
base_price = 1.0000
|
|
242
|
+
|
|
243
|
+
# Create realistic price movements
|
|
244
|
+
np.random.seed(42) # For consistent results
|
|
245
|
+
price_changes = np.random.randn(len(dates)) * 0.0005 # Smaller changes for more realistic data
|
|
246
|
+
prices = base_price + np.cumsum(price_changes)
|
|
247
|
+
|
|
248
|
+
# Ensure prices stay within reasonable bounds
|
|
249
|
+
prices = np.clip(prices, base_price * 0.5, base_price * 2.0)
|
|
250
|
+
|
|
251
|
+
return pd.DataFrame({
|
|
252
|
+
'Open': prices,
|
|
253
|
+
'High': prices + np.abs(np.random.randn(len(dates))) * 0.0002,
|
|
254
|
+
'Low': prices - np.abs(np.random.randn(len(dates))) * 0.0002,
|
|
255
|
+
'Close': prices + np.random.randn(len(dates)) * 0.0001,
|
|
256
|
+
'Volume': np.random.randint(1000000, 10000000, len(dates))
|
|
257
|
+
}, index=dates)
|
|
258
|
+
|
|
259
|
+
def is_available(self) -> bool:
|
|
260
|
+
"""Check if Alpha Vantage is available."""
|
|
261
|
+
return self._available and self.api_key and self.api_key != "demo"
|