bbstrader 0.1.94__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 +9 -9
- bbstrader/btengine/__init__.py +7 -7
- bbstrader/btengine/backtest.py +30 -26
- bbstrader/btengine/data.py +100 -79
- 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 +119 -94
- bbstrader/config.py +14 -8
- bbstrader/core/__init__.py +0 -0
- bbstrader/core/data.py +22 -0
- bbstrader/core/utils.py +57 -0
- bbstrader/ibkr/__init__.py +0 -0
- bbstrader/ibkr/utils.py +0 -0
- bbstrader/metatrader/__init__.py +5 -5
- bbstrader/metatrader/account.py +117 -121
- bbstrader/metatrader/rates.py +83 -80
- bbstrader/metatrader/risk.py +23 -37
- bbstrader/metatrader/trade.py +169 -140
- bbstrader/metatrader/utils.py +3 -3
- bbstrader/models/__init__.py +5 -5
- bbstrader/models/factors.py +280 -0
- bbstrader/models/ml.py +1092 -0
- bbstrader/models/optimization.py +31 -28
- bbstrader/models/{portfolios.py → portfolio.py} +64 -46
- bbstrader/models/risk.py +15 -9
- bbstrader/trading/__init__.py +2 -2
- bbstrader/trading/execution.py +252 -164
- bbstrader/trading/scripts.py +8 -4
- bbstrader/trading/strategies.py +79 -66
- bbstrader/tseries.py +482 -107
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/LICENSE +1 -1
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/METADATA +6 -1
- bbstrader-0.2.1.dist-info/RECORD +37 -0
- bbstrader-0.1.94.dist-info/RECORD +0 -32
- {bbstrader-0.1.94.dist-info → bbstrader-0.2.1.dist-info}/WHEEL +0 -0
- {bbstrader-0.1.94.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-
|
|
8
|
-
__email__ = "
|
|
7
|
+
__copyright__ = "2023-2025 Bertin Balouki SIMYELI"
|
|
8
|
+
__email__ = "bertin@bbstrader.com"
|
|
9
9
|
__license__ = "MIT"
|
|
10
|
-
__version__ = '0.
|
|
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,28 +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
|
|
158
|
+
|
|
156
159
|
@property
|
|
157
|
-
def data(self)-> Dict[str, pd.DataFrame]:
|
|
160
|
+
def data(self) -> Dict[str, pd.DataFrame]:
|
|
158
161
|
return self.symbol_data
|
|
162
|
+
|
|
159
163
|
@property
|
|
160
|
-
def
|
|
164
|
+
def datadir(self) -> str:
|
|
165
|
+
return self.csv_dir
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def labels(self) -> List[str]:
|
|
161
169
|
return self.columns
|
|
170
|
+
|
|
162
171
|
@property
|
|
163
|
-
def index(self)-> str | List[str]:
|
|
172
|
+
def index(self) -> str | List[str]:
|
|
164
173
|
return self._index
|
|
165
|
-
|
|
174
|
+
|
|
166
175
|
def _load_and_process_data(self):
|
|
167
176
|
"""
|
|
168
177
|
Opens the CSV files from the data directory, converting
|
|
169
178
|
them into pandas DataFrames within a symbol dictionary.
|
|
170
179
|
"""
|
|
171
180
|
default_names = pd.read_csv(
|
|
172
|
-
|
|
173
|
-
|
|
181
|
+
os.path.join(self.csv_dir, f'{self.symbol_list[0]}.csv')
|
|
182
|
+
).columns.to_list()
|
|
174
183
|
new_names = self.columns or default_names
|
|
175
|
-
new_names = [name.strip().lower().replace(' ', '_')
|
|
184
|
+
new_names = [name.strip().lower().replace(' ', '_')
|
|
185
|
+
for name in new_names]
|
|
176
186
|
self.columns = new_names
|
|
177
187
|
assert 'adj_close' in new_names or 'close' in new_names, \
|
|
178
188
|
"Column names must contain 'Adj Close' and 'Close' or adj_close and close"
|
|
@@ -203,6 +213,7 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
203
213
|
'adj_close' if 'adj_close' in new_names else 'close'
|
|
204
214
|
].pct_change().dropna()
|
|
205
215
|
self._index = self.symbol_data[s].index.name
|
|
216
|
+
self.symbol_data[s].to_csv(os.path.join(self.csv_dir, f'{s}.csv'))
|
|
206
217
|
if self.events is not None:
|
|
207
218
|
self.symbol_data[s] = self.symbol_data[s].iterrows()
|
|
208
219
|
|
|
@@ -280,7 +291,8 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
280
291
|
try:
|
|
281
292
|
return getattr(bars_list[-1][1], val_type)
|
|
282
293
|
except AttributeError:
|
|
283
|
-
print(
|
|
294
|
+
print(
|
|
295
|
+
f"Value type {val_type} not available in the historical data set.")
|
|
284
296
|
raise
|
|
285
297
|
|
|
286
298
|
def get_latest_bars_values(self, symbol: str, val_type: str, N=1) -> np.ndarray:
|
|
@@ -297,7 +309,8 @@ class BaseCSVDataHandler(DataHandler):
|
|
|
297
309
|
try:
|
|
298
310
|
return np.array([getattr(b[1], val_type) for b in bars_list])
|
|
299
311
|
except AttributeError:
|
|
300
|
-
print(
|
|
312
|
+
print(
|
|
313
|
+
f"Value type {val_type} not available in the historical data set.")
|
|
301
314
|
raise
|
|
302
315
|
|
|
303
316
|
def update_bars(self):
|
|
@@ -338,18 +351,18 @@ class CSVDataHandler(BaseCSVDataHandler):
|
|
|
338
351
|
events (Queue): The Event Queue.
|
|
339
352
|
symbol_list (List[str]): A list of symbol strings.
|
|
340
353
|
csv_dir (str): Absolute directory path to the CSV files.
|
|
341
|
-
|
|
354
|
+
|
|
342
355
|
NOTE:
|
|
343
356
|
All csv fille can be strored in 'Home/.bbstrader/csv_data'
|
|
344
357
|
|
|
345
358
|
"""
|
|
346
359
|
csv_dir = kwargs.get("csv_dir")
|
|
347
|
-
csv_dir =
|
|
360
|
+
csv_dir = csv_dir or BBSTRADER_DIR / 'csv_data'
|
|
348
361
|
super().__init__(
|
|
349
|
-
events,
|
|
350
|
-
symbol_list,
|
|
362
|
+
events,
|
|
363
|
+
symbol_list,
|
|
351
364
|
csv_dir,
|
|
352
|
-
columns
|
|
365
|
+
columns=kwargs.get('columns'),
|
|
353
366
|
index_col=kwargs.get('index_col', 0)
|
|
354
367
|
)
|
|
355
368
|
|
|
@@ -383,35 +396,35 @@ class MT5DataHandler(BaseCSVDataHandler):
|
|
|
383
396
|
See `bbstrader.metatrader.rates.Rates` for other arguments.
|
|
384
397
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
385
398
|
"""
|
|
386
|
-
self.tf
|
|
387
|
-
self.start
|
|
388
|
-
self.end
|
|
389
|
-
self.use_utc
|
|
390
|
-
self.filer
|
|
391
|
-
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)
|
|
392
405
|
self.lower_cols = kwargs.get('lower_cols', True)
|
|
393
|
-
self.data_dir
|
|
406
|
+
self.data_dir = kwargs.get('data_dir')
|
|
394
407
|
self.symbol_list = symbol_list
|
|
395
408
|
self.kwargs = kwargs
|
|
396
|
-
|
|
409
|
+
|
|
397
410
|
csv_dir = self._download_and_cache_data(self.data_dir)
|
|
398
411
|
super().__init__(
|
|
399
412
|
events,
|
|
400
413
|
symbol_list,
|
|
401
414
|
csv_dir,
|
|
402
|
-
columns
|
|
415
|
+
columns=kwargs.get('columns'),
|
|
403
416
|
index_col=kwargs.get('index_col', 0)
|
|
404
417
|
)
|
|
405
418
|
|
|
406
419
|
def _download_and_cache_data(self, cache_dir: str):
|
|
407
|
-
data_dir = cache_dir or BBSTRADER_DIR /
|
|
420
|
+
data_dir = cache_dir or BBSTRADER_DIR / 'mt5' / self.tf
|
|
408
421
|
data_dir.mkdir(parents=True, exist_ok=True)
|
|
409
422
|
for symbol in self.symbol_list:
|
|
410
423
|
try:
|
|
411
424
|
data = download_historical_data(
|
|
412
425
|
symbol=symbol,
|
|
413
426
|
timeframe=self.tf,
|
|
414
|
-
date_from=self.start,
|
|
427
|
+
date_from=self.start,
|
|
415
428
|
date_to=self.end,
|
|
416
429
|
utc=self.use_utc,
|
|
417
430
|
filter=self.filer,
|
|
@@ -452,17 +465,17 @@ class YFDataHandler(BaseCSVDataHandler):
|
|
|
452
465
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
453
466
|
"""
|
|
454
467
|
self.symbol_list = symbol_list
|
|
455
|
-
self.start_date
|
|
456
|
-
self.end_date
|
|
457
|
-
self.cache_dir
|
|
458
|
-
|
|
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
|
+
|
|
459
472
|
csv_dir = self._download_and_cache_data(self.cache_dir)
|
|
460
|
-
|
|
473
|
+
|
|
461
474
|
super().__init__(
|
|
462
475
|
events,
|
|
463
476
|
symbol_list,
|
|
464
477
|
csv_dir,
|
|
465
|
-
columns
|
|
478
|
+
columns=kwargs.get('columns'),
|
|
466
479
|
index_col=kwargs.get('index_col', 0)
|
|
467
480
|
)
|
|
468
481
|
|
|
@@ -474,7 +487,8 @@ class YFDataHandler(BaseCSVDataHandler):
|
|
|
474
487
|
filepath = os.path.join(cache_dir, f"{symbol}.csv")
|
|
475
488
|
try:
|
|
476
489
|
data = yf.download(
|
|
477
|
-
symbol, start=self.start_date, end=self.end_date,
|
|
490
|
+
symbol, start=self.start_date, end=self.end_date,
|
|
491
|
+
multi_level_index=False, progress=False)
|
|
478
492
|
if data.empty:
|
|
479
493
|
raise ValueError(f"No data found for {symbol}")
|
|
480
494
|
data.to_csv(filepath)
|
|
@@ -491,6 +505,7 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
491
505
|
To use this class, you need to sign up for an API key at
|
|
492
506
|
https://eodhistoricaldata.com/ and provide the key as an argument.
|
|
493
507
|
"""
|
|
508
|
+
|
|
494
509
|
def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
|
|
495
510
|
"""
|
|
496
511
|
Args:
|
|
@@ -506,19 +521,20 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
506
521
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
507
522
|
"""
|
|
508
523
|
self.symbol_list = symbol_list
|
|
509
|
-
self.start_date
|
|
510
|
-
self.end_date
|
|
511
|
-
|
|
512
|
-
self.
|
|
513
|
-
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')
|
|
514
530
|
|
|
515
531
|
csv_dir = self._download_and_cache_data(self.cache_dir)
|
|
516
|
-
|
|
532
|
+
|
|
517
533
|
super().__init__(
|
|
518
|
-
events,
|
|
534
|
+
events,
|
|
519
535
|
symbol_list,
|
|
520
536
|
csv_dir,
|
|
521
|
-
columns
|
|
537
|
+
columns=kwargs.get('columns'),
|
|
522
538
|
index_col=kwargs.get('index_col', 0)
|
|
523
539
|
)
|
|
524
540
|
|
|
@@ -529,7 +545,7 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
529
545
|
if period in ['d', 'w', 'm']:
|
|
530
546
|
return client.get_historical_data(
|
|
531
547
|
symbol=symbol,
|
|
532
|
-
interval=period,
|
|
548
|
+
interval=period,
|
|
533
549
|
iso8601_start=self.start_date,
|
|
534
550
|
iso8601_end=self.end_date,
|
|
535
551
|
)
|
|
@@ -544,11 +560,11 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
544
560
|
unix_end = int(enddt.timestamp())
|
|
545
561
|
return client.get_intraday_historical_data(
|
|
546
562
|
symbol=symbol,
|
|
547
|
-
interval=period,
|
|
563
|
+
interval=period,
|
|
548
564
|
from_unix_time=unix_start,
|
|
549
565
|
to_unix_time=unix_end,
|
|
550
566
|
)
|
|
551
|
-
|
|
567
|
+
|
|
552
568
|
def _forma_data(self, data: List[Dict] | pd.DataFrame) -> pd.DataFrame:
|
|
553
569
|
if isinstance(data, pd.DataFrame):
|
|
554
570
|
if data.empty or len(data) == 0:
|
|
@@ -556,7 +572,7 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
556
572
|
df = data.drop(labels=['symbol', 'interval'], axis=1)
|
|
557
573
|
df = df.rename(columns={'adjusted_close': 'adj_close'})
|
|
558
574
|
return df
|
|
559
|
-
|
|
575
|
+
|
|
560
576
|
elif isinstance(data, list):
|
|
561
577
|
if not data or len(data) == 0:
|
|
562
578
|
raise ValueError("No data found.")
|
|
@@ -564,11 +580,12 @@ class EODHDataHandler(BaseCSVDataHandler):
|
|
|
564
580
|
df = df.drop(columns=['timestamp', 'gmtoffset'], axis=1)
|
|
565
581
|
df = df.rename(columns={'datetime': 'date'})
|
|
566
582
|
df['adj_close'] = df['close']
|
|
567
|
-
df = df[['date', 'open', 'high', 'low',
|
|
583
|
+
df = df[['date', 'open', 'high', 'low',
|
|
584
|
+
'close', 'adj_close', 'volume']]
|
|
568
585
|
df.date = pd.to_datetime(df.date)
|
|
569
586
|
df = df.set_index('date')
|
|
570
587
|
return df
|
|
571
|
-
|
|
588
|
+
|
|
572
589
|
def _download_and_cache_data(self, cache_dir: str):
|
|
573
590
|
"""Downloads and caches historical data as CSV files."""
|
|
574
591
|
cache_dir = cache_dir or BBSTRADER_DIR / 'eodhd' / self.period
|
|
@@ -594,6 +611,7 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
594
611
|
provide the key as an argument.
|
|
595
612
|
|
|
596
613
|
"""
|
|
614
|
+
|
|
597
615
|
def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
|
|
598
616
|
"""
|
|
599
617
|
Args:
|
|
@@ -610,19 +628,20 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
610
628
|
See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
|
|
611
629
|
"""
|
|
612
630
|
self.symbol_list = symbol_list
|
|
613
|
-
self.start_date
|
|
614
|
-
self.end_date
|
|
615
|
-
|
|
616
|
-
self.
|
|
617
|
-
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')
|
|
618
637
|
|
|
619
638
|
csv_dir = self._download_and_cache_data(self.cache_dir)
|
|
620
|
-
|
|
639
|
+
|
|
621
640
|
super().__init__(
|
|
622
641
|
events,
|
|
623
642
|
symbol_list,
|
|
624
643
|
csv_dir,
|
|
625
|
-
columns
|
|
644
|
+
columns=kwargs.get('columns'),
|
|
626
645
|
index_col=kwargs.get('index_col', 0)
|
|
627
646
|
)
|
|
628
647
|
|
|
@@ -632,24 +651,26 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
632
651
|
toolkit = Toolkit(
|
|
633
652
|
symbol,
|
|
634
653
|
api_key=self.__api_key,
|
|
635
|
-
start_date=self.start_date,
|
|
654
|
+
start_date=self.start_date,
|
|
636
655
|
end_date=self.end_date,
|
|
637
|
-
benchmark_ticker=None
|
|
656
|
+
benchmark_ticker=None,
|
|
657
|
+
progress_bar=False
|
|
638
658
|
)
|
|
639
659
|
if period in ['daily', 'weekly', 'monthly', 'quarterly', 'yearly']:
|
|
640
|
-
return toolkit.get_historical_data(period=period)
|
|
660
|
+
return toolkit.get_historical_data(period=period, progress_bar=False)
|
|
641
661
|
elif period in ['1min', '5min', '15min', '30min', '1hour']:
|
|
642
|
-
return toolkit.get_intraday_data(period=period)
|
|
643
|
-
|
|
662
|
+
return toolkit.get_intraday_data(period=period, progress_bar=False)
|
|
663
|
+
|
|
644
664
|
def _format_data(self, data: pd.DataFrame, period: str) -> pd.DataFrame:
|
|
645
665
|
if data.empty or len(data) == 0:
|
|
646
666
|
raise ValueError("No data found.")
|
|
647
667
|
if period[0].isnumeric():
|
|
648
|
-
data = data.drop(
|
|
668
|
+
data = data.drop(
|
|
669
|
+
columns=['Return', 'Volatility', 'Cumulative Return'], axis=1)
|
|
649
670
|
else:
|
|
650
|
-
data = data.drop(columns=['Dividends', 'Return', 'Volatility',
|
|
651
|
-
|
|
652
|
-
|
|
671
|
+
data = data.drop(columns=['Dividends', 'Return', 'Volatility',
|
|
672
|
+
'Excess Return', 'Excess Volatility',
|
|
673
|
+
'Cumulative Return'], axis=1)
|
|
653
674
|
data = data.reset_index()
|
|
654
675
|
if 'Adj Close' not in data.columns:
|
|
655
676
|
data['Adj Close'] = data['Close']
|
|
@@ -657,7 +678,7 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
657
678
|
data['date'] = pd.to_datetime(data['date'])
|
|
658
679
|
data.set_index('date', inplace=True)
|
|
659
680
|
return data
|
|
660
|
-
|
|
681
|
+
|
|
661
682
|
def _download_and_cache_data(self, cache_dir: str):
|
|
662
683
|
"""Downloads and caches historical data as CSV files."""
|
|
663
684
|
cache_dir = cache_dir or BBSTRADER_DIR / 'fmp' / self.period
|
|
@@ -675,4 +696,4 @@ class FMPDataHandler(BaseCSVDataHandler):
|
|
|
675
696
|
|
|
676
697
|
# TODO Add data Handlers for Interactive Brokers
|
|
677
698
|
class TWSDataHandler(BaseCSVDataHandler):
|
|
678
|
-
...
|
|
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
|
|