19年创业做过一年的量化交易但没有成功,作为交易系统的开发人员积累了一些经验,最近想重新研究交易系统,一边整理一边写出来一些思考供大家参考,也希望跟做量化的朋友有更多的交流和合作。
接下来会对于富途牛牛平台介绍。
富途牛牛是一款提供股票交易、行情查看以及社区互动的综合性投资平台。随着量化交易的普及,富途牛牛平台也逐渐提供了支持程序化交易的接口和服务,通过其API开发者可以实现从数据获取到策略开发与回测的一体化流程。本文以一个经典的“双均线策略”为例,详细展示如何在富途牛牛平台上进行量化交易开发与回测。
1. 策略背景:双均线策略
策略逻辑
双均线策略是一种趋势跟随型策略,利用短期和长期均线的交叉关系生成买卖信号:
- 买入信号:短期均线(如10日均线)上穿长期均线(如50日均线),称为黄金交叉。
- 卖出信号:短期均线下穿长期均线,称为死亡交叉。
策略特点
- 适用场景:适用于趋势性较强的市场,能有效捕捉价格波动。
- 局限性:在震荡市场中可能出现较多错误信号。
2. 策略开发
富途牛牛支持通过其官方API接口开发量化交易策略。以下为基于“双均线策略”的开发和回测过程。
(1)准备工作
在使用富途API之前,需要完成以下准备:
- 注册API权限:通过富途牛牛的开放平台申请开发者权限。
- 安装SDK:富途牛牛提供了Python SDK,用户需先安装:
pip install futu-api
(2)初始化参数与环境
# 导入所需模块
from futu import OpenQuoteContext
import pandas as pd
# 策略参数
SHORT_PERIOD = 10 # 短期均线周期
LONG_PERIOD = 50 # 长期均线周期
INITIAL_CAPITAL = 100000 # 初始资金
STOCK_CODE = 'HK.00700' # 腾讯控股
(3)获取历史数据
通过富途API获取指定股票的历史数据。
# 获取股票历史数据函数
def get_stock_data(stock_code, start_date, end_date):
quote_ctx = OpenQuoteContext(host='127.0.0.1', port=11111) # 初始化行情接口
ret, df = quote_ctx.get_history_kline(stock_code, start=start_date, end=end_date, ktype='K_DAY', autype='qfq')
quote_ctx.close() # 关闭连接
if ret != 0:
raise Exception(f"获取数据失败: {df}")
df = df[['time_key', 'close']] # 提取日期和收盘价
df.rename(columns={'time_key': 'Date', 'close': 'Close'}, inplace=True)
df['Date'] = pd.to_datetime(df['Date'])
df.set_index('Date', inplace=True)
return df
# 加载历史数据
start_date = '2020-01-01'
end_date = '2022-01-01'
data = get_stock_data(STOCK_CODE, start_date, end_date)
(4)计算均线
根据收盘价计算短期和长期移动平均线。
# 计算均线
data['Short_MA'] = data['Close'].rolling(window=SHORT_PERIOD).mean()
data['Long_MA'] = data['Close'].rolling(window=LONG_PERIOD).mean()
(5)生成交易信号
根据均线交叉情况生成买卖信号。
# 生成买卖信号
data['Signal'] = 0 # 初始化信号
data.loc[data['Short_MA'] > data['Long_MA'], 'Signal'] = 1 # 买入信号
data.loc[data['Short_MA'] <= data['Long_MA'], 'Signal'] = -1 # 卖出信号
(6)回测逻辑
通过信号模拟交易,计算资金曲线与持仓。
# 初始化回测环境
data['Position'] = 0 # 持仓状态
data['Cash'] = INITIAL_CAPITAL # 初始资金
data['Portfolio'] = INITIAL_CAPITAL # 总资产
# 回测过程
holdings = 0 # 持仓数量
entry_price = 0 # 记录买入价格
for i in range(1, len(data)):
current_signal = data.iloc[i]['Signal'] # 当前信号
current_price = data.iloc[i]['Close'] # 当前收盘价
# 买入逻辑
if current_signal == 1 and holdings == 0:
holdings = data.iloc[i - 1]['Cash'] // current_price # 按当前资金最大买入
data.at[data.index[i], 'Cash'] = data.iloc[i - 1]['Cash'] - holdings * current_price
data.at[data.index[i], 'Position'] = holdings
entry_price = current_price # 记录买入价格
# 卖出逻辑
elif current_signal == -1 and holdings > 0:
data.at[data.index[i], 'Cash'] = data.iloc[i - 1]['Cash'] + holdings * current_price
holdings = 0
data.at[data.index[i], 'Position'] = holdings
# 更新总资产
current_holdings = holdings * current_price # 持仓市值
data.at[data.index[i], 'Portfolio'] = data.iloc[i]['Cash'] + current_holdings
3. 策略优化
(1)参数优化
通过网格搜索寻找短期和长期均线的最佳周期。
# 参数优化
best_params = None
best_return = -float('inf')
for short_period in range(5, 21):
for long_period in range(30, 101):
if short_period >= long_period:
continue
data['Short_MA'] = data['Close'].rolling(window=short_period).mean()
data['Long_MA'] = data['Close'].rolling(window=long_period).mean()
data['Signal'] = 0
data.loc[data['Short_MA'] > data['Long_MA'], 'Signal'] = 1
data.loc[data['Short_MA'] <= data['Long_MA'], 'Signal'] = -1
# 简化回测逻辑,仅计算最终收益率
holdings = 0
cash = INITIAL_CAPITAL
for i in range(1, len(data)):
signal = data.iloc[i]['Signal']
price = data.iloc[i]['Close']
if signal == 1 and holdings == 0:
holdings = cash // price
cash -= holdings * price
elif signal == -1 and holdings > 0:
cash += holdings * price
holdings = 0
portfolio = cash + holdings * data.iloc[-1]['Close']
total_return = (portfolio - INITIAL_CAPITAL) / INITIAL_CAPITAL
if total_return > best_return:
best_return = total_return
best_params = (short_period, long_period)
print(f"最佳参数:短期均线={best_params[0]}, 长期均线={best_params[1]}, 总收益率={best_return:.2%}")
(2)止盈止损
为策略引入止盈止损机制。
TAKE_PROFIT = 0.1 # 止盈10%
STOP_LOSS = 0.05 # 止损5%
for i in range(1, len(data)):
if holdings > 0:
pnl = (data.iloc[i]['Close'] - entry_price) / entry_price
if pnl >= TAKE_PROFIT or pnl <= -STOP_LOSS:
data.at[data.index[i], 'Cash'] = data.iloc[i - 1]['Cash'] + holdings * data.iloc[i]['Close']
holdings = 0
data.at[data.index[i], 'Position'] = holdings