上一节介绍了量化交易的工具以及基础研究,本文讲述了量化交易的策略实践,及其效果。
通过GPT4.0书写交易策略,零成本跨界金融编程领域。如果效果不理想,也可以让其分析盈透API返回的表格文件,从而优化参数(小心过拟合)。
免责声明:以下是我个人研究内容,代码由GPT生成,不代表任何投资建议,投资请谨慎。
个人代码库 Quant
快速入门
注册盈透账户 ,下载gateway
安装backtrader 库(量化回测框架)
安装ib_insync 库(对接盈透gateway,开发者老哥erdewit 于2024年3月11日去世,RIP )
通过ib_insync文档或是使用GPT写一个样例SMA策略
运行,观察图表
常见框架推荐 backtrader 传统策略框架,本人先不上AI玄学,先用好传统框架。该框架由德国人开发,代码风格、结构和设计很适合学习。
qlib AI量化框架
IB Gateway对接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import backtrader as btfrom ib_insync import IB, Stock, utilimport pandas as pdclass IBStore : def __init__ (self, host='127.0.0.1' , port=7497 , clientId=1 ) : self.ib = IB() self.ib.connect(host, port, clientId) def get_data (self, symbol, durationStr='1 Y' , barSizeSetting='1 day' , whatToShow='MIDPOINT' ) : contract = Stock(symbol, 'SMART' , 'USD' ) bars = self.ib.reqHistoricalData( contract, endDateTime='' , durationStr=durationStr, barSizeSetting=barSizeSetting, whatToShow=whatToShow, useRTH=True ) df = util.df(bars) df.to_csv('~/' + symbol + '.csv' , sep=',' , index=False , header=True ) return df if __name__ == '__main__' : ibstore = IBStore('127.0.0.1' , 4002 , clientId=1 ) df = ibstore.get_data('NVDA' ) df['date' ] = pd.to_datetime(df['date' ]) df.set_index('date' , inplace=True ) data = bt.feeds.PandasData(dataname=df) cerebro = bt.Cerebro(stdstats=True , cheat_on_open=True , optreturn=False ) cerebro.addstrategy(SmaCross) cerebro.broker.set_cash(100000 ) cerebro.adddata(data) cerebro.run() cerebro.plot()
SMA策略 简介 SMA(简单的移动平均线),名字看上去就很简单,因此用这个策略入门能快速跑通一个回测流程。
SMA是选取过去一段时间内的收盘价作为数据,并对这些数据进行平均计算得到的。
例如,要计算证券的20 天SMA,将过去20天的收盘价相加,然后除以20。
同样,要计算证券的200天SMA,将过去200天的收盘价相加,然后除以200。
SMA的每个数据都有相同权重,但投资者可能认为当前数据比之前更重要,需要赋予更高的权重。因此更倾向于另一种形式的移动平均线,即EMA(指数移动平均线)。EMA可以在行情发生快速、剧烈波动时更具参考价值。
趋势识别 高于SMA的证券交易处于上升趋势,而低于SMA处于下降趋势。
也可以识别支撑位和阻力位。
交叉 上升交叉和下跌交叉 涉及SMA的投资策略一般是上升交叉和下跌交叉。
当证券价格低于SMA后回升到SMA上方时,就会产生上升交叉。代表回调结束,预示上升趋势的开始。反之为下跌交叉,代表开始回调。
然而在处于震荡的市场中,这个指标不太适用。
黄金交叉和死亡交叉 当短期SMA跨越长期SMA时,会出现黄金交叉。例如50天SMA跨越200天SMA时,黄金交叉就会出现,是一个看涨的信号。反之就是死亡交叉,即看跌信号。
当出现死亡交叉时,投资者可以考虑暂时撤出市场。(回想我购买的医药基金,明明死亡交叉了两年,还要头铁去赌……)
代码 由GPT4生成,注意为了简单说明,代码中当出现交叉时全仓买入和卖出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class SmaCross (bt.Strategy) : params = dict(pfast=13 , pslow=25 ) def __init__ (self) : sma1 = bt.ind.SMA(period=self.p.pfast) sma2 = bt.ind.SMA(period=self.p.pslow) self.crossover = bt.ind.CrossOver(sma1, sma2) self.trade_data = [] def next (self) : size = int(self.broker.get_cash() / self.data.close[0 ]) if not self.position: if self.crossover > 0 : self.buy(size=size) self.entry_bar = len(self) elif self.crossover < 0 : self.close() def notify_trade (self, trade) : if trade.isclosed: exit_bar = len(self) holding_period = exit_bar - self.entry_bar trade_record = { 'entry' : self.entry_bar, 'exit' : exit_bar, 'duration' : holding_period, 'profit' : trade.pnl } self.trade_data.append(trade_record) def stop (self) : total_holding = sum([trade['duration' ] for trade in self.trade_data]) total_trades = len(self.trade_data) avg_holding_period = round(total_holding / total_trades) if total_trades > 0 else 0 winners = [trade for trade in self.trade_data if trade['profit' ] > 0 ] losers = [trade for trade in self.trade_data if trade['profit' ] < 0 ] avg_winner_holding = round(sum(trade['duration' ] for trade in winners) / len(winners))if winners else 0 avg_loser_holding = round(sum(trade['duration' ] for trade in losers) / len(losers)) if losers else 0 print('Average Holding Period:' , avg_holding_period) print('Average Winner Holding Period:' , avg_winner_holding) print('Average Loser Holding Period:' , avg_loser_holding)
效果
NVDA股票
AAPL
分析 SMA在单调递增的NVDA股票表现不错,NVDA乘着AI的爆点一路上涨,然而此类的爆点又会有多少呢?
更多的是AAPL的波动趋势,SMA在波动趋势中反而亏损了8%所有。
MACD策略 简介 MACD(Moving Average Convergence/Divergence)名为指数平滑异同平均线,一般交易所图表中都能看到,也被人称作技术指标之王 。
MACD指标由三个主要组件组成:DIF线、DEA线和MACD柱状图。参数为12、26、9,在下面的代码中可以看到。
DIF线是快速移动平均线(一般为12天)减去慢速移动平均线(一般为26天)
DEA线是DIF线的9天加权移动平均线
MACD柱状图是DIF线和DEA线的差异,用来显示DIF线和DEA线的背离程度。
交叉策略 MACD的基本原理基于金叉和死叉
金叉表示DIF线上穿DEA线,多头力量增强,预示着市场可能出现上涨趋势,是买入信号。
死叉表示DIF线下穿DEA线,空头力量增强,预示着市场可能出现下跌趋势,是卖出信号。
背驰策略 红绿柱背离 红柱顶背离:股价创新高,但红柱却没有创新高。上升势能越来越小,可能预示大幅调整。
绿珠底背离:虽然股价在下跌,但下降的动能却逐渐减少,一旦多方势能占据优势,市场走势将由空转多。
黄白线背离 股价创新高/低,但黄白线并没有创新高/低。预示大幅调整或转多。
买入点 底背离可能反复出现,进一步下探,可能被套。只能反复出现多次后(处于下降趋势个股,底背离出现的次数最好在两次以上),才能确认。
只有震荡走势到横盘走势,才考虑加仓。最佳买点在零轴金叉 。
如果没有出现零轴金叉:
寻找突破后的支撑点,即顶底转换的位置(金针探底收小阳)
找空中加油的地方(白线开始抬头往上走)
等放量突破现有横盘区域
慢慢把仓位加上,不要在第一天金针探底就满仓。
卖出点 股价创出新高,红白线没有出现顶背离,但红柱出现了顶背离,上升的势能变低。
当出现放量滞涨,违背量升价涨的逻辑,有很强的抛压。
可能是获利盘或知道是高点的人在卖,主力在跑,就是卖点信号。
双重顶背离信号出现后,大概率会有回撤来消化这次背离。
总结 出现顶背离,一般会迎来调整:情况好,波段调整;情况糟,趋势扭转。
出现底背离,很多票会经历长时间吸筹吸盘,然后迎来主升浪。
背离出现的次数越多越久,主升的行情也会越猛。
横有多长,竖有多高。
MACD是趋势指标,只适合做趋势票,不能做情绪票,因为其滞后性很强。(想想那3个参数)
市场没有成功率100%的方法。
雪球大神总结口诀 一个中心,两个基本点,四项基本原则
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 class DynamicMACDStrategy (bt.Strategy) : params = ( ('fast' , 12 ), ('slow' , 26 ), ('signal' , 9 ), ) def __init__ (self) : self.macd = bt.indicators.MACD(self.data.close, period_me1=self.params.fast, period_me2=self.params.slow, period_signal=self.params.signal) self.crossover = bt.indicators.CrossOver(self.macd.macd, self.macd.signal) def next (self) : if not self.position: if self.crossover > 0 : self.buy() elif self.crossover < 0 : self.sell()
效果
NVDA
AAPL
分析 看起来单调上涨能获得正收益,频繁波动则本金没有什么变化。
增强MACD策略 GPT4 从上面简单的MACD策略代码中,发现出现金叉或死叉时全仓买入和卖出。
因此将问题提给GPT4,看看它怎么解。
我运行了这个策略,但盈利太少了,可能是因为买入和卖出是全部持仓,请优化策略代码,要求通过合适的买入和卖出策略,获得更大盈利效果。
代码 GPT允许指定买入资金比例、止损止盈比例和盈利阈值,看起来比一把梭要科学多了。
另外提供了寻找快、慢、信号的参数方法。可以先按照默认方法执行,然后查看控制台输出的三个参数。然后将参数填入到cerebro.optstrategy(ImprovedMACDStrategy)中,例如:cerebro.optstrategy(ImprovedMACDStrategy, fast=10, slow=27, signal=5)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 class ImprovedMACDStrategy (bt.Strategy) : params = ( ('fast' , 12 ), ('slow' , 26 ), ('signal' , 9 ), ('order_percentage' , 0.95 ), ('stop_loss_percentage' , 0.95 ), ('take_profit_percentage' , 1.05 ), ('partial_sell_percentage' , 0.5 ), ('profit_threshold' , 1.02 ), ) def __init__ (self) : self.macd = bt.indicators.MACD(self.data.close, period_me1=self.params.fast, period_me2=self.params.slow, period_signal=self.params.signal) self.crossover = bt.indicators.CrossOver(self.macd.macd, self.macd.signal) self.order = None def notify_order (self, order) : if order.status in [order.Completed]: if order.isbuy(): self.buy_price = order.executed.price self.stop_price = self.buy_price * self.params.stop_loss_percentage self.limit_price = self.buy_price * self.params.take_profit_percentage self.order = None def next (self) : if self.order: return if not self.position: if self.crossover > 0 : self.order = self.buy(size=(self.broker.get_cash() / self.data.close[0 ]) * self.params.order_percentage) else : if self.data.close[0 ] / self.buy_price > self.params.profit_threshold: self.order = self.sell(size=self.position.size * self.params.partial_sell_percentage) elif self.crossover < 0 or self.data.close[0 ] < self.stop_price or self.data.close[0 ] > self.limit_price: self.order = self.close() if __name__ == '__main__' : ibstore = IBStore('127.0.0.1' , 4002 , clientId=1 ) df = ibstore.get_data('AAPL' ) df['date' ] = pd.to_datetime(df['date' ]) df.set_index('date' , inplace=True ) data = bt.feeds.PandasData(dataname=df) cerebro = bt.Cerebro(stdstats=True , cheat_on_open=True , optreturn=False ) cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe' ) cerebro.addanalyzer(bt.analyzers.Returns, _name='returns' ) cerebro.broker.set_cash(100000 ) cerebro.adddata(data) result = cerebro.run() best_params = None best_sharpe = float('-inf' ) for run in result: for strategy in run: sharpe = strategy.analyzers.sharpe.get_analysis()['sharperatio' ] params = strategy.params if sharpe > best_sharpe: best_sharpe = sharpe best_params = params print(f"Best Sharpe Ratio: {best_sharpe} " ) print(f"With Parameters: Fast={best_params.fast} , Slow={best_params.slow} , Signal={best_params.signal} " ) cerebro.plot()
效果 默认参数(12、26、9)
NVDA
AAPL
各支股票的最优参数 注意:不同股票的参数需要重新计算,如果不是震荡市,计算参数可能没有优化效果
NVDA
Best Sharpe Ratio: 0.8460012912318833 With Parameters: Fast=13, Slow=24, Signal=9
NVDA
AAPL
Best Sharpe Ratio: 0.801877924498146 With Parameters: Fast=10, Slow=27, Signal=5
AAPL
总结 本文搭建了一套回测工具集,对一些常见的技术指标进行了介绍,提供了对应的代码和运行效果。
接下来会继续优化MACD及其他策略,获得更好的效果。