第 4 章 编写第一个策略
通过前几章的学习,我们对股票数据和编程工具有了基本的了解,下面尝试编写一个简单的策略练练手。
我们以固定价格买卖策略作为第一个策略,策略内容是这样的:如果当日股票的最低价低于25元,就买入;反之如果股票的最高价高于30元,就全部卖出。然后循环进行这个步骤,看下能否赚钱。
这里我们假设,每次的买入价格为都25元,卖出价格都为30元,每次买入数量为1手(100股)。
想知道一个策略是否赚钱,回测是必不可少的。
回测是用来评估交易策略的通用方法。它通过计算策略在历史数据上的表现来评估策略的可行性。如果回测结果良好,交易者则会有信心在未来继续使用该策略。因此在回测时我们会计算一些指标来评估策略的表现。这里我们会用到以下几个回测指标。
买入次数:指成功购买某支股票的订单数。
卖出次数:指卖出某支股票的订单数。
买入资金:给定时间段内某支股票的总买入资金。
卖出资金:给定时间段内某支股票的总买入资金。
比如某支股票一周内的买卖资金如下表,那么一周内该股票的买入资金就是1500元,卖出资金是1600元。
买入资金 |
500 |
卖出资金 |
700 |
买入资金 |
1000 |
卖出资金 |
900 |
现金收益:指买卖股票所获得的价差收益,只计算已卖出的股票。例如,8块买入,买入100股,买入手续费5元;15元卖出,卖出手续费5元,那么现金收益就是15*100-5-8*100-5=690。
持仓价值:当前持有的股票价值。例如持有A股票100股,当前股价10元,那么A股票的持仓价值就是1000。
占用资金:在给定时间段内完成所有交易需要投入的现金综合(包括手续费)。比如某支股票一周内的买卖资金如下表。不难算出,要完成这些交易,需要投入800元,那么该股票的占用资金就是800元。
买入资金 |
500 |
卖出资金 |
700 |
买入资金 |
1000 |
卖出资金 |
900 |
收益率:指收益总额与投资额的比例。收益总额包括现金收益和持仓收益。投资额就是该股票的占用资金。
交易过程中还要考虑手续费,不同券商可能会不同,这里采用如下公式计算:
买入手续费 =max(交易金额 *0.03%,5)+ 交易金额 *0.001%
卖出手续费:max(买入金额 *0.03%,5)+ 买入金额 *(0.001%+0.05%)
我们先以购买一支股票为例,回测期为2024年一年。
首先我们先从手续费计算写起,可以用两个自定义函数来计算手续费,自定义函数在SPL里以func开头:
A |
B |
|
1 |
func bfee(x) |
=max(x*0.0003,5)+x*0.00001 |
2 |
func sfee(x) |
=bfee(x)+x*0.0005 |
A1:B1 自定义bfee()函数,计算买入手续费
A1格的func bfee(x)表示定义了一个名为bfee的函数它的输入参数为x,它的代码就是 A1 的代码块,即B1,计算买入手续费。
A2:B2 自定义sfee()函数,计算卖出手续费
SPL 碰到 func 格子,会先跳过这个代码块往后看去执行后续的代码。
SPL 会将自定义的函数自动登记起来,然后就可以像普通函数一样使用了。
在固定价格买卖策略中,需要根据每日的股票价格和策略设定的价格比较来判断什么时候买入和卖出,因此需要把先把K线数据和设定价格等参数准备好。
编写代码读取K线数据和设定买卖价格参数:
A |
B |
C |
|
… |
…… |
…… |
|
4 |
>call("init.splx") |
||
5 |
2024 |
||
6 |
=date(A5,1,1) |
=date(A5,12,31) |
|
7 |
600690 |
=Load(A7, A6,B6) |
|
8 |
25 |
30 |
100 |
A4 登记函数
A5 输入要回测的年份
A6:B6 计算回测的起止日期
date()函数可以将字符串或整数转化成日期,如date(2024,1,1)会返回2024-01-01
A7 输入股票代码
B7 导入回测期内的股票数据,无选项时Load函数返回的是前复权数据
A8:C8 输入买入价格、卖出价格和每次买入数量
当股票价格满足策略的买卖条件时,策略要发出交易信号,同时还要记录每次交易的价格、数量、买卖资金等信息用来回测。在程序中,我们可以创建一个序表来记录这些信息,每发生一次交易就在序表中追加一条交易数据。另外还要再定义一个变量用来记录持股数量。
在SPL里创建序表用create()函数:
A |
|
… |
…… |
9 |
=create(code, tdate, flag, share, price, amt) |
10 |
=holds=0 |
A9 创建一个空序表,用来记录买卖信息,以code,tdate……amt为字段名。其中flag为买卖标签,1表示买入,-1表示卖出;share表示每次买卖的股票数量;price表示买卖价格;amt表示买卖资金(包括手续费)。
A10 定义一个变量holds,用来记录持股数量,初始值设为0。
交易计算需要的数据、参数以及记录方式都准备好了,下面就可以写策略了。策略里需要判断每日的股票价格来计算是否买入或卖出,因此可以用for来循环K线数据,用if来判断每日股价是否满足买卖条件。
继续编写代码:
A |
B |
C |
|
… |
…… |
…… |
|
12 |
for B7 |
if high>=B8 && holds>0 |
=x=holds*B8,x-=sfee(x) |
13 |
=A9.insert(0, A12.code, A12.tdate, -1, holds, B8, x) |
||
14 |
=holds=0 |
||
15 |
if low<A8 |
=x=C8*A8, x+=bfee(x) |
|
16 |
=A9.insert(0, A12.code, A12.tdate, 1, C8, A8, x) |
||
17 |
=holds+=C8 |
A12:C17 是for循环,表示循环B7中每一行数据,当最高价高于30元并且持有股票时就全部以30元卖出,并记录卖出标签flag等于-1,卖出数量share等于持股数holds,卖出价格price等于30,卖出资金amt等于交易金额-手续费。
B12 中if后面的&&是且运算符,表示如果最高价大于30并且持股数大于0,就执行卖出计算。C12中计算的是卖出资金,等于交易金额减去手续费。C13表示,每卖出一次就在序表A9中插入一条数据,记录本次的卖出信息。insert函数表示在序表中插入记录,第1个参数0表示在末尾行插入,后面参数依次表示每个字段的取值。每卖出一次,还要修改股票数量holds值,将其置为0,即C14格内容。
类似的第二个if语句执行的是买入计算。当最低价格低于25元就买入,C15计算买入资金,C16记录买入信息,C17修改股票数量。
这样,A12:C17这块代码就把每次的交易信息计算清楚了。
最后再计算前面提到的几个回测指标就完事了。
继续代码:
A |
B |
|
… |
…… |
|
18 |
/Summary |
|
19 |
=A9.count(flag > 0) |
买入次数 |
20 |
=A9.count(flag < 0) |
卖出次数 |
21 |
=A9.sum(if(flag>0, amt) ) |
买入资金 |
22 |
=A9.sum(if(flag<0, amt) ) |
卖出资金 |
23 |
=A22-A21 |
现金价值 |
24 |
=holds*B7.m(-1).close |
持仓价值 |
25 |
=A9.max(cum(flag*amt)) |
占用资金 |
26 |
=(A23+A24)/A25 |
收益率 |
A19:A26这一段代码很简单,就是几个汇总函数,count表示计数,sum表示求和,max表示求最大值,cum表示累计求和。
A19和A20分别统计序表A9中flag大于0和小于0的数量,计算买入次数和卖出次数。
A21和A22分别对每次买入和卖出资金求和,就得到总的买入和卖出资金。if()是if函数,表示如果逻辑表达式为真就返回对应的amt值。
A23 现金价值就等于总的卖出-买入
A24计算的是持仓价值,等于最后一天的holds值乘以当天的股价。m()函数用来倒取数据,B7.m(-1).close表示取A7最后一行数据的收盘价。
A25计算占用资金,买入资金为正值,买入资金为负值,然后累计求和取其中的最大值,就是所需要的资金了。
A26计算收益率,总资产/总投入。
执行完这段代码后,可以看到用此策略购买股票600690,在2024年的收益率为30.62%,投入资金为15万多。
再用绘图脚本观察一下2024年的股票价格:
A |
|
… |
…… |
29 |
=Draw(B7,"tdate","close",,"600690.html") |
可以看到在2024年该策略基本抓住了两次大的上涨趋势,并避开了两次下跌,是一个收益还不错的策略。