Skip to content

Instantly share code, notes, and snippets.

@ceshine
Last active July 22, 2024 07:35
Show Gist options
  • Save ceshine/fc3af1f5c7c552738d403db47a8a2399 to your computer and use it in GitHub Desktop.
Save ceshine/fc3af1f5c7c552738d403db47a8a2399 to your computer and use it in GitHub Desktop.
A Simple Strategy Trading Two Stocks (back trader)
"""A Simple Strategy Trading Two Stocks
Original code: https://blog.csdn.net/qq_26948675/article/details/80016633
Modified based on: https://www.backtrader.com/blog/posts/2018-04-22-improving-code/improving-code.html
Replaced the local CSV files with online data from IEX.
Unfortunately, this strategy is not profitable for the two stocks picked.
"""
import datetime # For datetime objects
import os.path # To manage paths
import sys # To find out the script name (in argv[0])
import pandas as pd
# Import the backtrader platform
import backtrader as bt
# Data Source
import pandas_datareader.data as web
# To avoid downloading the same data more than once
import joblib
MEMORY = joblib.Memory(cachedir="cache/")
@MEMORY.cache
def get_data(symbol, start, end):
df_prices = web.DataReader(
symbol, 'iex',
start, end).reset_index()
df_prices["date"] = pd.to_datetime(df_prices.date)
return df_prices.set_index("date")
class TestStrategy(bt.Strategy):
params = (
# Standard MACD Parameters
('period', 252),
('prepend_constant', True),
)
def log(self, txt, dt=None):
''' Logging function for this strategy'''
dt = dt or self.datas[0].datetime.date(0)
print('%s, %s' % (dt.isoformat(), txt))
def __init__(self):
self.dataclose_x = self.datas[0].close
self.dataclose_y = self.datas[1].close
ma1 = bt.indicators.SMA(
self.data0, period=self.p.period)
ma2 = bt.indicators.SMA(
self.data1, period=self.p.period)
# Use a built-in indicator
ma1_pct = bt.ind.PctChange(ma1, period=1) # The ma1 percentage part
ma2_pct = bt.ind.PctChange(ma2, period=1) # The ma2 percentage part
# # Use line delay notation (-x) to get a ref to the -1 point
# ma1_pct = ma1 / ma1(-1) - 1.0 # The ma1 percentage part
# ma2_pct = ma2 / ma2(-1) - 1.0 # The ma2 percentage part
self.buy_sig = ma1_pct > ma2_pct # buy signal
self.sell_sig = ma1_pct <= ma2_pct # sell signal
self.order = None
self.buyprice = None
self.buycomm = None
def notify_cashvalue(self, cash, value):
self.log('Cash %s Value %s' % (cash, value))
def notify_order(self, order):
print(type(order), 'Is Buy ', order.isbuy())
if order.status in [order.Submitted, order.Accepted]:
# Buy/Sell order submitted/accepted to/by broker - Nothing to do
return
# Check if an order has been completed
# Attention: broker could reject order if not enough cash
if order.status in [order.Completed]:
if order.isbuy():
self.log(
'BUY EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.buyprice = order.executed.price
self.buycomm = order.executed.comm
else: # Sell
self.log('SELL EXECUTED, Price: %.2f, Cost: %.2f, Comm %.2f' %
(order.executed.price,
order.executed.value,
order.executed.comm))
self.bar_executed = len(self)
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self.log('Order Canceled/Margin/Rejected')
self.order = None
def notify_trade(self, trade):
if not trade.isclosed:
return
self.log('OPERATION PROFIT, GROSS %.2f, NET %.2f' %
(trade.pnl, trade.pnlcomm))
def next(self):
# Simply log the closing price of the series from the reference
self.log('Close, %.2f' % self.dataclose_x[0])
self.log('Close, %.2f' % self.dataclose_y[0])
# Check if we are in the market
if not self.getposition(self.datas[1]):
# Not yet ... we MIGHT BUY if ...
if self.buy_sig:
# if sma[0]<top[-5]:
# BUY, BUY, BUY!!! (with default parameters)
self.log('BUY CREATE,{},{}'.format(
self.dataclose_y[0], self.dataclose_x[0]))
# Keep track of the created order to avoid a 2nd order
self.order = self.buy(self.datas[0])
self.order = self.sell(self.datas[1])
else:
# Already in the market ... we might sell
if self.sell_sig:
# SELL, SELL, SELL!!! (with all possible default parameters)
self.log('BUY CREATE,{},{}'.format(
self.dataclose_y[0], self.dataclose_x[0]))
# Keep track of the created order to avoid a 2nd order
self.log('Pos size %s' % self.position.size)
self.order = self.close(self.datas[1])
self.order = self.close(self.datas[0])
if __name__ == '__main__':
# Create a cerebro entity
cerebro = bt.Cerebro()
cerebro.addstrategy(TestStrategy)
# Create a Data Feed
data_1 = bt.feeds.PandasData(
dataname=get_data(
'AAPL', datetime.datetime(2016, 1, 1),
datetime.datetime(2018, 7, 6))
)
data_2 = bt.feeds.PandasData(
dataname=get_data(
'DPZ', datetime.datetime(2016, 1, 1),
datetime.datetime(2018, 7, 6))
)
# Add the Data Feed to Cerebro
cerebro.adddata(data_1)
cerebro.adddata(data_2)
# Set our desired cash start
cerebro.broker.setcash(100000.0)
cerebro.broker.setcommission(commission=0.001)
cerebro.addsizer(bt.sizers.FixedSize, stake=100)
# Print out the starting conditions
print('Starting Portfolio Value: %.2f' % cerebro.broker.getvalue())
# Run over everything
cerebro.run()
# Print out the final result
print('Final Portfolio Value: %.2f' % cerebro.broker.getvalue())
cerebro.plot()
@shihao888
Copy link

Keep track of the created order to avoid a 2nd order

            self.order = self.buy(self.datas[0])
            self.order = self.sell(self.datas[1])   ## should it be buy as well? cause you are not in market yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment