Loading ...

吕神波动率策略模板

Author: 中本姜, Date: 2020-04-29 17:07:54
Tags:

吕神波动率模板

背景

吕神在T神(千千的量化)公众号上 https://mp.weixin.qq.com/s/JlmFrS9po8dp5HAUyDZDAA 慷慨分享,扁豆神(扁豆子)FMZ版本发布在 https://www.fmz.com/strategy/200131 本版本继承三神信仰,将代码模板化,以码会友,共同进步. 愿诸位道友不忘初心,砥砺前行,开量化世界之盛事.

2020年4月15日,CME修改规则,原油期货价格可以为负。2020年4月21日,CME的WTI原油5月期货收至每桶-37.63美元.2020年4月22日,中国银行发布公告,中行原油宝客户将按照-37.63美元作为结算价格,穿仓用户需承担此次交易的全部损失.

代码思想

务必先读一下文章 https://mp.weixin.qq.com/s/JlmFrS9po8dp5HAUyDZDAA 该策略算法采用对数价格一定周期涨跌幅的滚动收益率波动原理根据该波动区间计算一定周期滚动最高值与最小值的寻找,最高值作为上管道, 最小值作为下管道,突破上管道,开仓。上下管道滚动平均值作为平仓线

我的理解:

现在的价格与过去一定周期的价格做对数对比,类似于自己与过去的自己做统计套利,认为价差波动会在一定的振幅内理性震荡. 当超过最大的振幅时,则认为是一次有效的唐奇安通道突破. 当价格回归平均线附近(价差振幅波动回归正常),通道关闭,平仓离市。

免责声明

本代码为模板样例,别直接上实盘,别直接上实盘,别直接上实盘, okaygood. 本人能力有限,写出一些逻辑bug或者思想bug在所难免,由于代码bug等与本策略的代码编写问题带来任何损失恕概不负责!

代码输出

$.VixAlg = function(chart_obj_func, name, period, is_price_diff)

$.VixAlg 函数生成一个vix算法的handler,接下来只需要在handler里定时输入records, handler会自动计算 vix各个指标。通过交易函数IsOpenLong,IsOpenShort, IsCloseOpen, IsCloseShort 表达出来 @chart_obj_func: 画图类函数接口 @name: 给画的vix图起个名字 @period: 指数计算周期,默认VIX_N @is_price_diff: 目前没用

vix_alg.OnTick(records)

定时调用OnTick进行Vix相关指标更新 @ records: K线周期进行VIX算法计算

具体VIX交易相关函数见 $.DemoOnTick 用法

$.IsOpenLong, $.IsCloseLong $.IsOpenShort, $.IsCloseShort $.IsIdle, $.IsLong, $.IsShort

SetDirection 告诉handler当前切换到何种状态, 状态有$.IDLE, $.LONG, $.SHORT $.SetDirection

指标数据重载

this.BackupSetId = function(backup_id) 设置一个backupid, 该handler的相关指标数据会存储在backup_id的key数据库中。handler在每次 OnTick函数调用后会存储当前指标数据到数据库中

reload_json 为null, 程序会从数据库中读取上次存储的状态,一般该函数在程序重启后调用。 this.Reload = function(reload_json)

VIX相关指标数据输出参考 DemoStatusLog

vix_alg.GetTable() vix_alg.GetData()

一个带图的Demo

Demo: https://www.fmz.com/strategy/203386 JS 图表模板: https://www.fmz.com/strategy/48731

如果有bug

如果有逻辑上或者算法上的bug,希望能够告知,非常感谢!




/*backtest
start: 2020-03-01 00:00:00
end: 2020-04-25 00:00:00
period: 15m
basePeriod: 1m
exchanges: [{"eid":"Futures_BitMEX","currency":"XBT_USD"}]
args: [["LogLevel",5],["Amount",1000]]
*/

/* Changelog
 * 20200427
 * 增加reload功能
 * V0.1(20200423):
 *  衔接https://www.fmz.com/strategy/200131,作为模板使用,算法或有偏差
 *  具体细节请仔细瞅代码
 * */

/* 免责声明
 * 本代码为模板样例,别直接上实盘,别直接上实盘,别直接上实盘, okaygood.
 * 由于代码bug等与本策略的代码编写问题带来任何损失概不负责
 * */


$.LONG= 1; // 做多信号
$.SHORT = -1; // 做空信号
$.IDLE = 0; // 保持当前状态信号

// 数组最大长度
ARR_MAX = 2000;

// Critical Log, 出现该log时,整个程序将会退出
LOG_CRT = 0;
LOG_ERR = 1;
LOG_WARN = 2;
LOG_INFO = 3;
LOG_DA_DBG = 4;
LOG_WK_DBG = 5;

var RedColor = "#ff0000"; // 红色标记
var GreenColor = "#006600"; // 绿色标记
var YellowColor = "#FFA500"; // 橙色标记
var BlackColor = "#000000"
var BlueColor = "#0000FF"
var GreyColor = '#808080'
var LogLevelStr = ["LOG_CRT ", "LOG_ERR ", "LOG_WARN", "LOG_INFO ", "LOG_DA_DBG ", "LOG_WK_DBG "];
var LogColorStr = [RedColor, RedColor, YellowColor, GreenColor, "", ""];
function LogPush()
{
    var args = [].slice.call(arguments);
    var _LogLevel = args[0];

    if (_LogLevel <= LogLevel) {
        args[0] = LogLevelStr[_LogLevel];
        args.push(LogColorStr[_LogLevel]);
        if (_LogLevel == LOG_CRT) {
            //throw args;
            args.push("@");
            Log.apply(this, args);
        } else {
            Log.apply(this, args);
        }
    }
}

function EasyReadTime(millseconds) {
    if (typeof millseconds == 'undefined' ||
        !millseconds) {
        millseconds = new Date().getTime();
    }
    var newDate = new Date();
    newDate.setTime(millseconds);
    return newDate.toLocaleString();
}

function isNull(val) {
    if (typeof val == 'undefined' ||
        val == null) {
        return true;
    }
    return false;
}

function isFalse(value) {
    if (isNull(value) || !value) {
        return true;
    }
    return false
}

function isArrNull(val) {
    if (isNull(val)) {
        return true;
    }
    for (i=0;i < val.length; i++) {
        if (typeof (val[i]) == 'undefined' ||
            val[i] == null) {
            return true;
        }
    }
    return false;
}

//在同一个根K线上是否允许平仓后又开仓
var OpenSameK = false

function FakeSeriesObj() {
    this.AddData = function() {
    };
    this.AddKData = function() {
    };
    this.AddFData = function() {
    };
}

function FakeChartObj() {
    this.CreateSeries = function() {
        return new FakeSeriesObj()
    }
}

function create_fake_chart_obj() {
    return new FakeChartObj()
}

$.GetPairRecs = function(recs1, recs2, time_period, remove_last) {
    if (isNull(recs1) || isNull(recs2)) {
        return null;
    }
    len = Math.min(recs1.length, recs2.length)
    if (!isNull(time_period)) {
        if (time_period > len) {
            return null;
        }
    } else {
        time_period = len
    }
    start_index = len - time_period
    if (!isNull(remove_last) && remove_last) {
        end_index = len - 2
    } else {
        end_index = len - 1
    }
    if (recs1[start_index].Time != recs2[start_index].Time) {
        LogPush(LOG_DA_DBG, "开始时间不匹配")
        return null
    }

    if (recs1[end_index].Time != recs2[end_index].Time) {
        LogPush(LOG_DA_DBG, "结束时间不匹配")
        return null
    }

    return {
        "first": recs1.slice(start_index, end_index + 1),
        "second": recs2.slice(start_index, end_index + 1),
        "last_time": recs1[end_index].Time
    }

}

function _FN(v, precision) {
    if (typeof(precision) != 'number') {
        precision = 4;
    }
    if(isNull(v)) {
        return v;
    }
    var d = parseFloat(v.toFixed(Math.max(10, precision + 5)));
    s = d.toString().split(".");
    if (s.length < 2 || s[1].length <= precision) {
        return d;
    }

    var b = Math.pow(10, precision);
    return Math.floor(d * b + 0.01) / b;
}

function TradeStatusStr(signal) {
    if (signal == $.IDLE) {
        return "空闲中";
    }
    if (signal == $.LONG) {
        return "做多中";
    }
    if (signal == $.SHORT) {
        return "做空中";
    }
    return "无效状态";
}

function ArrHighest(arr, period) {
    if (isNull(arr)) {
        return null;
    }
    var high = null;
    var start_index = arr.length - Math.min(arr.length, period)

    for (i = start_index; i < arr.length; i++) {
        if (isNull(arr[i])) {
            continue;
        }
        if (isNull(high) || arr[i] > high) {
            high = arr[i];
        }
    }
    return high;
}

function ArrLowest(arr, period) {
    if (isNull(arr)) {
        return null;
    }
    var low = null;
    var start_index = arr.length - Math.min(arr.length, period)

    for (i = start_index; i < arr.length; i++) {
        if (isNull(arr[i])) {
            continue;
        }
        if (isNull(low) || arr[i] < low) {
            low = arr[i];
        }
    }
    return low;
}

function VixChart(chart_obj_func, name) {
    if (typeof chart_obj_func == "function") {
        this._obj = chart_obj_func(name)
    } else {
        this._obj = chart_obj_func
    }
    this._k = this._obj.CreateSeries("K线图", 'candlestick', true, true);
    this._up = this._obj.CreateSeries("上轨线", 'spline', null, false);
    this._ma = this._obj.CreateSeries("中线", 'spline', null, false);
    this._dw = this._obj.CreateSeries("下轨线", 'spline', null, false);
    this._arr = this._obj.CreateSeries("实时线", 'spline', null, false, true);
    this._f = this._obj.CreateSeries("标签", "flags", null, false);

    this.AddKline = function(record) {
        this._k.AddKData(record.Time, record.Close, record.High, record.Low,
                         record.Open)
    }

    this.AddIndex = function(up, ma, dw, time) {
        if (isArrNull([up, ma, dw, time])) {
            return;
        }
        this._up.AddData(_FN(up, 5), time)
        this._ma.AddData(_FN(ma, 5), time)
        this._dw.AddData(_FN(dw, 5), time)
    }

    this.AddArr = function(arr, time) {
        if (isNull(arr)) {
            retrun;
        }
        this._arr.AddData(_FN(arr, 5), time)
    }

    this.AddBuy = function(message, time) {
        this._f.AddFData(time, "Buy", message)
    }

    this.AddCloseBuy = function(message, time) {
        this._f.AddFData(time, "CloseBuy", message)
    }

    this.AddSell = function(message, time) {
        this._f.AddFData(time, "Sell", message);
    }

    this.AddCloseSell = function(message, time) {
        this._f.AddFData(time, "CloseSell", message);
    }
}

function _VixAlg(chart_obj_func, name, period, is_price_diff) {
    if (isNull(chart_obj_func)) {
        chart_obj_func = create_fake_chart_obj
    }
    if (isNull(period)) {
        period = VIX_N;
    }
    if (period >= ARR_MAX) {
        throw "Are you kidding me?"
    }
    this._period = period;
    /*K线基时*/
    this._time_k = 0;
    /*上次开仓K线基时*/
    this._open_k = 0;
    this._is_price_diff = is_price_diff;

    /*波动率图*/
    this._chart = new VixChart(chart_obj_func, name);

    /*波动率指标*/
    this._arr = [];
    this._arr_now = null;
    this._ma = null;
    this._ma_up = null;
    this._ma_dw = null;

    /* N轮K线前base价格*/
    this._base_price = null;
    this._now_price = null;
    this._time_now = 0;

    /*开平仓数据*/
    this._signal = $.IDLE
    this._open_price = null;
    this._open_asset_per = 1.0;

    /*策略评估数据*/
    this._n_long = 0.0;
    this._n_long_win = 0.0;
    // 盈利率
    this._long_profit = 1.0;

    this._n_short = 0.0;
    this._n_short_win = 0.0;
    this._short_profit = 1.0;

    this.GetChartObj = function() {
        this._chart._obj;
    }

    this._BackupId = null;

    this.BackupSetId = function(backup_id) {
        this._BackupId = backup_id + "_MaAlg";
    };

    this.Backup = function() {
        if (this._BackupId) {
            _G(this._BackupId, this);
        }
    };

	this.Reload = function(reload_json) {
        if (isNull(reload_json)) {
            if (this._BackupId) {
                reload_json = _G(this._BackupId);
            }
            LogPush(LOG_INFO, this._BackupId, "reload json from local db: ", reload_json);
        }

        if (isNull(reload_json)) {
            LogPush(LOG_ERR, "push error as reload_json is null");
            return;
        }

        LogPush(LOG_INFO, this._BackupId, "signal: ", reload_json._signal);
        /* 当前状态 */
        this._signal = reload_json._signal;
        this._time_k = reload_json._time_k
        this._open_k = reload_json._open_k
        this._arr = reload_json._arr
        this._arr_now = reload_json._arr_now
        this._ma = reload_json._ma
        this._ma_up = reload_json._ma_up
        this._ma_dw = reload_json._ma_dw

        this._base_price = reload_json._base_price
        this._now_price = reload_json._now_price
        this._time_now = reload_json._time_now

        this._open_price = reload_json._open_price
        this._open_asset_per = reload_json._open_asset_per
        this._n_long = reload_json._n_long
        this._n_long_win = reload_json._n_long_win
        this._long_profit = reload_json._long_profit

        this._n_short = reload_json._n_short
        this._n_short_win = reload_json._n_short_win
        this._short_profit = reload_json._short_profit
    };

    //TODO 返回正确的分钟线, 暂时返回60分钟
    this.GetNewKline = function () {
        return NewKMin;
    };

    this.GetPriceDiff = function(now_price, pre_price) {
        return Math.log(now_price) / Math.log(pre_price) - 1.0;
    }

    this.OnNewPrice = function(now_rec, time) {
        now_price = now_rec.Close
        if (isNull(time)) {
            time = now_rec.Time
        }
        if (isNull(this._base_price)) {
            return;
        }
        this._time_now = time;
        this._now_price = now_price;
        this._arr_now = this.GetPriceDiff(this._now_price, this._base_price)
        this._chart.AddArr(this._arr_now, time)
        //this._chart.AddKline(now_rec)
    }
    
    /*包含最新K线*/
    this.OnNewRecord = function(recs) {
        if (isNull(recs)) {
            LogPush(LOG_DA_DBG, "recs is null");
            return;
        }
        if (recs.length - 2 - this._period < 0) {
            LogPush(LOG_DA_DBG, "recs 长度 ", recs.length, "不够", this._period)
            return;
        }

        last_rec = recs[recs.length - 1];
        if (isNull(last_rec) || isNull(last_rec.Time) || isNull(last_rec.Close)) {
            LogPush(LOG_ERR, "最新K线有误", last_rec);
        }

        base_rec = recs[recs.length - 1 - this._period];
        if (isNull(base_rec) || isNull(base_rec.Time) || isNull(base_rec.Close)) {
            LogPush(LOG_ERR, "baseK线有误", base_rec);
        }
        /*非最新K线返回*/
        if (!isNull(this._time_k) && this._time_k >= last_rec.Time) {
            return;
        }

        now_rec = recs[recs.length - 2];
        pre_rec = recs[recs.length - 2 - this._period];
        if (isNull(now_rec) || isNull(pre_rec) ||
            isNull(now_rec.Close) || isNull(pre_rec.Close)) {
            LogPush(LOG_ERR, "数据有误", now_rec, pre_rec)
        }

        var price_diff = this.GetPriceDiff(now_rec.Close, pre_rec.Close)
        this._arr.push(price_diff)
        this._time_k = last_rec.Time;

        if (this._arr.length > ARR_MAX) {
            this._arr.shift()
        }

        if (this._arr.length <= this._period) {
            LogPush(LOG_DA_DBG, "收集数据不够");
            return;
        }
        vix_ma = TA.MA(this._arr, this._period);
        this._ma = vix_ma[vix_ma.length - 1]
        this._ma_up = ArrHighest(this._arr, this._period)
        this._ma_dw = ArrLowest(this._arr, this._period)

        this._chart.AddKline(now_rec)
        this._chart.AddIndex(this._ma_up, this._ma, this._ma_dw, now_rec.Time)

        this._base_price = base_rec.Close;

        this.OnNewPrice(last_rec, now_rec.Time);
    }

    this.OnTick = function(recs) {
        if (isNull(recs)) {
            return;
        }
        if (recs.length <= this._period + 2) {
            LogPush(LOG_ERR, "K线长度不足,算法无法进行计算", recs.length, this._period);
            return;
        }

        if (isNull(this._time_k) || !this._time_k) {
            start_index = this._period + 2;
            while (start_index < recs.length) {
                var new_recs = recs.slice(0, start_index)
                this.OnNewRecord(new_recs)
                start_index += 1;
            }
        } else {
            this.OnNewRecord(recs);
            //OpenMode	即时开仓|K线完成开仓
            //if (OpenMode === 0) {
            //    this.OnNewPrice(recs[recs.length - 1], new Date().getTime());
            //}
        }
        this.Backup();
    }

    this.IsLong = function() {
        return this._signal == $.LONG
    }

    this.IsShort = function() {
        return this._signal == $.SHORT
    }

    this.IsIdle = function() {
        return this._signal == $.IDLE
    }

    this.IsOpenLong = function() {
        if (!this.IsIdle() || isNull(this._arr_now) ||
            isNull(this._ma_up)) {
            return false;
        }

        /*波动突破上轨*/
        if (this._arr_now > this._ma_up) {
            if (!OpenSameK && this._open_k == this._time_k) {
                LogPush(LOG_INFO, "不允许开仓平仓后又在同一K线上开仓")
                return false;
            }

            LogPush(LOG_INFO, "当前比较价格:", this._now_price, ", 真实价格差: ",
                    _FN(this._now_price - this._base_price, 3), ", 波动价格差:",
                    _FN(this._arr_now, 5), ", 突破上轨: ", _FN(this._ma_up, 5),
                    ", 可以做多")
            return true;
        }
        return false;
    }

    this.IsOpenShort = function() {
        if (!this.IsIdle() || isNull(this._arr_now) ||
            isNull(this._ma_dw)) {
            return false;
        }

        /*波动突破上轨*/
        if (this._arr_now < this._ma_dw) {
            if (!OpenSameK && this._open_k == this._time_k) {
                LogPush(LOG_INFO, "不允许开仓平仓后又在同一K线上开仓")
                return false;
            }
            LogPush(LOG_INFO, "当前比较价格:", this._now_price, ", 真实价格差: ",
                    _FN(this._now_price - this._base_price, 3), ", 波动价格差:",
                    _FN(this._arr_now, 5), ", 突破下轨: ", _FN(this._ma_dw, 5),
                    ", 可以做空")
            return true;
        }
        return false;
    }

    this.IsCloseLong = function() {
        if (!this.IsLong()) {
            return false;
        }

        if (this._arr_now < this._ma) {
            LogPush(LOG_INFO, "当前比较价格:", this._now_price, ", 真实价格差: ",
                    this._now_price - this._base_price, ", 波动价格差:",
                    _FN(this._arr_now, 5), ", 下破中线: ", _FN(this._ma, 5),
                    ", 可以平多")
            return true;
        }
        return false;
    }

    this.IsCloseShort = function() {
        if (!this.IsShort()) {
            return false;
        }

        if (this._arr_now > this._ma) {
            LogPush(LOG_INFO, "当前比较价格:", this._now_price, ", 真实价格差: ",
                    this._now_price - this._base_price, ", 波动价格差:",
                    _FN(this._arr_now, 5), ", 上破中线: ", _FN(this._ma, 5),
                    ", 可以平空")
            return true;
        }
        return false;
    }

    this.SetDirection = function(next_signal, price, sheet) {
        message = "价格: " + _FN(price, 3) + ", 张: " + _FN(sheet, 3)
        if (!this.IsIdle() && this._signal != next_signal) {
            var earn_price_diff = price - this._open_price;
            if (this.IsShort()) {
                earn_price_diff = -earn_price_diff
            }

            Log(TradeStatusStr(this._signal), "价格:", this._open_price,
                "平仓价格:", price, "张数: ", sheet, ", 交易",
                earn_price_diff > 0 ? "胜利" + RedColor : "失败" + GreyColor);
            if (this.IsLong()) {
                this._n_long += 1.0;
                if (earn_price_diff > 0) {
                    this._n_long_win += 1.0;
                }
                this._chart.AddCloseBuy(message, this._time_k)
            } else {
                this._n_short += 1.0;
                if (earn_price_diff > 0) {
                    this._n_short_win += 1.0;
                }
                this._chart.AddCloseSell(message, this._time_k)
            }
            this._signal = $.IDLE;
            this._open_price = null;
        }

        if (next_signal != $.IDLE) {
            this._open_price = price;
            this._signal = next_signal;
            if (this.IsLong()) {
                Log("开多仓,开仓价格: ", this._open_price)
                this._chart.AddBuy(message, this._time_k)
            } else {
                Log("开空仓,开仓价格: ", this._open_price)
                this._chart.AddSell(message, this._time_k)
            }
            this._open_k = this._time_k;
        }
    }

    this.GetData = function() {
        return {
            "time_now": this._time_now,
            "now_price": this._now_price,
            "signal": this._signal,
            "open_price": this._open_price,

            "time_k": this._time_k,
            "base_price": this._base_price,
            "ma": this._ma,
            "ma_up": this._ma_up,
            "ma_dw": this._ma_dw,
            "arr_now": this._arr_now,
            "period": this._period,

            "n_long": this._n_long,
            "n_long_win": this._n_long_win,
            "long_profit": this._long_profit,

            "n_short": this._n_short,
            "n_short_win": this._n_short_win,
            "short_profit": this._short_profit,
        }
    }

    /* ["当前状态", "算法指标", "做多统计", "做空统计"]
     * */
    this.GetTable = function() {
        now_str = ""
        index_str = ""
        long_str = ""
        short_str = ""

        now_str += "最近比较时间: " + EasyReadTime(this._time_now)
        now_str += ", 最近比较价格: " + this._now_price
        now_str += ", 当前状态: " + TradeStatusStr(this._signal) 
        if (this.IsLong()) {
            now_str += ", 做多价格: " + this._open_price
        }
        if (this.IsShort()) {
            now_str += ", 做空价格: " + this._open_price
        }

        index_str += "周期基线: " + EasyReadTime(this._time_k)
        index_str += ", 价格基线: " + this._base_price
        index_str += ", MA 中线: " + _FN(this._ma, 5)
        index_str += ", MA 上轨: " + _FN(this._ma_up, 5)
        index_str += ", MA下轨: " + _FN(this._ma_dw, 5)
        index_str += ", 当前指标: " + _FN(this._arr_now, 5)
        index_str += ", 周线: " + this._period

        long_str += "做多次数: " + this._n_long
        if (this._n_long) {
            long_str += ", 做多胜率: " + _FN(this._n_long_win / this._n_long * 100, 2) + "%"
        }

        short_str += "做空次数: " + this._n_short
        if (this._n_short) {
            short_str += ", 做空胜率: " + _FN(this._n_short_win / this._n_short * 100, 2) + "%"
        }

        return [now_str, index_str, long_str, short_str]
    }

    this.StatusLog = function() {
        row = this.GetTable()
        return [row[0], row[2], row[3]].join("\n")
    }

    this.GetParms = function() {
        row = this.GetTable()
        return row[1]
    }
}

$.VixAlg = function(chart_obj_func, name, period, is_price_diff) {
    return new _VixAlg(chart_obj_func, name, period, is_price_diff);
}

function DemoStatusLog(vix_alg) {
    var rows = []
    rows.push(vix_alg.GetTable())
    table = {
        type: 'table',
        title: "Vix表",
        cols: ["当前状态", "算法指标", "做多统计", "做空统计"],
        rows: rows
    }
    status_str = "\n`" + JSON.stringify([table]) + "`"
    LogStatus(status_str)
}

$.DemoOnTick = function(vix_alg, records) {
    vix_alg.OnTick(records)

    ticker = _C(exchange.GetTicker)
    //平多仓
    if (vix_alg.IsCloseLong()) {
        exchange.SetDirection("closebuy")
        exchange.Sell(ticker.Buy, Amount)
        vix_alg.SetDirection($.IDLE, ticker.Buy)
    }

    //平空仓
    if (vix_alg.IsCloseShort()) {
        exchange.SetDirection("closesell")
        exchange.Buy(ticker.Sell, Amount)
        vix_alg.SetDirection($.IDLE, ticker.Sell)
    }

    //开多仓
    if (vix_alg.IsOpenLong()) {
        exchange.SetDirection("buy")
        exchange.Buy(ticker.Sell, Amount)
        vix_alg.SetDirection($.LONG, ticker.Sell)
    }

    //开空仓
    if (vix_alg.IsOpenShort()) {
        exchange.SetDirection("sell")
        exchange.Sell(ticker.Buy, Amount)
        vix_alg.SetDirection($.SHORT, ticker.Sell)
    }
    DemoStatusLog(vix_alg)
};

function main() {
    exchange.SetContractType('XBTUSD')
    var vix_alg = $.VixAlg(create_fake_chart_obj, "VIX_" + VIX_N, VIX_N);
    while (true) {
        records = _C(exchange.GetRecords);
        $.DemoOnTick(vix_alg, records);
        Sleep(60 * 1000);
    }
}

More