Loading ...

数字货币交易类库 (期货支持OKCoin期货/BitVC, 支持$.CTA函数)

Author: 小小梦, Date: 2017-10-13 14:36:30
Tags: OKEX Tool

数字货币交易类库 (期货支持OKCoin期货/BitVC) 目前测试阶段,使用请留意,如有问题 ,请联系作者 ,十分感谢!

  • 新增了 $.CTA 函数

    可以用 $.CTA 函数 快速构建 均线等 类型的策略 使用模板的 $.CTA 函数 构建的 策略代码如下(很精简): 文章 参看 商品期货交易类库 的 $.CTA 函数 使用 (使用、架构是类似的) : https://zhuanlan.zhihu.com/p/30016518

function main() {
    $.CTA(exchanges[0], 0.01, function(r, mp, pair){         // 第一个参数 是 要做的 交易所对象, 第二个参数 0.01 是交易所 要求的 最小下单数量, 第三个 匿名函数 function(){...}
                                                                                        // 是 回调函数, 交易逻辑 就写在这个函数中, 该回调函数 第一个参数 r 接收最新的 K线数据, 第二个参数 接收 持仓数, 第
                                                                                        // 三个参数 接收 交易对 名称
        if (r.length < 20) {                                                     // 判断 K线柱数量 
            return
        }
        var emaSlow = TA.EMA(r, 20)
        var emaFast = TA.EMA(r, 5)
        var cross = _Cross(emaFast, emaSlow);                // 判断指标 相交状态,   _Cross 参看 : https://www.fmz.com/bbs-topic/1140
        if (mp <= 0 && cross > 1) {
            Log(pair, "买, 金叉周期", cross, "mp:", mp);
            return 0.1 * (mp < 0 ? 2 : 1)                                //  返回的数值 就是 要开仓的 数量, 正数是 开多 ,负数是 开空,   0 是 全部平掉。
        } else if (mp >= 0 && cross < -1) {
            Log(pair, "卖, 死叉周期", cross, "mp:", mp);
            return -0.1 * (mp > 0 ? 2 : 1)
        }
    })
}

img img img

  • 注意:

    • 使用期货 功能下单时:

      注意不要忘记 先设置 合约类型 :exchange.SetContractType(“this_week”) 否则会报错。
      var p = $.NewPositionManager()
      // this_week 即 当周合约 开多仓   ,  PositionManager.prototype.OpenLong = function(contractType, shares, price)
      exchange.SetContractType("this_week")
      p.OpenLong("this_week", 1, 1000)
      // 这样就 1000 美元价格  开多仓 合约 1张
      

// 现货部分
function CancelPendingOrders(e, orderType) {
    while (true) {
        var orders = e.GetOrders();
        if (!orders) {
            Sleep(RetryDelay);
            continue;
        }
        var processed = 0;
        for (var j = 0; j < orders.length; j++) {
            if (typeof(orderType) === 'number' && orders[j].Type !== orderType) {
                continue;
            }
            e.CancelOrder(orders[j].Id, orders[j]);
            processed++;
            if (j < (orders.length - 1)) {
                Sleep(RetryDelay);
            }
        }
        if (processed === 0) {
            break;
        }
    }
}

function GetAccount(e, waitFrozen) {
    if (typeof(waitFrozen) == 'undefined') {
        waitFrozen = false;
    }
    var account = null;
    var alreadyAlert = false;
    while (true) {
        account = _C(e.GetAccount);
        if (!waitFrozen || (account.FrozenStocks < _GetMinStocks && account.FrozenBalance < 0.01)) {
            break;
        }
        if (!alreadyAlert) {
            alreadyAlert = true;
            Log("发现账户有冻结的钱或币", account);
        }
        Sleep(RetryDelay);
    }
    return account;
}


function StripOrders(e, orderId) {
    var order = null;
    if (typeof(orderId) == 'undefined') {
        orderId = null;
    }
    while (true) {
        var dropped = 0;
        var orders = _C(e.GetOrders);
        for (var i = 0; i < orders.length; i++) {
            if (orders[i].Id == orderId) {
                order = orders[i];
            } else {
                var extra = "";
                if (orders[i].DealAmount > 0) {
                    extra = "成交: " + orders[i].DealAmount;
                } else {
                    extra = "未成交";
                }
                e.CancelOrder(orders[i].Id, orders[i].Type == ORDER_TYPE_BUY ? "买单" : "卖单", extra);
                dropped++;
            }
        }
        if (dropped === 0) {
            break;
        }
        Sleep(RetryDelay);
    }
    return order;
}

// mode = 0 : direct buy, 1 : buy as buy1
function Trade(e, tradeType, tradeAmount, mode, slidePrice, maxAmount, maxSpace, retryDelay) {
    var initAccount = GetAccount(e, true);
    var nowAccount = initAccount;
    var orderId = null;
    var prePrice = 0;
    var dealAmount = 0;
    var diffMoney = 0;
    var isFirst = true;
    var tradeFunc = tradeType == ORDER_TYPE_BUY ? e.Buy : e.Sell;
    var isBuy = tradeType == ORDER_TYPE_BUY;
    while (true) {
        var ticker = _C(e.GetTicker);
        var tradePrice = 0;
        if (isBuy) {
            tradePrice = _N((mode === 0 ? ticker.Sell : ticker.Buy) + slidePrice, 4);
        } else {
            tradePrice = _N((mode === 0 ? ticker.Buy : ticker.Sell) - slidePrice, 4);
        }
        if (!orderId) {
            if (isFirst) {
                isFirst = false;
            } else {
                nowAccount = GetAccount(e, true);
            }
            var doAmount = 0;
            if (isBuy) {
                diffMoney = _N(initAccount.Balance - nowAccount.Balance, 4);
                dealAmount = _N(nowAccount.Stocks - initAccount.Stocks, 8);                                                // 如果保留小数过少,会引起在小交易量交易时,计算出的成交价格误差较大。
                doAmount = Math.min(maxAmount, tradeAmount - dealAmount, _N((nowAccount.Balance * 0.95) / tradePrice, 4));
            } else {
                diffMoney = _N(nowAccount.Balance - initAccount.Balance, 4);
                dealAmount = _N(initAccount.Stocks - nowAccount.Stocks, 8);
                doAmount = Math.min(maxAmount, tradeAmount - dealAmount, nowAccount.Stocks);
            }
            if (doAmount < _GetMinStocks) {
                break;
            }
            prePrice = tradePrice;
            orderId = tradeFunc(tradePrice, doAmount, ticker);
            if (!orderId) {
                CancelPendingOrders(e, tradeType);
            }
        } else {
            if (mode === 0 || (Math.abs(tradePrice - prePrice) > maxSpace)) {
                orderId = null;
            }
            var order = StripOrders(e, orderId);
            if (!order) {
                orderId = null;
            }
        }
        Sleep(retryDelay);
    }

    if (dealAmount <= 0) {
        return null;
    }

    return {
        price: _N(diffMoney / dealAmount, 4),
        amount: dealAmount
    };
}

$.Buy = function(e, amount) {
    if (typeof(e) === 'number') {
        amount = e;
        e = exchange;
    }
    return Trade(e, ORDER_TYPE_BUY, amount, OpMode, SlidePrice, MaxAmount, MaxSpace, RetryDelay);
};

$.Sell = function(e, amount) {
    if (typeof(e) === 'number') {
        amount = e;
        e = exchange;
    }
    return Trade(e, ORDER_TYPE_SELL, amount, OpMode, SlidePrice, MaxAmount, MaxSpace, RetryDelay);
};

$.CancelPendingOrders = function(e, orderType) {
    if (typeof(orderType) === 'undefined') {
        if (typeof(e) === 'number') {
            orderType = e;
            e = exchange;
        } else if (typeof(e) === 'undefined') {
            e = exchange;
        }
    }
    return CancelPendingOrders(e, orderType);
};

$.GetAccount = function(e) {
    if (typeof(e) === 'undefined') {
        e = exchange;
    }
    return _C(e.GetAccount);
};

var _MACalcMethod = [TA.EMA, TA.MA, talib.KAMA][MAType];

// 返回上穿的周期数. 正数为上穿周数, 负数表示下穿的周数, 0指当前价格一样
$.Cross = function(a, b) {
    var crossNum = 0;
    var arr1 = [];
    var arr2 = [];
    if (Array.isArray(a)) {
        arr1 = a;
        arr2 = b;
    } else {
        var records = null;
        while (true) {
            records = exchange.GetRecords();
            if (records && records.length > a && records.length > b) {
                break;
            }
            Sleep(RetryDelay);
        }
        arr1 = _MACalcMethod(records, a);
        arr2 = _MACalcMethod(records, b);
    }
    if (arr1.length !== arr2.length) {
        throw "array length not equal";
    }
    for (var i = arr1.length-1; i >= 0; i--) {
        if (typeof(arr1[i]) !== 'number' || typeof(arr2[i]) !== 'number') {
            break;
        }
        if (arr1[i] < arr2[i]) {
            if (crossNum > 0) {
                break;
            }
            crossNum--;
        } else if (arr1[i] > arr2[i]) {
            if (crossNum < 0) {
                break;
            }
            crossNum++;
        } else {
            break;
        }
    }
    return crossNum;
};

// 期货部分
function GetPosition(e, contractType, direction) {
    var allCost = 0;
    var allAmount = 0;
    var allProfit = 0;
    var allFrozen = 0;
    var posMargin = 0;
    var positions = _C(e.GetPosition);
    for (var i = 0; i < positions.length; i++) {
        if (positions[i].ContractType == contractType &&
            (((positions[i].Type == PD_LONG) && direction == PD_LONG) || ((positions[i].Type == PD_SHORT) && direction == PD_SHORT))
        ) {
            posMargin = positions[i].MarginLevel;
            allCost += (positions[i].Price * positions[i].Amount);
            allAmount += positions[i].Amount;
            allProfit += positions[i].Profit;
            allFrozen += positions[i].FrozenAmount;
        }
    }
    if (allAmount === 0) {
        return null;
    }
    return {
        MarginLevel: posMargin,
        FrozenAmount: allFrozen,
        Price: _N(allCost / allAmount),
        Amount: allAmount,
        Profit: allProfit,
        Type: direction,
        ContractType: contractType
    };
}

function Open(e, contractType, direction, opAmount, price) {
    var initPosition = GetPosition(e, contractType, direction);
    var isFirst = true;
    var initAmount = initPosition ? initPosition.Amount : 0;
    var positionNow = initPosition;
    var step = 0;
    while (true) {
        var needOpen = opAmount;
        if (isFirst) {
            isFirst = false;
        } else {
            positionNow = GetPosition(e, contractType, direction);
            if (positionNow) {
                needOpen = opAmount - (positionNow.Amount - initAmount);
            }
        }
        if (needOpen < 1) {
            break;
        }
        if (step > max_open_lv) {
            break;
        }
        var amount = needOpen;
        e.SetDirection(direction == PD_LONG ? "buy" : "sell");
        var orderId;
        if (direction == PD_LONG) {
            orderId = e.Buy(price + F_SlidePrice * (1 + step), amount, "开多仓", contractType, price);
        } else {
            orderId = e.Sell(price - F_SlidePrice * (1 + step), amount, "开空仓", contractType, price);
        }
        while (true) {
            var orders = _C(e.GetOrders);
            if (orders.length === 0) {
                break;
            }
            Sleep(Interval);
            for (var j = 0; j < orders.length; j++) {
                e.CancelOrder(orders[j].Id);
                if (j < (orders.length - 1)) {
                    Sleep(Interval);
                }
            }
        }
        step += lv;
    }
    var ret = {
        price: 0,
        amount: 0,
        position: positionNow
    };
    if (!positionNow) {
        return ret;
    }
    if (!initPosition) {
        ret.price = positionNow.Price;
        ret.amount = positionNow.Amount;
    } else {
        ret.amount = positionNow.Amount - initPosition.Amount;
        ret.price = _N(((positionNow.Price * positionNow.Amount) - (initPosition.Price * initPosition.Amount)) / ret.amount);
    }
    return ret;
}

function Cover(e, contractType, price, OP_amount, direction) {
    var initP = null;
    var positions = null;
    var isFirst = true;
    var ID = null;
    var step = 0;
    var index = 0;
    while (true) {
        var n = 0;
        positions = _C(e.GetPosition);
        if (isFirst === true) {
            if (typeof(direction) === 'undefined' && positions.length > 1 || (direction !== PD_LONG && direction !== PD_SHORT && typeof(direction) !== 'undefined')) {
                throw "有多,空双向持仓,并且参数direction未明确方向!或 direction 参数异常:" + direction;
            }
            initP = positions;
            isFirst = false;
        }
        for (var i = 0; i < positions.length; i++) {
            if (positions[i].ContractType != contractType || (positions[i].Type !== direction && typeof(direction) !== 'undefined')) {
                continue;
            }
            var amount = 0;
            if (typeof(OP_amount) === 'undefined') {
                amount = positions[i].Amount;
            } else {
                amount = OP_amount - (initP[i].Amount - positions[i].Amount);
            }

            if (amount === 0) {
                continue;
            }
            if (positions[i].Type == PD_LONG) {
                e.SetDirection("closebuy");
                ID = e.Sell(price - F_SlidePrice * (1 + step), amount, "平多仓", contractType, price);
                n++;
            } else if (positions[i].Type == PD_SHORT) {
                e.SetDirection("closesell");
                ID = e.Buy(price + F_SlidePrice * (1 + step), amount, "平空仓", contractType, price);
                n++;
            }
            index = i;
        }
        if (n === 0) {
            break;
        }
        Sleep(Interval);
        if (typeof(ID) !== 'number') {
            Log("ID:", ID);
            continue;
        }

        e.CancelOrder(ID);
        step += lv;
        if (step > max_cover_lv) {
            break;
        }
    }

    var nowP = _C(e.GetPosition);
    if (!nowP[index] || nowP[index].Type !== initP[index].Type) {
        return initP.length === 0 ? 0 : initP[index].Amount;
    } else {
        var diff = initP[index].Amount - nowP[index].Amount;
        return diff;
    }
}

var PositionManager = (function() {
    function PositionManager(e) {
        if (typeof(e) === 'undefined') {
            e = exchange;
        }
        if (e.GetName() !== 'Futures_OKCoin' && e.GetName() !== 'Futures_BitVC') {
            throw 'Only support Futures_OKCoin & Futures_BitVC';
        }
        this.e = e;
        this.account = null;
    }
    PositionManager.prototype.GetAccount = function() {
        return _C(this.e.GetAccount);
    };

    PositionManager.prototype.OpenLong = function(contractType, shares, price) {
        if (!this.account) {
            this.account = _C(exchange.GetAccount);
        }
        return Open(this.e, contractType, PD_LONG, shares, price);
    };

    PositionManager.prototype.OpenShort = function(contractType, shares, price) {
        if (!this.account) {
            this.account = _C(exchange.GetAccount);
        }
        return Open(this.e, contractType, PD_SHORT, shares, price);
    };

    PositionManager.prototype.Cover = function(contractType, price, OP_amount, direction) {
        if (!this.account) {
            this.account = _C(exchange.GetAccount);
        }
        return Cover(this.e, contractType, price, OP_amount, direction);
    };

    PositionManager.prototype.Profit = function(contractType) {
        var accountNow = _C(this.e.GetAccount);
        Log("NOW:", accountNow, "--account:", this.account);
        return _N(accountNow.Balance - this.account.Balance);
    };

    return PositionManager;
})();

$.NewPositionManager = function(e) {
    return new PositionManager(e);
};

// $.CTA  函数
$.CTA = function(Exchange, MinStock, onTick, interval){
    if(typeof(interval) !== "number"){
        interval = 500
    }
    
    var lastUpdate = 0
    var e = Exchange
    var pair = e.GetCurrency()
    var hold = 0
    var tradeInfo = null
    var initAccount = _C(e.GetAccount)
    var nowAccount = initAccount
    
    var CTAshowTable = function(r){
        var tbl = {
            type : "table", 
            title : "策略信息,交易对" + pair,
            cols : ["变量", "值"],
            rows : [],
        }
        tbl.rows.push(["初始账户:", initAccount])
        tbl.rows.push(["当前账户:", nowAccount])
        tbl.rows.push(["上次交易信息:", tradeInfo])
        tbl.rows.push(["持仓:", hold])
        tbl.rows.push(["最新K线柱:", r[r.length - 1]])
        tbl.rows.push(["Bar 数量:", r.length])
        LogStatus(_D() + '\n`' + JSON.stringify([tbl]) + '`')
    }

    Log("$.CTA 初始化 完成。")

    while(true){
        var ts = new Date().getTime()

        var r = e.GetRecords()
        if(!r || r.length == 0){
            continue
        }
        
        hold = nowAccount.Stocks - initAccount.Stocks
        if(Math.abs(hold) < MinStock){
            hold = 0
        }
        var n = onTick(r, hold, pair)
        var callBack = null
        if (typeof(n) == 'object' && typeof(n.length) == 'number' && n.length > 1) {
            if (typeof(n[1]) == 'function') {
                callBack = n[1]
            }
            n = n[0]
        }
        if(typeof(n) !== "number"){
            if(isCTAshowTable){
                CTAshowTable(r)
            }
            continue
        }

        if(n > 0){             // buy
            if(hold < 0){
                Log("平仓")     // 测试
                tradeInfo = $.Buy(e, Math.min(-hold, n))
                if(typeof(callBack) == 'function'){
                    callBack(tradeInfo)
                }
                n += hold
            }
            if(n > 0){
                Log("开仓 或 反手")     // 测试
                tradeInfo = $.Buy(e, n)
                if(typeof(callBack) == 'function'){
                    callBack(tradeInfo)
                }
            }
            nowAccount = _C(e.GetAccount)
        }else if(n < 0){       // sell
            if(hold > 0){
                Log("平仓")     // 测试
                tradeInfo = $.Sell(e, Math.min(hold, -n))
                if(typeof(callBack) == 'function'){
                    callBack(tradeInfo)
                }
                n += hold
            }
            if(n < 0){
                Log("开仓 或 反手")     // 测试
                tradeInfo = $.Sell(e, -n)
                if(typeof(callBack) == 'function'){
                    callBack(tradeInfo)
                }
            }
            nowAccount = _C(e.GetAccount)
        }else{                 // keep balance
            // nowAccount = _C(e.GetAccount)
            if(hold > 0){
                tradeInfo = $.Sell(e, hold)
                if(typeof(callBack) == 'function'){
                    callBack(tradeInfo)
                }
                nowAccount = _C(e.GetAccount)
            }else if(hold < 0){
                tradeInfo = $.Buy(e, -hold)
                if(typeof(callBack) == 'function'){
                    callBack(tradeInfo)
                }
                nowAccount = _C(e.GetAccount)
            }
        }
        
        if(isCTAshowTable){
            CTAshowTable(r)
        }
        
        Sleep(interval)
    }
}



// 测试代码
/*
2017.5.1 - 2017.10.11 ,  K线 天, 模拟级别, OK国际 , BTC 
*/
function main() {
    $.CTA(exchanges[0], 0.01, function(r, mp, pair){   // $.CTA = function(Exchange, MinStock, onTick, interval)
        // Log(r.length)   // 测试
        if (r.length < 20) {
            return
        }
        var emaSlow = TA.EMA(r, 20)
        var emaFast = TA.EMA(r, 5)
        var cross = _Cross(emaFast, emaSlow);

        if (mp <= 0 && cross > 1) {
            Log(pair, "买, 金叉周期", cross, "mp:", mp);
            return 0.1 * (mp < 0 ? 2 : 1)
        } else if (mp >= 0 && cross < -1) {
            Log(pair, "卖, 死叉周期", cross, "mp:", mp);
            return -0.1 * (mp > 0 ? 2 : 1)
        }
    })
}


Related

More

改革春风吹满地 请问,在平仓操作里的 e.CancelOrder(ID); step += lv; if (step > max_cover_lv) { break; } 这段是什么意思?为什么执行CancelOrder?

Chace 梦大,请问下图数据,是指合约张数吗?如果我进行期货买卖,能直接用币的数量进行交易吗? https://dn-filebox.qbox.me/093dcd0f3836077f3dde77d743310b45258c8eaa.png

guigui17f 报个BUG,378行 if (!nowP[index] || nowP[index].Type !== initP[index].Type) 当 initP.length == 0 时将因对 initP[0].Type 的引用而报错,应将对 initP 长度的判断提前。

bix29 期货可以直接调用 .cta 函数么

Loga 梦总666

小小梦 哦 这个错误 不影响什么 , 可能是交易所 接口反应 慢了

改革春风吹满地 我每次调用Cover函数运行到这里的时候,都会爆这个错。 Futures_OKCoin 错误 order already cancelled or not found https://dn-filebox.qbox.me/d4740db8759d0ca0bf0a782135f6e7e804a3c44c.jpg

小小梦 取消 平仓单,追单 超出 最大 范围了。

小小梦 不客气 ^^ , 我也再检查一下 这个 模板, 这个是很早写的 ,当时参照商品期货 模板写的, 设计上不是很好。

guigui17f 刚用上电脑……我大概清楚了,正常情况下如果 initP 为空那么 nowP 肯定也是空,所以可以保证不会出错。我那次报错应该是交易所返回数据异常,第一次获取仓位给了空,第二次给了实际数量,所以才会触发我一开始说的那个错误。我自己加个异常检测吧,谢谢梦总了。

小小梦 好的 我 测试下。 ``` // 测试代码 /* 2017.5.1 - 2017.10.11 , K线 天, 模拟级别, OK国际 , BTC */ function main() { var q = $.NewPositionManager() var ret = q.Cover() Log(ret) } ``` 我是这样测试的, 在没有持仓的时候调用 Cover 函数。 显示 : https://dn-filebox.qbox.me/c6fafdc01baad5502c9c6366bc37e61ccd114317.png

guigui17f 你的意思是在调用cover之前先判断一下吗?我是实盘报错了之后才报的bug,你可以模拟盘测一下看空仓的时候cover会不会出错,我现在在火车上一时碰不了电脑……

Chace 梦大大,合约张数,如何转为币的数量呢?这样才好做仓位管理

小小梦 initP 是初始持仓, 这个 Cover 函数 前面 判断了 , 如果初始 没有持仓 ,即 initP.length == 0 会提前跳出, nowP 也是一个 空数组 [] , !nowP[index] 就是 真, 也是不会判断后面的。

小小梦 不是 , 这个带小数的 是币数, OKEX 期货交易 是合约张数, 并且 OKEX 期货下单 只能是 合约张数 (交易所接口限定的。)

guigui17f !nowP[index] 判断的是 nowP,我说的是 initP

小小梦 !nowP[index] 这个 可以 保障一下,一般 || (或)判断 在第一个成立时 , 就不去判断下一个表达式了,记得是这个样子。

小小梦 这个 模板 本身是 现货 交易模板, 我扩展了 一个 期货下单功能,最近一次更新 增加了 现货CTA 功能。

bix29 有点疑问,$.CTA 函数本来不就是期货类库的吗,咋用它做现货而不做期货呢

小小梦 $.CTA 函数 是 现货的,暂时 不支持 期货。