How strategy works?
-
I Have a code
from datetime import datetime
import backtrader as bt
from BackTraderQuik.QKStore import QKStore
from BackTraderQuik.QKData import QKData
import talibclass Trend_Pull_Back_SBER(bt.Strategy):
params = ( ('period', 230), ('PullBack', 4), ('ticker_id', 'SPBFUT.SRZ1') ) def log(self, txt, dt=None): """Вывод строки с датой на консоль""" dt = bt.num2date(self.datas[0].datetime[0]) if dt is None else dt # Заданная дата или дата текущего бара print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль def __init__(self): """Инициализация торговой системы""" self.DataClose = self.datas[0].close self.isLive = False # Сначала будут приходить исторические данные self.order = None # Заявка def next(self): """Получение следующего исторического/нового бара""" if not self.isLive: return if self.order and self.order.status == bt.Order.Submitted: # Если заявка не исполнена (отправлена брокеру) return # то выходим, дальше не продолжаем if self.params.ticker_id==self.datas[0]._dataname: self.log(f'Position = {self.getposition(data = self.datas[0])}') self.log(f'DataClose SBER = {self.DataClose[0]:.5f}') if not self.getposition(data = self.datas[0]): self.log(f'NO Position') else: self.log(f'Have Position') def notify_data(self, data, status, *args, **kwargs): """Изменение статуса приходящих баров""" dataStatus = data._getstatusname(status) # Получаем статус (только при LiveBars=True) print(dataStatus + ' 1') # Не можем вывести в лог, т.к. первый статус DELAYED получаем до первого бара (и его даты) self.isLive = dataStatus == 'LIVE' def notify_order(self, order): """Изменение статуса заявки""" if order.status in [order.Submitted, order.Accepted]: # Если заявка не исполнена (отправлена брокеру или принята брокером) self.log(f'Order Status: {order.getstatusname()}. TransId={order.ref}') return # то выходим, дальше не продолжаем if order.status in [order.Canceled]: # Если заявка отменена self.log(f'Order Status: {order.getstatusname()}. TransId={order.ref}') return # то выходим, дальше не продолжаем if order.status in [order.Completed]: # Если заявка исполнена if order.isbuy(): # Заявка на покупку self.log(f'Bought @{order.executed.price:.2f}, Cost={order.executed.value:.2f}, Comm={order.executed.comm:.2f}') elif order.issell(): # Заявка на продажу self.log(f'Sold @{order.executed.price:.2f}, Cost={order.executed.value:.2f}, Comm={order.executed.comm:.2f}') elif order.status in [order.Margin, order.Rejected]: # Нет средств, или заявка отклонена брокером self.log(f'Order Status: {order.getstatusname()}. TransId={order.ref}') self.order = None # Этой заявки больше нет def notify_trade(self, trade): """Изменение статуса позиции""" if not trade.isclosed: # Если позиция не закрыта return # то статус позиции не изменился, выходим, дальше не продолжаем self.log(f'Trade Profit, Gross={trade.pnl:.2f}, NET={trade.pnlcomm:.2f}')
class Trend_Hilbert_GOLD(bt.Strategy):
params = ( # Параметры торговой системы ('ticker_id', 'SPBFUT.GDZ1'), ) def log(self, txt, dt=None): """Вывод строки с датой на консоль""" dt = bt.num2date(self.datas[2].datetime[0]) if dt is None else dt # Заданная дата или дата текущего бара print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль def __init__(self): """Инициализация торговой системы""" self.DataClose = self.datas[2].close self.isLive = False # Сначала будут приходить исторические данные self.order = None # Заявка def next(self): """Получение следующего исторического/нового бара""" if not self.isLive: return if self.order and self.order.status == bt.Order.Submitted: # Если заявка не исполнена (отправлена брокеру) return # то выходим, дальше не продолжаем if self.params.ticker_id==self.datas[2]._dataname: if not self.getposition(data = self.datas[2]): pass else: pass def notify_data(self, data, status, *args, **kwargs): """Изменение статуса приходящих баров""" dataStatus = data._getstatusname(status) # Получаем статус (только при LiveBars=True) print(dataStatus + ' 2') # Не можем вывести в лог, т.к. первый статус DELAYED получаем до первого бара (и его даты) self.isLive = dataStatus == 'LIVE' def notify_order(self, order): """Изменение статуса заявки""" if order.status in [order.Submitted, order.Accepted]: # Если заявка не исполнена (отправлена брокеру или принята брокером) self.log(f'Order Status: {order.getstatusname()}. TransId={order.ref}') return # то выходим, дальше не продолжаем if order.status in [order.Canceled]: # Если заявка отменена self.log(f'Order Status: {order.getstatusname()}. TransId={order.ref}') return # то выходим, дальше не продолжаем if order.status in [order.Completed]: # Если заявка исполнена if order.isbuy(): # Заявка на покупку self.log(f'Bought @{order.executed.price:.2f}, Cost={order.executed.value:.2f}, Comm={order.executed.comm:.2f}') elif order.issell(): # Заявка на продажу self.log(f'Sold @{order.executed.price:.2f}, Cost={order.executed.value:.2f}, Comm={order.executed.comm:.2f}') elif order.status in [order.Margin, order.Rejected]: # Нет средств, или заявка отклонена брокером self.log(f'Order Status: {order.getstatusname()}. TransId={order.ref}') self.order = None # Этой заявки больше нет def notify_trade(self, trade): """Изменение статуса позиции""" if not trade.isclosed: # Если позиция не закрыта return # то статус позиции не изменился, выходим, дальше не продолжаем self.log(f'Trade Profit, Gross={trade.pnl:.2f}, NET={trade.pnlcomm:.2f}')
class Trend_Hilbert_GAZP(bt.Strategy):
params = ( # Параметры торговой системы ('ticker_id', 'SPBFUT.GZZ1'), ) def log(self, txt, dt=None): """Вывод строки с датой на консоль""" dt = bt.num2date(self.datas[3].datetime[0]) if dt is None else dt # Заданная дата или дата текущего бара print(f'{dt.strftime("%d.%m.%Y %H:%M")}, {txt}') # Выводим дату и время с заданным текстом на консоль def __init__(self): """Инициализация торговой системы""" self.DataClose = self.datas[3].close self.isLive = False # Сначала будут приходить исторические данные self.order = None # Заявка def next(self): """Получение следующего исторического/нового бара""" if not self.isLive: return if self.order and self.order.status == bt.Order.Submitted: # Если заявка не исполнена (отправлена брокеру) return # то выходим, дальше не продолжаем if self.params.ticker_id==self.datas[3]._dataname: if not self.getposition(data = self.datas[3]): self.log(f'NO Position GAZP') else: self.log(f'Position GAZP') def notify_data(self, data, status, *args, **kwargs): """Изменение статуса приходящих баров""" dataStatus = data._getstatusname(status) # Получаем статус (только при LiveBars=True) print(dataStatus + ' 3') # Не можем вывести в лог, т.к. первый статус DELAYED получаем до первого бара (и его даты) self.isLive = dataStatus == 'LIVE' def notify_order(self, order): """Изменение статуса заявки""" if order.status in [order.Submitted, order.Accepted]: # Если заявка не исполнена (отправлена брокеру или принята брокером) self.log(f'Order Status: {order.getstatusname()}. TransId={order.ref}') return # то выходим, дальше не продолжаем if order.status in [order.Canceled]: # Если заявка отменена self.log(f'Order Status: {order.getstatusname()}. TransId={order.ref}') return # то выходим, дальше не продолжаем if order.status in [order.Completed]: # Если заявка исполнена if order.isbuy(): # Заявка на покупку self.log(f'Bought @{order.executed.price:.2f}, Cost={order.executed.value:.2f}, Comm={order.executed.comm:.2f}') elif order.issell(): # Заявка на продажу self.log(f'Sold @{order.executed.price:.2f}, Cost={order.executed.value:.2f}, Comm={order.executed.comm:.2f}') elif order.status in [order.Margin, order.Rejected]: # Нет средств, или заявка отклонена брокером self.log(f'Order Status: {order.getstatusname()}. TransId={order.ref}') self.order = None # Этой заявки больше нет def notify_trade(self, trade): """Изменение статуса позиции""" if not trade.isclosed: # Если позиция не закрыта return # то статус позиции не изменился, выходим, дальше не продолжаем self.log(f'Trade Profit, Gross={trade.pnl:.2f}, NET={trade.pnlcomm:.2f}')
if name == 'main': # Точка входа при запуске этого скрипта
cerebro = bt.Cerebro() # Инициируем "движок" BackTradersymbol0 = 'SPBFUT.SRZ1' # 0 1-resampledata symbol2 = 'SPBFUT.GDZ1' symbol3 = 'SPBFUT.GZZ1' cerebro.addstrategy(Trend_Pull_Back_SBER, ticker_id ='SPBFUT.SRZ1') # Добавляем торговую систему cerebro.addstrategy(Trend_Hilbert_GOLD, ticker_id ='SPBFUT.GDZ1') # Добавляем торговую систему cerebro.addstrategy(Trend_Hilbert_GAZP, ticker_id ='SPBFUT.GZZ1') # Добавляем торговую систему store = QKStore() # Хранилище QUIK broker = store.getbroker(ClientCode = '', FirmId = ' ', TradeAccountId= ' ' ) # Брокер со счетом по умолчанию (срочный рынок РФ) cerebro.setbroker(broker) # Устанавливаем брокера data0 = QKData(dataname=symbol0, timeframe=bt.TimeFrame.Minutes, compression=60, fromdate=datetime(2021, 5, 5, 19, 0), LiveBars=True) # Исторические и новые минутные бары за все время cerebro.adddata(data0) # Добавляем данные cerebro.resampledata(data0, timeframe = bt.TimeFrame.Days , compression = 1) data2 = QKData(dataname=symbol2, timeframe=bt.TimeFrame.Minutes, compression=60, fromdate=datetime(2021, 7, 5, 19, 0), LiveBars=True) # Исторические и новые минутные бары за все время data3 = QKData(dataname=symbol3, timeframe=bt.TimeFrame.Minutes, compression=60, fromdate=datetime(2021, 7, 5, 19, 0), LiveBars=True) # Исторические и новые минутные бары за все время cerebro.adddata(data2) # Добавляем данные cerebro.adddata(data3) # Добавляем данные cerebro.run() # Запуск торговой системы
And the result
DELAYED 1
DELAYED 2
DELAYED 3
CONNECTED 1
CONNECTED 2
CONNECTED 3
DELAYED 1
DELAYED 2
DELAYED 3
CONNECTED 1
CONNECTED 2
CONNECTED 3
DELAYED 1
DELAYED 2
DELAYED 3
CONNECTED 1
CONNECTED 2
CONNECTED 3
DISCONNECTED 1
DISCONNECTED 2
DISCONNECTED 3
DISCONNECTED 1
DISCONNECTED 2
DISCONNECTED 3
DISCONNECTED 1
DISCONNECTED 2
DISCONNECTED 3
LIVE 1
LIVE 2
LIVE 3
24.09.2021 16:00, Position = --- Position Begin- Size: 200
- Price: 330.38
- Price orig: 330.38
- Closed: 0
- Opened: 0
- Adjbase: None
--- Position End
24.09.2021 16:00, DataClose SBER = 330.38000
24.09.2021 16:00, Have Position
24.09.2021 17:00, Position GAZP
LIVE 1
LIVE 2
LIVE 3
LIVE 1
LIVE 2
LIVE 3
24.09.2021 17:00, Position = --- Position Begin - Size: 200
- Price: 330.38
- Price orig: 330.38
- Closed: 0
- Opened: 0
- Adjbase: None
--- Position End
24.09.2021 17:00, DataClose SBER = 331.09000
24.09.2021 17:00, Have Position
24.09.2021 17:00, Position GAZP
24.09.2021 17:00, Position = --- Position Begin - Size: 200
- Price: 330.38
- Price orig: 330.38
- Closed: 0
- Opened: 0
- Adjbase: None
--- Position End
24.09.2021 17:00, DataClose SBER = 331.09000
24.09.2021 17:00, Have Position
24.09.2021 17:00, Position GAZP
24.09.2021 18:00, Position = --- Position Begin - Size: 200
- Price: 330.38
- Price orig: 330.38
- Closed: 0
- Opened: 0
- Adjbase: None
--- Position End
24.09.2021 18:00, DataClose SBER = 330.35000
24.09.2021 18:00, Have Position
24.09.2021 18:00, Position GAZP
I removed some of the executable code in the next () construct. This is for simple.
My question is:
Why is the strategy executed 3 times? (I have a DELAYED take 1 2 3 3 times)
How to correctly implement a strategy with trading multiple strategies? -
@zoom Each added strategy has access to every data - so each strategy got notified about every change in every data.
So in your case, you've added 3 datas and 3 strategies. For each data's state change all 3 strategies will be notified. That's explain the multiple 'DELAYED' events.
Regarding the proper implementation of multiple strategies, each using its own data but all of them running against same broker. There are multiple ways to implement it - and your implementation is almost right.
Each strategy may get the data name as a parameter. The strategy may then calculate the
datas
index using this parameter. (in your case thedatas
index is hardcoded inside the strategy - which is suboptimal and will not scale) -
@vladisld
thanks for the answer.My program is printing Position, DataClose SBER, Have Position, Position GAZP. Why is data0 printed three times? Each strategy has a filter for input data. That is, if data0, data1, data 2 enters the Trend_Pull_Back_SBER strategy, only data with data0 should be printed, the same with the Trend_Hilbert_GAZP and Trend_Hilbert_GOLD strategies.
I understand it should be like this, the Trend_Pull_Back_SBER strategy is called three times with data data0, data1, data 2, but the data should be printed once.You could show an example of the optimal implementation of the strategy you are talking about.
-
@zoom said in How strategy works?:
Why is data0 printed three times? Each strategy has a filter for input data
Are you talking about this code?:
if self.params.ticker_id==self.datas[0]._dataname
It just checks that
datas[0]
indeed matches the parameter passed to the strategy. This has nothing to do with filtering which data is changed uponnext
call. So each time thenext
is called for different datas, the check above is always true. -
@vladisld
thanks for the answer.How would you do filtering? How would you make your trading system more optimal?
-
@zoom Please take a look at the following post, it seems the similar problem was discussed there:
https://community.backtrader.com/topic/1703/repeated-days-in-multi-symbol-strategy/2
-
@vladisld
if we use this, we solve the data sync problem and we don't have data duplication.if self.datas[0].datetime[0] == self.datas[1].datetime[0]
-
@zoom said in How strategy works?:
@vladisld
if we use this, we solve the data sync problem and we don't have data duplication.if self.datas[0].datetime[0] == self.datas[1].datetime[0]
hmm - I'm not sure. For backtest scenario it may depend on exact timestamps used by each data feed. In case they are different or offset (even slightly) - the above statement may always be false. For live trading scenario this may be even more emphasized.
IMHO the solution provided in the referenced post (above) seems pretty solid. If the length of each data is tracked inside the strategy - one may definitely state which data was updated in each
next
call.