R-Breaker是一种短线日内策略,因为策略没有用到指标,没有指标周期之类的参数。策略的触发条件完全是通过价格计算出来的支撑线、阻力线。避开了指标参数对于行情的拟合问题,并且模型原理非常简单,对于市场各种行情适应性较强。虽然说策略实盘不一定效果出众,但是也可以作为一个学习研究方向思考策略设计。
支撑线、阻力线算法 计算这些数据的时候用到三个数据分别是: 1、上一根日线BAR的最高价。 2、上一根日线BAR的收盘价。 3、上一根日线BAR的最低价。
这些数据非常好获取。
var r = exchange.GetRecords(PERIOD_D1) // 获取K线数据
var preBar = r[r.length - 2] // 上一根日线BAR
var preHigh = preBar.High // 上一根日线BAR最高价
var preClose = preBar.Close // 上一根日线BAR收盘价
var preLow = preBar.Low // 上一根日线BAR最低价
然后计算阻力价、支撑价。
var pivot = (preHigh + preLow + preClose) / 3 // 中线
var resistancePrice1 = 2 * pivot - preLow // 阻力价1
var resistancePrice2 = pivot + (preHigh - preLow) // 阻力价2
var resistancePrice3 = preHigh + 2 * (pivot - preLow) // 阻力价3
var strutPrice1 = 2 * pivot - preHigh // 支撑价1
var strutPrice2 = pivot - (preHigh - preLow) // 支撑价2
var strutPrice3 = preLow - 2 * (preHigh - pivot) // 支撑价3
图解:
触发交易
图解:
我们采用和之前文库中的一篇文章「商品期货多品种均线策略」中一样的程序架构。把均线模型替换成R-Breaker模型。但是增加一些其它扩展,给每个品种增加图表显示,显示支撑线、阻力线。给每个品种增加一个交互按钮,可以在盘中手动平仓。
策略参数很少,策略本身逻辑的参数一个也没有。
合约列表中的合约代码参数以英文逗号间隔,例如:rb2101,MA101,j2101,i2101,p2101,AP101
。这样策略就针对这些合约品种操作。
/*backtest
start: 2021-06-01 09:00:00
end: 2021-09-25 15:00:00
period: 1d
basePeriod: 5m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
args: [["Instruments","rb2201,MA201,j2201,i2201,p2201,AP201"]]
*/
// 类库交易对象
var _bot = $.NewPositionManager()
// 画图相关全局变量
var arrChart = []
var Index = 0
var Manager = {
New: function(needRestore, symbol, keepBalance, index) {
var symbolDetail = _C(exchange.SetContractType, symbol)
if (symbolDetail.VolumeMultiple == 0 || symbolDetail.MaxLimitOrderVolume == 0 || symbolDetail.MinLimitOrderVolume == 0 || symbolDetail.LongMarginRatio == 0 || symbolDetail.ShortMarginRatio == 0) {
Log(symbolDetail)
throw "合约信息异常"
} else {
Log("合约", symbolDetail.InstrumentName, "一手", symbolDetail.VolumeMultiple, "份, 最大下单量", symbolDetail.MaxLimitOrderVolume, "保证金率:", _N(symbolDetail.LongMarginRatio), _N(symbolDetail.ShortMarginRatio), "交割日期", symbolDetail.StartDelivDate)
}
// 声明枚举量
var ACT_IDLE = 0
var ACT_LONG = 1
var ACT_SHORT = 2
var ACT_COVER = 3
var ERR_SUCCESS = 0
var ERR_SET_SYMBOL = 1
var ERR_GET_ORDERS = 2
var ERR_GET_POS = 3
var ERR_TRADE = 4
var ERR_GET_DEPTH = 5
var ERR_NOT_TRADING = 6
var errMsg = ["成功", "切换合约失败", "获取订单失败", "获取持仓失败", "交易下单失败", "获取深度失败", "不在交易时间"]
// 构造对象
var obj = {
symbol: symbol,
keepBalance: keepBalance,
}
// 初始化任务对象
obj.task = {
action: ACT_IDLE,
amount: 0,
dealAmount: 0,
avgPrice: 0,
preCost: 0,
preAmount: 0,
init: false,
retry: 0,
desc: "空闲",
onFinish: null
}
obj.lastPrice = 0
obj.symbolDetail = symbolDetail
// 策略运行时数据
obj.currHigh = null
obj.currLow = null
obj.pivot = null
obj.resistancePrice1 = null
obj.resistancePrice2 = null
obj.resistancePrice3 = null
obj.strutPrice1 = null
obj.strutPrice2 = null
obj.strutPrice3 = null
obj.records = null
obj.index = index
obj.preBarTime = 0
obj.backhand = 0
obj.isBannedTrade = false
// 持仓状态信息
obj.status = {
symbol: symbol,
recordsLen: 0,
vm: [],
open: 0,
cover: 0,
st: 0,
marketPosition: 0,
lastPrice: 0,
holdPrice: 0,
holdAmount: 0,
holdProfit: 0,
symbolDetail: symbolDetail,
lastErr: "",
lastErrTime: "",
// 可以增加一些显示的数值属性
currHigh: null,
currLow: null,
pivot: null,
resistancePrice1: null,
resistancePrice2: null,
resistancePrice3: null,
strutPrice1: null,
strutPrice2: null,
strutPrice3: null,
isTrading: false
}
// 设置错误的功能函数
obj.setLastError = function(err) {
if (typeof(err) === 'undefined' || err === '') {
obj.status.lastErr = ""
obj.status.lastErrTime = ""
return
}
var t = new Date()
obj.status.lastErr = err
obj.status.lastErrTime = t.toLocaleString()
}
// 恢复函数
obj.reset = function(marketPosition) {
if (typeof(marketPosition) !== 'undefined') {
obj.marketPosition = marketPosition
var pos = _bot.GetPosition(obj.symbol, marketPosition > 0 ? PD_LONG : PD_SHORT)
if (pos) {
obj.holdPrice = pos.Price
obj.holdAmount = pos.Amount
Log(obj.symbol, "仓位", pos)
} else {
throw "恢复" + obj.symbol + "的持仓状态出错, 没有找到仓位信息"
}
Log("恢复", obj.symbol, "持仓均价:", obj.holdPrice, "持仓数量:", obj.holdAmount)
obj.status.vm = [obj.marketPosition]
} else {
obj.marketPosition = 0
obj.holdPrice = 0
obj.holdAmount = 0
obj.holdProfit = 0
}
obj.holdProfit = 0
obj.lastErr = ""
obj.lastErrTime = ""
}
// 更新状态,返回用于显示的状态数据
obj.Status = function() {
obj.status.marketPosition = obj.marketPosition
obj.status.holdPrice = obj.holdPrice
obj.status.holdAmount = obj.holdAmount
obj.status.lastPrice = obj.lastPrice
if (obj.lastPrice > 0 && obj.holdAmount > 0 && obj.marketPosition !== 0) {
// 计算收益
obj.status.holdProfit = _N((obj.lastPrice - obj.holdPrice) * obj.holdAmount * symbolDetail.VolumeMultiple, 4) * (obj.marketPosition > 0 ? 1 : -1)
} else {
obj.status.holdProfit = 0
}
return obj.status
}
// 处理交易的逻辑层面,设置交易任务的函数
obj.setTask = function(action, amount, onFinish) {
obj.task.init = false
obj.task.retry = 0
obj.task.action = action
obj.task.preAmount = 0
obj.task.preCost = 0
obj.task.amount = typeof(amount) === 'number' ? amount : 0
obj.task.onFinish = onFinish
if (action == ACT_IDLE) {
obj.task.desc = "空闲"
obj.task.onFinish = null
} else {
if (action !== ACT_COVER) {
obj.task.desc = (action == ACT_LONG ? "加多仓" : "加空仓") + "(" + amount + ")"
} else {
obj.task.desc = "平仓"
}
Log("接收到任务", obj.symbol, obj.task.desc)
// process immediately
obj.Poll(true)
}
}
// 处理交易任务的函数
obj.processTask = function() {
var insDetail = exchange.SetContractType(obj.symbol)
if (!insDetail) {
return ERR_SET_SYMBOL
}
var SlideTick = 1
var ret = false
if (obj.task.action == ACT_COVER) {
var hasPosition = false
do {
if (!$.IsTrading(obj.symbol)) {
return ERR_NOT_TRADING
}
hasPosition = false
var positions = exchange.GetPosition()
if (!positions) {
return ERR_GET_POS
}
var depth = exchange.GetDepth()
if (!depth) {
return ERR_GET_DEPTH
}
var orderId = null
for (var i = 0; i < positions.length; i++) {
if (positions[i].ContractType !== obj.symbol) {
continue
}
var amount = Math.min(insDetail.MaxLimitOrderVolume, positions[i].Amount)
if (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) {
exchange.SetDirection(positions[i].Type == PD_LONG ? "closebuy_today" : "closebuy")
orderId = exchange.Sell(_N(depth.Bids[0].Price - (insDetail.PriceTick * SlideTick), 2), Math.min(amount, depth.Bids[0].Amount), obj.symbol, positions[i].Type == PD_LONG ? "平今" : "平昨", 'Bid', depth.Bids[0])
hasPosition = true
} else if (positions[i].Type == PD_SHORT || positions[i].Type == PD_SHORT_YD) {
exchange.SetDirection(positions[i].Type == PD_SHORT ? "closesell_today" : "closesell")
orderId = exchange.Buy(_N(depth.Asks[0].Price + (insDetail.PriceTick * SlideTick), 2), Math.min(amount, depth.Asks[0].Amount), obj.symbol, positions[i].Type == PD_SHORT ? "平今" : "平昨", 'Ask', depth.Asks[0])
hasPosition = true
}
}
if (hasPosition) {
if (!orderId) {
return ERR_TRADE
}
Sleep(1000)
while (true) {
// Wait order, not retry
var orders = exchange.GetOrders()
if (!orders) {
return ERR_GET_ORDERS
}
if (orders.length == 0) {
break
}
for (var i = 0; i < orders.length; i++) {
exchange.CancelOrder(orders[i].Id)
Sleep(500)
}
}
}
} while (hasPosition)
ret = true
} else if (obj.task.action == ACT_LONG || obj.task.action == ACT_SHORT) {
do {
if (!$.IsTrading(obj.symbol)) {
return ERR_NOT_TRADING
}
Sleep(1000)
while (true) {
// Wait order, not retry
var orders = exchange.GetOrders()
if (!orders) {
return ERR_GET_ORDERS
}
if (orders.length == 0) {
break
}
for (var i = 0; i < orders.length; i++) {
exchange.CancelOrder(orders[i].Id)
Sleep(500)
}
}
var positions = exchange.GetPosition()
// Error
if (!positions) {
return ERR_GET_POS
}
// search position
var pos = null
for (var i = 0; i < positions.length; i++) {
if (positions[i].ContractType == obj.symbol && (((positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) && obj.task.action == ACT_LONG) || ((positions[i].Type == PD_SHORT || positions[i].Type == PD_SHORT_YD) && obj.task.action == ACT_SHORT))) {
if (!pos) {
pos = positions[i]
pos.Cost = positions[i].Price * positions[i].Amount
} else {
pos.Amount += positions[i].Amount
pos.Profit += positions[i].Profit
pos.Cost += positions[i].Price * positions[i].Amount
}
}
}
// record pre position
if (!obj.task.init) {
obj.task.init = true
if (pos) {
obj.task.preAmount = pos.Amount
obj.task.preCost = pos.Cost
} else {
obj.task.preAmount = 0
obj.task.preCost = 0
}
}
var remain = obj.task.amount
if (pos) {
obj.task.dealAmount = pos.Amount - obj.task.preAmount
remain = parseInt(obj.task.amount - obj.task.dealAmount)
if (remain <= 0 || obj.task.retry >= MaxTaskRetry) {
ret = {
price: (pos.Cost - obj.task.preCost) / (pos.Amount - obj.task.preAmount),
amount: (pos.Amount - obj.task.preAmount),
position: pos
}
break
}
} else if (obj.task.retry >= MaxTaskRetry) {
ret = null
break
}
var depth = exchange.GetDepth()
if (!depth) {
return ERR_GET_DEPTH
}
var orderId = null
if (obj.task.action == ACT_LONG) {
exchange.SetDirection("buy")
orderId = exchange.Buy(_N(depth.Asks[0].Price + (insDetail.PriceTick * SlideTick), 2), Math.min(remain, depth.Asks[0].Amount), obj.symbol, 'Ask', depth.Asks[0])
} else {
exchange.SetDirection("sell")
orderId = exchange.Sell(_N(depth.Bids[0].Price - (insDetail.PriceTick * SlideTick), 2), Math.min(remain, depth.Bids[0].Amount), obj.symbol, 'Bid', depth.Bids[0])
}
// symbol not in trading or other else happend
if (!orderId) {
obj.task.retry++
return ERR_TRADE
}
} while (true)
}
if (obj.task.onFinish) {
obj.task.onFinish(ret)
}
obj.setTask(ACT_IDLE)
return ERR_SUCCESS
}
// 策略逻辑执行函数
obj.Poll = function(subroutine) {
// 判断交易时段
obj.status.isTrading = $.IsTrading(obj.symbol)
if (!obj.status.isTrading) {
return
}
// 执行下单交易任务
if (obj.task.action != ACT_IDLE) {
var retCode = obj.processTask()
if (obj.task.action != ACT_IDLE) {
obj.setLastError("任务没有处理成功: " + errMsg[retCode] + ", " + obj.task.desc + ", 重试: " + obj.task.retry)
} else {
obj.setLastError()
}
return
}
// 调用Poll时如果设置了subroutine参数,只运行到此处,这个是程序设计的一个小技巧
if (typeof(subroutine) !== 'undefined' && subroutine) {
return
}
// 根据参数设置微信推送
var suffix = WXPush ? '@' : ''
// switch symbol
_C(exchange.SetContractType, obj.symbol)
// 获取K线数据
var records = exchange.GetRecords(PERIOD_D1)
if (!records) {
obj.setLastError("获取K线失败")
return
}
obj.status.recordsLen = records.length
if (records.length < 2) {
obj.setLastError("K线数据长度不足2")
return
}
// 0: IDLE 空闲, 1: LONG 做多, 2: SHORT 做空, 3: CoverALL 当前合约全部平仓。opCode 是操作码,以下策略逻辑检测到条件后设置对应的操作码
var opCode = 0
var lastPrice = records[records.length - 1].Close
obj.lastPrice = lastPrice
// 记录当日最高最低价
obj.currHigh = records[records.length - 1].High
obj.currLow = records[records.length - 1].Low
obj.status.currHigh = obj.currHigh
obj.status.currLow = obj.currLow
// 计算阻力位、支撑位
obj.records = records
var preBar = records[records.length - 2]
obj.pivot = (preBar.High + preBar.Low + preBar.Close) / 3
obj.resistancePrice1 = 2 * obj.pivot - preBar.Low
obj.resistancePrice2 = obj.pivot + (preBar.High - preBar.Low)
obj.resistancePrice3 = preBar.High + 2 * (obj.pivot - preBar.Low)
obj.strutPrice1 = 2 * obj.pivot - preBar.High
obj.strutPrice2 = obj.pivot - (preBar.High - preBar.Low)
obj.strutPrice3 = preBar.Low - 2 * (preBar.High - obj.pivot)
// 更新obj.status
obj.status.pivot = obj.pivot
obj.status.resistancePrice1 = obj.resistancePrice1
obj.status.resistancePrice2 = obj.resistancePrice2
obj.status.resistancePrice3 = obj.resistancePrice3
obj.status.strutPrice1 = obj.strutPrice1
obj.status.strutPrice2 = obj.strutPrice2
obj.status.strutPrice3 = obj.strutPrice3
// 下午收盘清仓
if (new Date().getHours() == 14 && new Date().getMinutes() > 58 && obj.marketPosition != 0) {
// 设置回调,清空保存的持久化数据
obj.setTask(ACT_COVER, 0, function(ret) {
obj.reset()
_G(obj.symbol, null)
})
obj.isBannedTrade = true
return
}
// 临近收盘禁止开仓
if (records[records.length - 1].Time != obj.preBarTime) {
obj.isBannedTrade = false
}
if (obj.isBannedTrade) {
return
}
// 策略逻辑
// 不持仓时
if (obj.marketPosition === 0) {
// 根据交易逻辑赋值opCode信号,程序后续根据信号处理
if(lastPrice > obj.resistancePrice3 || obj.backhand > 0) {
opCode = 1
} else if (lastPrice < obj.strutPrice3 || obj.backhand < 0) {
opCode = 2
}
// 反手后重置
if (obj.backhand != 0) {
obj.backhand = 0
}
} else {
if(obj.marketPosition > 0 && (obj.currHigh > obj.resistancePrice2 && lastPrice < obj.resistancePrice1 || lastPrice < obj.strutPrice3)) { // 持多仓
opCode = 3
// 平仓后反手做空
obj.backhand = -1
} else if (obj.marketPosition < 0 && (obj.currLow < obj.strutPrice2 && lastPrice > obj.strutPrice1 || lastPrice > obj.resistancePrice3)) { // 持空仓
opCode = 3
// 平仓后反手做多
obj.backhand = 1
}
}
// 如果不触发任何条件,操作码为0,返回
if (opCode == 0) {
return
}
// 执行平仓
if (opCode == 3) {
obj.setTask(ACT_COVER, 0, function(ret) {
obj.reset()
_G(obj.symbol, null)
})
return
}
var account = _bot.GetAccount()
var canOpen = parseInt((account.Balance-obj.keepBalance) / (opCode == 1 ? obj.symbolDetail.LongMarginRatio : obj.symbolDetail.ShortMarginRatio) / (lastPrice * 1.2) / obj.symbolDetail.VolumeMultiple)
var unit = Math.min(1, canOpen)
// 设置交易任务
obj.setTask((opCode == 1 ? ACT_LONG : ACT_SHORT), unit, function(ret) {
if (!ret) {
obj.setLastError("下单失败")
return
}
obj.holdPrice = ret.position.Price
obj.holdAmount = ret.position.Amount
obj.marketPosition += opCode == 1 ? 1 : -1
obj.status.vm = [obj.marketPosition]
_G(obj.symbol, obj.status.vm)
})
}
// 增加图表结构
obj.objChart = {
__isStock: true,
extension: {
layout: 'single',
height: 600,
},
title : { text : obj.symbol},
yAxis: {
plotLines: [
{value: 0,color: 'red',width: 1,label: {text: '阻力1',align: 'center'}},
{value: 0,color: 'red',width: 1,label: {text: '阻力2',align: 'center'}},
{value: 0,color: 'red',width: 1,label: {text: '阻力3',align: 'center'}},
{value: 0,color: 'green',width: 1,label: {text: '支撑1',align: 'center'}},
{value: 0,color: 'green',width: 1,label: {text: '支撑2',align: 'center'}},
{value: 0,color: 'green',width: 1,label: {text: '支撑3',align: 'center'}},
]
},
xAxis: { type: 'datetime'},
series : [
{
type: 'candlestick',
name: 'k',
id: 'k',
data: []
}
]
}
// 新增画图函数
obj.PlotRecords = function(chart) {
var r = obj.records
if(!r || r.length < 2) {
return
}
for (var j = 0; j < r.length; j++) {
if (r[j].Time == obj.preBarTime) {
chart.add(obj.index, [r[j].Time, r[j].Open, r[j].High, r[j].Low, r[j].Close], -1)
} else if (r[j].Time > obj.preBarTime) {
obj.preBarTime = r[j].Time
chart.add(obj.index, [r[j].Time, r[j].Open, r[j].High, r[j].Low, r[j].Close])
}
}
obj.objChart.yAxis.plotLines[0].value = _N(obj.resistancePrice1, 3) // 阻力1
obj.objChart.yAxis.plotLines[0].label.text = "阻力1:" + _N(obj.resistancePrice1, 3)
obj.objChart.yAxis.plotLines[1].value = _N(obj.resistancePrice2, 3)
obj.objChart.yAxis.plotLines[1].label.text = "阻力2:" + _N(obj.resistancePrice2, 3)
obj.objChart.yAxis.plotLines[2].value = _N(obj.resistancePrice3, 3)
obj.objChart.yAxis.plotLines[2].label.text = "阻力3:" + _N(obj.resistancePrice3, 3)
obj.objChart.yAxis.plotLines[3].value = _N(obj.strutPrice1, 3) // 支撑1
obj.objChart.yAxis.plotLines[3].label.text = "支撑1:" + _N(obj.strutPrice1, 3)
obj.objChart.yAxis.plotLines[4].value = _N(obj.strutPrice2, 3)
obj.objChart.yAxis.plotLines[4].label.text = "支撑2:" + _N(obj.strutPrice2, 3)
obj.objChart.yAxis.plotLines[5].value = _N(obj.strutPrice3, 3)
obj.objChart.yAxis.plotLines[5].label.text = "支撑3:" + _N(obj.strutPrice3, 3)
}
// 对象构造函数New函数的其它处理工作
var vm = null
if (RMode === 0) {
vm = _G(obj.symbol)
} else {
vm = JSON.parse(VMStatus)[obj.symbol]
}
if (vm) {
Log("准备恢复进度, 当前合约状态为", vm)
obj.reset(vm[0])
} else {
if (needRestore) {
Log("没有找到" + obj.symbol + "的进度恢复信息")
}
obj.reset()
}
return obj
}
}
function onexit() {
Log("已退出策略...")
}
function main() {
if (isReset) {
_G(null)
LogReset(1)
LogProfitReset()
LogVacuum()
Log("重置所有数据", "#FF0000")
}
if (exchange.GetName().indexOf('CTP') == -1) {
throw "只支持商品期货CTP"
}
SetErrorFilter("login|ready|流控|连接失败|初始|Timeout")
var mode = exchange.IO("mode", 0)
if (typeof(mode) !== 'number') {
throw "切换模式失败, 请更新到最新托管者!"
}
while (!exchange.IO("status")) {
Sleep(3000)
LogStatus("正在等待与交易服务器连接, " + new Date())
}
var positions = _C(exchange.GetPosition)
if (positions.length > 0) {
Log("检测到当前持有仓位, 系统将开始尝试恢复进度...")
Log("持仓信息", positions)
}
var initAccount = _bot.GetAccount()
// 恢复记录
var recoveryInitAcc = _G("initAccount")
if (!recoveryInitAcc) {
// 不存在恢复的数据,使用当前数据初始化
_G("initAccount", initAccount)
} else {
// 存在恢复的数据,恢复初始账户数据
initAccount = recoveryInitAcc
}
var initMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin
var keepBalance = _N((initAccount.Balance + initMargin) * (KeepRatio/100), 3)
Log("资产信息", initAccount, "保留资金:", keepBalance)
var tts = []
var filter = []
var arr = Instruments.split(',')
for (var i = 0; i < arr.length; i++) {
var symbol = arr[i].replace(/^\s+/g, "").replace(/\s+$/g, "")
if (typeof(filter[symbol]) !== 'undefined') {
throw symbol + "已经存在,请检查参数!"
}
filter[symbol] = true
var hasPosition = false
for (var j = 0; j < positions.length; j++) {
if (positions[j].ContractType == symbol) {
hasPosition = true
break
}
}
var obj = Manager.New(hasPosition, symbol, keepBalance, Index)
tts.push(obj)
arrChart.push(obj.objChart)
// 图表数据系列索引
Index += 1
}
// 创建图表对象
var chart = Chart(arrChart)
chart.reset()
var printProfitTS = 0
var preTotalHold = -1
var lastStatus = ''
while (true) {
// 交互
var cmd = GetCommand()
if (cmd) {
if (cmd === "暂停/继续") {
Log("暂停交易中...")
while (GetCommand() !== "暂停/继续") {
Sleep(1000)
}
Log("继续交易中...")
}
Log("交互命令:", cmd)
var arr = cmd.split(":")
if(arr[0] == "cover") {
var index = parseFloat(arr[1])
// 对指定的交易所对象,执行平仓
tts[index].setTask(ACT_COVER, 0, function(ret) {
tts[index].reset()
_G(tts[index].symbol, null)
})
}
}
while (!exchange.IO("status")) {
Sleep(3000)
LogStatus("正在等待与交易服务器连接, " + new Date() + "\n" + lastStatus)
}
var tblStatus = {
type: "table",
title: "持仓信息",
cols: ["合约名称", "持仓方向", "持仓均价", "持仓数量", "持仓盈亏", "加仓次数", "当前价格", "操作"],
rows: []
}
var tblMarket = {
type: "table",
title: "运行状态",
cols: ["合约名称", "合约乘数", "保证金率", "交易时间", "柱线长度", "异常描述", "发生时间"],
rows: []
}
var totalHold = 0
var vmStatus = {}
var ts = new Date().getTime()
var holdSymbol = 0
for (var i = 0; i < tts.length; i++) {
tts[i].Poll()
var d = tts[i].Status()
if (d.holdAmount > 0) {
vmStatus[d.symbol] = d.vm
holdSymbol++
}
tblStatus.rows.push([d.symbolDetail.InstrumentName, d.holdAmount == 0 ? '--' : (d.marketPosition > 0 ? '多' : '空'), d.holdPrice, d.holdAmount, d.holdProfit, Math.abs(d.marketPosition), d.lastPrice, {'type':'button', 'cmd': 'cover:' + String(i), 'name': '平仓'}]) // 加入交互按钮
tblMarket.rows.push([d.symbolDetail.InstrumentName, d.symbolDetail.VolumeMultiple, _N(d.symbolDetail.LongMarginRatio, 4) + '/' + _N(d.symbolDetail.ShortMarginRatio, 4), (d.isTrading ? '是#0000ff' : '否#ff0000'), d.recordsLen, d.lastErr, d.lastErrTime])
totalHold += Math.abs(d.holdAmount)
// 写入画图数据
tts[i].PlotRecords(chart)
}
// 更新图表
chart.update(arrChart)
// 显示状态栏信息
var now = new Date()
var elapsed = now.getTime() - ts
var tblAssets = _bot.GetAccount(true)
var nowAccount = _bot.Account()
if (tblAssets.rows.length > 10) {
// replace AccountId
tblAssets.rows[0] = ["InitAccount", "初始资产", initAccount]
} else {
tblAssets.rows.unshift(["NowAccount", "当前可用", nowAccount], ["InitAccount", "初始资产", initAccount])
}
lastStatus = '`' + JSON.stringify([tblStatus, tblMarket, tblAssets]) + '`\n轮询耗时: ' + elapsed + ' 毫秒, 当前时间: ' + now.toLocaleString() + ', 星期' + ['日', '一', '二', '三', '四', '五', '六'][now.getDay()] + ", 持有品种个数: " + holdSymbol
if (totalHold > 0) {
lastStatus += "\n手动恢复字符串: " + JSON.stringify(vmStatus)
}
LogStatus(lastStatus)
preTotalHold = totalHold
// 定时打印一次
if (ts - printProfitTS > 1000 * 60 * 60 * 24 && !IsVirtual()) {
LogProfit(nowAccount.Info.Balance - initAccount.Info.Balance)
printProfitTS = ts
}
Sleep(LoopInterval * 1000)
}
}