上一节介绍了量化交易的工具以及基础研究,本文讲述了量化交易的策略实践,及其效果。

通过GPT4.0书写交易策略,零成本跨界金融编程领域。如果效果不理想,也可以让其分析盈透API返回的表格文件,从而优化参数(小心过拟合)。

免责声明:以下是我个人研究内容,代码由GPT生成,不代表任何投资建议,投资请谨慎。

个人代码库

Quant

快速入门

  1. 注册盈透账户,下载gateway

  2. 安装backtrader库(量化回测框架)

  3. 安装ib_insync库(对接盈透gateway,开发者老哥erdewit于2024年3月11日去世,RIP

  4. 通过ib_insync文档或是使用GPT写一个样例SMA策略

  5. 运行,观察图表

常见框架推荐

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 bt
from ib_insync import IB, Stock, util
import pandas as pd

class IBStore:
def __init__(self, host='127.0.0.1', port=7497, clientId=1):
self.ib = IB()
self.ib.connect(host, port, clientId)

# 默认回测一年,每一天为一个bar
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)
# 输出csv,用于GPT解释或优化参数
df.to_csv('~/' + symbol + '.csv', sep=',', index=False, header=True)
return df

# 这里写策略类
# class SmaCross(bt.Strategy):

if __name__ == '__main__':
# 对接IB gateway API,端口改为IB gateway设置中的端口
ibstore = IBStore('127.0.0.1', 4002, clientId=1)
# 选股,建议多选几种走势的股票
df = ibstore.get_data('NVDA') # NVDA单调递增
# df = ibstore.get_data('AAPL') # AAPL波动剧烈

# Ensure the 'date' column is in the correct datetime format
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)

# Load data into Backtrader
data = bt.feeds.PandasData(dataname=df)

# Initialize and run Cerebro
cerebro = bt.Cerebro(stdstats=True, cheat_on_open=True, optreturn=False)
cerebro.addstrategy(SmaCross) # 这里改为你的策略类名
cerebro.broker.set_cash(100000) # 本金
# cerebro.broker.setcommission(commission=0.001) #交易手续费
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)

# Define trading strategy
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)

# Custom trade tracking
self.trade_data = []

# Execute trades
def next(self):
# Trading the entire portfolio
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) # Record entry bar index
elif self.crossover < 0:
self.close()

# Record trade details
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)

# Caclulating holding periods
def stop(self):
# Calculate and print average holding periods
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

# Calculating for winners and losers separately
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

# Display average holding period statistics
print('Average Holding Period:', avg_holding_period)
print('Average Winner Holding Period:', avg_winner_holding)
print('Average Loser Holding Period:', avg_loser_holding)

效果

NVDA股票
NVDA股票
AAPL
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: # MACD线上穿信号线
self.buy()
elif self.crossover < 0: # 已有持仓且MACD线下穿信号线
self.sell()

效果

NVDA
NVDA
AAPL
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: # MACD线上穿信号线,买入信号
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:
# MACD线下穿信号线,或触发止损止盈,全仓卖出
self.order = self.close()


if __name__ == '__main__':
ibstore = IBStore('127.0.0.1', 4002, clientId=1)
# df = ibstore.get_data('NVDA')
df = ibstore.get_data('AAPL')

# Ensure the 'date' column is in the correct datetime format
df['date'] = pd.to_datetime(df['date'])
df.set_index('date', inplace=True)

# Load data into Backtrader
data = bt.feeds.PandasData(dataname=df)

# Initialize and run Cerebro
cerebro = bt.Cerebro(stdstats=True, cheat_on_open=True, optreturn=False)

# 1-默认参数
# cerebro.optstrategy(ImprovedMACDStrategy)

# 2-范围里面找最优解,查看控制台With Parameters输出。不同股票需要重新计算。
# cerebro.optstrategy(ImprovedMACDStrategy, fast=range(10, 15), slow=range(20, 30), signal=range(5, 10))

# 3-根据With Parameters输出,配置最优解。这个是AAPL的最优解,不同股票需要重新计算。
# cerebro.optstrategy(ImprovedMACDStrategy, fast=10, slow=27, signal=5)
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')
cerebro.broker.set_cash(100000)
# cerebro.broker.setcommission(commission=0.001)
cerebro.adddata(data) # 确保这里的data是bt.feeds对象,不是pandas DataFrame
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
NVDA
AAPL
AAPL

各支股票的最优参数

注意:不同股票的参数需要重新计算,如果不是震荡市,计算参数可能没有优化效果

NVDA

Best Sharpe Ratio: 0.8460012912318833
With Parameters: Fast=13, Slow=24, Signal=9

NVDA
NVDA

AAPL

Best Sharpe Ratio: 0.801877924498146
With Parameters: Fast=10, Slow=27, Signal=5

AAPL
AAPL

总结

本文搭建了一套回测工具集,对一些常见的技术指标进行了介绍,提供了对应的代码和运行效果。

接下来会继续优化MACD及其他策略,获得更好的效果。