bbstrader 0.2.0__py3-none-any.whl → 0.2.1__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.
Potentially problematic release.
This version of bbstrader might be problematic. Click here for more details.
- bbstrader/__ini__.py +7 -7
- bbstrader/btengine/__init__.py +7 -7
- bbstrader/btengine/backtest.py +30 -26
- bbstrader/btengine/data.py +92 -81
- bbstrader/btengine/event.py +2 -1
- bbstrader/btengine/execution.py +18 -16
- bbstrader/btengine/performance.py +11 -7
- bbstrader/btengine/portfolio.py +35 -36
- bbstrader/btengine/strategy.py +113 -92
- bbstrader/config.py +12 -10
- bbstrader/core/data.py +4 -5
- bbstrader/core/utils.py +57 -0
- bbstrader/ibkr/utils.py +0 -0
- bbstrader/metatrader/__init__.py +5 -5
- bbstrader/metatrader/account.py +117 -121
- bbstrader/metatrader/rates.py +81 -78
- bbstrader/metatrader/risk.py +23 -37
- bbstrader/metatrader/trade.py +154 -138
- bbstrader/metatrader/utils.py +3 -3
- bbstrader/models/__init__.py +5 -5
- bbstrader/models/factors.py +17 -12
- bbstrader/models/ml.py +371 -305
- bbstrader/models/optimization.py +14 -12
- bbstrader/models/portfolio.py +44 -35
- bbstrader/models/risk.py +15 -9
- bbstrader/trading/__init__.py +2 -2
- bbstrader/trading/execution.py +245 -179
- bbstrader/trading/scripts.py +8 -4
- bbstrader/trading/strategies.py +78 -65
- bbstrader/tseries.py +124 -98
- {bbstrader-0.2.0.dist-info → bbstrader-0.2.1.dist-info}/LICENSE +1 -1
- {bbstrader-0.2.0.dist-info → bbstrader-0.2.1.dist-info}/METADATA +2 -1
- bbstrader-0.2.1.dist-info/RECORD +37 -0
- bbstrader-0.2.0.dist-info/RECORD +0 -36
- {bbstrader-0.2.0.dist-info → bbstrader-0.2.1.dist-info}/WHEEL +0 -0
- {bbstrader-0.2.0.dist-info → bbstrader-0.2.1.dist-info}/top_level.txt +0 -0
bbstrader/__ini__.py
CHANGED
|
@@ -4,15 +4,15 @@ Simplified Investment & Trading Toolkit
|
|
|
4
4
|
|
|
5
5
|
"""
|
|
6
6
|
__author__ = "Bertin Balouki SIMYELI"
|
|
7
|
-
__copyright__ = "2023-
|
|
7
|
+
__copyright__ = "2023-2025 Bertin Balouki SIMYELI"
|
|
8
8
|
__email__ = "bertin@bbstrader.com"
|
|
9
9
|
__license__ = "MIT"
|
|
10
10
|
__version__ = '0.2.0'
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
from bbstrader import btengine
|
|
14
|
-
from bbstrader import metatrader
|
|
15
|
-
from bbstrader import models
|
|
16
|
-
from bbstrader import trading
|
|
17
|
-
from bbstrader import tseries
|
|
18
|
-
from .config import config_logger
|
|
13
|
+
from bbstrader import btengine # noqa: F401
|
|
14
|
+
from bbstrader import metatrader # noqa: F401
|
|
15
|
+
from bbstrader import models # noqa: F401
|
|
16
|
+
from bbstrader import trading # noqa: F401
|
|
17
|
+
from bbstrader import tseries # noqa: F401
|
|
18
|
+
from .config import config_logger # noqa: F401
|
bbstrader/btengine/__init__.py
CHANGED
|
@@ -45,10 +45,10 @@ Notes
|
|
|
45
45
|
|
|
46
46
|
See `bbstrader.btengine.backtest.run_backtest` for more details on the backtesting process and its parameters.
|
|
47
47
|
"""
|
|
48
|
-
from bbstrader.btengine.
|
|
49
|
-
from bbstrader.btengine.
|
|
50
|
-
from bbstrader.btengine.
|
|
51
|
-
from bbstrader.btengine.
|
|
52
|
-
from bbstrader.btengine.
|
|
53
|
-
from bbstrader.btengine.
|
|
54
|
-
from bbstrader.btengine.
|
|
48
|
+
from bbstrader.btengine.backtest import * # noqa: F403
|
|
49
|
+
from bbstrader.btengine.data import * # noqa: F403
|
|
50
|
+
from bbstrader.btengine.event import * # noqa: F403
|
|
51
|
+
from bbstrader.btengine.execution import * # noqa: F403
|
|
52
|
+
from bbstrader.btengine.performance import * # noqa: F403
|
|
53
|
+
from bbstrader.btengine.portfolio import * # noqa: F403
|
|
54
|
+
from bbstrader.btengine.strategy import * # noqa: F403
|
bbstrader/btengine/backtest.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import pprint
|
|
2
1
|
import queue
|
|
3
2
|
import time
|
|
4
|
-
import yfinance as yf
|
|
5
|
-
from queue import Queue
|
|
6
3
|
from datetime import datetime
|
|
7
|
-
from
|
|
8
|
-
|
|
4
|
+
from typing import List, Literal, Optional
|
|
5
|
+
|
|
6
|
+
from tabulate import tabulate
|
|
7
|
+
|
|
8
|
+
from bbstrader.btengine.data import DataHandler
|
|
9
|
+
from bbstrader.btengine.execution import ExecutionHandler, SimExecutionHandler
|
|
9
10
|
from bbstrader.btengine.portfolio import Portfolio
|
|
10
|
-
from bbstrader.btengine.event import SignalEvent
|
|
11
11
|
from bbstrader.btengine.strategy import Strategy
|
|
12
|
-
from typing import Literal, Optional, List
|
|
13
|
-
from tabulate import tabulate
|
|
14
12
|
|
|
15
13
|
__all__ = [
|
|
16
14
|
"Backtest",
|
|
@@ -18,6 +16,7 @@ __all__ = [
|
|
|
18
16
|
"run_backtest"
|
|
19
17
|
]
|
|
20
18
|
|
|
19
|
+
|
|
21
20
|
class Backtest(object):
|
|
22
21
|
"""
|
|
23
22
|
The `Backtest()` object encapsulates the event-handling logic and essentially
|
|
@@ -60,6 +59,7 @@ class Backtest(object):
|
|
|
60
59
|
|
|
61
60
|
class BacktestEngine(Backtest):
|
|
62
61
|
__doc__ = Backtest.__doc__
|
|
62
|
+
|
|
63
63
|
def __init__(
|
|
64
64
|
self,
|
|
65
65
|
symbol_list: List[str],
|
|
@@ -141,7 +141,7 @@ class BacktestEngine(Backtest):
|
|
|
141
141
|
while True:
|
|
142
142
|
i += 1
|
|
143
143
|
value = self.portfolio.all_holdings[-1]['Total']
|
|
144
|
-
if self.data_handler.continue_backtest
|
|
144
|
+
if self.data_handler.continue_backtest is True:
|
|
145
145
|
# Update the market bars
|
|
146
146
|
self.data_handler.update_bars()
|
|
147
147
|
self.strategy.check_pending_orders()
|
|
@@ -152,7 +152,8 @@ class BacktestEngine(Backtest):
|
|
|
152
152
|
self.strategy.cash = value
|
|
153
153
|
else:
|
|
154
154
|
print("\n[======= BACKTEST COMPLETED =======]")
|
|
155
|
-
print(
|
|
155
|
+
print(
|
|
156
|
+
f"END DATE: {self.data_handler.get_latest_bar_datetime(self.symbol_list[0])}")
|
|
156
157
|
print(f"TOTAL BARS: {i} ")
|
|
157
158
|
print(f"PORFOLIO VALUE: {round(value, 2)}")
|
|
158
159
|
break
|
|
@@ -198,7 +199,8 @@ class BacktestEngine(Backtest):
|
|
|
198
199
|
stat2['Orders'] = self.orders
|
|
199
200
|
stat2['Fills'] = self.fills
|
|
200
201
|
stats.extend(stat2.items())
|
|
201
|
-
tab_stats = tabulate(
|
|
202
|
+
tab_stats = tabulate(
|
|
203
|
+
stats, headers=["Metric", "Value"], tablefmt="outline")
|
|
202
204
|
print(tab_stats, "\n")
|
|
203
205
|
if self.stats_file:
|
|
204
206
|
with open(self.stats_file, 'a') as f:
|
|
@@ -211,16 +213,16 @@ class BacktestEngine(Backtest):
|
|
|
211
213
|
print("\n[======= PORTFOLIO SUMMARY =======]")
|
|
212
214
|
print(
|
|
213
215
|
tabulate(
|
|
214
|
-
self.portfolio.equity_curve.tail(10),
|
|
215
|
-
headers="keys",
|
|
216
|
-
tablefmt="outline"),
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
self.portfolio.equity_curve.tail(10),
|
|
217
|
+
headers="keys",
|
|
218
|
+
tablefmt="outline"),
|
|
219
|
+
"\n"
|
|
220
|
+
)
|
|
221
|
+
|
|
220
222
|
def simulate_trading(self):
|
|
221
223
|
"""
|
|
222
224
|
Simulates the backtest and outputs portfolio performance.
|
|
223
|
-
|
|
225
|
+
|
|
224
226
|
Returns:
|
|
225
227
|
pd.DataFrame: The portfilio values over time (capital, equity, returns etc.)
|
|
226
228
|
"""
|
|
@@ -244,9 +246,9 @@ def run_backtest(
|
|
|
244
246
|
|
|
245
247
|
Args:
|
|
246
248
|
symbol_list (List[str]): List of symbol strings for the assets to be backtested.
|
|
247
|
-
|
|
249
|
+
|
|
248
250
|
start_date (datetime): Start date of the backtest.
|
|
249
|
-
|
|
251
|
+
|
|
250
252
|
data_handler (DataHandler): An instance of the `DataHandler` class, responsible for managing
|
|
251
253
|
and processing market data. Available options include `CSVDataHandler`,
|
|
252
254
|
`MT5DataHandler`, and `YFDataHandler`. Ensure that the `DataHandler`
|
|
@@ -275,7 +277,7 @@ def run_backtest(
|
|
|
275
277
|
|
|
276
278
|
**kwargs: Additional parameters passed to the `Backtest` instance, which may include strategy-specific,
|
|
277
279
|
data handler, portfolio, or execution handler options.
|
|
278
|
-
|
|
280
|
+
|
|
279
281
|
Returns:
|
|
280
282
|
pd.DataFrame: The portfolio values over time (capital, equities, returns etc.).
|
|
281
283
|
|
|
@@ -326,10 +328,12 @@ def run_backtest(
|
|
|
326
328
|
return portfolio
|
|
327
329
|
|
|
328
330
|
|
|
329
|
-
class CerebroEngine
|
|
331
|
+
class CerebroEngine:
|
|
332
|
+
...
|
|
330
333
|
|
|
331
334
|
|
|
332
|
-
class ZiplineEngine
|
|
335
|
+
class ZiplineEngine:
|
|
336
|
+
...
|
|
333
337
|
|
|
334
338
|
|
|
335
339
|
def run_backtest_with(engine: Literal["bbstrader", "cerebro", "zipline"], **kwargs):
|
|
@@ -347,8 +351,8 @@ def run_backtest_with(engine: Literal["bbstrader", "cerebro", "zipline"], **kwar
|
|
|
347
351
|
**kwargs
|
|
348
352
|
)
|
|
349
353
|
elif engine == "cerebro":
|
|
350
|
-
#TODO:
|
|
354
|
+
# TODO:
|
|
351
355
|
pass
|
|
352
356
|
elif engine == "zipline":
|
|
353
|
-
#TODO:
|
|
354
|
-
pass
|
|
357
|
+
# TODO:
|
|
358
|
+
pass
|
bbstrader/btengine/data.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import os.path
|
|
2
|
+
from abc import ABCMeta, abstractmethod
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from queue import Queue
|
|
5
|
+
from typing import Dict, List
|
|
6
|
+
|
|
2
7
|
import numpy as np
|
|
3
8
|
import pandas as pd
|
|
4
9
|
import yfinance as yf
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
from typing import List, Dict
|
|
7
|
-
from queue import Queue
|
|
8
|
-
from abc import ABCMeta, abstractmethod
|
|
9
|
-
from bbstrader.metatrader.rates import download_historical_data
|
|
10
|
-
from bbstrader.btengine.event import MarketEvent
|
|
11
|
-
from bbstrader.config import BBSTRADER_DIR
|
|
12
|
-
from datetime import datetime
|
|
13
10
|
from eodhd import APIClient
|
|
14
11
|
from financetoolkit import Toolkit
|
|
15
12
|
from pytz import timezone
|
|
16
13
|
|
|
14
|
+
from bbstrader.btengine.event import MarketEvent
|
|
15
|
+
from bbstrader.config import BBSTRADER_DIR
|
|
16
|
+
from bbstrader.metatrader.rates import download_historical_data
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"DataHandler",
|
|
@@ -47,12 +47,15 @@ class DataHandler(metaclass=ABCMeta):
|
|
|
47
47
|
@property
|
|
48
48
|
def symbols(self) -> List[str]:
|
|
49
49
|
pass
|
|
50
|
+
|
|
50
51
|
@property
|
|
51
52
|
def data(self) -> Dict[str, pd.DataFrame]:
|
|
52
53
|
pass
|
|
54
|
+
|
|
53
55
|
@property
|
|
54
56
|
def labels(self) -> List[str]:
|
|
55
57
|
pass
|
|
58
|
+
|
|
56
59
|
@property
|
|
57
60
|
def index(self) -> str | List[str]:
|
|
58
61
|
pass
|
|
@@ -122,16 +125,15 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
122
125
|
|
|
123
126
|
"""
|
|
124
127
|
|
|
125
|
-
def __init__(self, events: Queue,
|
|
126
|
-
symbol_list: List[str],
|
|
127
|
-
csv_dir: str,
|
|
128
|
-
columns: List[str]=None,
|
|
128
|
+
def __init__(self, events: Queue,
|
|
129
|
+
symbol_list: List[str],
|
|
130
|
+
csv_dir: str,
|
|
131
|
+
columns: List[str] = None,
|
|
129
132
|
index_col: str | int | List[str] | List[int] = 0):
|
|
130
|
-
|
|
131
133
|
"""
|
|
132
134
|
Initialises the data handler by requesting the location of the CSV files
|
|
133
135
|
and a list of symbols.
|
|
134
|
-
|
|
136
|
+
|
|
135
137
|
Args:
|
|
136
138
|
events : The Event Queue.
|
|
137
139
|
symbol_list : A list of symbol strings.
|
|
@@ -151,35 +153,36 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
151
153
|
self._load_and_process_data()
|
|
152
154
|
|
|
153
155
|
@property
|
|
154
|
-
def symbols(self)-> List[str]:
|
|
156
|
+
def symbols(self) -> List[str]:
|
|
155
157
|
return self.symbol_list
|
|
156
|
-
|
|
158
|
+
|
|
157
159
|
@property
|
|
158
|
-
def data(self)-> Dict[str, pd.DataFrame]:
|
|
160
|
+
def data(self) -> Dict[str, pd.DataFrame]:
|
|
159
161
|
return self.symbol_data
|
|
160
|
-
|
|
162
|
+
|
|
161
163
|
@property
|
|
162
|
-
def datadir(self)-> str:
|
|
164
|
+
def datadir(self) -> str:
|
|
163
165
|
return self.csv_dir
|
|
164
|
-
|
|
166
|
+
|
|
165
167
|
@property
|
|
166
|
-
def labels(self)-> List[str]:
|
|
168
|
+
def labels(self) -> List[str]:
|
|
167
169
|
return self.columns
|
|
168
|
-
|
|
170
|
+
|
|
169
171
|
@property
|
|
170
|
-
def index(self)-> str | List[str]:
|
|
172
|
+
def index(self) -> str | List[str]:
|
|
171
173
|
return self._index
|
|
172
|
-
|
|
174
|
+
|
|
173
175
|
def _load_and_process_data(self):
|
|
174
176
|
"""
|
|
175
177
|
Opens the CSV files from the data directory, converting
|
|
176
178
|
them into pandas DataFrames within a symbol dictionary.
|
|
177
179
|
"""
|
|
178
180
|
default_names = pd.read_csv(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
+
os.path.join(self.csv_dir, f'{self.symbol_list[0]}.csv')
|
|
182
|
+
).columns.to_list()
|
|
181
183
|
new_names = self.columns or default_names
|
|
182
|
-
new_names = [name.strip().lower().replace(' ', '_')
|
|
184
|
+
new_names = [name.strip().lower().replace(' ', '_')
|
|
185
|
+
for name in new_names]
|
|
183
186
|
self.columns = new_names
|
|
184
187
|
assert 'adj_close' in new_names or 'close' in new_names, \
|
|
185
188
|
"Column names must contain 'Adj Close' and 'Close' or adj_close and close"
|
|
@@ -288,7 +291,8 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
288
291
|
try:
|
|
289
292
|
return getattr(bars_list[-1][1], val_type)
|
|
290
293
|
except AttributeError:
|
|
291
|
-
print(
|
|
294
|
+
print(
|
|
295
|
+
f"Value type {val_type} not available in the historical data set.")
|
|
292
296
|
raise
|
|
293
297
|
|
|
294
298
|
def get_latest_bars_values(self, symbol: str, val_type: str, N=1) -> np.ndarray:
|
|
@@ -305,7 +309,8 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
305
309
|
try:
|
|
306
310
|
return np.array([getattr(b[1], val_type) for b in bars_list])
|
|
307
311
|
except AttributeError:
|
|
308
|
-
print(
|
|
312
|
+
print(
|
|
313
|
+
f"Value type {val_type} not available in the historical data set.")
|
|
309
314
|
raise
|
|
310
315
|
|
|
311
316
|
def update_bars(self):
|
|
@@ -346,18 +351,18 @@ class CSVDataHandler(BaseCSVDataHandler):
|
|
|
346
351
|
events (Queue): The Event Queue.
|
|
347
352
|
symbol_list (List[str]): A list of symbol strings.
|
|
348
353
|
csv_dir (str): Absolute directory path to the CSV files.
|
|
349
|
-
|
|
354
|
+
|
|
350
355
|
NOTE:
|
|
351
356
|
All csv fille can be strored in 'Home/.bbstrader/csv_data'
|
|
352
357
|
|
|
353
358
|
"""
|
|
354
359
|
csv_dir = kwargs.get("csv_dir")
|
|
355
|
-
csv_dir =
|
|
360
|
+
csv_dir = csv_dir or BBSTRADER_DIR / 'csv_data'
|
|
356
361
|
super().__init__(
|
|
357
|
-
events,
|
|
358
|
-
symbol_list,
|
|
362
|
+
events,
|
|
363
|
+
symbol_list,
|
|
359
364
|
csv_dir,
|
|
360
|
-
columns
|
|
365
|
+
columns=kwargs.get('columns'),
|
|
361
366
|
index_col=kwargs.get('index_col', 0)
|
|
362
367
|
)
|
|
363
368
|
|
|
@@ -391,35 +396,35 @@ class MT5DataHandler(BaseCSVDataHandler):
|
|
|
391
396
|
See `bbstrader.metatrader.rates.Rates` for other arguments.
|
|
392
397
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
393
398
|
"""
|
|
394
|
-
self.tf
|
|
395
|
-
self.start
|
|
396
|
-
self.end
|
|
397
|
-
self.use_utc
|
|
398
|
-
self.filer
|
|
399
|
-
self.fill_na
|
|
399
|
+
self.tf = kwargs.get('time_frame', 'D1')
|
|
400
|
+
self.start = kwargs.get('mt5_start', datetime(2000, 1, 1))
|
|
401
|
+
self.end = kwargs.get('mt5_end', datetime.now())
|
|
402
|
+
self.use_utc = kwargs.get('use_utc', False)
|
|
403
|
+
self.filer = kwargs.get('filter', False)
|
|
404
|
+
self.fill_na = kwargs.get('fill_na', False)
|
|
400
405
|
self.lower_cols = kwargs.get('lower_cols', True)
|
|
401
|
-
self.data_dir
|
|
406
|
+
self.data_dir = kwargs.get('data_dir')
|
|
402
407
|
self.symbol_list = symbol_list
|
|
403
408
|
self.kwargs = kwargs
|
|
404
|
-
|
|
409
|
+
|
|
405
410
|
csv_dir = self._download_and_cache_data(self.data_dir)
|
|
406
411
|
super().__init__(
|
|
407
412
|
events,
|
|
408
413
|
symbol_list,
|
|
409
414
|
csv_dir,
|
|
410
|
-
columns
|
|
415
|
+
columns=kwargs.get('columns'),
|
|
411
416
|
index_col=kwargs.get('index_col', 0)
|
|
412
417
|
)
|
|
413
418
|
|
|
414
419
|
def _download_and_cache_data(self, cache_dir: str):
|
|
415
|
-
data_dir = cache_dir or BBSTRADER_DIR /
|
|
420
|
+
data_dir = cache_dir or BBSTRADER_DIR / 'mt5' / self.tf
|
|
416
421
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
417
422
|
for symbol in self.symbol_list:
|
|
418
423
|
try:
|
|
419
424
|
data = download_historical_data(
|
|
420
425
|
symbol=symbol,
|
|
421
426
|
timeframe=self.tf,
|
|
422
|
-
date_from=self.start,
|
|
427
|
+
date_from=self.start,
|
|
423
428
|
date_to=self.end,
|
|
424
429
|
utc=self.use_utc,
|
|
425
430
|
filter=self.filer,
|
|
@@ -460,17 +465,17 @@ class YFDataHandler(BaseCSVDataHandler):
|
|
|
460
465
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
461
466
|
"""
|
|
462
467
|
self.symbol_list = symbol_list
|
|
463
|
-
self.start_date
|
|
464
|
-
self.end_date
|
|
465
|
-
self.cache_dir
|
|
466
|
-
|
|
468
|
+
self.start_date = kwargs.get('yf_start')
|
|
469
|
+
self.end_date = kwargs.get('yf_end', datetime.now())
|
|
470
|
+
self.cache_dir = kwargs.get('data_dir')
|
|
471
|
+
|
|
467
472
|
csv_dir = self._download_and_cache_data(self.cache_dir)
|
|
468
|
-
|
|
473
|
+
|
|
469
474
|
super().__init__(
|
|
470
475
|
events,
|
|
471
476
|
symbol_list,
|
|
472
477
|
csv_dir,
|
|
473
|
-
columns
|
|
478
|
+
columns=kwargs.get('columns'),
|
|
474
479
|
index_col=kwargs.get('index_col', 0)
|
|
475
480
|
)
|
|
476
481
|
|
|
@@ -482,7 +487,7 @@ class YFDataHandler(BaseCSVDataHandler):
|
|
|
482
487
|
filepath = os.path.join(cache_dir, f"{symbol}.csv")
|
|
483
488
|
try:
|
|
484
489
|
data = yf.download(
|
|
485
|
-
symbol, start=self.start_date, end=self.end_date,
|
|
490
|
+
symbol, start=self.start_date, end=self.end_date,
|
|
486
491
|
multi_level_index=False, progress=False)
|
|
487
492
|
if data.empty:
|
|
488
493
|
raise ValueError(f"No data found for {symbol}")
|
|
@@ -500,6 +505,7 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
500
505
|
To use this class, you need to sign up for an API key at
|
|
501
506
|
https://eodhistoricaldata.com/ and provide the key as an argument.
|
|
502
507
|
"""
|
|
508
|
+
|
|
503
509
|
def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
|
|
504
510
|
"""
|
|
505
511
|
Args:
|
|
@@ -515,19 +521,20 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
515
521
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
516
522
|
"""
|
|
517
523
|
self.symbol_list = symbol_list
|
|
518
|
-
self.start_date
|
|
519
|
-
self.end_date
|
|
520
|
-
|
|
521
|
-
self.
|
|
522
|
-
self.
|
|
524
|
+
self.start_date = kwargs.get('eodhd_start')
|
|
525
|
+
self.end_date = kwargs.get(
|
|
526
|
+
'eodhd_end', datetime.now().strftime('%Y-%m-%d'))
|
|
527
|
+
self.period = kwargs.get('eodhd_period', 'd')
|
|
528
|
+
self.cache_dir = kwargs.get('data_dir')
|
|
529
|
+
self.__api_key = kwargs.get('eodhd_api_key', 'demo')
|
|
523
530
|
|
|
524
531
|
csv_dir = self._download_and_cache_data(self.cache_dir)
|
|
525
|
-
|
|
532
|
+
|
|
526
533
|
super().__init__(
|
|
527
|
-
events,
|
|
534
|
+
events,
|
|
528
535
|
symbol_list,
|
|
529
536
|
csv_dir,
|
|
530
|
-
columns
|
|
537
|
+
columns=kwargs.get('columns'),
|
|
531
538
|
index_col=kwargs.get('index_col', 0)
|
|
532
539
|
)
|
|
533
540
|
|
|
@@ -538,7 +545,7 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
538
545
|
if period in ['d', 'w', 'm']:
|
|
539
546
|
return client.get_historical_data(
|
|
540
547
|
symbol=symbol,
|
|
541
|
-
interval=period,
|
|
548
|
+
interval=period,
|
|
542
549
|
iso8601_start=self.start_date,
|
|
543
550
|
iso8601_end=self.end_date,
|
|
544
551
|
)
|
|
@@ -553,11 +560,11 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
553
560
|
unix_end = int(enddt.timestamp())
|
|
554
561
|
return client.get_intraday_historical_data(
|
|
555
562
|
symbol=symbol,
|
|
556
|
-
interval=period,
|
|
563
|
+
interval=period,
|
|
557
564
|
from_unix_time=unix_start,
|
|
558
565
|
to_unix_time=unix_end,
|
|
559
566
|
)
|
|
560
|
-
|
|
567
|
+
|
|
561
568
|
def _forma_data(self, data: List[Dict] | pd.DataFrame) -> pd.DataFrame:
|
|
562
569
|
if isinstance(data, pd.DataFrame):
|
|
563
570
|
if data.empty or len(data) == 0:
|
|
@@ -565,7 +572,7 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
565
572
|
df = data.drop(labels=['symbol', 'interval'], axis=1)
|
|
566
573
|
df = df.rename(columns={'adjusted_close': 'adj_close'})
|
|
567
574
|
return df
|
|
568
|
-
|
|
575
|
+
|
|
569
576
|
elif isinstance(data, list):
|
|
570
577
|
if not data or len(data) == 0:
|
|
571
578
|
raise ValueError("No data found.")
|
|
@@ -573,11 +580,12 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
573
580
|
df = df.drop(columns=['timestamp', 'gmtoffset'], axis=1)
|
|
574
581
|
df = df.rename(columns={'datetime': 'date'})
|
|
575
582
|
df['adj_close'] = df['close']
|
|
576
|
-
df = df[['date', 'open', 'high', 'low',
|
|
583
|
+
df = df[['date', 'open', 'high', 'low',
|
|
584
|
+
'close', 'adj_close', 'volume']]
|
|
577
585
|
df.date = pd.to_datetime(df.date)
|
|
578
586
|
df = df.set_index('date')
|
|
579
587
|
return df
|
|
580
|
-
|
|
588
|
+
|
|
581
589
|
def _download_and_cache_data(self, cache_dir: str):
|
|
582
590
|
"""Downloads and caches historical data as CSV files."""
|
|
583
591
|
cache_dir = cache_dir or BBSTRADER_DIR / 'eodhd' / self.period
|
|
@@ -603,6 +611,7 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
603
611
|
provide the key as an argument.
|
|
604
612
|
|
|
605
613
|
"""
|
|
614
|
+
|
|
606
615
|
def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
|
|
607
616
|
"""
|
|
608
617
|
Args:
|
|
@@ -619,19 +628,20 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
619
628
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
620
629
|
"""
|
|
621
630
|
self.symbol_list = symbol_list
|
|
622
|
-
self.start_date
|
|
623
|
-
self.end_date
|
|
624
|
-
|
|
625
|
-
self.
|
|
626
|
-
self.
|
|
631
|
+
self.start_date = kwargs.get('fmp_start')
|
|
632
|
+
self.end_date = kwargs.get(
|
|
633
|
+
'fmp_end', datetime.now().strftime('%Y-%m-%d'))
|
|
634
|
+
self.period = kwargs.get('fmp_period', 'daily')
|
|
635
|
+
self.cache_dir = kwargs.get('data_dir')
|
|
636
|
+
self.__api_key = kwargs.get('fmp_api_key')
|
|
627
637
|
|
|
628
638
|
csv_dir = self._download_and_cache_data(self.cache_dir)
|
|
629
|
-
|
|
639
|
+
|
|
630
640
|
super().__init__(
|
|
631
641
|
events,
|
|
632
642
|
symbol_list,
|
|
633
643
|
csv_dir,
|
|
634
|
-
columns
|
|
644
|
+
columns=kwargs.get('columns'),
|
|
635
645
|
index_col=kwargs.get('index_col', 0)
|
|
636
646
|
)
|
|
637
647
|
|
|
@@ -641,7 +651,7 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
641
651
|
toolkit = Toolkit(
|
|
642
652
|
symbol,
|
|
643
653
|
api_key=self.__api_key,
|
|
644
|
-
start_date=self.start_date,
|
|
654
|
+
start_date=self.start_date,
|
|
645
655
|
end_date=self.end_date,
|
|
646
656
|
benchmark_ticker=None,
|
|
647
657
|
progress_bar=False
|
|
@@ -650,16 +660,17 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
650
660
|
return toolkit.get_historical_data(period=period, progress_bar=False)
|
|
651
661
|
elif period in ['1min', '5min', '15min', '30min', '1hour']:
|
|
652
662
|
return toolkit.get_intraday_data(period=period, progress_bar=False)
|
|
653
|
-
|
|
663
|
+
|
|
654
664
|
def _format_data(self, data: pd.DataFrame, period: str) -> pd.DataFrame:
|
|
655
665
|
if data.empty or len(data) == 0:
|
|
656
666
|
raise ValueError("No data found.")
|
|
657
667
|
if period[0].isnumeric():
|
|
658
|
-
data = data.drop(
|
|
668
|
+
data = data.drop(
|
|
669
|
+
columns=['Return', 'Volatility', 'Cumulative Return'], axis=1)
|
|
659
670
|
else:
|
|
660
|
-
data = data.drop(columns=['Dividends', 'Return', 'Volatility',
|
|
661
|
-
|
|
662
|
-
|
|
671
|
+
data = data.drop(columns=['Dividends', 'Return', 'Volatility',
|
|
672
|
+
'Excess Return', 'Excess Volatility',
|
|
673
|
+
'Cumulative Return'], axis=1)
|
|
663
674
|
data = data.reset_index()
|
|
664
675
|
if 'Adj Close' not in data.columns:
|
|
665
676
|
data['Adj Close'] = data['Close']
|
|
@@ -667,7 +678,7 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
667
678
|
data['date'] = pd.to_datetime(data['date'])
|
|
668
679
|
data.set_index('date', inplace=True)
|
|
669
680
|
return data
|
|
670
|
-
|
|
681
|
+
|
|
671
682
|
def _download_and_cache_data(self, cache_dir: str):
|
|
672
683
|
"""Downloads and caches historical data as CSV files."""
|
|
673
684
|
cache_dir = cache_dir or BBSTRADER_DIR / 'fmp' / self.period
|
|
@@ -685,4 +696,4 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
685
696
|
|
|
686
697
|
# TODO Add data Handlers for Interactive Brokers
|
|
687
698
|
class TWSDataHandler(BaseCSVDataHandler):
|
|
688
|
-
...
|
|
699
|
+
...
|
bbstrader/btengine/event.py
CHANGED
|
@@ -135,7 +135,8 @@ class OrderEvent(Event):
|
|
|
135
135
|
"""
|
|
136
136
|
print(
|
|
137
137
|
"Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s, Price=%s" %
|
|
138
|
-
(self.symbol, self.order_type,
|
|
138
|
+
(self.symbol, self.order_type,
|
|
139
|
+
self.quantity, self.direction, self.price)
|
|
139
140
|
)
|
|
140
141
|
|
|
141
142
|
|