Volume Filler – Execution Bits added repeatedly?
-
When adding a Volume Filler to a strategy, the strategy started executing a very small number of Trades, despite executing thousands of Orders. Without the Volume Filler, the strategy executes the number of Trades normally.
I created a test strategy to try to understand this behavior, using a data feed file from the github repository (file: 2006-01-02-volume-min-001.txt in datas directory).
The Volume Filler used to test always returns a constant maximum volume.
# A simple filler: max returned size is constant class TestFiller(with_metaclass(MetaParams, object)): def __call__(self, order, price, ago): maxsize = FILLER_VOLUME return min(maxsize, abs(order.executed.remsize))
And the strategy always tries to buy a constant maximum volume, but sends sell orders before reaching the maximum volume.
class TestStrategy(backtrader.Strategy): def next(self): # Computes volume of orders already opened buy_orders_volume = 0 sell_orders_volume = 0 for order in self.broker.get_orders_open(): remsize = order.executed.remsize if order.isbuy(): buy_orders_volume += remsize else: sell_orders_volume += abs(remsize) # Target volumes based on constant value and current position target_sell_volume = self.getposition().size; target_buy_volume = STRATEGY_VOLUME - self.getposition().size # Computes how much volume is needed to achieve target required_sell_volume = target_sell_volume - sell_orders_volume required_buy_volume = target_buy_volume - buy_orders_volume # Send orders immediately if required_sell_volume > 0: self.close(size=required_sell_volume) if required_buy_volume > 0: self.buy(size=required_buy_volume)
I added a notify_order() method to show the Execution Bits of the partial Buy Orders and also the associated Trade.
def notify_order(self, order): # Log only partial buy orders if order.isbuy(): if order.status in [backtrader.Order.Partial]: # Trade associated with the order # (code snippet from method _addnotification() of backtrader.Strategy class) datatrades = self._trades[order.data][order.tradeid] trade = datatrades[-1] print("TRADE - ref: {}, size: {} ".format(trade.ref, trade.size)) # Execution bits for exbit in order.executed.exbits: print('EXBIT - dt: {}, size:{}, price:{}, closed:{}, opened:{}'.format( order.data.num2date(exbit.dt), exbit.size, exbit.price, exbit.closed, exbit.opened)) print('---')
Complete code:
import backtrader import datetime from backtrader.utils.py3 import with_metaclass from backtrader.metabase import MetaParams # Test params STRATEGY_VOLUME = 100 FILLER_VOLUME = 5 USE_FILLER = True CASH = 1000000 FROM_DATE = datetime.datetime(2006, 1, 5) TO_DATE = datetime.datetime(2006, 1, 6) # A simple filler: max returned size is constant class TestFiller(with_metaclass(MetaParams, object)): def __call__(self, order, price, ago): maxsize = FILLER_VOLUME return min(maxsize, abs(order.executed.remsize)) class TestStrategy(backtrader.Strategy): def next(self): # Computes volume of orders already opened buy_orders_volume = 0 sell_orders_volume = 0 for order in self.broker.get_orders_open(): remsize = order.executed.remsize if order.isbuy(): buy_orders_volume += remsize else: sell_orders_volume += abs(remsize) # Target volumes based on constant value and current position target_sell_volume = self.getposition().size; target_buy_volume = STRATEGY_VOLUME - self.getposition().size # Computes how much volume is needed to achieve target required_sell_volume = target_sell_volume - sell_orders_volume required_buy_volume = target_buy_volume - buy_orders_volume # Send orders immediately if required_sell_volume > 0: self.close(size=required_sell_volume) if required_buy_volume > 0: self.buy(size=required_buy_volume) def notify_order(self, order): # Log only partial buy orders if order.isbuy(): if order.status in [backtrader.Order.Partial]: # Trade associated with the order # (code snippet from method _addnotification() of backtrader.Strategy class) datatrades = self._trades[order.data][order.tradeid] trade = datatrades[-1] print("TRADE - ref: {}, size: {} ".format(trade.ref, trade.size)) # Execution bits for exbit in order.executed.exbits: print('EXBIT - dt: {}, size:{}, price:{}, closed:{}, opened:{}'.format( order.data.num2date(exbit.dt), exbit.size, exbit.price, exbit.closed, exbit.opened)) print('---') def run(): cerebro = backtrader.Cerebro() cerebro.broker.setcash(CASH) if USE_FILLER: cerebro.broker.set_filler(TestFiller()) data = backtrader.feeds.BacktraderCSVData(dataname='../../datas/2006-01-02-volume-min-001.txt', fromdate = FROM_DATE, todate = TO_DATE, timeframe = backtrader.TimeFrame.Minutes, ) cerebro.adddata(data) #cerebro.replaydata(data, timeframe=bt.TimeFrame.Days) cerebro.addstrategy(TestStrategy) cerebro.addanalyzer(backtrader.analyzers.TradeAnalyzer, _name="myTradeAnalysis") st = cerebro.run()[0] trade_analyzer = st.analyzers.myTradeAnalysis.get_analysis() print("Total trades: " + str(trade_analyzer.total.total)) cerebro.plot() if __name__ == '__main__': run()
Log output (truncated):
TRADE - ref: 1, size: 5 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 --- TRADE - ref: 1, size: 10 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:03:00, size:5, price:3661.0, closed:0, opened:5 --- TRADE - ref: 1, size: 25 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:03:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:04:00, size:5, price:3662.0, closed:0, opened:5 --- TRADE - ref: 1, size: 45 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:03:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:04:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:05:00, size:5, price:3660.0, closed:0, opened:5 --- TRADE - ref: 1, size: 60 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:03:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:04:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:05:00, size:5, price:3660.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:06:00, size:5, price:3661.0, closed:0, opened:5 --- TRADE - ref: 1, size: 80 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:03:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:04:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:05:00, size:5, price:3660.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:06:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:07:00, size:5, price:3661.0, closed:0, opened:5 --- TRADE - ref: 1, size: 80 EXBIT - dt: 2006-01-05 09:07:00, size:5, price:3661.0, closed:0, opened:5 --- TRADE - ref: 1, size: 115 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:03:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:04:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:05:00, size:5, price:3660.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:06:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:07:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:08:00, size:5, price:3662.0, closed:0, opened:5 --- TRADE - ref: 1, size: 115 EXBIT - dt: 2006-01-05 09:08:00, size:5, price:3662.0, closed:0, opened:5 --- TRADE - ref: 1, size: 155 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:03:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:04:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:05:00, size:5, price:3660.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:06:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:07:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:08:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:09:00, size:5, price:3662.0, closed:0, opened:5 --- TRADE - ref: 1, size: 155 EXBIT - dt: 2006-01-05 09:09:00, size:5, price:3662.0, closed:0, opened:5 --- TRADE - ref: 1, size: 200 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:03:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:04:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:05:00, size:5, price:3660.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:06:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:07:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:08:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:09:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:10:00, size:5, price:3659.0, closed:0, opened:5 --- TRADE - ref: 1, size: 200 EXBIT - dt: 2006-01-05 09:10:00, size:5, price:3659.0, closed:0, opened:5 --- TRADE - ref: 1, size: 235 EXBIT - dt: 2006-01-05 09:02:00, size:5, price:3666.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:03:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:04:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:05:00, size:5, price:3660.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:06:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:07:00, size:5, price:3661.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:08:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:09:00, size:5, price:3662.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:10:00, size:5, price:3659.0, closed:0, opened:5 EXBIT - dt: 2006-01-05 09:11:00, size:5, price:3659.0, closed:0, opened:5 --- ...
Analyzing the log:
- Only one Trade is created during the entire test
- The size of the executed Trade (235 in the truncated log) is much larger than the maximum that the strategy tries to limit when sending orders (100 in this test)
- The cause of this seems to be that the Execution Bits of the partial Order are added up repeatedly
In the log, see the increasing size of the trade (5, 10, 25, 45, 60 ...) and how the exbits with the same minute seem to be being added up even if they had already been added in the previous call (+5, +10, + 15, + 20, + 25...) . I think that with each call it should add an execution bit (ie +5, +5, + 5 ...)
This log is to try to explain, but first I tried to debug the Backtrader source code, putting breakpoints in the strategy class for example, but my Python skills are not great (I actually learned this language to try to use Backtrader) ...
So: is there an error in the code that deals with Execution Bits? Or am I missing something?