#面向对象编程
from collections import namedtuple, OrderedDict
from functools import reduce #原文此处没有import reduce
class StockTradeDays(object):
def __init__(self, price_array, start_date, date_array=None):
#私有价格序列
self.__price_array=price_array
#私有日期序列
self.__date_array=self._init_days(start_date, date_array) #调用_init_days() 保护方法
#私有涨跌幅序列
self.__change_array=self.__init_change( ) #调用__init_change()私有方法
#进行OrderDict组装:
self.stock_dict=self._init_stock_dict()
def _init_days(self,start_date,date_array):
"""
protect方法,
:param start_date:初始日期
:param date_array:给定日期序列
:return:
"""
if date_array is None:
#由start_date和self.__price_array来确定日期序列
date_array = [str(start_date + ind) \
for ind,_ in enumerate(self.__price_array)]
else:
#稍后的内容会使用外部直接设置的方式
#如果外面设置了date_array,就直接转换str类型组成新date_array
date_array=[str(date) for date in date_array]
return date_array
def __init_change(self):
"""
从price_array生成change_array
:return:
"""
price_float_array=[float(price_str) for price_str in self.__price_array]
#通过时间平移形成两个错开的收盘价格序列,通过zip函数打包成为一个新的序列
#每个元素为相邻的两个收盘价格
pp_array=[(price1,price2) for price1,price2 in \
zip(price_float_array[:-1],price_float_array[1:])]
change_array=map(lambda pp: reduce(lambda a,b:round((b-a)/a,3),pp),pp_array)
#list insert函数插入数据,将第一天的涨跌幅设置为0
result=list(change_array)
result.insert(0,0)
return result
def _init_stock_dict(self):
"""
使用namedtuple和OrderedDoct将结果合并
:return:
"""
stock_namedtuple=namedtuple('stock',('date','price','change'))
#使用以被赋值的__date_array等进行OrderedDict的组装
stock_dict=OrderedDict(
(date,stock_namedtuple(date,price,change))
for date,price,change in
zip(self.__date_array,self.__price_array,self.__change_array))
return stock_dict
def filter_stock(self,want_up=True,want_calc_sum=False):
"""
筛选结果子集
:param want_up:是否筛选上涨
:param want_calc_sum:是否计算涨跌幅之和
:return:
"""
filter_func = (lambda day: day.change >0) if want_up \
else (lambda day: day.change<0)
#使用filter_func作为筛选函数
want_days= filter(filter_func,self.stock_dict.values())
if not want_calc_sum:
return want_days
#需要计算涨跌幅之和
change_sum=0.0
for day in want_days:
change_sum+=day.change
return change_sum
def __str__(self):
return str(self.stock_dict)
__repr__=__str__ #自定义__repr__()和__str__()的目的是简化调试和实例输出复杂度,使对象更具可读性。
def __iter__(self):
for key in self.stock_dict:
yield self.stock_dict[key] #通过实现__iter__()方法来支持迭代操作。设置stock_dict的迭代,yield 元素
def __getitem__(self,ind):
date_key=self.__date_array[ind]
return self.stock_dict[date_key]
def __len__(self): #通过代理self.stock_dict的len()方法简单实现打印出对象长度
return len(self.stock_dict)
price_array = '30.14,29.58,26.36,32.56,32.82'.split(',')
date_base = 20170118
trade_days=StockTradeDays(price_array,date_base) #实例化
trade_days
len(trade_days)
from collections import Iterable
if isinstance(trade_days,Iterable):
for day in trade_days:
print(day)
list(trade_days.filter_stock())
import tushare as ts
stockdata_hist=ts.get_hist_data(code='510500',start='2020-01-01',end='2020-05-26',)
type(stockdata_hist)
import six
from abc import ABCMeta,abstractmethod
class TradeStrategyBase(six.with_metaclass(ABCMeta,object)):
"""
交易策略抽象基类
"""
@abstractmethod
def buy_strategy(self,*args,**kwargs):
#买入策略基类
pass
@abstractmethod
def sell_strategy(self,*args,**kwargs):
#卖出策略基类
pass
class TradeStrategy1(TradeStrategyBase):
"""
买入策略1:追涨策略 ,当股价上涨一个阀值默认为7%时,买入并持有s_keep_stock_threshold(20)天
"""
s_keep_stock_threshold = 20
def __init__(self):
self.keep_stock_day = 0
# 7%上涨幅度 作为买入 策略阀值
self.__buy_change_threshold = 0.07
def buy_strategy(self, trade_ind, trade_day, trade_days):
if self.keep_stock_day == 0 and trade_day.change > self.__buy_change_threshold:
#当没有 持有股票的时候self.keep_stock_day == 0 并且 符合 买入条件上涨一个阀值
self.keep_stock_day += 1
elif self.keep_stock_day > 0:
#代表持有股票,持有股票天数递增
self.keep_stock_day += 1
def sell_strategy(self, trade_ind, trade_day, trade_days):
if self.keep_stock_day >= TradeStrategy1.s_keep_stock_threshold:
# 当持有股票天数超过阀值s_keep_stock_threshold,卖出股票
self.keep_stock_day = 0
@property
def buy_change_threshold(self):
return self.__buy_change_threshold
@buy_change_threshold.setter
def buy_change_threshold(self, buy_change_threshold):
if not isinstance(buy_change_threshold, float):
raise TypeError('buy_change_threshold must be float.')
self.__buy_change_threshold=round(buy_change_threshold, 2)
class TradeLoopBack(object):
"""
交易回测系统
"""
def __init__(self, trade_days, trade_strategy):
self.trade_days = trade_days
self.trade_strategy = trade_strategy
self.profit_array=[]
def execute_trade(self):
"""
执行交易回测
"""
for ind, day in enumerate(trade_days):
"""
以时间驱动,完成交易回测
"""
if self.trade_strategy.keep_stock_day > 0:
self.profit_array.append(day.change)
#hasattr用来查询对象有没有实现某个方法
if hasattr(self.trade_strategy,'buy_strategy'):
#买入策略执行
self.trade_strategy.buy_strategy(ind, day, self.trade_days)
if hasattr(self.trade_strategy,'sell_strategy'):
#买入策略执行
self.trade_strategy.sell_strategy(ind, day, self.trade_days)
class TradeStrategy2(TradeStrategyBase):
"""
交易策略2: 均值回复策略,当股价连续两个交易日下跌,
且下跌幅度超过阀值默认s_buy_change_threshold(-10%),
买入股票并持有s_keep_stock_threshold(10)天
"""
#买入后持有天数
s_keep_stock_threshold = 10
#下跌买入阀值
s_buy_change_threshold = -0.10
def __init__(self):
self.keep_stock_day = 0
def buy_strategy(self, trade_ind, trade_day, trade_days):
if self.keep_stock_day == 0 and trade_ind >= 1:
"""
当没有持有股票的时候self.keep_stock_day == 0 并且
trade_ind >= 1, 不是交易开始的第一天,因为需要yesterday数据
"""
# trade_day.change < 0 bool:今天股价是否下跌
today_down = trade_day.change < 0
#昨天股价是否下跌
yesterday_down = trade_days[trade_ind - 1].change < 0
#两天总跌幅
down_rate = trade_day.change + trade_days[trade_ind - 1].change
if today_down and yesterday_down and down_rate < TradeStrategy2.s_buy_change_threshold:
#买入条件成立:连跌两天,跌幅超过s_buy_change_threshold
self.keep_stock_day += 1
elif self.keep_stock_day > 0:
# self.keep_stock_day > 0代表持有股票,持有股票天数递增
self.keep_stock_day += 1
def sell_strategy(self, trade_ind, trade_day, trade_days):
if self.keep_stock_day >= TradeStrategy2.s_keep_stock_threshold:
# 当持有股票天数超过阀值s_keep_stock_threshold,卖出股票
self.keep_stock_day = 0
@classmethod
def set_keep_stock_threshold(cls,keep_stock_threshold):
cls.keep_stock_threshold = keep_stock_threshold
@staticmethod
def set_buy_change_threshold(buy_change_threshold):
TradeStrategy2.s_buy_change_threshold=buy_change_threshold
#构造日期序列和股票价格数据
import datetime
from random import random
datestart = '20200101'
dateend = datetime.datetime.now().strftime('%Y%m%d')
# 转为日期格式
datestart=datetime.datetime.strptime(datestart,'%Y%m%d')
dateend=datetime.datetime.strptime(dateend,'%Y%m%d')
date_array = []
date_array.append(datestart.strftime('%Y%m%d'))
while datestart<dateend:
# 日期叠加一天
datestart+=datetime.timedelta(days=+1)
# 日期转字符串存入列表
date_array.append(datestart.strftime('%Y%m%d'))
#根据日期序列构造交易数据
price_array=[]
for ind,_ in enumerate(date_array):
price_array.append(10+random()-random())
#策略1回测
date_base = "20200101"
trade_strategy1=TradeStrategy1()
trade_strategy1.buy_change_threshold = 0.03
trade_days=StockTradeDays(price_array,date_base,date_array=date_array) #实例化
trade_loop_back=TradeLoopBack(trade_days,trade_strategy1)
trade_loop_back.execute_trade()
print ('回测策略1 总盈亏为:{}%'.format(reduce(lambda a, b: a + b, trade_loop_back.profit_array) * 100))
#策略2回测
date_base = "20200101"
trade_strategy2=TradeStrategy2()
trade_days=StockTradeDays(price_array,date_base,date_array=date_array) #实例化
trade_loop_back=TradeLoopBack(trade_days,trade_strategy2)
trade_loop_back.execute_trade()
print ('回测策略1 总盈亏为:{}%'.format(reduce(lambda a, b: a + b, trade_loop_back.profit_array) * 100))
#性能效率
import itertools
items=[1,3,2]
#顺序组合
for item in itertools.permutations(items):
print(item)
#不排序组合
for item in itertools.combinations(items,2):
print(item)
#放回组合
for item in itertools.combinations_with_replacement(items,2):
print(item)
#笛卡尔积
ab=['a','b']
cd=['c','d']
for item in itertools.product(ab,cd):
print(item)
"""
修改TradeStrategy2策略基础参数并执行回测的代码抽象出一个函数calc(),
该函数的输入参数有两个,分别是持股天数和下跌买入阀值;
输出返回值为3个,分别是盈亏情况、输入的持股天数和下跌买入阀值。
"""
def calc(keep_stock_threshold,buy_change_threshold):
"""
:param keep_stock_threshold: 持股天数
:param buy_change_threshold: 下跌买入阀值
:return: 盈亏情况,输入的持股天数, 输入的下跌买入阀值
"""
tradestrategy2 = TradeStrategy2()
TradeStrategy2.set_keep_stock_threshold(keep_stock_threshold)
TradeStrategy2.set_buy_change_threshold(buy_change_threshold)
trade_loop_back = TradeLoopBack(trade_days, trade_strategy2)
trade_loop_back.execute_trade()
profit= 0.0 if len(trade_loop_back.profit_array) == 0 else \
reduce(lambda a,b: a+b, trade_loop_back.profit_array)
return profit, keep_stock_threshold, buy_change_threshold
calc(20, 0.08)
#多个有限集合进行笛卡尔积,寻找最佳持股天数和下跌阀值的组合
import itertools
keep_stock_list = list(range(2, 30 ,2))
#range集合:买入后持股天数从2~30天,间隔两天
print ('持股天数参数组:{}'.format(keep_stock_list))
buy_change_list = [buy_change /100.0 for buy_change in range(-5, -16, -1)]
# 下跌买入阀值从-0.05到-0.15,即从5%下跌到15%
print ('下跌阀值参数组:{}'.format(buy_change_list))
result=[]
for keep_stock_threshold, buy_change_threshold in itertools.product(keep_stock_list, buy_change_list):
# 使用calc()函数计算参数对应的最终盈利,结果加入result序列
result.append(calc(keep_stock_threshold,buy_change_threshold))
print ('笛卡尔积参数集合总共结果为:{}个'.format(len(result)))
#使用sorted(result)将结果序列排序:
# [::-1]将整个排序结果反转,反转后盈亏收益从最高向低开始排序
# [:10]取出收益最高的前10个组合查看
sorted(result)[::-1][:10]