from __future__ import (absolute_import, division, print_function,
unicode_literals)
import argparse
from datetime import datetime
import pandas as pd
#from pylab import mpl
import numpy as np
# The above could be sent to an independent module
import backtrader as bt
import backtrader.feeds as btfeeds
import backtrader.indicators as btind
import akshare as ak
class PairTradingStrategy(bt.Strategy):
params = dict(
period=10,
stake=10,
qty1=0,
qty2=0,
printout=True,
#设置上限为2.1
upper=2.1,
#设置下限为-2.1
lower=-2.1,
up_medium=0.5,
low_medium=-0.5,
status=0,
portfolio_value=10000,
)
def log(self, txt, dt=None):
if self.p.printout:
dt = dt or self.data.datetime[0]
dt = bt.num2date(dt)
print('%s, %s' % (dt.isoformat(), txt))
def notify_order(self, order):
if order.status in [bt.Order.Submitted, bt.Order.Accepted]:
return # Await further notifications
if order.status == order.Completed:
if order.isbuy():
buytxt = 'BUY COMPLETE, %.2f' % order.executed.price
self.log(buytxt, order.executed.dt)
else:
selltxt = 'SELL COMPLETE, %.2f' % order.executed.price
self.log(selltxt, order.executed.dt)
elif order.status in [order.Expired, order.Canceled, order.Margin]:
self.log('%s ,' % order.Status[order.status])
pass # Simply log
# Allow new orders 允许开新单
self.orderid = None
def __init__(self):
# To control operation entries
self.orderid = None
self.qty1 = self.p.qty1
self.qty2 = self.p.qty2
self.upper_limit = self.p.upper
self.lower_limit = self.p.lower
self.up_medium = self.p.up_medium
self.low_medium = self.p.low_medium
self.status = self.p.status
self.portfolio_value = self.p.portfolio_value
# Signals performed with PD.OLS : 计算data0上data1的回归和zscord值
self.transform = btind.OLS_TransformationN(self.data0, self.data1,
period=self.p.period)
self.zscore = self.transform.zscore
# Checking signals built with StatsModel.API :
# self.ols_transfo = btind.OLS_Transformation(self.data0, self.data1,
# period=self.p.period,
# plot=True)
def next(self):
if self.orderid:
return # if an order is active, no new orders are allowed
if self.p.printout:
print('Self len:', len(self))
print('Data0 len:', len(self.data0))
print('Data1 len:', len(self.data1))
print('Data0 len == Data1 len:',
len(self.data0) == len(self.data1))
print('Data0 dt:', self.data0.datetime.datetime())
print('Data1 dt:', self.data1.datetime.datetime())
print('status is', self.status)
print('zscore is', self.zscore[0])
# Step 2: Check conditions for SHORT & place the order 检查是否需要卖出
# Checking the condition for SHORT
if (self.zscore[0] > self.upper_limit) and (self.status != 1):
# Calculating the number of shares for each stock 计算股票的数量
value = 0.5 * self.portfolio_value # Divide the cash equally
x = int(value / (self.data0.close)) # Find the number of shares for Stock1
y = int(value / (self.data1.close)) # Find the number of shares for Stock2
print('x + self.qty1 is', x + self.qty1)
print('y + self.qty2 is', y + self.qty2)
# Placing the order 下单,卖1买2
self.log('SELL CREATE %s, price = %.2f, qty = %d' % ("PEP", self.data0.close[0], x + self.qty1))
self.sell(data=self.data0, size=(x + self.qty1)) # Place an order for buying y + qty2 shares
self.log('BUY CREATE %s, price = %.2f, qty = %d' % ("KO", self.data1.close[0], y + self.qty2))
self.buy(data=self.data1, size=(y + self.qty2)) # Place an order for selling x + qty1 shares
# Updating the counters with new value 更新持仓值
self.qty1 = x # The new open position quantity for Stock1 is x shares
self.qty2 = y # The new open position quantity for Stock2 is y shares
self.status = 1 # The current status is "short the spread"
# Step 3: Check conditions for LONG & place the order 检查是否买入
# Checking the condition for LONG
elif (self.zscore[0] < self.lower_limit) and (self.status != 2):
# Calculating the number of shares for each stock 计算买入数量
value = 0.5 * self.portfolio_value # Divide the cash equally
x = int(value / (self.data0.close)) # Find the number of shares for Stock1
y = int(value / (self.data1.close)) # Find the number of shares for Stock2
print('x + self.qty1 is', x + self.qty1)
print('y + self.qty2 is', y + self.qty2)
# Place the order 下单买入股票1,卖出股票2
self.log('BUY CREATE %s, price = %.2f, qty = %d' % ("PEP", self.data0.close[0], x + self.qty1))
self.buy(data=self.data0, size=(x + self.qty1)) # Place an order for buying x + qty1 shares
self.log('SELL CREATE %s, price = %.2f, qty = %d' % ("KO", self.data1.close[0], y + self.qty2))
self.sell(data=self.data1, size=(y + self.qty2)) # Place an order for selling y + qty2 shares
# Updating the counters with new value 刷新持仓值
self.qty1 = x # The new open position quantity for Stock1 is x shares
self.qty2 = y # The new open position quantity for Stock2 is y shares
self.status = 2 # The current status is "long the spread"
# Step 4: Check conditions for No Trade 检查是否无需交易
# If the z-score is within the two bounds, close all
elif (self.zscore[0] < self.up_medium and self.zscore[0] > self.low_medium):
self.log('CLOSE LONG %s, price = %.2f' % ("PEP", self.data0.close[0]))
self.close(self.data0)
self.log('CLOSE LONG %s, price = %.2f' % ("KO", self.data1.close[0]))
self.close(self.data1)
def stop(self):
print('==================================================')
print('Starting Value - %.2f' % self.broker.startingcash)
print('Ending Value - %.2f' % self.broker.getvalue())
print('==================================================')
def parse_args():
parser = argparse.ArgumentParser(description='MultiData Strategy')
parser.add_argument('--data0', '-d0',
default='../../datas/daily-PEP.csv',
help='1st data into the system')
parser.add_argument('--data1', '-d1',
default='../../datas/daily-KO.csv',
help='2nd data into the system')
parser.add_argument('--fromdate', '-f',
default='1997-01-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--todate', '-t',
default='1998-06-01',
help='Starting date in YYYY-MM-DD format')
parser.add_argument('--period', default=10, type=int,
help='Period to apply to the Simple Moving Average')
parser.add_argument('--cash', default=100000, type=int,
help='Starting Cash')
parser.add_argument('--runnext', action='store_true',
help='Use next by next instead of runonce')
parser.add_argument('--nopreload', action='store_true',
help='Do not preload the data')
parser.add_argument('--oldsync', action='store_true',
help='Use old data synchronization method')
parser.add_argument('--commperc', default=0.005, type=float,
help='Percentage commission (0.005 is 0.5%%')
parser.add_argument('--stake', default=10, type=int,
help='Stake to apply in each operation')
parser.add_argument('--plot', '-p', default=True, action='store_true',
help='Plot the read data')
parser.add_argument('--numfigs', '-n', default=1,
help='Plot using numfigs figures')
return parser.parse_args()
def runstrategy():
args = parse_args()
# Create a cerebro 建立大脑
cerebro = bt.Cerebro()
# Get the dates from the args 从csv取数据
stock_hfq_df = ak.stock_zh_a_hist(symbol="000001", adjust="hfq").iloc[:, :6]
# 处理字段命名,以符合 Backtrader 的要求
stock_hfq_df.columns = ['date', 'open','close','high','low','volume',]
# 把 date 作为日期索引,以符合 Backtrader 的要求
stock_hfq_df.index = pd.to_datetime(stock_hfq_df['date'])
start_date = datetime(2021, 4, 3) # 回测开始时间
end_date = datetime(2022, 6, 16) # 回测结束时间
data0 = bt.feeds.PandasData(dataname=stock_hfq_df,
fromdate=start_date,
todate=end_date) # 加载数据
stock_hfq_df = ak.stock_zh_a_hist(symbol="000002", adjust="hfq").iloc[:, :6]
# 处理字段命名,以符合 Backtrader 的要求
stock_hfq_df.columns = ['date', 'open','close','high','low','volume',]
# 把 date 作为日期索引,以符合 Backtrader 的要求
stock_hfq_df.index = pd.to_datetime(stock_hfq_df['date'])
data1 = bt.feeds.PandasData(dataname=stock_hfq_df,
fromdate=start_date,
todate=end_date) # 加载数据
# Add the 1st data to cerebro 引入数据
cerebro.adddata(data0)
# Add the 2nd data to cerebro
cerebro.adddata(data1)
# Add the strategy 引入策略
cerebro.addstrategy(PairTradingStrategy,
period=args.period,
stake=args.stake)
# Add the commission - only stocks like a for each operation 假如资金量
cerebro.broker.setcash(args.cash)
# Add the commission - only stocks like a for each operation 引入佣金率
cerebro.broker.setcommission(commission=args.commperc)
# And run it 运行
# cerebro.run(runonce=not args.runnext,
# preload=not args.nopreload,
# oldsync=args.oldsync)
cerebro.run()
# Plot if requested
if args.plot:
cerebro.plot(numfigs=args.numfigs, volume=False, zdown=False)
if __name__ == '__main__':
runstrategy()