bbstrader 0.0.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.

@@ -0,0 +1,374 @@
1
+ import os.path
2
+ import numpy as np
3
+ import pandas as pd
4
+ import yfinance as yf
5
+ from pathlib import Path
6
+ from typing import List
7
+ from queue import Queue
8
+ from abc import ABCMeta, abstractmethod
9
+ from bbstrader.metatrader.rates import Rates
10
+ from bbstrader.btengine.event import MarketEvent
11
+
12
+
13
+ __all__ = [
14
+ "DataHandler",
15
+ "BaseCSVDataHandler",
16
+ "HistoricCSVDataHandler",
17
+ "MT5HistoricDataHandler",
18
+ "YFHistoricDataHandler"
19
+ ]
20
+
21
+
22
+ class DataHandler(metaclass=ABCMeta):
23
+ """
24
+ One of the goals of an event-driven trading system is to minimise
25
+ duplication of code between the backtesting element and the live execution
26
+ element. Ideally it would be optimal to utilise the same signal generation
27
+ methodology and portfolio management components for both historical testing
28
+ and live trading. In order for this to work the Strategy object which generates
29
+ the Signals, and the `Portfolio` object which provides Orders based on them,
30
+ must utilise an identical interface to a market feed for both historic and live
31
+ running.
32
+
33
+ This motivates the concept of a class hierarchy based on a `DataHandler` object,
34
+ which givesall subclasses an interface for providing market data to the remaining
35
+ components within thesystem. In this way any subclass data handler can be "swapped out",
36
+ without affecting strategy or portfolio calculation.
37
+
38
+ Specific example subclasses could include `HistoricCSVDataHandler`,
39
+ `YFinanceDataHandler`, `FMPDataHandler`, `IBMarketFeedDataHandler` etc.
40
+ """
41
+
42
+ @abstractmethod
43
+ def get_latest_bar(self, symbol):
44
+ """
45
+ Returns the last bar updated.
46
+ """
47
+ raise NotImplementedError(
48
+ "Should implement get_latest_bar()"
49
+ )
50
+
51
+ @abstractmethod
52
+ def get_latest_bars(self, symbol, N=1):
53
+ """
54
+ Returns the last N bars updated.
55
+ """
56
+ raise NotImplementedError(
57
+ "Should implement get_latest_bars()"
58
+ )
59
+
60
+ @abstractmethod
61
+ def get_latest_bar_datetime(self, symbol):
62
+ """
63
+ Returns a Python datetime object for the last bar.
64
+ """
65
+ raise NotImplementedError(
66
+ "Should implement get_latest_bar_datetime()"
67
+ )
68
+
69
+ @abstractmethod
70
+ def get_latest_bar_value(self, symbol, val_type):
71
+ """
72
+ Returns one of the Open, High, Low, Close, Adj Close, Volume or Returns
73
+ from the last bar.
74
+ """
75
+ raise NotImplementedError(
76
+ "Should implement get_latest_bar_value()"
77
+ )
78
+
79
+ @abstractmethod
80
+ def get_latest_bars_values(self, symbol, val_type, N=1):
81
+ """
82
+ Returns the last N bar values from the
83
+ latest_symbol list, or N-k if less available.
84
+ """
85
+ raise NotImplementedError(
86
+ "Should implement get_latest_bars_values()"
87
+ )
88
+
89
+ @abstractmethod
90
+ def update_bars(self):
91
+ """
92
+ Pushes the latest bars to the bars_queue for each symbol
93
+ in a tuple OHLCVI format: (datetime, Open, High, Low,
94
+ Close, Adj Close, Volume, Retruns).
95
+ """
96
+ raise NotImplementedError(
97
+ "Should implement update_bars()"
98
+ )
99
+
100
+
101
+ class BaseCSVDataHandler(DataHandler):
102
+ """
103
+ Base class for handling data loaded from CSV files.
104
+ """
105
+
106
+ def __init__(self, events: Queue, symbol_list: List[str], csv_dir: str):
107
+ self.events = events
108
+ self.symbol_list = symbol_list
109
+ self.csv_dir = csv_dir
110
+ self.symbol_data = {}
111
+ self.latest_symbol_data = {}
112
+ self.continue_backtest = True
113
+ self._load_and_process_data()
114
+
115
+ def _load_and_process_data(self):
116
+ """
117
+ Opens the CSV files from the data directory, converting
118
+ them into pandas DataFrames within a symbol dictionary.
119
+ """
120
+ comb_index = None
121
+ for s in self.symbol_list:
122
+ # Load the CSV file with no header information,
123
+ # indexed on date
124
+ self.symbol_data[s] = pd.read_csv(
125
+ os.path.join(self.csv_dir, f'{s}.csv'),
126
+ header=0, index_col=0, parse_dates=True,
127
+ names=[
128
+ 'Datetime', 'Open', 'High',
129
+ 'Low', 'Close', 'Adj Close', 'Volume'
130
+ ]
131
+ )
132
+ self.symbol_data[s].sort_index(inplace=True)
133
+ # Combine the index to pad forward values
134
+ if comb_index is None:
135
+ comb_index = self.symbol_data[s].index
136
+ else:
137
+ comb_index.union(self.symbol_data[s].index)
138
+ # Set the latest symbol_data to None
139
+ self.latest_symbol_data[s] = []
140
+
141
+ # Reindex the dataframes
142
+ for s in self.symbol_list:
143
+ self.symbol_data[s] = self.symbol_data[s].reindex(
144
+ index=comb_index, method='pad'
145
+ )
146
+ self.symbol_data[s]["Returns"] = self.symbol_data[s][
147
+ "Adj Close"
148
+ ].pct_change().dropna()
149
+ self.symbol_data[s] = self.symbol_data[s].iterrows()
150
+
151
+ def _get_new_bar(self, symbol: str):
152
+ """
153
+ Returns the latest bar from the data feed.
154
+ """
155
+ for b in self.symbol_data[symbol]:
156
+ yield b
157
+
158
+ def get_latest_bar(self, symbol: str):
159
+ """
160
+ Returns the last bar from the latest_symbol list.
161
+ """
162
+ try:
163
+ bars_list = self.latest_symbol_data[symbol]
164
+ except KeyError:
165
+ print("Symbol not available in the historical data set.")
166
+ raise
167
+ else:
168
+ return bars_list[-1]
169
+
170
+ def get_latest_bars(self, symbol: str, N=1):
171
+ """
172
+ Returns the last N bars from the latest_symbol list,
173
+ or N-k if less available.
174
+ """
175
+ try:
176
+ bars_list = self.latest_symbol_data[symbol]
177
+ except KeyError:
178
+ print("Symbol not available in the historical data set.")
179
+ raise
180
+ else:
181
+ return bars_list[-N:]
182
+
183
+ def get_latest_bar_datetime(self, symbol: str):
184
+ """
185
+ Returns a Python datetime object for the last bar.
186
+ """
187
+ try:
188
+ bars_list = self.latest_symbol_data[symbol]
189
+ except KeyError:
190
+ print("Symbol not available in the historical data set.")
191
+ raise
192
+ else:
193
+ return bars_list[-1][0]
194
+
195
+ def get_latest_bar_value(self, symbol: str, val_type: str):
196
+ """
197
+ Returns one of the Open, High, Low, Close, Volume or OI
198
+ values from the pandas Bar series object.
199
+ """
200
+ try:
201
+ bars_list = self.latest_symbol_data[symbol]
202
+ except KeyError:
203
+ print("Symbol not available in the historical data set.")
204
+ raise
205
+ else:
206
+ return getattr(bars_list[-1][1], val_type)
207
+
208
+ def get_latest_bars_values(self, symbol: str, val_type: str, N=1):
209
+ """
210
+ Returns the last N bar values from the
211
+ latest_symbol list, or N-k if less available.
212
+ """
213
+ try:
214
+ bars_list = self.get_latest_bars(symbol, N)
215
+ except KeyError:
216
+ print("That symbol is not available in the historical data set.")
217
+ raise
218
+ else:
219
+ return np.array([getattr(b[1], val_type) for b in bars_list])
220
+
221
+ def update_bars(self):
222
+ """
223
+ Pushes the latest bar to the latest_symbol_data structure
224
+ for all symbols in the symbol list.
225
+ """
226
+ for s in self.symbol_list:
227
+ try:
228
+ bar = next(self._get_new_bar(s))
229
+ except StopIteration:
230
+ self.continue_backtest = False
231
+ else:
232
+ if bar is not None:
233
+ self.latest_symbol_data[s].append(bar)
234
+ self.events.put(MarketEvent())
235
+
236
+
237
+ class HistoricCSVDataHandler(BaseCSVDataHandler):
238
+ """
239
+ `HistoricCSVDataHandler` is designed to read CSV files for
240
+ each requested symbol from disk and provide an interface
241
+ to obtain the "latest" bar in a manner identical to a live
242
+ trading interface.
243
+
244
+ This class is useful when you have your own data or you want
245
+ to cutomize specific data in some form based on your `Strategy()` .
246
+ """
247
+
248
+ def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
249
+ """
250
+ Initialises the historic data handler by requesting
251
+ the location of the CSV files and a list of symbols.
252
+ It will be assumed that all files are of the form
253
+ `symbol.csv`, where `symbol` is a string in the list.
254
+
255
+ Args:
256
+ events (Queue): The Event Queue.
257
+ csv_dir (str): Absolute directory path to the CSV files.
258
+ symbol_list (List[str]): A list of symbol strings.
259
+ """
260
+ csv_dir = kwargs.get("csv_dir")
261
+ super().__init__(events, symbol_list, csv_dir)
262
+
263
+ MAX_BARS = 10_000_000
264
+ class MT5HistoricDataHandler(BaseCSVDataHandler):
265
+ """
266
+ Downloads historical data from MetaTrader 5 (MT5) and provides
267
+ an interface for accessing this data bar-by-bar, simulating
268
+ a live market feed for backtesting.
269
+
270
+ Data is downloaded from MT5, saved as CSV files, and then loaded
271
+ using the functionality inherited from `BaseCSVDataHandler`.
272
+
273
+ This class is useful when you need to get data from specific broker
274
+ for different time frames.
275
+ """
276
+
277
+ def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
278
+ """
279
+ Args:
280
+ events (Queue): The Event Queue for passing market events.
281
+ symbol_list (List[str]): A list of symbol strings to download data for.
282
+ **kwargs: Keyword arguments for data retrieval:
283
+ time_frame (str): MT5 time frame (e.g., 'D1' for daily).
284
+ max_bars (int): Maximum number of bars to download per symbol.
285
+ start_pos (int | str, optional): Starting bar position (default: 0).
286
+ If it set to `str`, it must be in 'YYYY-MM-DD' format and
287
+ session_duration (int | float): Number of trading hours per day.
288
+ mt5_data (str): Directory for storing data (default: 'mt5_data').
289
+
290
+ Note:
291
+ Requires a working connection to an MT5 terminal.
292
+ """
293
+ self.tf = kwargs.get('time_frame', 'D1')
294
+ self.start_pos = kwargs.get('start_pos', 0)
295
+ self.max_bars = kwargs.get('max_bars', MAX_BARS)
296
+ self.sd = kwargs.get('session_duration', 6.5)
297
+ self.data_dir = kwargs.get('mt5_data', 'mt5_data')
298
+ self.symbol_list = symbol_list
299
+ csv_dir = self._download_data(self.data_dir)
300
+ super().__init__(events, symbol_list, csv_dir)
301
+
302
+ def _download_data(self, cache_dir: str):
303
+ data_dir = Path() / cache_dir
304
+ data_dir.mkdir(parents=True, exist_ok=True)
305
+ for symbol in self.symbol_list:
306
+ try:
307
+ rate = Rates(symbol, self.tf, self.start_pos, self.max_bars, self.sd)
308
+ data = rate.get_rates_from_pos()
309
+ if data is None:
310
+ raise ValueError(f"No data found for {symbol}")
311
+ data.to_csv(data_dir / f'{symbol}.csv')
312
+ except Exception as e:
313
+ raise ValueError(f"Error downloading {symbol}: {e}")
314
+ return data_dir
315
+
316
+
317
+ class YFHistoricDataHandler(BaseCSVDataHandler):
318
+ """
319
+ Downloads historical data from Yahoo Finance and provides
320
+ an interface for accessing this data bar-by-bar, simulating
321
+ a live market feed for backtesting.
322
+
323
+ Data is fetched using the `yfinance` library and optionally cached
324
+ to disk to speed up subsequent runs.
325
+
326
+ This class is useful when working with historical daily prices.
327
+ """
328
+
329
+ def __init__(self, events: Queue, symbol_list: List[str], **kwargs):
330
+ """
331
+ Args:
332
+ events (Queue): The Event Queue for passing market events.
333
+ symbol_list (list[str]): List of symbols to download data for.
334
+ yf_start (str): Start date for historical data (YYYY-MM-DD).
335
+ yf_end (str): End date for historical data (YYYY-MM-DD).
336
+ cache_dir (str, optional): Directory for caching data (default: 'yf_cache').
337
+ """
338
+ 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')
342
+ csv_dir = self._download_and_cache_data(self.cache_dir)
343
+ super().__init__(events, symbol_list, csv_dir)
344
+
345
+ def _download_and_cache_data(self, cache_dir: str):
346
+ """Downloads and caches historical data as CSV files."""
347
+ os.makedirs(cache_dir, exist_ok=True)
348
+ for symbol in self.symbol_list:
349
+ 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}")
359
+ return cache_dir
360
+
361
+
362
+ # TODO # Get data from FinancialModelingPrep ()
363
+ class FMPHistoricDataHandler(BaseCSVDataHandler):
364
+ ...
365
+
366
+
367
+ class BaseFMPDataHanler(object):
368
+ ...
369
+
370
+
371
+ class FMPFundamentalDataHandler(BaseFMPDataHanler):
372
+ ...
373
+
374
+ # TODO Add other Handlers
@@ -0,0 +1,201 @@
1
+ from datetime import datetime
2
+
3
+ __all__ = [
4
+ "Event",
5
+ "MarketEvent",
6
+ "SignalEvent",
7
+ "OrderEvent",
8
+ "FillEvent"
9
+ ]
10
+
11
+
12
+ class Event(object):
13
+ """
14
+ Event is base class providing an interface for all subsequent
15
+ (inherited) events, that will trigger further events in the
16
+ trading infrastructure.
17
+ Since in many implementations the Event objects will likely develop greater
18
+ complexity, it is thus being "future-proofed" by creating a class hierarchy.
19
+ The Event class is simply a way to ensure that all events have a common interface
20
+ and can be handled in a consistent manner.
21
+ """
22
+ ...
23
+
24
+
25
+ class MarketEvent(Event):
26
+ """
27
+ Market Events are triggered when the outer while loop of the backtesting
28
+ system begins a new `"heartbeat"`. It occurs when the `DataHandler` object
29
+ receives a new update of market data for any symbols which are currently
30
+ being tracked. It is used to `trigger the Strategy object` generating
31
+ new `trading signals`. The event object simply contains an identification
32
+ that it is a market event, with no other structure.
33
+ """
34
+
35
+ def __init__(self):
36
+ """
37
+ Initialises the MarketEvent.
38
+ """
39
+ self.type = 'MARKET'
40
+
41
+
42
+ class SignalEvent(Event):
43
+ """
44
+ The `Strategy object` utilises market data to create new `SignalEvents`.
45
+ The SignalEvent contains a `strategy ID`, a `ticker symbol`, a `timestamp`
46
+ for when it was generated, a `direction` (long or short) and a `"strength"`
47
+ indicator (this is useful for mean reversion strategies) and the `quantiy`
48
+ to buy or sell. The `SignalEvents` are utilised by the `Portfolio object`
49
+ as advice for how to trade.
50
+ """
51
+
52
+ def __init__(self,
53
+ strategy_id: int,
54
+ symbol: str,
55
+ datetime: datetime,
56
+ signal_type: str,
57
+ quantity: int | float = 100,
58
+ strength: int | float = 1.0
59
+ ):
60
+ """
61
+ Initialises the SignalEvent.
62
+
63
+ Args:
64
+ strategy_id (int): The unique identifier for the strategy that
65
+ generated the signal.
66
+
67
+ symbol (str): The ticker symbol, e.g. 'GOOG'.
68
+ datetime (datetime): The timestamp at which the signal was generated.
69
+ signal_type (str): 'LONG' or 'SHORT'.
70
+ quantity (int | float): An optional integer (or float) representing the order size.
71
+ strength (int | float): An adjustment factor "suggestion" used to scale
72
+ quantity at the portfolio level. Useful for pairs strategies.
73
+ """
74
+ self.type = 'SIGNAL'
75
+ self.strategy_id = strategy_id
76
+ self.symbol = symbol
77
+ self.datetime = datetime
78
+ self.signal_type = signal_type
79
+ self.quantity = quantity
80
+ self.strength = strength
81
+
82
+
83
+ class OrderEvent(Event):
84
+ """
85
+ When a Portfolio object receives `SignalEvents` it assesses them
86
+ in the wider context of the portfolio, in terms of risk and position sizing.
87
+ This ultimately leads to `OrderEvents` that will be sent to an `ExecutionHandler`.
88
+
89
+ The `OrderEvents` is slightly more complex than a `SignalEvents` since
90
+ it contains a quantity field in addition to the aforementioned properties
91
+ of SignalEvent. The quantity is determined by the Portfolio constraints.
92
+ In addition the OrderEvent has a `print_order()` method, used to output the
93
+ information to the console if necessary.
94
+ """
95
+
96
+ def __init__(self,
97
+ symbol: str,
98
+ order_type: str,
99
+ quantity: int | float,
100
+ direction: str
101
+ ):
102
+ """
103
+ Initialises the order type, setting whether it is
104
+ a Market order ('MKT') or Limit order ('LMT'), has
105
+ a quantity (integral or float) and its direction ('BUY' or 'SELL').
106
+
107
+ Args:
108
+ symbol (str): The instrument to trade.
109
+ order_type (str): 'MKT' or 'LMT' for Market or Limit.
110
+ quantity (int | float): Non-negative number for quantity.
111
+ direction (str): 'BUY' or 'SELL' for long or short.
112
+ """
113
+ self.type = 'ORDER'
114
+ self.symbol = symbol
115
+ self.order_type = order_type
116
+ self.quantity = quantity
117
+ self.direction = direction
118
+
119
+ def print_order(self):
120
+ """
121
+ Outputs the values within the Order.
122
+ """
123
+ print(
124
+ "Order: Symbol=%s, Type=%s, Quantity=%s, Direction=%s" %
125
+ (self.symbol, self.order_type, self.quantity, self.direction)
126
+ )
127
+
128
+
129
+ class FillEvent(Event):
130
+ """
131
+ When an `ExecutionHandler` receives an `OrderEvent` it must transact the order.
132
+ Once an order has been transacted it generates a `FillEvent`, which describes
133
+ the cost of purchase or sale as well as the transaction costs, such as fees
134
+ or slippage.
135
+
136
+ The `FillEvent` is the Event with the greatest complexity.
137
+ It contains a `timestamp` for when an order was filled, the `symbol`
138
+ of the order and the `exchange` it was executed on, the `quantity`
139
+ of shares transacted, the `actual price of the purchase` and the `commission
140
+ incurred`.
141
+
142
+ The commission is calculated using the Interactive Brokers commissions.
143
+ For US API orders this commission is `1.30 USD` minimum per order, with a flat
144
+ rate of either 0.013 USD or 0.08 USD per share depending upon whether
145
+ the trade size is below or above `500 units` of stock.
146
+ """
147
+
148
+ def __init__(self,
149
+ timeindex: datetime,
150
+ symbol: str,
151
+ exchange: str,
152
+ quantity: int | float,
153
+ direction: str,
154
+ fill_cost: int | float,
155
+ commission: float | None = None
156
+ ):
157
+ """
158
+ Initialises the FillEvent object. Sets the symbol, exchange,
159
+ quantity, direction, cost of fill and an optional
160
+ commission.
161
+
162
+ If commission is not provided, the Fill object will
163
+ calculate it based on the trade size and Interactive
164
+ Brokers fees.
165
+
166
+ Args:
167
+ timeindex (datetime): The bar-resolution when the order was filled.
168
+ symbol (str): The instrument which was filled.
169
+ exchange (str): The exchange where the order was filled.
170
+ quantity (int | float): The filled quantity.
171
+ direction (str): The direction of fill ('BUY' or 'SELL')
172
+ fill_cost (int | float): The holdings value in dollars.
173
+ commission (float | None): An optional commission sent from IB.
174
+ """
175
+ self.type = 'FILL'
176
+ self.timeindex = timeindex
177
+ self.symbol = symbol
178
+ self.exchange = exchange
179
+ self.quantity = quantity
180
+ self.direction = direction
181
+ self.fill_cost = fill_cost
182
+ # Calculate commission
183
+ if commission is None:
184
+ self.commission = self.calculate_ib_commission()
185
+ else:
186
+ self.commission = commission
187
+
188
+ def calculate_ib_commission(self):
189
+ """
190
+ Calculates the fees of trading based on an Interactive
191
+ Brokers fee structure for API, in USD.
192
+ This does not include exchange or ECN fees.
193
+ Based on "US API Directed Orders":
194
+ https://www.interactivebrokers.com/en/index.php?f=commission&p=stocks2
195
+ """
196
+ full_cost = 1.3
197
+ if self.quantity <= 500:
198
+ full_cost = max(1.3, 0.013 * self.quantity)
199
+ else:
200
+ full_cost = max(1.3, 0.008 * self.quantity)
201
+ return full_cost
@@ -0,0 +1,83 @@
1
+ import datetime
2
+ from queue import Queue
3
+ from abc import ABCMeta, abstractmethod
4
+ from bbstrader.btengine.event import FillEvent, OrderEvent
5
+
6
+ __all__ = [
7
+ "ExecutionHandler",
8
+ "SimulatedExecutionHandler"
9
+ ]
10
+
11
+
12
+ class ExecutionHandler(metaclass=ABCMeta):
13
+ """
14
+ The ExecutionHandler abstract class handles the interaction
15
+ between a set of order objects generated by a Portfolio and
16
+ the ultimate set of Fill objects that actually occur in the
17
+ market.
18
+
19
+ The handlers can be used to subclass simulated brokerages
20
+ or live brokerages, with identical interfaces. This allows
21
+ strategies to be backtested in a very similar manner to the
22
+ live trading engine.
23
+
24
+ The ExecutionHandler described here is exceedingly simple,
25
+ since it fills all orders at the current market price.
26
+ This is highly unrealistic, but serves as a good baseline for improvement.
27
+ """
28
+
29
+ @abstractmethod
30
+ def execute_order(self, event: OrderEvent):
31
+ """
32
+ Takes an Order event and executes it, producing
33
+ a Fill event that gets placed onto the Events queue.
34
+
35
+ Args:
36
+ event (OrderEvent): Contains an Event object with order information.
37
+ """
38
+ raise NotImplementedError(
39
+ "Should implement execute_order()"
40
+ )
41
+
42
+
43
+ class SimulatedExecutionHandler(ExecutionHandler):
44
+ """
45
+ The simulated execution handler simply converts all order
46
+ objects into their equivalent fill objects automatically
47
+ without latency, slippage or fill-ratio issues.
48
+
49
+ This allows a straightforward "first go" test of any strategy,
50
+ before implementation with a more sophisticated execution
51
+ handler.
52
+ """
53
+
54
+ def __init__(self, events: Queue):
55
+ """
56
+ Initialises the handler, setting the event queues
57
+ up internally.
58
+
59
+ Args:
60
+ events (Queue): The Queue of Event objects.
61
+ """
62
+ self.events = events
63
+
64
+ def execute_order(self, event: OrderEvent):
65
+ """
66
+ Simply converts Order objects into Fill objects naively,
67
+ i.e. without any latency, slippage or fill ratio problems.
68
+
69
+ Args:
70
+ event (OrderEvent): Contains an Event object with order information.
71
+ """
72
+ if event.type == 'ORDER':
73
+ fill_event = FillEvent(
74
+ datetime.datetime.now(), event.symbol,
75
+ 'ARCA', event.quantity, event.direction, None
76
+ )
77
+ self.events.put(fill_event)
78
+
79
+ # TODO # Use in live execution
80
+
81
+
82
+ class MT5ExecutionHandler(ExecutionHandler):
83
+ ...