bbstrader 0.1.8__tar.gz → 0.1.91__tar.gz

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.

Files changed (43) hide show
  1. bbstrader-0.1.91/MANIFEST.in +1 -0
  2. {bbstrader-0.1.8 → bbstrader-0.1.91}/PKG-INFO +11 -3
  3. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/__ini__.py +4 -2
  4. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/btengine/__init__.py +5 -5
  5. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/btengine/backtest.py +51 -10
  6. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/btengine/data.py +147 -55
  7. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/btengine/event.py +4 -1
  8. bbstrader-0.1.91/bbstrader/btengine/execution.py +245 -0
  9. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/btengine/performance.py +4 -7
  10. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/btengine/portfolio.py +34 -13
  11. bbstrader-0.1.91/bbstrader/btengine/strategy.py +492 -0
  12. bbstrader-0.1.91/bbstrader/config.py +111 -0
  13. bbstrader-0.1.91/bbstrader/metatrader/__init__.py +6 -0
  14. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/metatrader/account.py +357 -55
  15. bbstrader-0.1.91/bbstrader/metatrader/rates.py +462 -0
  16. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/metatrader/risk.py +35 -24
  17. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/metatrader/trade.py +361 -173
  18. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/metatrader/utils.py +2 -53
  19. bbstrader-0.1.91/bbstrader/models/factors.py +0 -0
  20. bbstrader-0.1.91/bbstrader/models/ml.py +0 -0
  21. bbstrader-0.1.91/bbstrader/models/optimization.py +0 -0
  22. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/trading/__init__.py +1 -1
  23. bbstrader-0.1.91/bbstrader/trading/execution.py +537 -0
  24. bbstrader-0.1.91/bbstrader/trading/scripts.py +57 -0
  25. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/trading/strategies.py +49 -71
  26. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/tseries.py +274 -39
  27. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader.egg-info/PKG-INFO +11 -3
  28. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader.egg-info/SOURCES.txt +7 -0
  29. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader.egg-info/requires.txt +11 -2
  30. bbstrader-0.1.91/requirements.txt +24 -0
  31. {bbstrader-0.1.8 → bbstrader-0.1.91}/setup.py +28 -18
  32. bbstrader-0.1.8/bbstrader/btengine/execution.py +0 -143
  33. bbstrader-0.1.8/bbstrader/btengine/strategy.py +0 -32
  34. bbstrader-0.1.8/bbstrader/metatrader/__init__.py +0 -6
  35. bbstrader-0.1.8/bbstrader/metatrader/rates.py +0 -259
  36. bbstrader-0.1.8/bbstrader/trading/execution.py +0 -423
  37. {bbstrader-0.1.8 → bbstrader-0.1.91}/LICENSE +0 -0
  38. {bbstrader-0.1.8 → bbstrader-0.1.91}/README.md +0 -0
  39. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/models/__init__.py +0 -0
  40. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader/models/risk.py +0 -0
  41. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader.egg-info/dependency_links.txt +0 -0
  42. {bbstrader-0.1.8 → bbstrader-0.1.91}/bbstrader.egg-info/top_level.txt +0 -0
  43. {bbstrader-0.1.8 → bbstrader-0.1.91}/setup.cfg +0 -0
@@ -0,0 +1 @@
1
+ include requirements.txt
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: bbstrader
3
- Version: 0.1.8
3
+ Version: 0.1.91
4
4
  Summary: Simplified Investment & Trading Toolkit
5
5
  Home-page: https://github.com/bbalouki/bbstrader
6
6
  Download-URL: https://pypi.org/project/bbstrader/
@@ -34,12 +34,20 @@ Requires-Dist: seaborn
34
34
  Requires-Dist: statsmodels
35
35
  Requires-Dist: matplotlib
36
36
  Requires-Dist: filterpy
37
+ Requires-Dist: pykalman
37
38
  Requires-Dist: pytest
38
39
  Requires-Dist: CurrencyConverter
39
40
  Requires-Dist: tabulate
41
+ Requires-Dist: python-dotenv
40
42
  Requires-Dist: ipython
41
- Requires-Dist: quantstats
42
- Requires-Dist: Metatrader5
43
+ Requires-Dist: QuantStats
44
+ Requires-Dist: exchange-calendars
45
+ Requires-Dist: tqdm
46
+ Requires-Dist: scikit-learn
47
+ Requires-Dist: notify-py
48
+ Requires-Dist: python-telegram-bot
49
+ Provides-Extra: mt5
50
+ Requires-Dist: MetaTrader5; extra == "mt5"
43
51
 
44
52
  # Simplified Investment & Trading Toolkit
45
53
  ![bbstrader](https://github.com/bbalouki/bbstrader/blob/main/assets/bbstrader_logo.png?raw=true)
@@ -7,10 +7,12 @@ __author__ = "Bertin Balouki SIMYELI"
7
7
  __copyright__ = "2023-2024 Bertin Balouki SIMYELI"
8
8
  __email__ = "bbalouki@outlook.com"
9
9
  __license__ = "MIT"
10
- __version__ = "1.0.0"
10
+ __version__ = '0.1.91'
11
+
11
12
 
12
13
  from bbstrader import btengine
13
14
  from bbstrader import metatrader
14
15
  from bbstrader import models
15
16
  from bbstrader import trading
16
- from bbstrader import tseries
17
+ from bbstrader import tseries
18
+ from .config import config_logger
@@ -18,11 +18,11 @@ Features
18
18
  Components
19
19
  ==========
20
20
 
21
- - **Backtest**: Orchestrates the backtesting process, managing events and invoking components.
21
+ - **BacktestEgine**: Orchestrates the backtesting process, managing events and invoking components.
22
22
  - **Event**: Abstract class for events, with implementations for market data, signals, fill and order events.
23
- - **DataHandler**: Abstract class for market data handling, with an implementation for `HistoricalCSVHandler`, `MT5HistoricDataHandler`, `YFHistoricDataHandler`. We will add another data handling in the future such as MacroEconomic Data, Fundamental Data, TICK Data and Real-time Data.
23
+ - **DataHandler**: Abstract class for market data handling, with an implementation for `CSVDataHandler`, `MT5DataHandler`, `YFDataHandler`. We will add another data handling in the future such as MacroEconomic Data, Fundamental Data, TICK Data and Real-time Data.
24
24
  - **Portfolio**: Manages positions and calculates performance metrics, responding to market data and signals.
25
- - **ExecutionHandler**: Abstract class for order execution, with a simulated execution handler provided with an implementation for `SimulatedExecutionHandler`.
25
+ - **ExecutionHandler**: Abstract class for order execution, with a simulated execution handler provided with an implementation for `SimExecutionHandler`.
26
26
  - **Performance**: Utility functions for calculating performance metrics and visualizing strategy performance.
27
27
 
28
28
  Examples
@@ -50,5 +50,5 @@ from bbstrader.btengine.event import *
50
50
  from bbstrader.btengine.execution import *
51
51
  from bbstrader.btengine.performance import *
52
52
  from bbstrader.btengine.backtest import *
53
- from bbstrader.btengine.strategy import Strategy
54
- from bbstrader.btengine.portfolio import Portfolio
53
+ from bbstrader.btengine.strategy import *
54
+ from bbstrader.btengine.portfolio import *
@@ -55,7 +55,11 @@ class Backtest(object):
55
55
  the new positions.
56
56
 
57
57
  """
58
+ pass
58
59
 
60
+
61
+ class BacktestEngine(Backtest):
62
+ __doc__ = Backtest.__doc__
59
63
  def __init__(
60
64
  self,
61
65
  symbol_list: List[str],
@@ -123,7 +127,7 @@ class Backtest(object):
123
127
  self.initial_capital, **self.kwargs
124
128
  )
125
129
  self.execution_handler: ExecutionHandler = self.eh_cls(
126
- self.events, **self.kwargs)
130
+ self.events, self.data_handler, **self.kwargs)
127
131
 
128
132
  def _run_backtest(self):
129
133
  """
@@ -132,11 +136,13 @@ class Backtest(object):
132
136
  i = 0
133
137
  while True:
134
138
  i += 1
135
- print(i)
136
139
  # Update the market bars
137
140
  if self.data_handler.continue_backtest == True:
138
141
  self.data_handler.update_bars()
142
+ self.strategy.check_pending_orders()
139
143
  else:
144
+ print("\n[======= BACKTEST COMPLETE =======]\n")
145
+ print(f"Total bars: {i} ")
140
146
  break
141
147
 
142
148
  # Handle the events
@@ -189,7 +195,7 @@ class Backtest(object):
189
195
 
190
196
  if self.show_equity:
191
197
  print("\nCreating equity curve...")
192
- print("\n[======= EQUITY CURVE =======]")
198
+ print("\n[======= PORTFOLIO SUMMARY =======]")
193
199
  print(
194
200
  tabulate(
195
201
  self.portfolio.equity_curve.tail(10),
@@ -201,11 +207,14 @@ class Backtest(object):
201
207
  def simulate_trading(self):
202
208
  """
203
209
  Simulates the backtest and outputs portfolio performance.
210
+
211
+ Returns:
212
+ pd.DataFrame: The portfilio values over time (capital, equity, returns etc.)
204
213
  """
205
214
  self._run_backtest()
206
215
  self._output_performance()
216
+ return self.portfolio.equity_curve
207
217
 
208
- BacktestEngine = Backtest
209
218
 
210
219
  def run_backtest(
211
220
  symbol_list: List[str],
@@ -226,8 +235,8 @@ def run_backtest(
226
235
  start_date (datetime): Start date of the backtest.
227
236
 
228
237
  data_handler (DataHandler): An instance of the `DataHandler` class, responsible for managing
229
- and processing market data. Available options include `HistoricCSVDataHandler`,
230
- `MT5HistoricDataHandler`, and `YFHistoricDataHandler`. Ensure that the `DataHandler`
238
+ and processing market data. Available options include `CSVDataHandler`,
239
+ `MT5DataHandler`, and `YFDataHandler`. Ensure that the `DataHandler`
231
240
  instance is initialized before passing it to the function.
232
241
 
233
242
  strategy (Strategy): The trading strategy to be employed during the backtest.
@@ -253,6 +262,9 @@ def run_backtest(
253
262
 
254
263
  **kwargs: Additional parameters passed to the `Backtest` instance, which may include strategy-specific,
255
264
  data handler, portfolio, or execution handler options.
265
+
266
+ Returns:
267
+ pd.DataFrame: The portfolio values over time (capital, equities, returns etc.).
256
268
 
257
269
  Notes:
258
270
  This function generates three outputs:
@@ -263,7 +275,7 @@ def run_backtest(
263
275
  Example:
264
276
  >>> from bbstrader.trading.strategies import StockIndexSTBOTrading
265
277
  >>> from bbstrader.metatrader.utils import config_logger
266
- >>> from bbstrader.datahandlers import MT5HistoricDataHandler
278
+ >>> from bbstrader.datahandlers import MT5DataHandler
267
279
  >>> from bbstrader.execution import MT5ExecutionHandler
268
280
  >>> from datetime import datetime
269
281
  >>>
@@ -281,7 +293,7 @@ def run_backtest(
281
293
  >>> run_backtest(
282
294
  ... symbol_list=symbol_list,
283
295
  ... start_date=start,
284
- ... data_handler=MT5HistoricDataHandler(),
296
+ ... data_handler=MT5DataHandler(),
285
297
  ... strategy=StockIndexSTBOTrading(),
286
298
  ... exc_handler=MT5ExecutionHandler(),
287
299
  ... initial_capital=100000.0,
@@ -290,11 +302,40 @@ def run_backtest(
290
302
  ... )
291
303
  """
292
304
  if exc_handler is None:
293
- execution_handler = SimulatedExecutionHandler
305
+ execution_handler = SimExecutionHandler
294
306
  else:
295
307
  execution_handler = exc_handler
296
308
  engine = BacktestEngine(
297
309
  symbol_list, initial_capital, heartbeat, start_date,
298
310
  data_handler, execution_handler, strategy, **kwargs
299
311
  )
300
- engine.simulate_trading()
312
+ portfolio = engine.simulate_trading()
313
+ return portfolio
314
+
315
+
316
+ class CerebroEngine:...
317
+
318
+
319
+ class ZiplineEngine:...
320
+
321
+
322
+ def run_backtest_with(engine: Literal["bbstrader", "cerebro", "zipline"], **kwargs):
323
+ """
324
+ """
325
+ if engine == "bbstrader":
326
+ return run_backtest(
327
+ symbol_list=kwargs.get("symbol_list"),
328
+ start_date=kwargs.get("start_date"),
329
+ data_handler=kwargs.get("data_handler"),
330
+ strategy=kwargs.get("strategy"),
331
+ exc_handler=kwargs.get("exc_handler"),
332
+ initial_capital=kwargs.get("initial_capital"),
333
+ heartbeat=kwargs.get("heartbeat", 0.0),
334
+ **kwargs
335
+ )
336
+ elif engine == "cerebro":
337
+ #TODO:
338
+ pass
339
+ elif engine == "zipline":
340
+ #TODO:
341
+ pass
@@ -3,20 +3,20 @@ import numpy as np
3
3
  import pandas as pd
4
4
  import yfinance as yf
5
5
  from pathlib import Path
6
- from typing import List
6
+ from typing import List, Dict
7
7
  from queue import Queue
8
8
  from abc import ABCMeta, abstractmethod
9
- from bbstrader.metatrader.rates import Rates
9
+ from bbstrader.metatrader.rates import download_historical_data
10
10
  from bbstrader.btengine.event import MarketEvent
11
+ from bbstrader.config import BBSTRADER_DIR
11
12
  from datetime import datetime
12
13
 
13
14
 
14
15
  __all__ = [
15
16
  "DataHandler",
16
- "BaseCSVDataHandler",
17
- "HistoricCSVDataHandler",
18
- "MT5HistoricDataHandler",
19
- "YFHistoricDataHandler"
17
+ "CSVDataHandler",
18
+ "MT5DataHandler",
19
+ "YFDataHandler"
20
20
  ]
21
21
 
22
22
 
@@ -39,9 +39,21 @@ class DataHandler(metaclass=ABCMeta):
39
39
  Specific example subclasses could include `HistoricCSVDataHandler`,
40
40
  `YFinanceDataHandler`, `FMPDataHandler`, `IBMarketFeedDataHandler` etc.
41
41
  """
42
+ @property
43
+ def symbols(self) -> List[str]:
44
+ pass
45
+ @property
46
+ def data(self) -> Dict[str, pd.DataFrame]:
47
+ pass
48
+ @property
49
+ def labels(self) -> List[str]:
50
+ pass
51
+ @property
52
+ def index(self) -> str | List[str]:
53
+ pass
42
54
 
43
55
  @abstractmethod
44
- def get_latest_bar(self, symbol):
56
+ def get_latest_bar(self, symbol) -> pd.Series:
45
57
  """
46
58
  Returns the last bar updated.
47
59
  """
@@ -50,7 +62,7 @@ class DataHandler(metaclass=ABCMeta):
50
62
  )
51
63
 
52
64
  @abstractmethod
53
- def get_latest_bars(self, symbol, N=1):
65
+ def get_latest_bars(self, symbol, N=1, df=True) -> pd.DataFrame | List[pd.Series]:
54
66
  """
55
67
  Returns the last N bars updated.
56
68
  """
@@ -59,7 +71,7 @@ class DataHandler(metaclass=ABCMeta):
59
71
  )
60
72
 
61
73
  @abstractmethod
62
- def get_latest_bar_datetime(self, symbol):
74
+ def get_latest_bar_datetime(self, symbol) -> datetime | pd.Timestamp:
63
75
  """
64
76
  Returns a Python datetime object for the last bar.
65
77
  """
@@ -68,7 +80,7 @@ class DataHandler(metaclass=ABCMeta):
68
80
  )
69
81
 
70
82
  @abstractmethod
71
- def get_latest_bar_value(self, symbol, val_type):
83
+ def get_latest_bar_value(self, symbol, val_type) -> float:
72
84
  """
73
85
  Returns one of the Open, High, Low, Close, Adj Close, Volume or Returns
74
86
  from the last bar.
@@ -78,7 +90,7 @@ class DataHandler(metaclass=ABCMeta):
78
90
  )
79
91
 
80
92
  @abstractmethod
81
- def get_latest_bars_values(self, symbol, val_type, N=1):
93
+ def get_latest_bars_values(self, symbol, val_type, N=1) -> np.ndarray:
82
94
  """
83
95
  Returns the last N bar values from the
84
96
  latest_symbol list, or N-k if less available.
@@ -102,40 +114,78 @@ class DataHandler(metaclass=ABCMeta):
102
114
  class BaseCSVDataHandler(DataHandler):
103
115
  """
104
116
  Base class for handling data loaded from CSV files.
117
+
105
118
  """
106
119
 
107
- def __init__(self, events: Queue, symbol_list: List[str], csv_dir: str):
120
+ def __init__(self, events: Queue,
121
+ symbol_list: List[str],
122
+ csv_dir: str,
123
+ columns: List[str]=None,
124
+ index_col: str | int | List[str] | List[int] = 0):
125
+
126
+ """
127
+ Initialises the data handler by requesting the location of the CSV files
128
+ and a list of symbols.
129
+
130
+ Args:
131
+ events : The Event Queue.
132
+ symbol_list : A list of symbol strings.
133
+ csv_dir : Absolute directory path to the CSV files.
134
+ columns : List of column names to use for the data.
135
+ index_col : Column to use as the index.
136
+ """
108
137
  self.events = events
109
138
  self.symbol_list = symbol_list
110
139
  self.csv_dir = csv_dir
140
+ self.columns = columns
141
+ self.index_col = index_col
111
142
  self.symbol_data = {}
112
143
  self.latest_symbol_data = {}
113
144
  self.continue_backtest = True
145
+ self._index = None
114
146
  self._load_and_process_data()
115
147
 
148
+ @property
149
+ def symbols(self)-> List[str]:
150
+ return self.symbol_list
151
+ @property
152
+ def data(self)-> Dict[str, pd.DataFrame]:
153
+ return self.symbol_data
154
+ @property
155
+ def labels(self)-> List[str]:
156
+ return self.columns
157
+ @property
158
+ def index(self)-> str | List[str]:
159
+ return self._index
160
+
116
161
  def _load_and_process_data(self):
117
162
  """
118
163
  Opens the CSV files from the data directory, converting
119
164
  them into pandas DataFrames within a symbol dictionary.
120
165
  """
166
+ default_names = pd.read_csv(
167
+ os.path.join(self.csv_dir, f'{self.symbol_list[0]}.csv')
168
+ ).columns.to_list()
169
+ new_names = self.columns or default_names
170
+ new_names = [name.lower().replace(' ', '_') for name in new_names]
171
+ self.columns = new_names
172
+ assert 'adj_close' in new_names or 'close' in new_names, \
173
+ "Column names must contain 'Adj Close' and 'Close' or adj_close and close"
121
174
  comb_index = None
122
175
  for s in self.symbol_list:
123
176
  # Load the CSV file with no header information,
124
177
  # indexed on date
125
178
  self.symbol_data[s] = pd.read_csv(
126
179
  os.path.join(self.csv_dir, f'{s}.csv'),
127
- header=0, index_col=0, parse_dates=True,
128
- names=[
129
- 'Datetime', 'Open', 'High',
130
- 'Low', 'Close', 'Adj Close', 'Volume'
131
- ]
180
+ header=0, index_col=self.index_col, parse_dates=True,
181
+ names=new_names
132
182
  )
133
183
  self.symbol_data[s].sort_index(inplace=True)
134
184
  # Combine the index to pad forward values
135
185
  if comb_index is None:
136
186
  comb_index = self.symbol_data[s].index
137
- else:
138
- comb_index.union(self.symbol_data[s].index)
187
+ elif len(self.symbol_data[s].index) > len(comb_index):
188
+ comb_index = self.symbol_data[s].index
139
189
  # Set the latest symbol_data to None
140
190
  self.latest_symbol_data[s] = []
141
191
 
@@ -144,10 +194,12 @@ class BaseCSVDataHandler(DataHandler):
144
194
  self.symbol_data[s] = self.symbol_data[s].reindex(
145
195
  index=comb_index, method='pad'
146
196
  )
147
- self.symbol_data[s]["Returns"] = self.symbol_data[s][
148
- "Adj Close"
197
+ self.symbol_data[s]['returns'] = self.symbol_data[s][
198
+ 'adj_close' if 'adj_close' in new_names else 'close'
149
199
  ].pct_change().dropna()
150
- self.symbol_data[s] = self.symbol_data[s].iterrows()
200
+ self._index = self.symbol_data[s].index.name
201
+ if self.events is not None:
202
+ self.symbol_data[s] = self.symbol_data[s].iterrows()
151
203
 
152
204
  def _get_new_bar(self, symbol: str):
153
205
  """
@@ -156,7 +208,7 @@ class BaseCSVDataHandler(DataHandler):
156
208
  for b in self.symbol_data[symbol]:
157
209
  yield b
158
210
 
159
- def get_latest_bar(self, symbol: str):
211
+ def get_latest_bar(self, symbol: str) -> pd.Series:
160
212
  """
161
213
  Returns the last bar from the latest_symbol list.
162
214
  """
@@ -168,7 +220,7 @@ class BaseCSVDataHandler(DataHandler):
168
220
  else:
169
221
  return bars_list[-1]
170
222
 
171
- def get_latest_bars(self, symbol: str, N=1):
223
+ def get_latest_bars(self, symbol: str, N=1, df=True) -> pd.DataFrame | List[pd.Series]:
172
224
  """
173
225
  Returns the last N bars from the latest_symbol list,
174
226
  or N-k if less available.
@@ -179,9 +231,13 @@ class BaseCSVDataHandler(DataHandler):
179
231
  print("Symbol not available in the historical data set.")
180
232
  raise
181
233
  else:
234
+ if df:
235
+ df = pd.DataFrame([bar[1] for bar in bars_list[-N:]])
236
+ df.index.name = self._index
237
+ return df
182
238
  return bars_list[-N:]
183
239
 
184
- def get_latest_bar_datetime(self, symbol: str):
240
+ def get_latest_bar_datetime(self, symbol: str) -> datetime | pd.Timestamp:
185
241
  """
186
242
  Returns a Python datetime object for the last bar.
187
243
  """
@@ -193,7 +249,19 @@ class BaseCSVDataHandler(DataHandler):
193
249
  else:
194
250
  return bars_list[-1][0]
195
251
 
196
- def get_latest_bar_value(self, symbol: str, val_type: str):
252
+ def get_latest_bars_datetime(self, symbol: str, N=1) -> List[datetime | pd.Timestamp]:
253
+ """
254
+ Returns a list of Python datetime objects for the last N bars.
255
+ """
256
+ try:
257
+ bars_list = self.get_latest_bars(symbol, N)
258
+ except KeyError:
259
+ print("Symbol not available in the historical data set.")
260
+ raise
261
+ else:
262
+ return [b[0] for b in bars_list]
263
+
264
+ def get_latest_bar_value(self, symbol: str, val_type: str) -> float:
197
265
  """
198
266
  Returns one of the Open, High, Low, Close, Volume or OI
199
267
  values from the pandas Bar series object.
@@ -206,13 +274,13 @@ class BaseCSVDataHandler(DataHandler):
206
274
  else:
207
275
  return getattr(bars_list[-1][1], val_type)
208
276
 
209
- def get_latest_bars_values(self, symbol: str, val_type: str, N=1):
277
+ def get_latest_bars_values(self, symbol: str, val_type: str, N=1) -> np.ndarray:
210
278
  """
211
279
  Returns the last N bar values from the
212
280
  latest_symbol list, or N-k if less available.
213
281
  """
214
282
  try:
215
- bars_list = self.get_latest_bars(symbol, N)
283
+ bars_list = self.get_latest_bars(symbol, N, df=False)
216
284
  except KeyError:
217
285
  print("That symbol is not available in the historical data set.")
218
286
  raise
@@ -235,9 +303,9 @@ class BaseCSVDataHandler(DataHandler):
235
303
  self.events.put(MarketEvent())
236
304
 
237
305
 
238
- class HistoricCSVDataHandler(BaseCSVDataHandler):
306
+ class CSVDataHandler(BaseCSVDataHandler):
239
307
  """
240
- `HistoricCSVDataHandler` is designed to read CSV files for
308
+ `CSVDataHandler` is designed to read CSV files for
241
309
  each requested symbol from disk and provide an interface
242
310
  to obtain the "latest" bar in a manner identical to a live
243
311
  trading interface.
@@ -255,14 +323,19 @@ class HistoricCSVDataHandler(BaseCSVDataHandler):
255
323
 
256
324
  Args:
257
325
  events (Queue): The Event Queue.
258
- csv_dir (str): Absolute directory path to the CSV files.
259
326
  symbol_list (List[str]): A list of symbol strings.
327
+ csv_dir (str): Absolute directory path to the CSV files.
328
+
329
+ NOTE:
330
+ All csv fille can be strored in 'Home/.bbstrader/csv_data'
331
+
260
332
  """
261
333
  csv_dir = kwargs.get("csv_dir")
334
+ csv_dir = csv_dir or BBSTRADER_DIR / 'csv_data'
262
335
  super().__init__(events, symbol_list, csv_dir)
263
336
 
264
337
 
265
- class MT5HistoricDataHandler(BaseCSVDataHandler):
338
+ class MT5DataHandler(BaseCSVDataHandler):
266
339
  """
267
340
  Downloads historical data from MetaTrader 5 (MT5) and provides
268
341
  an interface for accessing this data bar-by-bar, simulating
@@ -284,27 +357,39 @@ class MT5HistoricDataHandler(BaseCSVDataHandler):
284
357
  time_frame (str): MT5 time frame (e.g., 'D1' for daily).
285
358
  mt5_start (datetime): Start date for historical data.
286
359
  mt5_end (datetime): End date for historical data.
287
- mt5_data (str): Directory for storing data (default: 'mt5_data').
360
+ data_dir (str): Directory for storing data .
288
361
 
289
362
  Note:
290
363
  Requires a working connection to an MT5 terminal.
364
+ See `bbstrader.metatrader.rates.Rates` for other arguments.
365
+ See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
291
366
  """
292
367
  self.tf = kwargs.get('time_frame', 'D1')
293
- self.start = kwargs.get('mt5_start')
368
+ self.start = kwargs.get('mt5_start', datetime(2000, 1, 1))
294
369
  self.end = kwargs.get('mt5_end', datetime.now())
295
- self.data_dir = kwargs.get('mt5_data', 'mt5_data')
370
+ self.use_utc = kwargs.get('use_utc', False)
371
+ self.filer = kwargs.get('filter', False)
372
+ self.fill_na = kwargs.get('fill_na', False)
373
+ self.lower_cols = kwargs.get('lower_cols', True)
374
+ self.data_dir = kwargs.get('data_dir')
296
375
  self.symbol_list = symbol_list
297
376
  csv_dir = self._download_data(self.data_dir)
298
377
  super().__init__(events, symbol_list, csv_dir)
299
378
 
300
379
  def _download_data(self, cache_dir: str):
301
- data_dir = Path() / cache_dir
380
+ data_dir = cache_dir or BBSTRADER_DIR / 'mt5_data' / self.tf
302
381
  data_dir.mkdir(parents=True, exist_ok=True)
303
382
  for symbol in self.symbol_list:
304
383
  try:
305
- rate = Rates(symbol=symbol, time_frame=self.tf)
306
- data = rate.get_historical_data(
307
- date_from=self.start, date_to=self.end
384
+ data = download_historical_data(
385
+ symbol=symbol,
386
+ time_frame=self.tf,
387
+ date_from=self.start,
388
+ date_to=self.end,
389
+ utc=self.use_utc,
390
+ filter=self.filer,
391
+ fill_na=self.fill_na,
392
+ lower_colnames=self.lower_cols
308
393
  )
309
394
  if data is None:
310
395
  raise ValueError(f"No data found for {symbol}")
@@ -314,7 +399,7 @@ class MT5HistoricDataHandler(BaseCSVDataHandler):
314
399
  return data_dir
315
400
 
316
401
 
317
- class YFHistoricDataHandler(BaseCSVDataHandler):
402
+ class YFDataHandler(BaseCSVDataHandler):
318
403
  """
319
404
  Downloads historical data from Yahoo Finance and provides
320
405
  an interface for accessing this data bar-by-bar, simulating
@@ -333,40 +418,47 @@ class YFHistoricDataHandler(BaseCSVDataHandler):
333
418
  symbol_list (list[str]): List of symbols to download data for.
334
419
  yf_start (str): Start date for historical data (YYYY-MM-DD).
335
420
  yf_end (str): End date for historical data (YYYY-MM-DD).
336
- cache_dir (str, optional): Directory for caching data (default: 'yf_cache').
421
+ data_dir (str, optional): Directory for caching data .
422
+
423
+ Note:
424
+ See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
337
425
  """
338
426
  self.symbol_list = symbol_list
339
- self.start_date = kwargs.get('yf_start')
340
- self.end_date = kwargs.get('yf_end')
341
- self.cache_dir = kwargs.get('yf_cache', 'yf_cache')
427
+ self.start_date = kwargs.get('yf_start', '2000-01-01')
428
+ self.end_date = kwargs.get('yf_end', datetime.now().strftime('%Y-%m-%d'))
429
+ self.cache_dir = kwargs.get('data_dir')
342
430
  csv_dir = self._download_and_cache_data(self.cache_dir)
343
431
  super().__init__(events, symbol_list, csv_dir)
344
432
 
345
433
  def _download_and_cache_data(self, cache_dir: str):
346
434
  """Downloads and caches historical data as CSV files."""
435
+ cache_dir = cache_dir or BBSTRADER_DIR / 'yf_data' / 'daily'
347
436
  os.makedirs(cache_dir, exist_ok=True)
348
437
  for symbol in self.symbol_list:
349
438
  filepath = os.path.join(cache_dir, f"{symbol}.csv")
350
- if not os.path.exists(filepath):
351
- try:
352
- data = yf.download(
353
- symbol, start=self.start_date, end=self.end_date)
354
- if data.empty:
355
- raise ValueError(f"No data found for {symbol}")
356
- data.to_csv(filepath) # Cache the data
357
- except Exception as e:
358
- raise ValueError(f"Error downloading {symbol}: {e}")
439
+ try:
440
+ data = yf.download(
441
+ symbol, start=self.start_date, end=self.end_date, multi_level_index=False)
442
+ if data.empty:
443
+ raise ValueError(f"No data found for {symbol}")
444
+ data.to_csv(filepath) # Cache the data
445
+ except Exception as e:
446
+ raise ValueError(f"Error downloading {symbol}: {e}")
359
447
  return cache_dir
360
448
 
361
449
 
362
450
  # TODO # Get data from EODHD
363
451
  # https://eodhd.com/
364
- class EODHDHistoricDataHandler(BaseCSVDataHandler):
452
+ class EODHDataHandler(BaseCSVDataHandler):
365
453
  ...
366
454
 
367
455
  # TODO # Get data from FMP using Financialtoolkit API
368
456
  # https://github.com/bbalouki/FinanceToolkit
369
- class FMPHistoricDataHandler(BaseCSVDataHandler):
457
+ class FMPDataHandler(BaseCSVDataHandler):
458
+ ...
459
+
460
+
461
+ class AlgoseekDataHandler(BaseCSVDataHandler):
370
462
  ...
371
463
 
372
464
 
@@ -57,7 +57,8 @@ class SignalEvent(Event):
57
57
  signal_type: Literal['LONG', 'SHORT', 'EXIT'],
58
58
  quantity: int | float = 100,
59
59
  strength: int | float = 1.0,
60
- price: int | float = None
60
+ price: int | float = None,
61
+ stoplimit: int | float = None
61
62
  ):
62
63
  """
63
64
  Initialises the SignalEvent.
@@ -73,6 +74,7 @@ class SignalEvent(Event):
73
74
  strength (int | float): An adjustment factor "suggestion" used to scale
74
75
  quantity at the portfolio level. Useful for pairs strategies.
75
76
  price (int | float): An optional price to be used when the signal is generated.
77
+ stoplimit (int | float): An optional stop-limit price for the signal
76
78
  """
77
79
  self.type = 'SIGNAL'
78
80
  self.strategy_id = strategy_id
@@ -82,6 +84,7 @@ class SignalEvent(Event):
82
84
  self.quantity = quantity
83
85
  self.strength = strength
84
86
  self.price = price
87
+ self.stoplimit = stoplimit
85
88
 
86
89
 
87
90
  class OrderEvent(Event):