bbstrader 0.1.9__tar.gz → 0.1.92__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 (44) hide show
  1. bbstrader-0.1.92/MANIFEST.in +1 -0
  2. {bbstrader-0.1.9 → bbstrader-0.1.92}/PKG-INFO +12 -3
  3. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/__ini__.py +4 -2
  4. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/btengine/__init__.py +5 -5
  5. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/btengine/backtest.py +60 -12
  6. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/btengine/data.py +157 -57
  7. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/btengine/event.py +14 -5
  8. bbstrader-0.1.92/bbstrader/btengine/execution.py +245 -0
  9. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/btengine/performance.py +4 -7
  10. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/btengine/portfolio.py +83 -36
  11. bbstrader-0.1.92/bbstrader/btengine/strategy.py +551 -0
  12. bbstrader-0.1.92/bbstrader/config.py +111 -0
  13. bbstrader-0.1.92/bbstrader/metatrader/__init__.py +6 -0
  14. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/metatrader/account.py +348 -53
  15. bbstrader-0.1.92/bbstrader/metatrader/rates.py +510 -0
  16. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/metatrader/risk.py +34 -23
  17. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/metatrader/trade.py +328 -169
  18. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/metatrader/utils.py +2 -53
  19. bbstrader-0.1.92/bbstrader/models/factors.py +0 -0
  20. bbstrader-0.1.92/bbstrader/models/ml.py +0 -0
  21. bbstrader-0.1.92/bbstrader/models/optimization.py +170 -0
  22. bbstrader-0.1.92/bbstrader/models/portfolios.py +202 -0
  23. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/trading/__init__.py +1 -1
  24. bbstrader-0.1.92/bbstrader/trading/execution.py +539 -0
  25. bbstrader-0.1.92/bbstrader/trading/scripts.py +57 -0
  26. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/trading/strategies.py +41 -65
  27. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/tseries.py +274 -39
  28. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader.egg-info/PKG-INFO +12 -3
  29. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader.egg-info/SOURCES.txt +8 -0
  30. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader.egg-info/requires.txt +12 -2
  31. bbstrader-0.1.92/requirements.txt +25 -0
  32. {bbstrader-0.1.9 → bbstrader-0.1.92}/setup.py +28 -18
  33. bbstrader-0.1.9/bbstrader/btengine/execution.py +0 -143
  34. bbstrader-0.1.9/bbstrader/btengine/strategy.py +0 -32
  35. bbstrader-0.1.9/bbstrader/metatrader/__init__.py +0 -6
  36. bbstrader-0.1.9/bbstrader/metatrader/rates.py +0 -257
  37. bbstrader-0.1.9/bbstrader/trading/execution.py +0 -433
  38. {bbstrader-0.1.9 → bbstrader-0.1.92}/LICENSE +0 -0
  39. {bbstrader-0.1.9 → bbstrader-0.1.92}/README.md +0 -0
  40. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/models/__init__.py +0 -0
  41. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader/models/risk.py +0 -0
  42. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader.egg-info/dependency_links.txt +0 -0
  43. {bbstrader-0.1.9 → bbstrader-0.1.92}/bbstrader.egg-info/top_level.txt +0 -0
  44. {bbstrader-0.1.9 → bbstrader-0.1.92}/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.9
3
+ Version: 0.1.92
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,21 @@ 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
+ Requires-Dist: pyportfolioopt
50
+ Provides-Extra: mt5
51
+ Requires-Dist: MetaTrader5; extra == "mt5"
43
52
 
44
53
  # Simplified Investment & Trading Toolkit
45
54
  ![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],
@@ -107,8 +111,9 @@ class Backtest(object):
107
111
  their class types.
108
112
  """
109
113
  print(
110
- f"\nStarting Backtest on {self.symbol_list} "
111
- f"with ${self.initial_capital} Initial Capital\n"
114
+ f"\n[======= STARTING BACKTEST =======]\n"
115
+ f"START DATE: {self.start_date} \n"
116
+ f"INITIAL CAPITAL: {self.initial_capital}\n"
112
117
  )
113
118
  self.data_handler: DataHandler = self.dh_cls(
114
119
  self.events, self.symbol_list, **self.kwargs
@@ -123,7 +128,7 @@ class Backtest(object):
123
128
  self.initial_capital, **self.kwargs
124
129
  )
125
130
  self.execution_handler: ExecutionHandler = self.eh_cls(
126
- self.events, **self.kwargs)
131
+ self.events, self.data_handler, **self.kwargs)
127
132
 
128
133
  def _run_backtest(self):
129
134
  """
@@ -132,11 +137,14 @@ class Backtest(object):
132
137
  i = 0
133
138
  while True:
134
139
  i += 1
135
- print(i)
136
140
  # Update the market bars
137
141
  if self.data_handler.continue_backtest == True:
138
142
  self.data_handler.update_bars()
143
+ self.strategy.check_pending_orders()
139
144
  else:
145
+ print("\n[======= BACKTEST COMPLETED =======]")
146
+ print(f"END DATE: {self.data_handler.get_latest_bar_datetime()}")
147
+ print(f"TOTAL BARS: {i} ")
140
148
  break
141
149
 
142
150
  # Handle the events
@@ -162,6 +170,11 @@ class Backtest(object):
162
170
  elif event.type == 'FILL':
163
171
  self.fills += 1
164
172
  self.portfolio.update_fill(event)
173
+ self.strategy.update_trades_from_fill(event)
174
+ self.strategy.get_update_from_portfolio(
175
+ self.portfolio.current_positions,
176
+ self.portfolio.current_holdings
177
+ )
165
178
 
166
179
  time.sleep(self.heartbeat)
167
180
 
@@ -189,7 +202,7 @@ class Backtest(object):
189
202
 
190
203
  if self.show_equity:
191
204
  print("\nCreating equity curve...")
192
- print("\n[======= EQUITY CURVE =======]")
205
+ print("\n[======= PORTFOLIO SUMMARY =======]")
193
206
  print(
194
207
  tabulate(
195
208
  self.portfolio.equity_curve.tail(10),
@@ -201,11 +214,14 @@ class Backtest(object):
201
214
  def simulate_trading(self):
202
215
  """
203
216
  Simulates the backtest and outputs portfolio performance.
217
+
218
+ Returns:
219
+ pd.DataFrame: The portfilio values over time (capital, equity, returns etc.)
204
220
  """
205
221
  self._run_backtest()
206
222
  self._output_performance()
223
+ return self.portfolio.equity_curve
207
224
 
208
- BacktestEngine = Backtest
209
225
 
210
226
  def run_backtest(
211
227
  symbol_list: List[str],
@@ -226,8 +242,8 @@ def run_backtest(
226
242
  start_date (datetime): Start date of the backtest.
227
243
 
228
244
  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`
245
+ and processing market data. Available options include `CSVDataHandler`,
246
+ `MT5DataHandler`, and `YFDataHandler`. Ensure that the `DataHandler`
231
247
  instance is initialized before passing it to the function.
232
248
 
233
249
  strategy (Strategy): The trading strategy to be employed during the backtest.
@@ -253,6 +269,9 @@ def run_backtest(
253
269
 
254
270
  **kwargs: Additional parameters passed to the `Backtest` instance, which may include strategy-specific,
255
271
  data handler, portfolio, or execution handler options.
272
+
273
+ Returns:
274
+ pd.DataFrame: The portfolio values over time (capital, equities, returns etc.).
256
275
 
257
276
  Notes:
258
277
  This function generates three outputs:
@@ -263,7 +282,7 @@ def run_backtest(
263
282
  Example:
264
283
  >>> from bbstrader.trading.strategies import StockIndexSTBOTrading
265
284
  >>> from bbstrader.metatrader.utils import config_logger
266
- >>> from bbstrader.datahandlers import MT5HistoricDataHandler
285
+ >>> from bbstrader.datahandlers import MT5DataHandler
267
286
  >>> from bbstrader.execution import MT5ExecutionHandler
268
287
  >>> from datetime import datetime
269
288
  >>>
@@ -281,7 +300,7 @@ def run_backtest(
281
300
  >>> run_backtest(
282
301
  ... symbol_list=symbol_list,
283
302
  ... start_date=start,
284
- ... data_handler=MT5HistoricDataHandler(),
303
+ ... data_handler=MT5DataHandler(),
285
304
  ... strategy=StockIndexSTBOTrading(),
286
305
  ... exc_handler=MT5ExecutionHandler(),
287
306
  ... initial_capital=100000.0,
@@ -290,11 +309,40 @@ def run_backtest(
290
309
  ... )
291
310
  """
292
311
  if exc_handler is None:
293
- execution_handler = SimulatedExecutionHandler
312
+ execution_handler = SimExecutionHandler
294
313
  else:
295
314
  execution_handler = exc_handler
296
315
  engine = BacktestEngine(
297
316
  symbol_list, initial_capital, heartbeat, start_date,
298
317
  data_handler, execution_handler, strategy, **kwargs
299
318
  )
300
- engine.simulate_trading()
319
+ portfolio = engine.simulate_trading()
320
+ return portfolio
321
+
322
+
323
+ class CerebroEngine:...
324
+
325
+
326
+ class ZiplineEngine:...
327
+
328
+
329
+ def run_backtest_with(engine: Literal["bbstrader", "cerebro", "zipline"], **kwargs):
330
+ """
331
+ """
332
+ if engine == "bbstrader":
333
+ return run_backtest(
334
+ symbol_list=kwargs.get("symbol_list"),
335
+ start_date=kwargs.get("start_date"),
336
+ data_handler=kwargs.get("data_handler"),
337
+ strategy=kwargs.get("strategy"),
338
+ exc_handler=kwargs.get("exc_handler"),
339
+ initial_capital=kwargs.get("initial_capital"),
340
+ heartbeat=kwargs.get("heartbeat", 0.0),
341
+ **kwargs
342
+ )
343
+ elif engine == "cerebro":
344
+ #TODO:
345
+ pass
346
+ elif engine == "zipline":
347
+ #TODO:
348
+ 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.
@@ -204,20 +272,28 @@ class BaseCSVDataHandler(DataHandler):
204
272
  print("Symbol not available in the historical data set.")
205
273
  raise
206
274
  else:
207
- return getattr(bars_list[-1][1], val_type)
275
+ try:
276
+ return getattr(bars_list[-1][1], val_type)
277
+ except AttributeError:
278
+ print(f"Value type {val_type} not available in the historical data set.")
279
+ raise
208
280
 
209
- def get_latest_bars_values(self, symbol: str, val_type: str, N=1):
281
+ def get_latest_bars_values(self, symbol: str, val_type: str, N=1) -> np.ndarray:
210
282
  """
211
283
  Returns the last N bar values from the
212
284
  latest_symbol list, or N-k if less available.
213
285
  """
214
286
  try:
215
- bars_list = self.get_latest_bars(symbol, N)
287
+ bars_list = self.get_latest_bars(symbol, N, df=False)
216
288
  except KeyError:
217
289
  print("That symbol is not available in the historical data set.")
218
290
  raise
219
291
  else:
220
- return np.array([getattr(b[1], val_type) for b in bars_list])
292
+ try:
293
+ return np.array([getattr(b[1], val_type) for b in bars_list])
294
+ except AttributeError:
295
+ print(f"Value type {val_type} not available in the historical data set.")
296
+ raise
221
297
 
222
298
  def update_bars(self):
223
299
  """
@@ -235,9 +311,9 @@ class BaseCSVDataHandler(DataHandler):
235
311
  self.events.put(MarketEvent())
236
312
 
237
313
 
238
- class HistoricCSVDataHandler(BaseCSVDataHandler):
314
+ class CSVDataHandler(BaseCSVDataHandler):
239
315
  """
240
- `HistoricCSVDataHandler` is designed to read CSV files for
316
+ `CSVDataHandler` is designed to read CSV files for
241
317
  each requested symbol from disk and provide an interface
242
318
  to obtain the "latest" bar in a manner identical to a live
243
319
  trading interface.
@@ -255,14 +331,19 @@ class HistoricCSVDataHandler(BaseCSVDataHandler):
255
331
 
256
332
  Args:
257
333
  events (Queue): The Event Queue.
258
- csv_dir (str): Absolute directory path to the CSV files.
259
334
  symbol_list (List[str]): A list of symbol strings.
335
+ csv_dir (str): Absolute directory path to the CSV files.
336
+
337
+ NOTE:
338
+ All csv fille can be strored in 'Home/.bbstrader/csv_data'
339
+
260
340
  """
261
341
  csv_dir = kwargs.get("csv_dir")
342
+ csv_dir = csv_dir or BBSTRADER_DIR / 'csv_data'
262
343
  super().__init__(events, symbol_list, csv_dir)
263
344
 
264
345
 
265
- class MT5HistoricDataHandler(BaseCSVDataHandler):
346
+ class MT5DataHandler(BaseCSVDataHandler):
266
347
  """
267
348
  Downloads historical data from MetaTrader 5 (MT5) and provides
268
349
  an interface for accessing this data bar-by-bar, simulating
@@ -284,27 +365,39 @@ class MT5HistoricDataHandler(BaseCSVDataHandler):
284
365
  time_frame (str): MT5 time frame (e.g., 'D1' for daily).
285
366
  mt5_start (datetime): Start date for historical data.
286
367
  mt5_end (datetime): End date for historical data.
287
- mt5_data (str): Directory for storing data (default: 'mt5_data').
368
+ data_dir (str): Directory for storing data .
288
369
 
289
370
  Note:
290
371
  Requires a working connection to an MT5 terminal.
372
+ See `bbstrader.metatrader.rates.Rates` for other arguments.
373
+ See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
291
374
  """
292
375
  self.tf = kwargs.get('time_frame', 'D1')
293
- self.start = kwargs.get('mt5_start')
376
+ self.start = kwargs.get('mt5_start', datetime(2000, 1, 1))
294
377
  self.end = kwargs.get('mt5_end', datetime.now())
295
- self.data_dir = kwargs.get('mt5_data', 'mt5_data')
378
+ self.use_utc = kwargs.get('use_utc', False)
379
+ self.filer = kwargs.get('filter', False)
380
+ self.fill_na = kwargs.get('fill_na', False)
381
+ self.lower_cols = kwargs.get('lower_cols', True)
382
+ self.data_dir = kwargs.get('data_dir')
296
383
  self.symbol_list = symbol_list
297
384
  csv_dir = self._download_data(self.data_dir)
298
385
  super().__init__(events, symbol_list, csv_dir)
299
386
 
300
387
  def _download_data(self, cache_dir: str):
301
- data_dir = Path() / cache_dir
388
+ data_dir = cache_dir or BBSTRADER_DIR / 'mt5_data' / self.tf
302
389
  data_dir.mkdir(parents=True, exist_ok=True)
303
390
  for symbol in self.symbol_list:
304
391
  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
392
+ data = download_historical_data(
393
+ symbol=symbol,
394
+ time_frame=self.tf,
395
+ date_from=self.start,
396
+ date_to=self.end,
397
+ utc=self.use_utc,
398
+ filter=self.filer,
399
+ fill_na=self.fill_na,
400
+ lower_colnames=self.lower_cols
308
401
  )
309
402
  if data is None:
310
403
  raise ValueError(f"No data found for {symbol}")
@@ -314,7 +407,7 @@ class MT5HistoricDataHandler(BaseCSVDataHandler):
314
407
  return data_dir
315
408
 
316
409
 
317
- class YFHistoricDataHandler(BaseCSVDataHandler):
410
+ class YFDataHandler(BaseCSVDataHandler):
318
411
  """
319
412
  Downloads historical data from Yahoo Finance and provides
320
413
  an interface for accessing this data bar-by-bar, simulating
@@ -333,40 +426,47 @@ class YFHistoricDataHandler(BaseCSVDataHandler):
333
426
  symbol_list (list[str]): List of symbols to download data for.
334
427
  yf_start (str): Start date for historical data (YYYY-MM-DD).
335
428
  yf_end (str): End date for historical data (YYYY-MM-DD).
336
- cache_dir (str, optional): Directory for caching data (default: 'yf_cache').
429
+ data_dir (str, optional): Directory for caching data .
430
+
431
+ Note:
432
+ See `bbstrader.btengine.data.BaseCSVDataHandler` for other arguments.
337
433
  """
338
434
  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')
435
+ self.start_date = kwargs.get('yf_start', '2000-01-01')
436
+ self.end_date = kwargs.get('yf_end', datetime.now().strftime('%Y-%m-%d'))
437
+ self.cache_dir = kwargs.get('data_dir')
342
438
  csv_dir = self._download_and_cache_data(self.cache_dir)
343
439
  super().__init__(events, symbol_list, csv_dir)
344
440
 
345
441
  def _download_and_cache_data(self, cache_dir: str):
346
442
  """Downloads and caches historical data as CSV files."""
443
+ cache_dir = cache_dir or BBSTRADER_DIR / 'yf_data' / 'daily'
347
444
  os.makedirs(cache_dir, exist_ok=True)
348
445
  for symbol in self.symbol_list:
349
446
  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}")
447
+ try:
448
+ data = yf.download(
449
+ symbol, start=self.start_date, end=self.end_date, multi_level_index=False)
450
+ if data.empty:
451
+ raise ValueError(f"No data found for {symbol}")
452
+ data.to_csv(filepath) # Cache the data
453
+ except Exception as e:
454
+ raise ValueError(f"Error downloading {symbol}: {e}")
359
455
  return cache_dir
360
456
 
361
457
 
362
458
  # TODO # Get data from EODHD
363
459
  # https://eodhd.com/
364
- class EODHDHistoricDataHandler(BaseCSVDataHandler):
460
+ class EODHDataHandler(BaseCSVDataHandler):
365
461
  ...
366
462
 
367
463
  # TODO # Get data from FMP using Financialtoolkit API
368
464
  # https://github.com/bbalouki/FinanceToolkit
369
- class FMPHistoricDataHandler(BaseCSVDataHandler):
465
+ class FMPDataHandler(BaseCSVDataHandler):
466
+ ...
467
+
468
+
469
+ class AlgoseekDataHandler(BaseCSVDataHandler):
370
470
  ...
371
471
 
372
472