从零开始量化交易 - 工具选择与第一个策略
在学习了一定的理论知识之后,需要对其进行模拟练习以加深理解。
量化交易的核心在于如何构建自己的策略,以及对于构建的策略进行验证。就好像写代码一样,首先要配置代码的编写缓解,以及代码的运行测试环境。
因此,首先进行工具或平台的选择,以及编写策略的必要配置。
平台的选择
对于一窍不通的我来说,这一步首先还是先用万能的Google
进行资料的查询。根据前人整理和总结的资料,经过初步的信息阅读,根据我的理解对其进行归类,如下所示:
名称 | 涵盖金融产品 | 是否付费 | 回测与交易 | 特点 | 策略编写语言 | 国内/国外 | 机构或个人 |
---|---|---|---|---|---|---|---|
BigQuant | 股票、基金、期货、期权、可转债、数字货币 | 未知 | 均支持 | AI,低代码 | 可视化 | 未知 | 均可 |
聚宽 | 股票(A股,非科创板)、场内基金 | 是 | 均支持 | 优秀社区、教程丰富 | Python | 未知 | 均可 |
DigQuant | / | / | 不支持 | 教育学院、卖课程 | / | / | / |
老虎量化 | / | / | / | 美股交易、接口 | / | / | / |
TradingView | 能源、贵金属、股票期指合约、外汇 | 是 | 未知 | 图表交易 | 国外 | 均可 | |
发明者 | 商品期货CTP/易盛API/中泰证券XTP/腾讯富途证券(美股港股) | 是 | 支持 | 策略超市 | 支持Python | 国内 | 均可 |
VNPY | 股票、期货、期权、外汇、数字货币 | 否 | 实盘交易 | 私人部署、开发框架 | Python | 未知 | 均可 |
结合自身的实际情况,最适合的方式是从聚宽
这个平台开始,先忽略底层的实现,专注在策略本身,同时在社区中学习经验,让自己对于量化交易
了解的更为深刻,其目标就在于:
- 了解什么是策略,其如何运行。
- 了解支撑策略的运行机制是什么。其所需要的数据有哪些?
- 了解怎么去评判一个策略,具体指标有哪些?
当有了一定的了解之后,可以从VNPY
这个框架入手,按自己的习惯搭建一个最符合自己的量化交易系统。
使用聚宽
跟随着聚宽官网
上的新手教程:https://www.joinquant.com/view/community/detail/8ec7aaaa899cf928550f89a104637f22。初步熟悉了聚宽工具的使用,还是相当简单和友好的。新手教程中对于之前没有从事过编程的人员也非常友好,给出了很清晰的编程入门信息,并结合策略例子执行。
其界面如下图所示:
对于结果的验证也比较清晰,分为编译运行结果和回测结果。编译运行结果如下图所示:
在回测结果中可以查看策略的收益概览,以及实际的交易情况(包括买入卖出股票,以及交易后的实际金额),如下图所示:
并且聚宽
还提供了统计分析工具,用于更好的了解策略回测情况。
美中不足
美中不足的地方在于JQData SDK
的本地化测试需要申请,并且试用期只有三个月。而在平台上编码确实不是很顺手。
另外,限制了免费的回测时间(60分钟)。
当然天下没有免费的午餐,提供这些能力去支付费用是应该的。所以,美中不足的不是平台,而是我们自己。
聚宽上的第一个策略
在聚宽
的新手教程中,给出了一个策略编写自测的题目,正好拿来当作自己的第一个策略。
其描述如下:
1. 每天找出市值排名最小的 N 只股票作为买入股票
2. 若持仓股票不在买入股票列表中,则清仓
3. 买入N只股票,买入资金为 总资金 / N
4. 设定止盈止损条件
5. 排除ST股票、停牌股票、涨停股票、跌停股票
6. 控制交易周期:即不再是每日交易,而是每 T 日交易
实现与结果
对于有一定编程经验的我来说,这块内容的实现并不复杂,参考了聚宽 Context
中的变量含义,实现代码如下所示:
from jqdata import *
def initialize(context):
"""
初始化
"""
# 设置股票数量
g.stock_number = 10
g.loss_limit = -0.01 # 跌5个点就卖出
g.win_limit = 0.02 # 涨5个点就卖出
# 周期运行. 开盘前运行. 停牌数据最好是在开盘后获取。
run_daily(period, time='09:10')
def period(context):
"""
周期运行函数
"""
# 找出要交易的股票
find_stocks(context)
# 卖出持仓的不在g.security列表中的股票
for stock in context.portfolio.positions:
if stock not in g.security:
# 清仓
order_target(stock, 0)
# 买入股票
for stock in g.security:
stk_cnt = context.portfolio.cash / g.stock_number
order_value(stock, stk_cnt)
# 止损与止盈
sellLoss(context, stock)
sellWin(context, stock)
def find_stocks(context):
"""
找寻市值最小的N个股票
"""
## step1: 获取当天存在的股票
stocks = get_all_securities(date=context.current_dt).index.tolist()
## step2: 过滤stocks, 去除st股票
### Tips: 通过sum计算,若第二列>0则表示至少出现过一次True。则为ST
### Tips: 只要出现过ST,都不包含在内。保守策略
st_stocks = get_extras("is_st", stocks, context.run_params.start_date, context.run_params.end_date, df=True).T
stocks = st_stocks[~st_stocks.iloc[:,0]].index.tolist()
## step3: 过滤stocks, 去除当天停牌的股票
### Tips: 根据价格表中过滤
### Tips: 过滤今日停牌股票
stopped_stocks = get_price(stocks, end_date=context.current_dt, count=1, fields='paused').paused.sum()
stocks = stopped_stocks[stopped_stocks < 1].index.tolist()
## step4: 过滤stocks, 去除当前涨停或跌停的股票。 这里待优化
## step2: 按市值排序,获取最小的N个
## 这里是SQL Alchemy的语法。从市值表里查询
condition = query( valuation.code
).filter(
valuation.code.in_(stocks)
).order_by(
valuation.market_cap.asc()
).limit(g.stock_number)
df = get_fundamentals(condition)
# 选取股票代码并转为list
g.security = list(df['code'])
print(g.security)
def sellLoss(context, stock):
"""
止损卖出
"""
cost = context.portfolio.positions[stock].avg_cost
current_price = context.portfolio.positions[stock].price
if cost <= 0:
return
ret = current_price / cost - 1
if ret < g.loss_limit:
order_target(stock, 0)
def sellWin(context, stock):
"""
止盈卖出
"""
cost = context.portfolio.positions[stock].avg_cost
current_price = context.portfolio.positions[stock].price
if cost <= 0:
return
ret = current_price / cost - 1
if ret > g.win_limit:
order_target(stock, 0)
其回测结果如下图所示:
可以看到结果并不是很好。这时候想起了一句话“让盈利奔跑吧”,所以我去掉止盈和止损策略之后,再看看结果:
虽然跑赢了“大盘”,但结果并不是很理想。因此可以断定,小市值的股票在选定的时间范围内表现不理想。或许也是近期大盘萎靡不振的原因。如果放在2015年的牛市跑这个策略,可以看到如下的结果:
可以看到这个策略的收益明显非常夸张。结合
问题与思考
-
当 总资金/N 不足以买一手(即100股,根据A股的交易逻辑,股票交易需要按手买入卖出),如何处理?
在实际的测试中,看到
聚宽
平台有如下日志输出:下单检查标的数量:StockOrder(entrust_id=1682588131 security=300478.XSHE mode=OrderValue: _value=10000.0 style=MarketOrderStyle: _limit_price=0.0 side=long action=open margin=False entrust_time=2023-01-03 09:10:00 error=开仓数量必须是 100 的整数倍,调整为 1000)
可以看到,平台在此处做了整数保护,当然,在后续的过程中也可以自己做处理。或向上调整,或向下调整。
-
在每日开盘前运行这个策略,获取当天的涨停和跌停股票是存在使用未来数据的问题。如何修改?
在此处,设定了在"09:10"这个时间点运行。以当前时间点计算涨停和跌停。当然,个人觉得还不是很保险。最好是使用昨天的数据判断昨日是否涨停或跌停
-
如何修改不同的初始化参数,针对不同的N进行遍历,取最优?是否需要使用
JQData SDK
进行本地回测JQData SDK
需要申请使用,暂时没有处理。目前是手动的进行参数调整多次回测。并在平台的回测列表
中查看不同参数的运行结果。也不失为一个方法。 -
当前没有过滤科创板。需要进行过滤。
-
在止盈止损过程中,设定了清仓。但是在买入之后。按照A股T+1的原则,应该是无法清仓的。这里还需要处理