В прошлом выпуске мы создали простую программу торговли товарными фьючерсами с помощью JavaScript в системе Sandbox и приятно поиграли в Sandbox. Автоматизированные торговые программы основаны на данных, что означает, что они должны получать актуальные данные вокруг себя в режиме реального времени, предоставляя большое количество информации для торговли, принятия решений, анализа и вычислений.
GetTicker: получает данные о рынке в режиме реального времени, которые представляют собой объекты, содержащие информацию о рынке.
GetRecords: получает исторические данные K-строка, можно задать параметры для контроля размера цикла, возвращаемого K-строковым данным. Параметры: PERIOD_M1 - 1 минута, PERIOD_M5 - 5 минут, PERIOD_M15 - 15 минут, PERIOD_M30 - 30 минут, PERIOD_H1 - 1 час, PERIOD_D1 - день
GetAccount: получить информацию о счете фьючерсной компании, установленную в конфигурации. Друзья, которые имеют опыт торговли фьючерсами, наверняка посмотрят на возвращение функции GetAccount и скажут, что информация о фьючерсном счете не может быть такой простой структурой, которую можно описать. Это действительно так, здесь собраны самые основные данные. Подробные данные можно просматривать с помощью такого кода, и все же в функции MainLoop мы добавили маленькому парню функцию, чтобы он запросил подробную информацию об аккаунте.
function MainLoop(){
var account = exchange.GetAccount(); // 框架的接口函数,获取配置的账户信息。
var obj = JSON.parse(exchange.GetRawJSON()); // 框架的接口函数,获取上一次调用的原始调用信息。这里就是GetAccount 调用时 接收到的原始信息。
var nowTime = new Date();
var table = {
type : "table",
title : "商品期货账户详细信息",
cols : ["key", "value"],
rows : [],
}
for(var key in obj){
table.rows.push([key, obj[key]]);
}
LogStatus("time:" + nowTime + JSON.stringify(account) + '\n`' + JSON.stringify(table) + '`');
}
функция main() {
состояние var = нуль;
пока ((правда) {
статус =exchange.IO("status"); // вызов API для определения состояния соединения
if ((status === true) { // Определение состояния
// LogStatus ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
// Поскольку в MainLoop используется LogStatus, это место нужно сначала прокомментировать, чтобы не перекрыть информацию
MainLoop ((); // После подключения к серверу биржи выполняет основные рабочие функции.
}else{ // Если нет подключения, тоexchange.IOФункция ("status") возвращает false
LogStatus ((
##### 如图显示:
![img](/upload/asset/d2be567a7af2fb63c7834703d362136de7e794b6.png)
![img](/upload/asset/f8383059b540e417161d5ac49e9465dd1178d5db.png)
第一次打印出详细信息的时候我也一头雾水,查询了CTP相关资料知道了具体各个字段的意思。
可以用以下这个对象去在程序中匹配翻译下。
```
var trans = {
"AccountID": "投资者帐号",
"Available": "可用资金",
"Balance": "期货结算准备金",
"BrokerID": "经纪公司代码",
"CashIn": "资金差额",
"CloseProfit": "平仓盈亏",
"Commission": "手续费",
"Credit": "信用额度",
"CurrMargin": "当前保证金总额",
"CurrencyID": "币种代码",
"DeliveryMargin": "投资者交割保证金",
"Deposit": "入金金额",
"ExchangeDeliveryMargin": "交易所交割保证金",
"ExchangeMargin": "交易所保证金",
"FrozenCash": "冻结的资金",
"FrozenCommission": "冻结的手续费",
"FrozenMargin": "冻结的保证金",
"FundMortgageAvailable": "货币质押余额",
"FundMortgageIn": "货币质入金额",
"FundMortgageOut": "货币质出金额",
"Interest": "利息收入",
"InterestBase": "利息基数",
"Mortgage": "质押金额",
"MortgageableFund": "可质押货币金额",
"PositionProfit": "持仓盈亏",
"PreBalance": "上次结算准备金",
"PreCredit": "上次信用额度",
"PreDeposit": "上次存款额",
"PreFundMortgageIn": "上次货币质入金额",
"PreFundMortgageOut": "上次货币质出金额",
"PreMargin": "上次占用的保证金",
"PreMortgage": "上次质押金额",
"Reserve": "基本准备金",
"ReserveBalance": "保底期货结算准备金",
"SettlementID": "结算编号",
"SpecProductCloseProfit": "特殊产品持仓盈亏",
"SpecProductCommission": "特殊产品手续费",
"SpecProductExchangeMargin": "特殊产品交易所保证金",
"SpecProductFrozenCommission": "特殊产品冻结手续费",
"SpecProductFrozenMargin": "特殊产品冻结保证金",
"SpecProductMargin": "特殊产品占用保证金",
"SpecProductPositionProfit": "特殊产品持仓盈亏",
"SpecProductPositionProfitByAlg": "根据持仓盈亏算法计算的特殊产品持仓盈亏",
"TradingDay": "交易日",
"Withdraw": "出金金额",
"WithdrawQuota": "可取资金",
};
```
##### 先复制这个用来翻译的对象到代码中。
![img](/upload/asset/4f034b84a65aae2f4f4f0cccf601df857f040825.png)
##### 修改前
![img](/upload/asset/0b2a2ba84c6c90ac4cc9dd15021e7dd0bfcd3a76.png)
##### 修改后
```
var desc = trans[key];
table.rows.push([key, typeof(desc) === "undefined" ? "--" : desc, obj[key]]);
```
![img](/upload/asset/f2e3a47db7556f7a64479877982b26244fdc0b0a.png)
##### 这下直白多了。
![img](/upload/asset/b8d3053f361a8af3ed11e5e2ad30c104968fe854.png)
В следующий раз попробуйте в системе песочницы: ((Только добавленный код в функции MainLoop))
function MainLoop(){
var account = exchange.GetAccount(); // 框架的接口函数,获取配置的账户信息。
var obj = JSON.parse(exchange.GetRawJSON()); // 框架的接口函数,获取上一次调用的原始调用信息。这里就是GetAccount 调用时 接收到的原始信息。
var nowTime = new Date();
var table = {
type : "table",
title : "商品期货账户详细信息",
cols : ["key", "value"],
rows : [],
}
for(var key in obj){
var desc = trans[key];
table.rows.push([key, typeof(desc) === "undefined" ? "--" : desc, obj[key]]);
}
// 还记得状态栏表格么? 就是刚才我们翻译的账户信息所在的表格。其实可以多表格显示,我们来试下。
var table2 = {
type : "table",
title : "GetDepth、GetOrders、GetPosition",
cols : ["function Name", "return value"],
rows : [],
}
// 使用 GetDepth 获取深度数据
exchange.SetContractType("MA705"); // 一定记得 不论是获取数据还是下单,一定要设置或切换操作的合约。
var depth = exchange.GetDepth();
table2.rows.push(["GetDepth()", depth]);
// 下个低价买单挂单、然后获取未完成的订单
exchange.SetDirection("buy"); // 一定记得 下单前要明确下单方向, 这里是开多仓。
exchange.Buy(depth.Bids[0].Price - 20, 1);
var PendingOrders = exchange.GetOrders();
table2.rows.push(["GetOrders()", PendingOrders]);
// 下个高价买单吃掉盘口的卖单,用获取持仓函数 GetPosition 获取信息来看下是否成交。
exchange.SetDirection("buy"); // 一定记得 下单前要明确下单方向, 这里是开多仓。
exchange.Buy(depth.Asks[0].Price + 1, 1); // 比卖一价 这个价格再多出一块钱,确保吃到单子
Sleep(1000);
var positions = exchange.GetPosition();
table2.rows.push(["GetPosition()", positions]);
var tables = [table, table2];
LogStatus("time:" + nowTime + JSON.stringify(account) + '\n`' + JSON.stringify(tables) + '`');
throw "仅仅执行一次,抛出个错误停止程序,老白在平时也经常使用这个办法调试代码!因为有时会输出一大堆信息无从下手。";
}
Для получения интересующих данных, таких как: K-линия, информация о расчете, также необходимо SHOW. Sandbox объединяет набор графических библиотек, и мы используем JS, чтобы показать интересные данные, полученные от серверов фьючерсных компаний CTP:
var chart = null // 一个全局 变量 用来接收 Chart 函数返回的 对象。
var series = [] // 数据系列数组,用来保存 图表上的线、标记、K线等数据。
var labelIdx = [] // label 索引,
var preBarTime = 0 // K线前一柱的 时间戳 ( 毫秒数 )
var preFlagTime = 0 // 前一个 标记 的时间戳
var preDotTime = [] // 时间戳
var cfg = { // 用来初始化设置图表的对象(即图表设置) 具体可见 HighCharts
tooltip: {
xDateFormat: '%Y-%m-%d %H:%M:%S, %A'
},
legend: {
enabled: true,
},
plotOptions: {
candlestick: {
color: '#d75442', // 颜色值
upColor: '#6ba583' // 颜色值
}
},
rangeSelector: { // 范围选择
buttons: [{ // 按钮
type: 'hour',
count: 1,
text: '1h'
}, {
type: 'hour',
count: 3,
text: '3h'
}, {
type: 'hour',
count: 8,
text: '8h'
}, {
type: 'all',
text: 'All'
}],
selected: 2,
inputEnabled: true
},
series: series, // 全局变量赋值给该属性, 注意 数组是引用传递, 即: 浅拷贝。(不明白的可以百度:引用传递)
}
$.GetCfg = function() { // 导出函数 , 用来获取 cfg 对象。
return cfg
}
$.PlotHLine = function(value, label, color, style) { // 画 x 轴 水平线。
if (typeof(cfg.yAxis) === 'undefined') { // 如果 没有定义 y 轴 属性, 添加y轴属性定义。
cfg.yAxis = {
plotLines: [] // y 轴上的 平行于x 轴的 水平线数组。
}
} else if (typeof(cfg.yAxis.plotLines) === 'undefined') { // 如果定义了 y轴 ,没有定义 水平线数组,则添加水平线数组。
cfg.yAxis.plotLines = []
}
/*
Solid
ShortDash
ShortDot
ShortDashDot
ShortDashDotDot
Dot
Dash
LongDash
DashDot
LongDashDot
LongDashDotDot
*/
var obj = { // 定义一个 对象,保存水平线的 各个属性。
value: value, // 在y轴上的 位置值。
color: color || 'red', // 没有传入 默认红色。
width: 2, // 线的宽度是2
dashStyle: style || 'Solid', // 选择线的类型,可以设置为虚线。 'dash'
label: { //
text: label || '',
align: 'center' // 居中显示
},
}
var found = false
for (var i = 0; i < cfg.yAxis.plotLines.length; i++) { // 遍历 水平线这个属性的 数组。
if (cfg.yAxis.plotLines[i].label.text == label) { // 如果找到对应的 label 的水平线
cfg.yAxis.plotLines[i] = obj // 把 obj对象赋值给这个索引 指向的元素。(替换)
found = true // 标记为找到
}
}
if (!found) { // 如果是没找到 (新压入)
cfg.yAxis.plotLines.push(obj)
}
if (!chart) { // 如果 chart 对象是 null
chart = Chart(cfg) // 调用API Chart 初始化图表,返回 对象赋值给chart
} else { // else
chart.update(cfg) // 更新图表
}
}
$.PlotRecords = function(records, title) { // 画K线 ,参数是 K线数据(数组), 标题
var seriesIdx = labelIdx["candlestick"]; // 数据序列 的索引
if (!chart) { // 如果chart 图表对象为 null
chart = Chart(cfg) // 初始化图表,并且返回图表对象给 chart 变量
}
if (typeof(seriesIdx) == 'undefined') { // 如果获取到的数据序列索引 是未定义,执行以下if代码
cfg.__isStock = true // 设置 cfg 对象 __isStock 属性为 true
seriesIdx = series.length // 设置 当前数据序列索引 为 已经存在的索引之后。
series.push({ // 压入series 数据序列数组 ,该数据序列属性如下:
type: 'candlestick', // type 属性 K线图
name: typeof(title) == 'undefined' ? '' : title, // 如果 $.PlotRecords 导出函数调用时,没有传入 title参数,则该数据序列 标题为 空字符串。
id: 'primary', // 数据序列的 id 为 primary (首要的)
data: [] // 数据序列的 数据数组 ,图表上显示的数据值都储存在该数组内。
});
chart.update(cfg) // 用设置过的 cfg 对象 更新 chart 对象。
labelIdx["candlestick"] = seriesIdx // 给 labelIdx (索引的标签) 添加 K线数据序列的索引,即 当前的 seriesIdx 值。
}
if (typeof(records.Time) !== 'undefined') { // 如果传入的是单根 K线 数据执行以下if 代码
var Bar = records; // 把records 赋值给 Bar
if (Bar.Time == preBarTime) { // preBarTime 初始是0, 如果参数传入的records 的时间戳 和 全局变量 preBarTime 相等,即认为当前的Bar (即 records) 没有更新(没有出现新的K线柱)
chart.add(seriesIdx, [Bar.Time, Bar.Open, Bar.High, Bar.Low, Bar.Close], -1) // 根据参数传入的records 更新图表上的 值为 labelIdx["candlestick"] 的数据序列的 倒数第一个数据( 因为 add函数 最后一个参数传入的是 -1)。
} else if (Bar.Time > preBarTime) { // 如果 参数传入的 records 也就是 Bar 的时间戳 比 preBarTime 大,即 新出现了Bar 。
preBarTime = Bar.Time // 更新preBarTime 这个全局变量用于 下次的比较。
chart.add(seriesIdx, [Bar.Time, Bar.Open, Bar.High, Bar.Low, Bar.Close]) // 区别于 Bar.Time == preBarTime 时的操作,没有传参数 -1 ,为在数据序列最后添加一个数据。
}
} else { // 如果传入的是一个K线数据数组, 此情况在第一次 preBarTime = 0 时 ,执行比较特殊:会逐个添加全部 records数据
for (var i = 0; i < records.length; i++) { // 遍历records
if (records[i].Time == preBarTime) { // 如果 索引i 这个元素的时间戳 等于 上一次记录的 preBarTime 更新图表 该数据序列的最后一个数据
chart.add(seriesIdx, [records[i].Time, records[i].Open, records[i].High, records[i].Low, records[i].Close], -1) // 更新
} else if (records[i].Time > preBarTime) { // 如果索引i 这个元素的时间戳大于 preBarTime 则添加
preBarTime = records[i].Time // 更新preBarTime
chart.add(seriesIdx, [records[i].Time, records[i].Open, records[i].High, records[i].Low, records[i].Close]) // 添加
}
}
}
return chart // 返回 chart
}
$.PlotLine = function(label, dot, time) { // 画线
if (!chart) { // 如果 chart 为 null 执行以下
cfg.xAxis = { // 设置 cfg对象 x轴 的类型为 datatime 时间类型
type: 'datetime'
}
chart = Chart(cfg) // 调用 Chart 这个API函数(cfg为参数) 初始化图表
}
var seriesIdx = labelIdx[label] // 按照 label参数获取 标签索引 赋值给 数据序列索引。
if (typeof(seriesIdx) === 'undefined') { // 如果 labelIdx 内没有 参数传入的 label 这个标签的索引 执行以下代码 添加这个数据序列
seriesIdx = series.length // 根据已有的数据序列的长度,赋值 新数据序列的索引
preDotTime[seriesIdx] = 0 // 在线条的 前一个值(preDotTime中的元素) 使用了一个数组储存(因为可能有多个线条,所以会有多个 “前一个值” ,所以用数组储存,这个“前一个值”作用类似于 preBarTime)
labelIdx[label] = seriesIdx; // 把当前标签 对应的索引 储存在标签索引。
series.push({ // 把数据序列 push 进 数据序列数组
type: 'line', // 设置当前的数据序列 类型为: 线
yAxis: 0, // 使用的y轴 为索引为 0 的y轴(highcharts 图表 可以有 多个 y 坐标轴,这里指定索引0的y轴)
showInLegend: true, //
name: label, // 根据 函数传入的 参数 label 设置
data: [], // 数据序列的数据项
tooltip: { // 工具提示
valueDecimals: 5 // 值的小数点 保留5位
}
})
chart.update(cfg) // 用cfg 对象 更新图表对象 chart
}
if (typeof(time) == 'undefined') { // 如果参数没有传入 要画线(其实是线上的点)的时间。
time = new Date().getTime() // 给形参 time 赋值当前的时间戳
}
if (preDotTime[seriesIdx] != time) { // 对比当前时间如果不等于执行if 内的代码
preDotTime[seriesIdx] = time // 更新 上一次时间
chart.add(seriesIdx, [time, dot]) // 添加参数传进的点 在x轴值为 time 的时间上
} else {
chart.add(seriesIdx, [time, dot], -1) // 如果时间相等 ,则更新数据序列的最后的点
}
return chart // 返回图表对象
}
$.PlotFlag = function(time, text, title, shape, color) { // 在图表上添加 旗帜标签
if (!chart) { // 如果 chart 为 null
chart = Chart(cfg) // 初始化 图表
}
label = "flag"; // 设置标签类型为 flag (旗帜)
var seriesIdx = labelIdx[label] // 在数据序列索引数组中获取 旗帜 类型的索引
if (typeof(seriesIdx) === 'undefined') { // 如果没有该索引
seriesIdx = series.length // 根据现有的索引数组长度 新设置一个索引
labelIdx[label] = seriesIdx; // 储存该索引 到标签索引数组
series.push({ // 向数据序列数组 压入 当前数据序列
type: 'flags', // 设置当前压入的数据序列 类型为 旗帜类型
onSeries: 'primary', // 设置 旗帜标记在哪个数据序列上, 这里设置 标记在 id 为 primary的数据序列上(即 K线数据序列)
data: [] // 数据序列的 数据项
})
chart.update(cfg) // 更新图表对象
}
var obj = { // 根据函数参数 初始化一个对象 用来 加载到图表。
x: time, // x轴的值, 即时间戳
color: color, // 颜色
shape: shape, // 形状
title: title, // 标题
text: text // 文本
}
if (preFlagTime != time) { // 上一次的标记时间 如果不等于当前时间
preFlagTime = time // 更新上一次的标记时间
chart.add(seriesIdx, obj) // 用设置好的obj对象添加该标记(写入图表)
} else {
chart.add(seriesIdx, obj, -1) // 如果时间相同,则更新最后一个旗帜标记。
}
return chart // 返回图表对象
}
$.PlotTitle = function(title, chartTitle) { // 设置 图表 标题
cfg.subtitle = { // 根据参数 title设置子标题
text: title
};
if (typeof(chartTitle) !== 'undefined') { // 如果 chartTitle 不等于 未定义,即传入了值
cfg.title = { // 设置 图表标题
text: chartTitle // 文本为 chartTitle
};
}
if (chart) { // 如果 图表已经初始化,更新图表
chart.update(cfg)
}
}
var isFirstPlotLine = true;
var PreBarTime = 0;
function MainLoop(){
var info = exchange.SetContractType("MA705");
if(!info){
return;
}
var records = exchange.GetRecords();
if(!records || records.length < 10){
return;
}
var depth705 = exchange.GetDepth();
if(!depth705 || depth705.Asks.length == 0 || depth705.Bids.length == 0){
return;
}
info = exchange.SetContractType("MA709");
if(!info){
return;
}
var depth709 = exchange.GetDepth();
if(!depth709 || depth709.Asks.length == 0 || depth709.Bids.length == 0){
return;
}
$.PlotRecords(records, "MA705"); // 在图表上画出 MA705 甲醇 合约的 K线数据
var diffA_B = depth705.Bids[0].Price - depth709.Asks[0].Price;
var diffB_A = depth705.Asks[0].Price - depth709.Bids[0].Price;
if(PreBarTime !== records[records.length - 1].Time){
$.PlotLine("MA705->MA709", diffA_B);
$.PlotLine("MA705<-MA709", diffB_A);
PreBarTime = records[records.length - 1].Time;
if(isFirstPlotLine){
for(var j = 0;j < cfg.series.length; j++){
if(cfg.series[j].name == "MA705->MA709" || cfg.series[j].name == "MA705<-MA709"){
cfg.series[j].yAxis = 1;
}
}
isFirstPlotLine = false;
}
}
}
var cfg = $.GetCfg();
function main() {
var status = null;
cfg.yAxis = [{
title: {text: 'K线'}, //标题
style: {color: '#4572A7'}, //样式
opposite: false //生成右边Y轴
},
{
title:{text: "另一个Y轴"},
opposite: true //生成右边Y轴 ceshi
}
];
while(true){
status = exchange.IO("status"); // 调用API 确定连接状态
if(status === true){ // 判断状态
// LogStatus("已连接!"); // 在回测或者实际运行中显示一些实时数据、信息。
// 由于MainLoop 中用到了LogStatus 所以这个地方的要先注释掉, 以免覆盖掉信息
MainLoop(); // 连接上 交易所服务器后,执行主要工作函数。
}else{ // 如果没有连接上 即 exchange.IO("status") 函数返回 false
LogStatus("未连接状态!"); // 显示 未连接状态。
}
Sleep(1000); // 封装的睡眠函数,需要有轮询间隔, 以免访问过于频繁。CTP协议是每秒推送2次数据。
}
}
Начнем с того, что мы можем увидеть, что уже прорисованная линия дифференциации между контрактами MA705 и MA709 позволяет построить дифференциацию между контрактами, основанную на дифференциации.
https://www.fmz.com/bbs-topic/726
Средняя стоимостьif ((isFirstPlotLine) { Если вы не хотите, чтобы это произошло for ((var j = 0; j < cfg.series.length; j++) { if ((cfg.series[j].name == "MA705->MA709"), cfg.series[j].name == "MA705<-MA709") cfg.series[j].yAxis = 1; {y:bi} {y:bi} isFirstPlotLine = false; Я не понимаю.
Маленькие мечтыЭто должно быть установка двух линий.