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,173 @@
1
+ """OANDA 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 OANDAProvider(IDataProvider):
12
+ """OANDA data provider for forex data."""
13
+
14
+ def __init__(self, api_key: str = None, account_id: str = None):
15
+ self.api_key = api_key or "demo" # Use demo if none provided
16
+ self.account_id = account_id or "101-001-123456-001" # Demo account
17
+ self.base_url = "https://api-fxpractice.oanda.com" # Demo environment
18
+ self._available = True
19
+
20
+ def get_data(self, symbol: str, start: str, end: str,
21
+ interval: str = "1h") -> pd.DataFrame:
22
+ """Get historical data from OANDA."""
23
+ try:
24
+ # Convert forex symbol to OANDA format
25
+ if symbol.endswith('=X'):
26
+ symbol = symbol.replace('=X', '')
27
+
28
+ # OANDA uses different format (e.g., EUR_USD instead of EURUSD)
29
+ oanda_symbol = f"{symbol[:3]}_{symbol[3:]}"
30
+
31
+ # Map interval to OANDA granularity
32
+ granularity = self._map_interval(interval)
33
+
34
+ # Convert dates to OANDA format
35
+ start_date = pd.to_datetime(start).strftime('%Y-%m-%dT%H:%M:%S.000000000Z')
36
+ end_date = pd.to_datetime(end).strftime('%Y-%m-%dT%H:%M:%S.000000000Z')
37
+
38
+ url = f"{self.base_url}/v3/instruments/{oanda_symbol}/candles"
39
+ params = {
40
+ 'from': start_date,
41
+ 'to': end_date,
42
+ 'granularity': granularity,
43
+ 'price': 'M' # Mid prices
44
+ }
45
+
46
+ headers = {
47
+ 'Authorization': f'Bearer {self.api_key}',
48
+ 'Content-Type': 'application/json'
49
+ }
50
+
51
+ response = requests.get(url, params=params, headers=headers, timeout=30)
52
+ data = response.json()
53
+
54
+ if 'candles' not in data:
55
+ print(f"No candles data for {symbol}: {data.get('errorMessage', 'Unknown error')}")
56
+ return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
57
+
58
+ candles = data['candles']
59
+ if not candles:
60
+ print(f"No candles available for {symbol}")
61
+ return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
62
+
63
+ # Convert to DataFrame
64
+ df_data = []
65
+ for candle in candles:
66
+ if candle['complete']: # Only use complete candles
67
+ df_data.append({
68
+ 'Open': float(candle['mid']['o']),
69
+ 'High': float(candle['mid']['h']),
70
+ 'Low': float(candle['mid']['l']),
71
+ 'Close': float(candle['mid']['c']),
72
+ 'Volume': int(candle['volume'])
73
+ })
74
+
75
+ if not df_data:
76
+ print(f"No complete candles for {symbol}")
77
+ return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
78
+
79
+ df = pd.DataFrame(df_data)
80
+ df.index = pd.to_datetime([candle['time'] for candle in candles if candle['complete']])
81
+
82
+ return df.sort_index()
83
+
84
+ except Exception as e:
85
+ print(f"Error getting OANDA data for {symbol}: {e}")
86
+ return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
87
+
88
+ def get_latest_price(self, symbol: str) -> Optional[float]:
89
+ """Get latest price from OANDA."""
90
+ try:
91
+ # Convert forex symbol to OANDA format
92
+ if symbol.endswith('=X'):
93
+ symbol = symbol.replace('=X', '')
94
+
95
+ oanda_symbol = f"{symbol[:3]}_{symbol[3:]}"
96
+
97
+ url = f"{self.base_url}/v3/accounts/{self.account_id}/pricing"
98
+ params = {
99
+ 'instruments': oanda_symbol
100
+ }
101
+
102
+ headers = {
103
+ 'Authorization': f'Bearer {self.api_key}',
104
+ 'Content-Type': 'application/json'
105
+ }
106
+
107
+ response = requests.get(url, params=params, headers=headers, timeout=30)
108
+ data = response.json()
109
+
110
+ if 'prices' in data and data['prices']:
111
+ price_data = data['prices'][0]
112
+ if 'bids' in price_data and 'asks' in price_data:
113
+ bid = float(price_data['bids'][0]['price'])
114
+ ask = float(price_data['asks'][0]['price'])
115
+ return (bid + ask) / 2 # Return mid price
116
+
117
+ return None
118
+
119
+ except Exception as e:
120
+ print(f"Error getting OANDA latest price for {symbol}: {e}")
121
+ return None
122
+
123
+ def get_historical_data(self, symbol: str, period: str = '1d', interval: str = '1h') -> pd.DataFrame:
124
+ """Get historical data for a symbol with specified period and interval."""
125
+ try:
126
+ # Convert period to start/end dates
127
+ now = datetime.now()
128
+
129
+ if period == '1d':
130
+ end_date = now
131
+ start_date = now - timedelta(days=1)
132
+ elif period == '5d':
133
+ end_date = now
134
+ start_date = now - timedelta(days=5)
135
+ elif period == '1mo':
136
+ end_date = now
137
+ start_date = now - timedelta(days=30)
138
+ elif period == '3mo':
139
+ end_date = now
140
+ start_date = now - timedelta(days=90)
141
+ elif period == '6mo':
142
+ end_date = now
143
+ start_date = now - timedelta(days=180)
144
+ elif period == '1y':
145
+ end_date = now
146
+ start_date = now - timedelta(days=365)
147
+ else:
148
+ # Default to 1 day
149
+ end_date = now
150
+ start_date = now - timedelta(days=1)
151
+
152
+ return self.get_data(symbol, start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'), interval)
153
+
154
+ except Exception as e:
155
+ print(f"Error getting OANDA historical data for {symbol}: {e}")
156
+ return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
157
+
158
+ def _map_interval(self, interval: str) -> str:
159
+ """Map interval to OANDA granularity."""
160
+ mapping = {
161
+ '1m': 'M1',
162
+ '5m': 'M5',
163
+ '15m': 'M15',
164
+ '30m': 'M30',
165
+ '1h': 'H1',
166
+ '4h': 'H4',
167
+ '1d': 'D'
168
+ }
169
+ return mapping.get(interval, 'H1')
170
+
171
+ def is_available(self) -> bool:
172
+ """Check if OANDA is available."""
173
+ return self._available
@@ -0,0 +1,114 @@
1
+ """Stooq provider implementation (public CSV market data endpoint)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timedelta
6
+ from io import StringIO
7
+ from typing import Optional
8
+
9
+ import pandas as pd
10
+ import requests
11
+
12
+ from ...core.interfaces import IDataProvider
13
+
14
+
15
+ class StooqProvider(IDataProvider):
16
+ """Stooq provider for free forex data."""
17
+
18
+ def __init__(self):
19
+ self._available = True
20
+
21
+ def _to_stooq_symbol(self, symbol: str) -> str:
22
+ raw = symbol.replace("=X", "").replace("/", "").upper()
23
+ if len(raw) != 6:
24
+ raise ValueError(f"Unsupported forex symbol format: {symbol}")
25
+ return raw.lower()
26
+
27
+ def _map_interval(self, interval: str) -> str:
28
+ mapping = {
29
+ "1m": "1",
30
+ "5m": "5",
31
+ "15m": "15",
32
+ "30m": "30",
33
+ "1h": "60",
34
+ "4h": "240",
35
+ "1d": "d",
36
+ }
37
+ return mapping.get(interval, "d")
38
+
39
+ def get_data(self, symbol: str, start: str, end: str, interval: str = "1h") -> pd.DataFrame:
40
+ try:
41
+ s = self._to_stooq_symbol(symbol)
42
+ i = self._map_interval(interval)
43
+ # Stooq CSV endpoint
44
+ url = f"https://stooq.com/q/d/l/?s={s}&i={i}"
45
+ response = requests.get(url, timeout=15)
46
+ response.raise_for_status()
47
+ csv_txt = response.text
48
+ df = pd.read_csv(StringIO(csv_txt))
49
+ if df.empty or "Date" not in df.columns:
50
+ return pd.DataFrame(columns=["Open", "High", "Low", "Close", "Volume"])
51
+
52
+ if "Time" in df.columns:
53
+ df["datetime"] = pd.to_datetime(df["Date"] + " " + df["Time"], errors="coerce")
54
+ else:
55
+ df["datetime"] = pd.to_datetime(df["Date"], errors="coerce")
56
+ df = df.set_index("datetime").sort_index()
57
+ df = df.rename(
58
+ columns={
59
+ "Open": "Open",
60
+ "High": "High",
61
+ "Low": "Low",
62
+ "Close": "Close",
63
+ "Volume": "Volume",
64
+ }
65
+ )
66
+ for col in ["Open", "High", "Low", "Close"]:
67
+ df[col] = pd.to_numeric(df[col], errors="coerce")
68
+ if "Volume" not in df.columns:
69
+ df["Volume"] = 0.0
70
+ else:
71
+ df["Volume"] = pd.to_numeric(df["Volume"], errors="coerce").fillna(0.0)
72
+ df = df.dropna(subset=["Open", "High", "Low", "Close"])
73
+
74
+ start_dt = pd.to_datetime(start)
75
+ end_dt = pd.to_datetime(end)
76
+ df = df[(df.index >= start_dt) & (df.index <= end_dt)]
77
+ return df[["Open", "High", "Low", "Close", "Volume"]]
78
+ except Exception:
79
+ return pd.DataFrame(columns=["Open", "High", "Low", "Close", "Volume"])
80
+
81
+ def get_latest_price(self, symbol: str) -> Optional[float]:
82
+ try:
83
+ s = self._to_stooq_symbol(symbol)
84
+ # Current quote endpoint
85
+ url = f"https://stooq.com/q/l/?s={s}&f=sd2t2ohlcv&h&e=csv"
86
+ response = requests.get(url, timeout=10)
87
+ response.raise_for_status()
88
+ df = pd.read_csv(StringIO(response.text))
89
+ if df.empty or "Close" not in df.columns:
90
+ return None
91
+ price = pd.to_numeric(df["Close"].iloc[0], errors="coerce")
92
+ if pd.isna(price):
93
+ return None
94
+ return float(price)
95
+ except Exception:
96
+ return None
97
+
98
+ def get_historical_data(self, symbol: str, period: str = "1d", interval: str = "1h") -> pd.DataFrame:
99
+ now = datetime.now()
100
+ period_map = {
101
+ "1d": timedelta(days=1),
102
+ "5d": timedelta(days=5),
103
+ "1mo": timedelta(days=30),
104
+ "3mo": timedelta(days=90),
105
+ "6mo": timedelta(days=180),
106
+ "1y": timedelta(days=365),
107
+ }
108
+ delta = period_map.get(period, timedelta(days=1))
109
+ start = (now - delta).strftime("%Y-%m-%d")
110
+ end = now.strftime("%Y-%m-%d")
111
+ return self.get_data(symbol, start, end, interval)
112
+
113
+ def is_available(self) -> bool:
114
+ return self._available
@@ -0,0 +1,117 @@
1
+ """Twelve Data provider implementation for forex market data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime, timedelta
6
+ from typing import Optional
7
+
8
+ import pandas as pd
9
+ import requests
10
+
11
+ from ...core.interfaces import IDataProvider
12
+
13
+
14
+ class TwelveDataProvider(IDataProvider):
15
+ """Twelve Data provider for historical candles and latest price."""
16
+
17
+ def __init__(self, api_key: str = ""):
18
+ self.api_key = api_key
19
+ self.base_url = "https://api.twelvedata.com"
20
+ self._available = bool(api_key)
21
+
22
+ def _to_twelve_symbol(self, symbol: str) -> str:
23
+ raw = symbol.replace("=X", "").replace("/", "").upper()
24
+ if len(raw) != 6:
25
+ raise ValueError(f"Unsupported forex symbol format: {symbol}")
26
+ return f"{raw[:3]}/{raw[3:]}"
27
+
28
+ def _map_interval(self, interval: str) -> str:
29
+ mapping = {
30
+ "1m": "1min",
31
+ "5m": "5min",
32
+ "15m": "15min",
33
+ "30m": "30min",
34
+ "1h": "1h",
35
+ "4h": "4h",
36
+ "1d": "1day",
37
+ }
38
+ return mapping.get(interval, "1h")
39
+
40
+ def get_data(self, symbol: str, start: str, end: str, interval: str = "1h") -> pd.DataFrame:
41
+ if not self._available:
42
+ return pd.DataFrame(columns=["Open", "High", "Low", "Close", "Volume"])
43
+
44
+ try:
45
+ td_symbol = self._to_twelve_symbol(symbol)
46
+ params = {
47
+ "symbol": td_symbol,
48
+ "interval": self._map_interval(interval),
49
+ "start_date": pd.to_datetime(start).strftime("%Y-%m-%d %H:%M:%S"),
50
+ "end_date": pd.to_datetime(end).strftime("%Y-%m-%d %H:%M:%S"),
51
+ "apikey": self.api_key,
52
+ "outputsize": 5000,
53
+ }
54
+ response = requests.get(f"{self.base_url}/time_series", params=params, timeout=20)
55
+ data = response.json()
56
+
57
+ if "values" not in data:
58
+ return pd.DataFrame(columns=["Open", "High", "Low", "Close", "Volume"])
59
+
60
+ rows = data["values"]
61
+ if not rows:
62
+ return pd.DataFrame(columns=["Open", "High", "Low", "Close", "Volume"])
63
+
64
+ df = pd.DataFrame(rows)
65
+ df["datetime"] = pd.to_datetime(df["datetime"])
66
+ df = df.set_index("datetime").sort_index()
67
+ df = df.rename(
68
+ columns={
69
+ "open": "Open",
70
+ "high": "High",
71
+ "low": "Low",
72
+ "close": "Close",
73
+ "volume": "Volume",
74
+ }
75
+ )
76
+ for col in ["Open", "High", "Low", "Close", "Volume"]:
77
+ if col not in df.columns:
78
+ df[col] = 0.0 if col == "Volume" else pd.NA
79
+ df[["Open", "High", "Low", "Close", "Volume"]] = df[
80
+ ["Open", "High", "Low", "Close", "Volume"]
81
+ ].apply(pd.to_numeric, errors="coerce")
82
+ df = df.dropna(subset=["Open", "High", "Low", "Close"])
83
+ return df[["Open", "High", "Low", "Close", "Volume"]]
84
+ except Exception:
85
+ return pd.DataFrame(columns=["Open", "High", "Low", "Close", "Volume"])
86
+
87
+ def get_latest_price(self, symbol: str) -> Optional[float]:
88
+ if not self._available:
89
+ return None
90
+ try:
91
+ td_symbol = self._to_twelve_symbol(symbol)
92
+ params = {"symbol": td_symbol, "apikey": self.api_key}
93
+ response = requests.get(f"{self.base_url}/price", params=params, timeout=10)
94
+ data = response.json()
95
+ if "price" in data:
96
+ return float(data["price"])
97
+ return None
98
+ except Exception:
99
+ return None
100
+
101
+ def get_historical_data(self, symbol: str, period: str = "1d", interval: str = "1h") -> pd.DataFrame:
102
+ now = datetime.now()
103
+ period_map = {
104
+ "1d": timedelta(days=1),
105
+ "5d": timedelta(days=5),
106
+ "1mo": timedelta(days=30),
107
+ "3mo": timedelta(days=90),
108
+ "6mo": timedelta(days=180),
109
+ "1y": timedelta(days=365),
110
+ }
111
+ delta = period_map.get(period, timedelta(days=1))
112
+ start = (now - delta).strftime("%Y-%m-%d")
113
+ end = now.strftime("%Y-%m-%d")
114
+ return self.get_data(symbol, start, end, interval)
115
+
116
+ def is_available(self) -> bool:
117
+ return self._available
@@ -0,0 +1,246 @@
1
+ """Yahoo Finance data provider implementation."""
2
+
3
+ import yfinance as yf
4
+ import pandas as pd
5
+ from typing import Optional
6
+ from ...core.interfaces import IDataProvider
7
+
8
+
9
+ class YFinanceProvider(IDataProvider):
10
+ """Yahoo Finance data provider."""
11
+
12
+ def __init__(self):
13
+ self._available = True
14
+
15
+ def get_data(self, symbol: str, start: str, end: str,
16
+ interval: str = "1h") -> pd.DataFrame:
17
+ """Get historical data from Yahoo Finance."""
18
+ try:
19
+ # Convert dates to timezone-naive datetime objects
20
+ start_date = pd.to_datetime(start).tz_localize(None) if pd.to_datetime(start).tz is not None else pd.to_datetime(start)
21
+ end_date = pd.to_datetime(end).tz_localize(None) if pd.to_datetime(end).tz is not None else pd.to_datetime(end)
22
+
23
+ # Convert forex symbol to yfinance format if needed
24
+ if not symbol.endswith('=X'):
25
+ symbol = symbol.upper() + "=X"
26
+
27
+ ticker = yf.Ticker(symbol)
28
+ date_range_days = (end_date - start_date).days
29
+
30
+ # Try different approaches to get data
31
+ df = pd.DataFrame()
32
+
33
+ # First try: Get data with the requested interval
34
+ try:
35
+ if date_range_days > 365:
36
+ # For long ranges, use daily data and resample
37
+ df = ticker.history(start=start_date, end=end_date, interval="1d", auto_adjust=True, progress=False)
38
+ else:
39
+ # For shorter ranges, use the requested interval
40
+ df = ticker.history(start=start_date, end=end_date, interval=interval, auto_adjust=True, progress=False)
41
+
42
+ # Convert index to timezone-naive if needed
43
+ if not df.empty and df.index.tz is not None:
44
+ df.index = df.index.tz_localize(None)
45
+ except Exception:
46
+ pass
47
+
48
+ # Second try: Get maximum available data if first attempt failed
49
+ if df.empty:
50
+ try:
51
+ df = ticker.history(period="max", interval="1d", auto_adjust=True, progress=False)
52
+ if not df.empty:
53
+ # Convert index to timezone-naive for comparison
54
+ df.index = df.index.tz_localize(None) if df.index.tz is not None else df.index
55
+ # Filter to requested date range
56
+ df = df[(df.index >= start_date) & (df.index <= end_date)]
57
+ except Exception:
58
+ pass
59
+
60
+ # Third try: Get recent data if all else fails
61
+ if df.empty:
62
+ try:
63
+ df = ticker.history(period="5d", interval="1d", auto_adjust=True, progress=False)
64
+ # Convert index to timezone-naive if needed
65
+ if not df.empty and df.index.tz is not None:
66
+ df.index = df.index.tz_localize(None)
67
+ except Exception:
68
+ pass
69
+
70
+ if df.empty:
71
+ print(f"Warning: No data available for {symbol}")
72
+ return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
73
+
74
+ # Resample if needed and interval is not daily
75
+ if not df.empty and interval != "1d" and len(df) > 1:
76
+ try:
77
+ # Convert interval to pandas frequency format
78
+ freq_map = {'1h': '1h', '4h': '4h', '1m': '1T'}
79
+ freq = freq_map.get(interval, interval)
80
+ df = df.resample(freq).interpolate(method='linear')
81
+ except Exception:
82
+ pass
83
+
84
+ # Ensure proper column names
85
+ df = df.rename(columns={
86
+ 'Open': 'Open',
87
+ 'High': 'High',
88
+ 'Low': 'Low',
89
+ 'Close': 'Close',
90
+ 'Volume': 'Volume'
91
+ })
92
+
93
+ # Fill missing volume data
94
+ if 'Volume' not in df.columns or df['Volume'].isna().all():
95
+ df['Volume'] = 1000000 # Default volume for forex
96
+
97
+ # Ensure all required columns exist
98
+ required_columns = ['Open', 'High', 'Low', 'Close', 'Volume']
99
+ for col in required_columns:
100
+ if col not in df.columns:
101
+ if col == 'Volume':
102
+ df[col] = 1000000
103
+ else:
104
+ df[col] = df['Close'] # Use Close price as fallback
105
+
106
+ return df.dropna()
107
+
108
+ except Exception as e:
109
+ print(f"Error getting data for {symbol}: {e}")
110
+ return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
111
+
112
+ def _generate_synthetic_data(self, start_date, end_date, interval):
113
+ """Generate synthetic forex data for testing purposes."""
114
+ import numpy as np
115
+
116
+ # Create date range
117
+ if interval == "1h":
118
+ freq = '1h'
119
+ elif interval == "4h":
120
+ freq = '4h'
121
+ elif interval == "1m":
122
+ freq = '1T'
123
+ else:
124
+ freq = '1d'
125
+
126
+ date_range = pd.date_range(start=start_date, end=end_date, freq=freq)
127
+
128
+ if len(date_range) == 0:
129
+ # Fallback to daily if no data points
130
+ date_range = pd.date_range(start=start_date, end=end_date, freq='1d')
131
+
132
+ # Generate synthetic price data (starting around 1.1000 for EURUSD-like pair)
133
+ base_price = 1.1000
134
+ n_points = len(date_range)
135
+
136
+ # Generate random walk
137
+ returns = np.random.normal(0, 0.001, n_points) # 0.1% daily volatility
138
+ prices = [base_price]
139
+
140
+ for i in range(1, n_points):
141
+ new_price = prices[-1] * (1 + returns[i])
142
+ prices.append(new_price)
143
+
144
+ # Create OHLC data
145
+ data = []
146
+ for i, (date, price) in enumerate(zip(date_range, prices)):
147
+ # Add some intraday variation
148
+ high = price * (1 + abs(np.random.normal(0, 0.0005)))
149
+ low = price * (1 - abs(np.random.normal(0, 0.0005)))
150
+ open_price = price * (1 + np.random.normal(0, 0.0002))
151
+ close_price = price
152
+
153
+ data.append({
154
+ 'Open': open_price,
155
+ 'High': max(open_price, high, close_price),
156
+ 'Low': min(open_price, low, close_price),
157
+ 'Close': close_price,
158
+ 'Volume': 1000000
159
+ })
160
+
161
+ df = pd.DataFrame(data, index=date_range)
162
+ return df
163
+
164
+ def get_latest_price(self, symbol: str) -> Optional[float]:
165
+ """Get latest price from Yahoo Finance."""
166
+ try:
167
+ # Convert forex symbol to yfinance format if needed
168
+ if not symbol.endswith('=X'):
169
+ symbol = symbol.upper() + "=X"
170
+
171
+ ticker = yf.Ticker(symbol)
172
+
173
+ # Try to get price from info first
174
+ try:
175
+ info = ticker.info
176
+ if 'regularMarketPrice' in info and info['regularMarketPrice'] is not None:
177
+ return float(info['regularMarketPrice'])
178
+ elif 'currentPrice' in info and info['currentPrice'] is not None:
179
+ return float(info['currentPrice'])
180
+ except Exception:
181
+ pass
182
+
183
+ # Fallback to recent data
184
+ try:
185
+ df = ticker.history(period="1d", interval="1m", progress=False)
186
+ if not df.empty and 'Close' in df.columns:
187
+ return float(df['Close'].iloc[-1])
188
+ except Exception:
189
+ pass
190
+
191
+ # Second fallback: try daily data
192
+ try:
193
+ df = ticker.history(period="5d", interval="1d", progress=False)
194
+ if not df.empty and 'Close' in df.columns:
195
+ return float(df['Close'].iloc[-1])
196
+ except Exception:
197
+ pass
198
+
199
+ except Exception as e:
200
+ print(f"Error getting latest price for {symbol}: {e}")
201
+
202
+ # Return None if all attempts fail
203
+ return None
204
+
205
+ def get_historical_data(self, symbol: str, period: str = '1d', interval: str = '1h') -> pd.DataFrame:
206
+ """Get historical data for a symbol with specified period and interval."""
207
+ try:
208
+ # Convert period to start/end dates
209
+ from datetime import datetime, timedelta
210
+
211
+ # Use timezone-naive datetime objects
212
+ now = datetime.now()
213
+
214
+ if period == '1d':
215
+ end_date = now
216
+ start_date = now - timedelta(days=1)
217
+ elif period == '5d':
218
+ end_date = now
219
+ start_date = now - timedelta(days=5)
220
+ elif period == '1mo':
221
+ end_date = now
222
+ start_date = now - timedelta(days=30)
223
+ elif period == '3mo':
224
+ end_date = now
225
+ start_date = now - timedelta(days=90)
226
+ elif period == '6mo':
227
+ end_date = now
228
+ start_date = now - timedelta(days=180)
229
+ elif period == '1y':
230
+ end_date = now
231
+ start_date = now - timedelta(days=365)
232
+ else:
233
+ # Default to 1 day
234
+ end_date = now
235
+ start_date = now - timedelta(days=1)
236
+
237
+ # Use the existing get_data method
238
+ return self.get_data(symbol, start_date.strftime('%Y-%m-%d'), end_date.strftime('%Y-%m-%d'), interval)
239
+
240
+ except Exception as e:
241
+ print(f"Error getting historical data for {symbol}: {e}")
242
+ return pd.DataFrame(columns=['Open', 'High', 'Low', 'Close', 'Volume'])
243
+
244
+ def is_available(self) -> bool:
245
+ """Check if Yahoo Finance is available."""
246
+ return self._available
@@ -0,0 +1,28 @@
1
+ """
2
+ Analytics module for ForexSmartBot v3.2.0
3
+ Provides portfolio analytics, risk analytics, and advanced charting features.
4
+ """
5
+
6
+ from .portfolio_analytics import PortfolioAnalytics
7
+ from .risk_analytics import RiskAnalytics
8
+ from .chart_patterns import ChartPatternRecognizer
9
+ from .performance_attribution import PerformanceAttribution
10
+ from .market_depth import MarketDepthWidget, MarketDepthProvider
11
+ from .correlation_matrix import CorrelationMatrixWidget, CorrelationAnalyzer
12
+ from .economic_calendar import EconomicCalendarWidget, EconomicCalendarProvider
13
+ from .trade_journal import TradeJournalWidget, TradeJournalManager
14
+
15
+ __all__ = [
16
+ 'PortfolioAnalytics',
17
+ 'RiskAnalytics',
18
+ 'ChartPatternRecognizer',
19
+ 'PerformanceAttribution',
20
+ 'MarketDepthWidget',
21
+ 'MarketDepthProvider',
22
+ 'CorrelationMatrixWidget',
23
+ 'CorrelationAnalyzer',
24
+ 'EconomicCalendarWidget',
25
+ 'EconomicCalendarProvider',
26
+ 'TradeJournalWidget',
27
+ 'TradeJournalManager',
28
+ ]