在当天收盘,计算两个值: 最高价-收盘价,和收盘价-最低价。然后取这两个值较大的那个,乘以k值,结果称为触发值。 在第二天开盘,记录开盘价,然后在价格超过(开盘+触发值)时马上买入,或者价格低于(开盘-触发值)时马上卖空。 这个系统是反转系统,没有单独止损。也就是说,反向信号也同时就是平仓信号。
Dual Thrust 策略包含完整的图表显示, 图表动态更新,模板引用等功能, 可做学习模板使用.
策略的详细介绍 : http://xueqiu.com/5256769224/32429363
/* 引用的类库
《商品期货交易类库》 引用了这个 模板,具体模板代码 可以在 策略广场 找到,也有注释版的(注释版在论坛,即 交流社区)
*/
/* 参数名称(全局变量) 中文含义 数据类型 值
ContractTypeName 合约品种 字符串(string) MA701
NPeriod 计算周期 数字型(number) 4
Ks 上轨系数 数字型(number) 0.5
Kx 下轨系数 数字型(number) 0.5
AmountOP 开仓合约张数 数字型(number) 1
Interval 重试间隔(毫秒) 数字型(number) 2000
LoopInterval 轮询间隔(秒) 数字型(number) 3
PeriodShow 图表最大显示K线柱数 数字型(number) 500
NotifyWX 下单微信通知 布尔型(true/false) true
CoverAll 启动策略时清空合约仓位 布尔型(true/false) false
*/
var ChartCfg = { // ChartCfg 是一个 全局 JS 对象, 用来 初始化 策略图表 设置。 对象有很多关于图表功能的属性。 图表库为:HighCharts
__isStock: true, // 该属性用于 控制 是否显示为 单独控制 数据序列(可以在图表上取消单独一个 数据序列的显示),如果指定__isStock: false, 则显示为普通图表
title: { // title : 图表的主要 标题
text: 'Dual Thrust 上下轨图' // title 的一个属性 text : 标题的 文本, 这里 设置为 'Dual Thrust 上下轨图' 该文本就会显示在标题位置
},
yAxis: { // 图表 坐标Y轴的 相关设置
plotLines: [{ // Y轴上的 水平线 (和Y轴垂直) ,该属性的值是一个数组,即 多条水平线的设置
value: 0, // 水平线 在Y轴上的坐标值
color: 'red', // 水平线的颜色
width: 2, // 水平线的 线宽
label: { // 水平线 上的 标签
text: '上轨', // 标签的 文本
align: 'center' // 标签 的显示位置,这里设置 为 居中(即 :'center')
},
}, { // 第二条 水平线 ( [{...},{...}] 数组中的第二个 元素)
value: 0, // 水平线 在Y轴上的坐标值
color: 'green', // 水平线的颜色
width: 2, // 水平线的 线宽
label: { // 标签
text: '下轨',
align: 'center'
},
}]
},
series: [{ // 数据序列, 即用来 在图表上显示 数据线 、K线、标记 等等 内容的 数据。 也是一个数组 第一个 索引为 0 。
type: 'candlestick', // 索引为0 数据序列的 类型: 'candlestick' 表示为 K线图
name: '当前周期', // 数据序列的 名称
id: 'primary', // 数据序列的ID ,用于 下一个数据序列相关设置。
data: [] // 数据序列的 数组, 用于储存具体的 K线数据
}, {
type: 'flags', // 数据序列 ,类型 : 'flags',在图表上显示 标签,表示 做多 和 做空。 索引为 1 。
onSeries: 'primary', // 这个属性 表示 标签 显示在 id 为 'primary' 上。
data: [], // 保存 标签数据的 数组。
}]
};
var STATE_IDLE = 0; // 状态常量, 表示 空闲
var STATE_LONG = 1; // 状态常量, 表示 持多仓
var STATE_SHORT = 2; // 状态常量, 表示 持空仓
var State = STATE_IDLE; // 表示当前程序状态 ,初始 赋值为空闲
var LastBarTime = 0; // K线最后一柱的时间戳(单位为 毫秒, 1000毫秒 等于 1秒, 时间戳是 1970年1月1日 到现在时刻的 毫秒数是一个很大的 正整数)
var UpTrack = 0; // 上轨 值
var BottomTrack = 0; // 下轨 值
var chart = null; // 用于接受 Chart 这个 API 函数返回的 图表控制对象。用该对象(chart) 可以调用其成员函数 向图表内写入数据。
var Counter = { // 计数器, 用于记录 盈亏次数
w: 0, // 赢次数
l: 0 // 亏次数
};
var manager = null; // 用于储存 商品期货交易类库 模板导出函数 返回的 交易对象。该对象用于下单交易逻辑处理
var logSuffix = NotifyWX ? '@' : ''; // 微信开关, 界面上的参数NotifyWX 如果为true , logSuffix 就被初始化 为 '@' , 在程序中Log的时候 就会推送绑定的微信
// 具体可以参见Log 函数 这个API。
function onTick(exchange) { // 程序 主要函数,程序主要逻辑都是在该函数内处理。
if (!manager) { // 判断 manager 是否初始化,如果 为null 即 没有初始化,则执行 if 模块 ( {..} 内 )。
manager = $.NewPositionManager(); // 调用 商品期货交易类库 的导出函数 $.NewPositionManager , 该函数返回一个 用于控制具体下单交易的对象 给 manager。
if (_C(exchange.GetPosition).length > 0) { // 调用 exchange.GetPosition 函数获取 当前持仓信息, _C 是容错函数,用来重试(即 返回null了就会重试)。
// exchange.GetPosition 函数 返回一个数组, 访问 这个返回值的 length 属性,如果大于0,即,返回的数组内有元素,代表当前有持仓。
if (CoverAll) { // 如果 界面参数 CoverAll 被设置为 true ,即:启动策略时清空合约仓位
manager.Cover(ContractTypeName); // 调用 商品期货交易类库 生成的 底层交易控制对象 manager 的成员函数Cover ,参数为 界面参数 上设置的合约代码,
// 平掉所有合约为 ContractTypeName 的持仓, 回测时注意 该合约ContractTypeName是不是 在回测时间段内存在。
Log("已清空所有相关仓位"); // 输出日志 提示信息
} else { // CoverAll 被设置为false 则执行以下
throw "策略启动前不能有持仓."; // 抛出错误 “策略启动前不能有持仓”
}
}
Log('交易平台:', exchange.GetName(), _C(exchange.GetAccount)); // 输出日志, 显示当前设定的交易所对象的名称(调用 exchange.GetName获取),显示当前
// 交易所对象的账户信息(调用 exchange.GetAccount获取)
var insDetail = _C(exchange.SetContractType, ContractTypeName); // 获取 ContractTypeName 合约的 详细信息 ,并订阅切换为ContractTypeName合约。
Log("合约", insDetail.InstrumentName, "一手", insDetail.VolumeMultiple, "份, 最大下单量", insDetail.MaxLimitOrderVolume, "保证金率:", insDetail.LongMarginRatio.toFixed(4), insDetail.ShortMarginRatio.toFixed(4), "交割日期", insDetail.StartDelivDate);
// 输出ContractTypeName 合约的详细信息。
}
var records = _C(exchange.GetRecords); // 调用 exchange.GetRecords 函数 获取K线数据
if (!records || records.length <= NPeriod) { // 如果 records 为 null 或者 records 数组长度 小于等于 界面参数 NPeriod (计算周期)执行if 块内代码
LogStatus("Calc Bars..."); // 调用 LogStatus 在状态栏 输出 提示信息 : 意思为 正在收集K线数据
return; // 返回 即 onTick 函数本次运行结束。用此处 判断确保 K线数量足够(超过 设置的 NPeriod 参数)
}
var Bar = records[records.length - 1]; // 当K线长度条件符合执行到此时, 取K线数据 records 的最后一个数据(即倒数第一根K线柱)
if (LastBarTime !== Bar.Time) { // 全局变量LastBarTime 记录的时间戳 如果不等于当前的K线数据最后一个Bar的时间戳,即 :当前的K线数据最后一个Bar 是更新的(新出现的Bar)。
var HH = TA.Highest(records, NPeriod, 'High'); // 声明HH 变量 ,调用 TA.Highest 函数计算 当前K线数据 NPeriod 周期内最高价 的最大值 赋值给HH。
var HC = TA.Highest(records, NPeriod, 'Close'); // 声明HC 变量 ,获取 NPeriod 周期内的 收盘价的最大值。
var LL = TA.Lowest(records, NPeriod, 'Low'); // 声明LL 变量 ,获取 NPeriod 周期内的 最低价的最小值。
var LC = TA.Lowest(records, NPeriod, 'Close'); // 声明LC 变量 ,获取 NPeriod 周期内的 收盘价的最小值。 具体 TA 指标API 参见平台API 指标页面。
var Range = Math.max(HH - LC, HC - LL); // 计算出 范围
UpTrack = _N(Bar.Open + (Ks * Range)); // 根据 界面参数的 上轨系数 Ks 最新K线柱的 开盘价 等,计算出 上轨值。
DownTrack = _N(Bar.Open - (Kx * Range)); // 计算下轨值
if (LastBarTime > 0) { // 由于LastBarTime 该变量初始化设置的 值为 0 ,所以第一次运行到此处 LastBarTime > 0 必定是 false ,不会执行if 块内的代码,而是会执行else 块内的代码
var PreBar = records[records.length - 2]; // 声明一个变量 含义是 “前一个Bar” 把当前K线的 倒数第二Bar 赋值给它。
chart.add(0, [PreBar.Time, PreBar.Open, PreBar.High, PreBar.Low, PreBar.Close], -1); // 调用 chart 图标控制对象的 add函数 更新K线数据(用获取的K线数据的倒数第二Bar 去更新 图标的倒数第一个Bar,因为有新的K线Bar 生成)
// chart.add 函数的具体用法 参见API 文档,和论坛里的文章。
} else { // 程序第一次运行到此 必定执行 else 块内代码,主要作用是 把第一次获取的K线一次性全部添加到图表上。
for (var i = Math.min(records.length, NPeriod * 3); i > 1; i--) { // 此处执行一个 for 循环, 循环次数 使用 K线长度和NPeriod 3倍 二者中最小的值,可以保证初始的K线不会画的太多太长。索引是从大到小的。
var b = records[records.length - i]; // 声明一个 临时 变量b 用来取每次循环 索引为 records.length - i 的K线柱 数据。
chart.add(0, [b.Time, b.Open, b.High, b.Low, b.Close]); // 调用 chart.add 函数 向图表添加K线柱,注意 add函数最后一个参数如果传入-1 就是更新图表上最后一个Bar(柱),如果没传参数,就是向最后添加Bar
// 执行完 i 等于 2 这次循环后(i-- 了已经,此时为1了),就会触发 i > 1 为false 停止循环, 可见 此处代码只处理到 records.length - 2 这个Bar
// 最后一个Bar 没有处理。
}
}
chart.add(0, [Bar.Time, Bar.Open, Bar.High, Bar.Low, Bar.Close]); // 由于以上 if 的2个分支 都没处理 records.length - 1这个Bar,所以 此处 处理。添加最新出现的Bar 到图表中。
ChartCfg.yAxis.plotLines[0].value = UpTrack; // 把计算出来的 上轨值 赋值给 图表对象(区别于 图表控制对象chart),用于稍后显示。
ChartCfg.yAxis.plotLines[1].value = DownTrack; // 赋值下轨值
ChartCfg.subtitle = { // 设置副标题
text: '上轨: ' + UpTrack + ' 下轨: ' + DownTrack // 副标题文本 设置 ,在副标题上显示出 上轨 下轨 值。
};
chart.update(ChartCfg); // 用图表对象 ChartCfg 更新图表
chart.reset(PeriodShow); // 刷新 根据界面参数 设置的 PeriodShow 变量 ,只保留PeriodShow 的值数量的 K线柱。
LastBarTime = Bar.Time; // 此次新产生的 Bar 的时间戳 更新给 LastBarTime 用于判断 下次循环 获取的K线数据最后一个Bar 是否是新产生的。
} else { // 如果 LastBarTime 等于 Bar.Time 即: 没有新的K线Bar 产生。 则执行一下 {..} 内代码
chart.add(0, [Bar.Time, Bar.Open, Bar.High, Bar.Low, Bar.Close], -1); // 用当前K线数据的最后一个Bar(K线的最后一个Bar 即当前周期的Bar是不断在变化的),更新图表上的最后一个K线柱。
}
LogStatus("Price:", Bar.Close, "Up:", UpTrack, "Down:", DownTrack, "Wins: ", Counter.w, "Losses:", Counter.l, "Date:", new Date()); // 调用LogStatus 函数显示 当前策略的数据在状态栏上。
var msg; // 定义一个 变量msg 意义是 消息。
if (State === STATE_IDLE || State === STATE_SHORT) { // 判断 当前状态变量 State 是否等于 空闲 或者 State 是否等于 持空仓, 在空闲状态下可以触发做多, 在持空仓状态下可以触发 平多仓,并反手。
if (Bar.Close >= UpTrack) { // 如果当前K线的 收盘价 大于 上轨值 ,执行 if 块内代码。
msg = '做多 触发价: ' + Bar.Close + ' 上轨:' + UpTrack; // 给 msg 赋值 ,把需要显示的 数值 组合成字符串。
if (State !== STATE_IDLE) { // 如果 当前状态不为空闲(只有空闲和持空仓可以进入此处代码,若不为空闲,必定是持空仓),所以以下{..}内 处理 平空仓工作。
manager.Cover(ContractTypeName); // 调用 manager 的 Cover 函数 参数传入 ContractTypeName 确定平仓 品种。执行 平仓具体操作。
var profit = manager.Profit(); // 声明 profit 变量 储存 manager 对象的成员函数 Profit 函数返回的 收益值。(Profit函数用于计算收益。)
LogProfit(profit); // 调用LogProfit 输出收益并打印收益曲线, 此函数 可详见API文档。
msg += ' 平仓利润: ' + profit; // 在msg 消息字符串后 添加收益信息。
}
Log(msg + logSuffix); // 输出 日志信息, 会根据logSuffix 的值(是否是 "@")进行微信同步消息推送, "@"的作用具体参见 API文档 中的Log 函数。
manager.OpenLong(ContractTypeName, AmountOP); // 开多 或 反手 : 调用manager 对象的OpenLong 根据界面参数AmountOP设置的手数下单开多仓。
State = STATE_LONG; // 无论 开多仓 还是 反手 ,此刻程序状态 要更新为 持多仓。
chart.add(1, {x:Bar.Time, color: 'red', shape: 'flag', title: '多', text: msg}); // 在K线相应的位置 添加一个 标记 显示 开多。
}
}
if (State === STATE_IDLE || State === STATE_LONG) { // 做空方向 与 以上 同理,不在赘述。代码完全一致。
if (Bar.Close <= DownTrack) {
msg = '做空 触发价: ' + Bar.Close + ' 下轨:' + DownTrack;
if (State !== STATE_IDLE) {
manager.Cover(ContractTypeName);
var profit = manager.Profit();
LogProfit(profit);
msg += ' 平仓利润: ' + profit;
}
Log(msg + logSuffix);
manager.OpenShort(ContractTypeName, AmountOP);
chart.add(1, {x:Bar.Time, color: 'green', shape: 'circlepin', title: '空', text: msg});
State = STATE_SHORT;
}
}
}
function onexit() { // 扫尾函数,停止 程序时会触发该函数执行。
var pos = _C(exchange.GetPosition); // 获取持仓信息。
if (pos.length > 0) { // 如果有持仓信息
Log("警告, 退出时有持仓", pos); // 输出 日志 提示。
}
}
function main() { // 策略程序的 主函数。(入口函数)
if (exchange.GetName() !== 'Futures_CTP') { // 判断添加的交易所对象 的名称(通过exchange.GetName函数获取) 如果不等于 'Futures_CTP' 即:添加的不是传统期货交易所对象。
throw "只支持传统商品期货(CTP)"; // 抛出异常
}
SetErrorFilter("login|ready"); // 设置错误过滤
LogStatus("Ready..."); // 状态栏显示 “准备..”文本
LogProfitReset(); // 清空之前的收益日志
chart = Chart(ChartCfg); // 调用API Chart 函数, 用图表对象ChartCfg初始化,返回 图表控制对象。
chart.reset(); // 调用图表控制对象的成员函数 reset 清空所有图表上的 数据(数据序列的内容)
LoopInterval = Math.max(LoopInterval, 1); // 设定循环 时间,最小 不小于1.
while (true) { // 主要循环
if (exchange.IO("status") && $.IsTrading(ContractTypeName)) { // 必须确保 与交易所服务器 连接的状态下(exchange.IO("status") 返回true 就是链接上了) 并且 在该品种(ContractTypeName)的交易时间内 (通过IsTrading函数判断) 才可执行 onTick 函数
onTick(exchange); // 执行 onTick 函数,即: 策略主要逻辑
} else { // exchange.IO("status") 返回 false 即: 未连接
LogStatus("未登录状态或不在交易时间段内"); // 在状态栏 显示 提示信息。
}
Sleep(LoopInterval * 1000); // 根据参数 LoopInterval 轮训等待, 避免程序访问 交易所 API 过于频繁导致 问题。
}
}
期货程序化 python的呢