本篇讲解设计一个简单的趋势策略,仅仅从策略设计层面讲解,帮助初学者学习如何设计一个简单的策略并且理解策略程序执行流程。至于策略绩效优劣很大程度上和策略参数有关了(几乎大部分趋势策略都是这样)。
使用两条EMA均线指标,当EMA均线都出现拐点时。拐点作为开多、开空的信号开仓(或者反手),设计了固定目标利润差价平仓。注释直接写在策略代码中方便阅读。策略代码总体非常简短,适合入门学习。
/*backtest
start: 2021-09-01 00:00:00
end: 2021-12-02 00:00:00
period: 1h
basePeriod: 5m
exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT"}]
*/
// 以上 /**/ 内为回测默认设置,在回测页面可以用回测页面上的相关控件重新设置
var LONG = 1 // 持多头仓位的标记,枚举常量
var SHORT = -1 // 持空头仓位的标记,枚举常量
var IDLE = 0 // 不持仓时的标记,枚举常量
// 获取指定方向的持仓,positions为持仓数据,direction为要获取的持仓方向
function getPosition(positions, direction) {
var ret = {Price : 0, Amount : 0, Type : ""} // 定义一个不持仓时的结构
// 遍历positions,从中找到符合direction方向的持仓
_.each(positions, function(pos) {
if (pos.Type == direction) {
ret = pos
}
})
// 返回找到的持仓
return ret
}
// 取消当前交易对、合约的所有挂单
function cancellAll() {
// 死循环,不停检测,直到触发break跳出
while (true) {
// 获取当前交易对、合约的挂单数据,即orders
var orders = _C(exchange.GetOrders)
if (orders.length == 0) {
// 当orders为空数组时,即 orders.length == 0 时,执行break跳出while循环
break
} else {
// 遍历当前所有挂单,逐个取消挂单
for (var i = 0 ; i < orders.length ; i++) {
// 具体撤销某个订单的函数,撤销ID为:orders[i].Id的订单
exchange.CancelOrder(orders[i].Id, orders[i])
Sleep(500)
}
}
Sleep(500)
}
}
// 平仓函数,根据传入的交易函数tradeFunc,方向direction,去执行平仓
function cover(tradeFunc, direction) {
var mapDirection = {"closebuy": PD_LONG, "closesell": PD_SHORT}
var positions = _C(exchange.GetPosition) // 获取当前交易对、合约的持仓数据
var pos = getPosition(positions, mapDirection[direction]) // 找到指定的平仓方向的持仓信息
// 当持仓量大于0(有仓位才能平仓)
if (pos.Amount > 0) {
// 撤销所有可能存在的挂单
cancellAll()
// 设置交易方向
exchange.SetDirection(direction)
// 执行平仓交易函数
if (tradeFunc(-1, pos.Amount)) {
// 下单成功返回 true
return true
} else {
// 下单失败返回 false
return false
}
}
// 没有仓位返回 true
return true
}
// 策略主函数
function main() {
// 用于切换到OKEX V5模拟盘
if (okexSimulate) {
exchange.IO("simulate", true) // 切换到OKEX V5模拟盘测试
Log("切换到OKEX V5模拟盘")
}
// 设置合约代码,ct为swap 即设置当前操作的合约是永续合约
exchange.SetContractType(ct)
// 初始化状态为未持仓
var state = IDLE
// 初始化持仓价格为0
var holdPrice = 0
// 初始化对比用的时间戳,用于对比当前K线BAR是否变化
var preTime = 0
// 策略主循环
while (true) {
// 获取当前交易对、合约的K线数据
var r = _C(exchange.GetRecords)
// 获取K线数据长度,即l
var l = r.length
// 判断K线长度 l 必须大于指标周期(不大于指标周期,指标函数无法计算出有效的指标数据),否则重新循环
if (l < Math.max(ema1Period, ema2Period)) {
// 等待1000毫秒,即1秒,避免轮转过快
Sleep(1000)
// 忽略当前if以后的代码, 重新while循环
continue
}
// 计算ema指标数据
var ema1 = TA.EMA(r, ema1Period)
var ema2 = TA.EMA(r, ema2Period)
// 画图
$.PlotRecords(r, 'K线') // 画K线图
// 当最后一个BAR时间戳发生变化时,即有新K线BAR产生时
if(preTime !== r[l - 1].Time){
// 新BAR出现之前的倒数第一根BAR最后一次更新
$.PlotLine('ema1', ema1[l - 2], r[l - 2].Time)
$.PlotLine('ema2', ema2[l - 2], r[l - 2].Time)
// 画新BAR的指标线,即当前倒数第一根BAR上的指标数据
$.PlotLine('ema1', ema1[l - 1], r[l - 1].Time)
$.PlotLine('ema2', ema2[l - 1], r[l - 1].Time)
// 更新用于对比的时间戳
preTime = r[l - 1].Time
} else {
// 当没有新BAR产生时,仅仅更新图表上倒数第一根BAR的指标数据
$.PlotLine('ema1', ema1[l - 1], r[l - 1].Time)
$.PlotLine('ema2', ema2[l - 1], r[l - 1].Time)
}
// 开多仓的条件,拐点
var up = (ema1[l - 2] > ema1[l - 3] && ema1[l - 4] > ema1[l - 3]) && (ema2[l - 2] > ema2[l - 3] && ema2[l - 4] > ema2[l - 3])
// 开空仓的条件,拐点
var down = (ema1[l - 2] < ema1[l - 3] && ema1[l - 4] < ema1[l - 3]) && (ema2[l - 2] < ema2[l - 3] && ema2[l - 4] < ema2[l - 3])
// 开多仓的条件触发并且当前持有空头仓位,或者开多仓的条件触发并且没持仓
if (up && (state == SHORT || state == IDLE)) {
// 如果持有空头仓位,先平仓
if (state == SHORT && cover(exchange.Buy, "closesell")) {
// 平仓后标记未持仓状态
state = IDLE
// 重置持仓价格为0
holdPrice = 0
// 在图表上标记
$.PlotFlag(r[l - 1].Time, 'coverShort', 'CS')
}
// 平仓后反手开多仓
exchange.SetDirection("buy")
if (exchange.Buy(-1, amount)) {
// 标记当前状态
state = LONG
// 记录当前价格
holdPrice = r[l - 1].Close
$.PlotFlag(r[l - 1].Time, 'openLong', 'L')
}
} else if (down && (state == LONG || state == IDLE)) {
// 和 up 条件的判断同理
if (state == LONG && cover(exchange.Sell, "closebuy")) {
state = IDLE
holdPrice = 0
$.PlotFlag(r[l - 1].Time, 'coverLong', 'CL')
}
exchange.SetDirection("sell")
if (exchange.Sell(-1, amount)) {
state = SHORT
holdPrice = r[l - 1].Close
$.PlotFlag(r[l - 1].Time, 'openShort', 'S')
}
}
// 止盈
if (state == LONG && r[l - 1].Close - holdPrice > profitTarget && cover(exchange.Sell, "closebuy")) {
state = IDLE
holdPrice = 0
$.PlotFlag(r[l - 1].Time, 'coverLong', 'CL')
} else if (state == SHORT && holdPrice - r[l - 1].Close > profitTarget && cover(exchange.Buy, "closesell")) {
state = IDLE
holdPrice = 0
$.PlotFlag(r[l - 1].Time, 'coverShort', 'CS')
}
// 在状态栏上显示时间
LogStatus(_D())
Sleep(500)
}
}
策略源码:https://www.fmz.com/strategy/333269
策略纯属程序设计教学,请勿实盘。
梦想身价八位数 均线时间是用的那个时间的?1H还是4H?又或者是什么?
轻轻的云 谢谢梦大
发明者量化-小小梦 K线周期设置多大,算出的均线就是多大时间周期上的均线,可以看策略源码,都注释了。