第 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中计算的是卖出资金,等于交易金额减去手续费。这里x-=sfee(x)是x=x-sfee(x)的缩写,表示将x- sfee(x)的值赋给x。计算完后x的值就是卖出资金。
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 函数我们在前面已经讲过,它会在逻辑表达式为真时返回对应的 amt 值。
A23 现金价值就等于总的卖出 - 买入
A24计算的是持仓价值,等于最后一天的 holds 值乘以当天的股价。m()函数用来倒取数据,B7.m(-1).close 表示取 B7 最后一行数据的收盘价。
A25计算占用资金,买入资金为正值,卖出资金为负值,然后累计求和取其中的最大值,就是所需要的资金了。
A26计算收益率,总资产 / 总投入。
执行完这段代码后,可以看到用此策略购买股票 600690,在 2024 年的收益率为 30.62%,投入资金为 15 万多。
再用绘图脚本观察一下 2024 年的股票价格:
A |
|
… |
…… |
29 |
=Draw(B7,"tdate","close",,"600690.html") |
可以看到在 2024 年该策略基本抓住了两次大的上涨趋势,并避开了两次下跌,是一个收益还不错的策略。