mylanguage core


Created on: 2021-10-22 14:56:00 Modified on: 2026-01-08 16:17:13
Copy: 0 Number of hits: 0
avatar of 扫地僧 扫地僧
1
Follow
270
Followers

此策略为麦语言交易策略的低层交易类库具体实现 开源在于让一些想了解低层工作原理的程序员更好的理解麦语言的交易逻辑

Strategy source code


let NewGUI = function(name, period) {
    let chart = Chart({__isCandle: true})
    let preTime = _G("preTime")
    if (!preTime) {
        preTime = 0
    }
    if (!AutoRecover) {
        chart.reset();
        preTime = 0
    }
    return {
        feed: function(bar, runtime) {
            if ((bar.Time < preTime) || (AutoRecover && bar.Time <= preTime && RunMode == 0)) {
                return;
            }
            let obj = {
            timestamp: bar.Time,
            open: bar.Open,
            high: bar.High,
            low: bar.Low,
            close: bar.Close,
            volume: bar.Volume,
            }
            if (runtime) {
                obj.runtime = runtime
            }
            if (bar.Time == preTime) {
                chart.add(0, obj, -1)
            } else {
                chart.add(0, obj)
            }
            preTime = obj.timestamp;
            _G("preTime", obj.timestamp);
        }
    }
}



let OpMode = 0;
let MaxSpace = 0.5;
let _depth = null; // only for backtest

function TrySleep(n) {
    if (IsVirtual()) {
        return;
    }
    Sleep(n);
}

function isFuturesLaw(s) {
    return s == 'Futures_CTP' || s == 'Futures_Esunny' || s == 'Futures_XTP' || s == 'Futures_Futu';
}

function isFuturesCrypto(s) {
    return s.indexOf("Futures_") == 0 && !isFuturesLaw(s);
}

function SafeRetry(method) {
    let r;
    let isShow = false;
    while (!(r = method.apply(this, Array.prototype.slice.call(arguments).slice(1)))) {
        if (isFuturesLaw(exchange.GetName())) {
            while (!exchange.IO("status")) {
                if (!isShow) {
                    isShow = true;
                    Log("正在等待与期货服务器连接....");
                }
                // sleep always
                Sleep(500);
            }
        }
        TrySleep(1000);
    }
    if (isShow) {
        Log("连接成功...");
    }
    return r;
}


function TryDepth(e) {
    if (IsVirtual() && _depth) {
        return _depth;
    }
    _depth = SafeRetry(e.GetDepth);
    return _depth;
}

function CancelPendingOrders(e, orderType) {
    while (true) {
        let orders = e.GetOrders();
        if (!orders) {
            TrySleep(RetryDelay);
            continue;
        }
        let processed = 0;
        for (let j = 0; j < orders.length; j++) {
            if (typeof(orderType) === 'number' && orders[j].Type !== orderType) {
                continue;
            }
            e.CancelOrder(orders[j].Id, orders[j]);
            _depth = null;
            processed++;
            if (j < (orders.length - 1)) {
                TrySleep(RetryDelay);
            }
        }
        if (processed === 0) {
            break;
        }
    }
}

function GetAccount(e, waitFrozen) {
    if (typeof(waitFrozen) == 'undefined') {
        waitFrozen = false;
    }
    let account = null;
    let alreadyAlert = false;
    while (true) {
        account = _C(e.GetAccount);
        if (!waitFrozen || (account.FrozenStocks < MinStock && account.FrozenBalance < 0.01)) {
            break;
        }
        if (!alreadyAlert) {
            alreadyAlert = true;
            Log(_T("发现账户有冻结的钱或币|Find Frozen Asset"), account);
        }
        TrySleep(RetryDelay);
    }
    // Hide AccountID
    if (typeof(account.Info) !== 'undefined' && typeof(account.Info.AccountID) !== 'undefined') {
        delete account.Info.AccountID;
    }
    account.Time = _D();
    return account;
}


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

function getDeepAmount(books, price, t) {
    let n = 0;
    for (let i = 0; i < books.length; i++) {
        if (i == 0 || (t == ORDER_TYPE_BUY && price >= books[i].Price) || (t == ORDER_TYPE_SELL && price <= books[i].Price)) {
            n += books[i].Amount;
        } else {
            break;
        }
    }
    return n;
}


// mode = 0 : direct buy, 1 : buy as buy1
function Trade(e, tradeType, tradeAmount, mode, slideTick, maxAmount, maxSpace, retryDelay) {
    let initAccount = GetAccount(e, true);
    let nowAccount = initAccount;
    let orderId = null;
    let prePrice = 0;
    let dealAmount = 0;
    let diffMoney = 0;
    let isFirst = true;
    let tradeFunc = tradeType == ORDER_TYPE_BUY ? e.Buy : e.Sell;
    let isBuy = tradeType == ORDER_TYPE_BUY;
    let slidePrice = Math.pow(10, -ZPrecision) * slideTick;
    let totalDealNet = 0
    let totalDealAmount = 0
    let fetchOrderFail = false
    while (true) {
        let depth = TryDepth(e);
        let depthSell = depth.Asks[0].Price;
        let depthBuy = depth.Bids[0].Price;
        let tradePrice = 0;
        if (isBuy) {
            tradePrice = _N((mode === 0 ? depthSell : depthBuy) + slidePrice, ZPrecision);
            if (tradePrice <= 0) {
                tradePrice = depth.Asks[0].Price;
            }
        } else {
            tradePrice = _N((mode === 0 ? depthBuy : depthSell) - slidePrice, ZPrecision);
            if (tradePrice <= 0) {
                tradePrice = depth.Bids[0].Price;
            }
        }
        if (!orderId) {
            if (isFirst) {
                isFirst = false;
            } else {
                nowAccount = GetAccount(e, true);
            }
            let doAmount = 0;
            if (isBuy) {
                diffMoney = _N(initAccount.Balance - nowAccount.Balance, ZPrecision);
                dealAmount = nowAccount.Stocks - initAccount.Stocks;
                doAmount = _N(Math.min(maxAmount, tradeAmount - dealAmount, nowAccount.Balance / (tradePrice * (1 + TradeFee))), XPrecision);
            } else {
                diffMoney = _N(nowAccount.Balance - initAccount.Balance, ZPrecision);
                dealAmount = initAccount.Stocks - nowAccount.Stocks;
                doAmount = _N(Math.min(maxAmount, tradeAmount - dealAmount, nowAccount.Stocks), XPrecision);
            }
            if (doAmount < MinStock) {
                break;
            }
            
            let deepAmount = _N(getDeepAmount((isBuy ? depth.Asks : depth.Bids), tradePrice, isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL), XPrecision);
            // spot market Amount may less than MinStock
            if (deepAmount > 0) {
                doAmount = Math.min(doAmount, deepAmount)
            }
            
            prePrice = tradePrice;
            orderId = tradeFunc(tradePrice, doAmount/*, "Depth", depth[tradeType == ORDER_TYPE_SELL ? "Asks" : "Bids"][0]*/)
            if (!orderId) {
                CancelPendingOrders(e, tradeType);
            }
        } else {
            let calcOrderId = orderId
            if (mode === 0 || (Math.abs(tradePrice - prePrice) > maxSpace)) {
                orderId = null;
            }
            let order = StripOrders(e, orderId);
            if (!order) {
                orderId = null;
            }
            // order complete or cancelled
            if (!orderId && !fetchOrderFail) {
                let historyOrder = null
                for (let i = 0; i < 10; i++) {
                    historyOrder = exchange.GetOrder(calcOrderId)
                    if (historyOrder) {
                        break
                    } else {
                        let lastErr = GetLastError()
                        if (lastErr && lastErr.toLowerCase().indexOf("not support") != -1) {
                            break
                        }
                        Log("Retry GetOrder", calcOrderId)
                        Sleep(1000)
                    }
                }
                if (historyOrder) {
                    if (historyOrder.AvgPrice > 0 && historyOrder.DealAmount > 0) {
                        totalDealNet += historyOrder.AvgPrice * historyOrder.DealAmount
                        totalDealAmount += historyOrder.DealAmount
                    }
                } else {
                    fetchOrderFail = true
                }
            }
        }
        TrySleep(SyncDelay);
    }

    // adjust diffMoney for calc real price
    if (totalDealNet > 0 && !fetchOrderFail) {
        diffMoney = totalDealNet
        dealAmount = totalDealAmount
    }

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

    return {
        direction: tradeType == ORDER_TYPE_BUY ? PD_LONG : PD_SHORT,
        price: _N(diffMoney / dealAmount, ZPrecision),
        amount: _N(dealAmount, XPrecision),
        account: nowAccount,
    };
}

function NewFuturesTrader(e, symbol) {
    let arr = symbol.split('/');
    if (arr.length == 2 && arr[1].length > 0) {
        // 使用映射合约
        symbol = arr[1];
    }

    function GetPosition(e, contractType, direction, positions) {
        let allCost = 0;
        let allAmount = 0;
        let allProfit = 0;
        let allFrozen = 0;
        let allMargin = 0;
        let posMargin = 0;
        if (typeof(positions) === 'undefined' || !positions) {
            positions = SafeRetry(e.GetPosition);
        }
        if (typeof(direction) === 'undefined') {
            for (let i = 0; i < positions.length; i++) {
                if (positions[i].ContractType == contractType) {
                    let posDirection = (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) ? PD_LONG : PD_SHORT;
                    if (typeof(direction) !== 'undefined' && direction != posDirection) {
                        throw "不允许持有多空双仓位";
                    }
                    direction = posDirection;
                }
            }
        }
        for (let i = 0; i < positions.length; i++) {
            if (positions[i].ContractType == contractType &&
                (((positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) && direction == PD_LONG) || ((positions[i].Type == PD_SHORT || positions[i].Type == PD_SHORT_YD) && 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 (typeof(positions[i].Margin) === 'number') {
                    allMargin += positions[i].Margin;
                }
            }
        }
        if (allAmount === 0) {
            return null;
        }
        return {
            MarginLevel: posMargin,
            Margin: allMargin,
            FrozenAmount: allFrozen,
            Price: _N(allCost / allAmount),
            Amount: allAmount,
            Profit: allProfit,
            Type: direction,
            ContractType: contractType
        };
    }

    function SetContractType(e, ct) {
        let eName = e.GetName();
        if (IsVirtual()) {
            if (eName == 'Futures_BitMEX' && ct != 'XBTUSD') {
                throw eName +" only support XBTUSD"
            } else if (eName == 'Futures_Binance' && ct != 'swap') {
                throw eName +" only support swap, not support " + ct
            }
        }
        let insDetail = SafeRetry(e.SetContractType, ct);
        let margin = 1 / MarginLevel;
        if (isFuturesLaw(eName)) {
            if (typeof(insDetail.MinLimitOrderVolume) === 'undefined') {
                insDetail.MinLimitOrderVolume = 1
            }
            if (typeof(insDetail.MaxLimitOrderVolume) != 'number' || insDetail.MaxLimitOrderVolume == 0) {
                insDetail.MaxLimitOrderVolume = 50
                insDetail.MinLimitOrderVolume = 1
            }
            if (insDetail.MinLimitOrderVolume > 5) {
                insDetail.MinLimitOrderVolume = 1
            }
            if (typeof(insDetail.PriceTick) === 'number') {
                let arr = insDetail.PriceTick.toString().split('.')
                if (arr.length == 2 && ZPrecision != arr[1].length) {
                    ZPrecision = arr[1].length;
                    Log("重置定价货币精度为", ZPrecision);
                }
            }
            if (IsVirtual()) {
                insDetail.InstrumentID = ct
            }
            return insDetail;
        } else if (eName == "Futures_BitMEX") {
            return {
                InstrumentID: ct,
                MaxLimitOrderVolume: 1000000,
                MinLimitOrderVolume: 1,
                VolumeMultiple: 1,
                LongMarginRatio: margin,
                ShortMarginRatio: margin,
                PriceTick: Math.max(0.5, Math.pow(10, -ZPrecision))
            }
        } else {
            let baseCurrency = exchange.GetCurrency().split('_')[0]
            let info = {
                InstrumentID: ct,
                MaxLimitOrderVolume: 1000000,
                MinLimitOrderVolume: 0.001,
                LongMarginRatio: margin,
                ShortMarginRatio: margin,
                VolumeMultiple: eName == "Futures_Deribit" ? 10 : (baseCurrency == 'BTC' ? 100 : 10),
                PriceTick: Math.pow(10, -ZPrecision)
            }
            if (eName == 'Futures_Binance' && ct == 'swap') {
                MinLot = 0.001
            }
            if (exchange.GetCurrency().indexOf("_USDT") != -1) {
                info.VolumeMultiple = 1
            } else {
                info.VolumeMultiple = -info.VolumeMultiple
            }
            return info
        }
    }

    function Open(contractType, direction, opAmount, slideTick) {
        if (typeof(slideTick) !== 'number') {
            slideTick = 2;
        }
        let insDetail = SetContractType(e, contractType);
        if (typeof(insDetail.MaxLimitOrderVolume) != 'number' || insDetail.MaxLimitOrderVolume == 0) {
            insDetail.MaxLimitOrderVolume = 50
            insDetail.MinLimitOrderVolume = 1
        }
        let tradeSymbol = insDetail.InstrumentID;
        
        // 切换到真正的主力合约上
        if (tradeSymbol != contractType) {
            SetContractType(e, tradeSymbol);
        }


        let positions = SafeRetry(e.GetPosition);
        for (let i = 0; i < positions.length; i++) {
            if (positions[i].ContractType != tradeSymbol) {
                continue;
            }
            if (((positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) && direction != PD_LONG) ||
                ((positions[i].Type == PD_SHORT || positions[i].Type == PD_SHORT_YD) && direction != PD_SHORT)) {
                throw _T("发现有相反仓位, 终止开仓|Discover the opposite position and terminate opening");
            }
        }
        let initPosition = GetPosition(e, tradeSymbol, direction, positions);
        let isFirst = true;
        let initAmount = initPosition ? initPosition.Amount : 0;
        let positionNow = initPosition;
        let retryCount = 0;
        while (true) {
            let needOpen = opAmount;
            if (isFirst) {
                isFirst = false;
            } else {
                positionNow = GetPosition(e, tradeSymbol, direction);
                if (positionNow) {
                    needOpen = opAmount - (positionNow.Amount - initAmount);
                }
            }
            //Log("初始持仓", initAmount, "当前持仓", positionNow, "需要加仓", needOpen);
            if (needOpen < insDetail.MinLimitOrderVolume) {
                if (needOpen > 0) {
                    Log(tradeSymbol, "交易忽略, 交易所限制最小下单量", insDetail.MinLimitOrderVolume, "当前", needOpen, "当前持仓", positionNow, "#676767");
                }
                break
            }
            let err = GetLastError();
            if (err.indexOf('资金不足') != -1) {
                Log(err);
                break;
            }
            let depth = TryDepth(e);
            let amount = Math.min(MaxAmountOnce, Math.min(insDetail.MaxLimitOrderVolume, needOpen));
            e.SetDirection(direction == PD_LONG ? "buy" : "sell");
            let orderId;
            let deepAmount;
            if (direction == PD_LONG) {
                let buyPrice = _N(depth.Asks[0].Price + (insDetail.PriceTick * slideTick), ZPrecision);
                deepAmount = getDeepAmount(depth.Asks, buyPrice, ORDER_TYPE_BUY);
                orderId = e.Buy(buyPrice, _N(Math.min(amount, deepAmount), XPrecision), tradeSymbol, 'ask', depth.Asks[0]);
            } else {
                let sellPrice = _N(depth.Bids[0].Price - (insDetail.PriceTick * slideTick), ZPrecision);
                deepAmount = getDeepAmount(depth.Bids, sellPrice, ORDER_TYPE_SELL);
                orderId = e.Sell(sellPrice, _N(Math.min(amount, deepAmount), XPrecision), tradeSymbol, 'bid', depth.Bids[0]);
            }
            if (!orderId) {
                retryCount++;
            }
            if (retryCount > MaxRetry) {
                break;
            }
            // CancelPendingOrders
            while (true) {
                TrySleep(SyncDelay);
                let orders = SafeRetry(e.GetOrders);
                if (orders.length === 0) {
                    break;
                }
                _depth = null; // reset depth
                let nowDepth = TryDepth(e)
                if (nowDepth.Asks[0].Price == depth.Asks[0].Price && nowDepth.Bids[0].Price == depth.Bids[0].Price) {
                    continue
                }
                for (let j = 0; j < orders.length; j++) {
                    e.CancelOrder(orders[j].Id);
                    _depth = null;
                    if (j < (orders.length - 1)) {
                        TrySleep(SyncDelay);
                    }
                }
            }
        }

        if (!positionNow) {
            return null;
        }
        let ret = {
            price: 0,
            amount: 0,
            position: positionNow,
            direction: direction,
            symbol: tradeSymbol,
        };
        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);
        }
        if (ret.amount == 0) {
            return null;
        }
        return ret;
    }

    function Cover(contractType, coverType, lots, slideTick) {
        if (typeof(slideTick) !== 'number') {
            slideTick = 2;
        }
        let insDetail = SetContractType(e, contractType);
        if (typeof(insDetail.MaxLimitOrderVolume) != 'number' || insDetail.MaxLimitOrderVolume == 0) {
            insDetail.MaxLimitOrderVolume = 50
            insDetail.MinLimitOrderVolume = 1
        }
        let tradeSymbol = insDetail.InstrumentID;
        if (tradeSymbol != contractType) {
            SetContractType(e, tradeSymbol);
        }
        let isCTP = e.GetName() == "Futures_CTP" || e.GetName() == 'Futures_Esunny';
        let initAmount = 0;
        let firstLoop = true;
        let totalDealAmount = 0;
        let totalDealNet = 0;
        while (true) {
            let n = 0;
            let depth = null;
            let positions = SafeRetry(e.GetPosition);
            let nowAmount = 0;
            for (let i = 0; i < positions.length; i++) {
                if (positions[i].ContractType != tradeSymbol) {
                    continue;
                }
                if (typeof(coverType) === 'undefined' || (coverType == PD_LONG && (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD)) ||
                    (coverType == PD_SHORT && (positions[i].Type == PD_SHORT || positions[i].Type == PD_SHORT_YD))) {
                    nowAmount += positions[i].Amount;
                }
            }
            if (firstLoop) {
                initAmount = nowAmount;
                firstLoop = false;
            }
            let amountChange = initAmount - nowAmount;
            if (typeof(lots) == 'number' && amountChange >= lots) {
                break;
            }

            let orderIds = [];
            for (let i = 0; i < positions.length; i++) {
                if (positions[i].ContractType != tradeSymbol) {
                    continue;
                }

                let canCover = typeof(coverType) === 'undefined' ||
                    (coverType == PD_LONG && (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD)) ||
                    (coverType == PD_SHORT && (positions[i].Type == PD_SHORT || positions[i].Type == PD_SHORT_YD));
                if (!canCover) {
                    continue;
                }
                let amount = Math.min(MaxAmountOnce, insDetail.MaxLimitOrderVolume, positions[i].Amount);
                let deepAmount = 0
                let opPrice = 0;
                let orderId = null;
                if (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) {
                    depth = TryDepth(e);
                    opPrice = depth.Bids[0].Price - (insDetail.PriceTick * slideTick);
                    deepAmount = getDeepAmount(depth.Bids, opPrice, ORDER_TYPE_SELL);
                } else if (positions[i].Type == PD_SHORT || positions[i].Type == PD_SHORT_YD) {
                    depth = TryDepth(e);
                    opPrice = depth.Asks[0].Price + (insDetail.PriceTick * slideTick);
                    deepAmount = getDeepAmount(depth.Asks, opPrice, ORDER_TYPE_BUY)
                }
                let opAmount = amount;
                if (typeof(lots) === 'number') {
                    opAmount = Math.min(opAmount, lots - (initAmount - nowAmount));
                }
                opPrice = _N(opPrice, ZPrecision);
                opAmount = _N(opAmount, XPrecision);
                if (opAmount >= MinLot || positions[i].Amount < MinLot) {
                    if (deepAmount > 0) {
                        opAmount = Math.min(opAmount, deepAmount);
                    }
                    // FIXED
                    if ((positions[i].Amount - opAmount) < MinLot) {
                        opAmount = positions[i].Amount
                    }
                    if (positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) {
                        e.SetDirection((isCTP && positions[i].Type == PD_LONG) ? "closebuy_today" : "closebuy");
                        orderId = e.Sell(opPrice, opAmount, tradeSymbol, (isCTP ? (positions[i].Type == PD_LONG ? "平今" : "平昨") : ''), 'bid', depth.Bids[0]);
                    } else {
                        e.SetDirection((isCTP && positions[i].Type == PD_SHORT) ? "closesell_today" : "closesell");
                        orderId = e.Buy(opPrice, opAmount, tradeSymbol, (isCTP ? (positions[i].Type == PD_SHORT ? "平今" : "平昨") : ''), 'ask', depth.Asks[0]);
                    }
                    if (orderId) {
                        orderIds.push(orderId)
                    }
                    n++
                }
                // break to check always
                if (typeof(lots) === 'number') {
                    break;
                }
            }
            if (n === 0) {
                break;
            }
            while (true) {
                TrySleep(SyncDelay);
                let orders = SafeRetry(e.GetOrders);
                if (orders.length === 0) {
                    break;
                }
                if (depth) {
                    _depth = null; // reset depth
                    let nowDepth = TryDepth(e)
                    if (nowDepth.Asks[0].Price == depth.Asks[0].Price && nowDepth.Bids[0].Price == depth.Bids[0].Price) {
                        continue
                    }
                }
                for (let j = 0; j < orders.length; j++) {
                    e.CancelOrder(orders[j].Id);
                    _depth = null;
                    if (j < (orders.length - 1)) {
                        TrySleep(SyncDelay);
                    }
                }
            }
            // calcAvgPrice
            if (orderIds.length > 0) {
                orderIds.forEach(function(id) {
                    let order = SafeRetry(exchange.GetOrder, id)
                    if (order.DealAmount > 0) {
                        totalDealNet += order.AvgPrice * order.DealAmount
                        totalDealAmount += order.DealAmount
                    }
                })
            }
        }
        if (totalDealAmount <= 0) {
            return null
        }
        return {
            direction: coverType == PD_LONG ? PD_SHORT : PD_LONG,
            price: _N(totalDealNet / totalDealAmount, ZPrecision),
            amount: _N(totalDealAmount, XPrecision)
        };
    }
    let obj = {};
    let lastTradeKey = "lastTradeSymbol";
    obj.symbol = symbol;
    obj.lastSync = 0;
    obj.insDetail = SetContractType(e, symbol);
    obj.isAbstract = isFuturesLaw(e.GetName()) && (symbol.indexOf('888') != -1 || symbol.indexOf('000') != -1);
    obj.positions = null;
    obj.lastTradeSymbol = _G(lastTradeKey) || obj.insDetail.InstrumentID;
    
    obj.GetTradeSymbol = function() {
        return obj.lastTradeSymbol;
    }
    obj.Poll = function() {
        if (!obj.isAbstract) {
            return;
        }
        if (obj.lastTradeAmount == 0 || !obj.positions) {
            return;
        }
        // Update Symbol
        obj.insDetail = SetContractType(e, obj.symbol);
        if (obj.insDetail.InstrumentID != obj.lastTradeSymbol) {
            // Try Switch symbol
            let pos = GetPosition(e, obj.lastTradeSymbol, undefined, null);
            if (pos && pos.Amount > 0) {
                Log("主力合约切换", obj.lastTradeSymbol, "->", obj.insDetail.InstrumentID, "移仓数量", pos.Amount, "#0000ff");
                Cover(pos.ContractType, pos.Type, pos.Amount, SlideTick);
                Log(pos.ContractType, "平仓结束");
                let r = Open(obj.insDetail.InstrumentID, pos.Type, pos.Amount, SlideTick);
                Log("移仓完成:", r);
            }
            _G(lastTradeKey, obj.insDetail.InstrumentID);
            obj.lastTradeSymbol = obj.insDetail.InstrumentID;
        }
    }
    
    obj.Sync = function(force) {
        if ((new Date().getTime() - obj.lastSync) > (SyncTime * 1000) || (typeof(force) !== 'undefined' && force)) {
            obj.nowAccount = SafeRetry(e.GetAccount);
            obj.nowAccount.Time = _D();
            obj.positions = SafeRetry(e.GetPosition);
            obj.lastSync = new Date().getTime();
        }
    }
    obj.GetInitAccount = function() {
        return obj.initAccount;
    }
    obj.GetAccount = function() {
        obj.Sync();
        return obj.nowAccount;
    }

    obj.GetPosition = function() {
        let insDetail = SetContractType(e, obj.symbol);
        let tradeSymbol = insDetail.InstrumentID;
        obj.Sync();
        return GetPosition(e, tradeSymbol, undefined, obj.positions)
    }
    obj.OpenShort = function(amount) {
        let r = Open(obj.symbol, PD_SHORT, amount, SlideTick);
        if (r) {
            obj.lastTradeSymbol = r.symbol;
        }
        obj.Sync(true);
        return r;
    }
    obj.OpenLong = function(amount) {
        let r = Open(obj.symbol, PD_LONG, amount, SlideTick);
        if (r) {
            obj.lastTradeSymbol = r.symbol;
        }
        obj.Sync(true);
        return r;
    }
    obj.CloseShort = function(amount) {
        let r = Cover(obj.symbol, PD_SHORT, amount, SlideTick);
        obj.Sync(true);
        return r
    }
    obj.CloseLong = function(amount) {
        let r = Cover(obj.symbol, PD_LONG, amount, SlideTick);
        obj.Sync(true);
        return r
    }
    obj.GetInsDetail = function() {
        return {
            MARGIN: Math.max(obj.insDetail.LongMarginRatio, obj.insDetail.ShortMarginRatio),
            UNIT: obj.insDetail.VolumeMultiple,
            MINPRICE: obj.insDetail.PriceTick
        };
    }
    obj.IsTrading = function() {
        let eName = e.GetName()
        if (eName === 'Futures_IB') {
            if (typeof(obj.insDetail.IsTrading) !== 'undefined') {
                return obj.insDetail.IsTrading
            }
        } else if (eName === 'Futures_CTP') {
            let d = new Date();
            let day = d.getDay();
            let hour = d.getHours();
            let minute = d.getMinutes();
            if (day === 0 || (day == 6 && hour >= 3)) {
                return false;
            }
        
            let period = [
                [8, 55, 11, 35],
                [12, 55, 15, 20],
                [20, 55, 24, 0],
                [0, 0, 2, 35]
            ];
        
            for (let i = 0; i < period.length; i++) {
                let p = period[i];
                if ((hour > p[0] || (hour == p[0] && minute >= p[1])) && (hour < p[2] || (hour == p[2] && minute < p[3]))) {
                    return true;
                }
            }
            return false; 
        }
        return true
    }

    if (isFuturesCrypto(exchange.GetName())) {
        exchange.SetMarginLevel(MarginLevel);
    }

    // init
    obj.initAccount = GetAccount(e);
    obj.nowAccount = obj.GetAccount();
    let accountRecover = false;
    if (AutoRecover && !IsVirtual()) {
        let s = _G("_account");
        if (s) {
            obj.initAccount = JSON.parse(s);
            Log(_T("恢复账户信息成功|Restore account success"), obj.initAccount);
            accountRecover = true;
        } else {
            Log(_T("没有发现恢复信息, 初始化运行|Init account success"));
        }
        if (e.GetName().indexOf("Futures_") == -1) {
            let pos = _G("_pos");
            if (pos) {
                obj.pos = JSON.parse(pos);
                Log(_T("恢复持仓信息成功|Restore position success"));
            } else {
                Log(_T("没有发现持仓信息|Init position success"));
            }
        }
    }

    if (!accountRecover) {
        let eName = e.GetName();
        if (eName.indexOf('Futures_') == -1) {
            let ele = obj.GetPosition()
            if (ele) {
                obj.initAccount.Balance += ele.Margin + ele.Profit;
            }
        } else {
            let positions = _C(exchange.GetPosition);
            positions.forEach(function(ele) {
                if (isFuturesLaw(eName)) {
                    obj.initAccount.Balance += ele.Margin + ele.Profit;
                } else {
                    let realMargin = ele.Margin;
                    if (eName == 'Futures_BitMEX') {
                        realMargin = ele.Amount/ele.Price/ele.MarginLevel;
                    }
                    obj.initAccount.Stocks += realMargin + ele.Profit;
                }
            });
        }
    }
    _G("_account", JSON.stringify(obj.initAccount));
    Log(_T("账户信息初始化成功|Initialize success"), obj.initAccount);
    return obj;
}


$.NewTradeManager = function() {
    let e = exchange;
    if (e.GetName().indexOf("Futures") !== -1) {
        return NewFuturesTrader(e, ContractType);
    }
    let obj = {};
    obj.lastSync = 0;
    obj.pos = {Amount: 0, Net: 0, Type: PD_LONG};
    obj.Sync = function(r, t, isCover) {
        if (!r) {
            return;
        }
        if (isCover) {
            let diff = (r.account.Stocks + r.account.FrozenStocks) - (obj.initAccount.Stocks + obj.initAccount.FrozenStocks);
            if (Math.abs(diff) <= 0 || Math.abs(diff) < MinStock) {
                obj.pos.Amount = 0;
                obj.pos.Net = 0;
            }
            return;
        }
        // reset
        if (obj.pos.Type != t) {
            obj.pos.Amount = 0;
            obj.pos.Net = 0;
            obj.pos.Type = t;
        }
        obj.pos.Amount += r.amount;
        obj.pos.Net += r.amount * r.price;
        _G("_pos", JSON.stringify(obj.pos));
    }
    obj.GetInitAccount = function() {
        return obj.initAccount;
    }
    obj.GetAccount = function() {
        let now = new Date().getTime();
        if ((now - obj.lastSync) > (SyncTime * 1000)) {
            obj.nowAccount = GetAccount(e);
            obj.lastSync = now;
        }
        return obj.nowAccount;
    }
    obj.GetTradeSymbol = function() {
        return exchange.GetCurrency();
    }

    obj.GetPosition = function() {
        obj.GetAccount();
        let diff = (obj.nowAccount.Stocks + obj.nowAccount.FrozenStocks) - (obj.initAccount.Stocks + obj.initAccount.FrozenStocks);
        if (Math.abs(diff) <= 0 || Math.abs(diff) < MinStock) {
            return null;
        }
        let amount = _N(Math.abs(diff), XPrecision);
        let price = _N(((obj.nowAccount.Balance + obj.nowAccount.FrozenBalance) - (obj.initAccount.Balance + obj.initAccount.FrozenBalance)) / -diff, ZPrecision);
        let margin = price * amount;
        
        if (obj.pos.Amount > 0) {
            price = _N(obj.pos.Net / obj.pos.Amount, ZPrecision);
        }

        if (diff > 0) {
            return {
                Amount: amount,
                Type: PD_LONG,
                Price: price,
                Margin: margin
            };
        } else {
            return {
                Amount: amount,
                Type: PD_SHORT,
                Price: price,
                Margin: margin
            };
        }
        return null;
    }

    obj.Buy = function(amount) {
        let r = Trade(e, ORDER_TYPE_BUY, amount, OpMode, SlideTick, MaxAmountOnce, MaxSpace, RetryDelay);
        if (r) {
            obj.nowAccount = r.account;
        }
        return r;
    };

    obj.Sell = function(amount) {
        let r = Trade(e, ORDER_TYPE_SELL, amount, OpMode, SlideTick, MaxAmountOnce, MaxSpace, RetryDelay);
        if (r) {
            obj.nowAccount = r.account;
        }
        return r;
    };

    obj.OpenShort = function(amount) {
        let r = obj.Sell(amount);
        obj.Sync(r, PD_SHORT, false);
        return r;
    }
    
    obj.CloseLong = function(amount) {
        let r = obj.Sell(amount);
        obj.Sync(r, PD_LONG, true);
        return r;
    }
    
    obj.OpenLong = function(amount) {
        let r = obj.Buy(amount);
        obj.Sync(r, PD_LONG, false);
        return r;
    }
    obj.CloseShort = function(amount) {
        let r = obj.Buy(amount);
        obj.Sync(r, PD_SHORT, true);
        return r;
    }

    obj.GetInsDetail = function() {
        return {
            MARGIN: 1,
            UNIT: Lot,
            MINPRICE: Math.pow(10, -ZPrecision)
        };
    }
    
    obj.IsTrading = function() {
        return true
    }

    obj.Poll = function() {
    }

    // init
    obj.initAccount = GetAccount(e);
    obj.nowAccount = GetAccount(e);
    if (AutoRecover && !IsVirtual()) {
        let s = _G("_account");
        if (s) {
            obj.initAccount = JSON.parse(s);
            Log(_T("恢复账户信息成功|Restore Account success"), obj.initAccount);
        } else {
            Log(_T("没有发现恢复信息, 初始化运行|init Account success"));
        }
    }
    _G("_account", JSON.stringify(obj.initAccount));
    Log(_T("账户信息初始化成功|Initialize success"), obj.initAccount);
    return obj;
}

$.GetContractType = function() {
    if (exchange.GetName().indexOf('Futures') != -1) {
        if (typeof(ContractType) !== 'string' || ContractType == '') {
            throw _T("请先在参数里设置合约|Please set contracttype first");
        }
        // 返回数据合约
        return ContractType.split('/')[0];
    }
    return ContractType;
}

$.GetTradeAmount = function() {
    return TradeAmount;
}

$.SaveCtx = function(ctx) {
    _G("_ctx", JSON.stringify(ctx));
    // reset depth
    _depth = null;
};

$.SetDepth = function(d) {
    _depth = d;
};

$.GetCtx = function(ctx) {
    let r = null;
    if (AutoRecover && !IsVirtual()) {
        let s = _G("_ctx");
        if (s) {
            r = JSON.parse(s);
            Log(_T("恢复状态信息成功..|Restore status success..."));
        } else {
            Log(_T("没有发现有需要恢复的状态信息|No status to restore"));
        }
    }
    return r;
}

$.GetMode = function() {
    return RunMode;
}

$.GetInterval = function() {
    if (typeof(LoopInterval) === 'undefined') {
        LoopInterval = 500;
    }
    return LoopInterval;
}

$.GetSettings = function() {
    return {
        maxBarLen: MaxCacheLen,
    }
}

function init() {
    // before SetContractType
    exchange.SetMaxBarLen(MaxCacheLen)
    
    if (typeof(SwitchBase) !== 'undefined' && SwitchBase) {
        exchange.IO("base", BaseAddr);
        Log("切换其地址到", BaseAddr);
    }
    let eid = exchange.GetName();
    let symbol = ContractType;
    if (eid.indexOf('Futures') == -1) {
        symbol = exchange.GetCurrency();
    }
    let recoverKey = eid+'/'+exchange.GetPeriod()+'/'+symbol;
    if (AutoRecover && (_G("ename") !== recoverKey)) {
        Log(_T("交易配置有变化, 禁用恢复模式|Exchange or symbol or period is changed, set recover mode to init"));
        AutoRecover = false;
    }
    if (!AutoRecover) {
        LogReset();
        LogProfitReset();
        Chart({}).reset();
        _G(null);
        Log(_T("已重置所有信息..|Reset Everything"));
    }
    _G("ename", recoverKey);
    
    if (UseProxy && typeof(ProxyAddr) === 'string' && ProxyAddr.indexOf('socks5') !== -1) {
        exchange.SetProxy(ProxyAddr);
        Log(_T("设置代理成功..|Set Proxy success"));
    }
    if (eid == 'Futures_BitMEX' && (ZPrecision != 1 || XPrecision != 8)) {
        ZPrecision = 1;
        XPrecision = 8;
        exchange.SetPrecision(ZPrecision, XPrecision);
        Log(_T("已经强制指定BitMEX精度|Force set bitmex precision success"));
    }
    if (exchange.GetCurrency().indexOf("_BTC") !== -1) {
        ZPrecision = 8;
        XPrecision = 8;
        exchange.SetPrecision(ZPrecision, XPrecision);
        Log(_T("已经强制指定币币交易精度|Force set precision success"));
    }
    if (isFuturesLaw(eid)) {
        XPrecision = 1;
    }
    if (HideError) {
        SetErrorFilter("not login|not ready|settlement|初始化|timeout|初始化|未就绪|Too Many");
    }
}

let gui = null;
let preSignalTime = _G("preSignalTime") || 0;
let _preHold = 0;
let _updateCount = 0;
$.UpdateStatus = function(scope) {
    _updateCount++;
    if (WXNotify) {
        if (scope.trade_ctx.preSignalObj && scope.trade_ctx.preSignalObj.time != preSignalTime) {
            preSignalTime = scope.trade_ctx.preSignalObj.time;
            _G("preSignalTime", preSignalTime);
            Log(_T("信号|Signal"), scope.trade_ctx.preSignalObj.action, (scope.trade_ctx.preSignalObj.action=='CLOSEOUT' ? '' : _T('数量|Lot') + ': ' + scope.trade_ctx.preSignalObj.lot), "@");
        }
    }

    let forceGUI = typeof(scope.gui) !== 'undefined' && scope.gui;
    if ((!IsVirtual()) || forceGUI) {
        if (!gui) {
            gui = NewGUI(scope.name + "_" + scope.symbol, exchange.GetPeriod());
        }
        gui.feed(scope.bar, scope.runtime);
    }
    if (!scope.canTrade) {
        return;
    }
    let trader = scope.env ? scope.env.trader : scope.ctx.trader;
    let tMarket = {
        type: 'table',
        title: _T('行情信息|Market'),
        cols: [_T('周期|Period'), _T('交易品种|Symbol'), _T('持仓量|Position'), _T('持仓价|AvgPrice'), _T('最新价|Close'), _T('上次信号|LastSignal'), _T('持仓后最高价|HighAfterOpen'), _T('持仓后最低价|LowAfterOpen'), _T('更新次数|UpdateCount'), _T('更新时间|Update')],
        rows: [
            [
                _D(scope.bar.Time),
                trader.GetTradeSymbol(),
                (scope.trade_ctx.bkVol > 0 ? _T('多 |Long ') + scope.trade_ctx.bkVol + '#0000ff' : (scope.trade_ctx.skVol > 0 ? _T('空 |Short ') + scope.trade_ctx.skVol + '#ff0000' : '--')),
                (scope.trade_ctx.bkVol > 0 ? scope.trade_ctx.bkPriceAv + '#0000ff' : (scope.trade_ctx.skVol > 0 ? scope.trade_ctx.skPriceAv + '#ff0000' : '--')),
                scope.bar.Close,
                scope.trade_ctx.preActionSignal,
                (scope.trade_ctx.bkVol > 0 ? scope.trade_ctx.bkHigh : (scope.trade_ctx.skVol > 0 ? scope.trade_ctx.skHigh : '--')),
                (scope.trade_ctx.bkVol > 0 ? scope.trade_ctx.bkLow : (scope.trade_ctx.skVol > 0 ? scope.trade_ctx.skLow : '--')),
                _updateCount,
                _D(),
            ],
        ]
    };

    let initAccount = trader.GetInitAccount();
    let account = trader.GetAccount();
    let quoteCurrency = exchange.GetQuoteCurrency();
    let isUBase = quoteCurrency == 'USDT' || quoteCurrency == 'BUSD';
    let str_s = _T('初始|Init');
    let str_n = _T('当前|Current')+'#0000ff';
    let str_diff = _T('差异|Diff')+'#ff0000';
    let str_free = _T('可用的|Free');
    let str_frozen = _T('冻结的|Frozen');
    let str_update = _T('数据时间|Update');
    let str_label = exchange.GetLabel();
    let tAsset = {
        type: 'table',
        title: _T('资金信息|Asset'),
        cols: [str_label, str_free + quoteCurrency, str_frozen + quoteCurrency, str_update],
        rows: [
            [str_n, _N(account.Balance, ZPrecision), _N(account.FrozenBalance, ZPrecision), account.Time],
            [str_s, _N(initAccount.Balance, ZPrecision), _N(initAccount.FrozenBalance, ZPrecision), initAccount.Time],
            [str_diff, _N(account.Balance - initAccount.Balance, ZPrecision) + '#ff0000', _N(account.FrozenBalance - initAccount.FrozenBalance, ZPrecision), _D()]
        ]
    };

    if (!isFuturesLaw(exchange.GetName())) {
        let arr = exchange.GetCurrency().split('_');
        let stockName = arr[0];
        if (exchange.GetName().indexOf("Futures_") !== -1) {
            if (!isUBase) {
                tAsset.cols = [str_label, str_free + stockName, str_frozen + stockName, str_update];
                tAsset.rows = [
                    [str_n, _N(account.Stocks, XPrecision), _N(account.FrozenStocks, XPrecision), account.Time],
                    [str_s, _N(initAccount.Stocks, XPrecision), _N(initAccount.FrozenStocks, XPrecision), initAccount.Time],

                    [str_diff, _N(account.Stocks - initAccount.Stocks, XPrecision) + '#ff0000', _N(account.FrozenStocks - initAccount.FrozenStocks, XPrecision), _D()]

                ];
            }
        } else {
            tAsset.cols = [str_label, str_free + quoteCurrency, str_frozen + quoteCurrency, str_free + stockName, str_frozen + stockName, str_update];
            tAsset.rows = [
                [str_n, _N(account.Balance, ZPrecision), _N(account.FrozenBalance, ZPrecision), _N(account.Stocks, XPrecision), _N(account.FrozenStocks, XPrecision), account.Time],
                [str_s, _N(initAccount.Balance, ZPrecision), _N(initAccount.FrozenBalance, ZPrecision), _N(initAccount.Stocks, XPrecision), _N(initAccount.FrozenStocks, XPrecision), initAccount.Time],
                [str_diff, _N(account.Balance - initAccount.Balance, ZPrecision) + '#ff0000', _N(account.FrozenBalance - initAccount.FrozenBalance, ZPrecision),
                    _N(account.Stocks - initAccount.Stocks, XPrecision) + '#ff0000', _N(account.FrozenStocks - initAccount.FrozenStocks, XPrecision), _D()
                ]
            ];
        }
    }

    LogStatus('`' + JSON.stringify([tMarket, tAsset]) + '`');
    
    if (forceGUI || !IsVirtual()) {
        let profit = null;
        let preAccTime = _G("_accTime");
        let accTime = AccInterval == 0 ? new Date().getHours() : new Date().getDate();
        let nowHold = scope.trade_ctx.bkVol + scope.trade_ctx.skVol;
        let isCover = _preHold > 0 && nowHold == 0;
        _preHold = nowHold;
        if (isCover || (accTime != preAccTime)) {
            // Draw Profit
            if (exchange.GetName().indexOf("Futures") == -1) {
                // SPOT
                profit = _N((account.Balance + account.FrozenBalance - initAccount.Balance - initAccount.FrozenBalance) +
                    (account.Stocks + account.FrozenStocks - initAccount.Stocks - initAccount.FrozenStocks) * scope.bar.Close, ZPrecision);
            } else if (isFuturesLaw(exchange.GetName())) {
                // CTP
                if (IsVirtual()) {
                    if (scope.trade_ctx.bkVol == 0 && scope.trade_ctx.skVol == 0) {
                        profit = _N(account.Balance - initAccount.Balance, 2);
                    } else {
                        profit = null;
                    }
                } else {
                    profit = _N(account.Info.Balance - initAccount.Info.Balance, 2);
                }
            } else {
                if (scope.trade_ctx.bkVol == 0 && scope.trade_ctx.skVol == 0) {
                    if (isUBase) {
                        profit = _N(account.Balance - initAccount.Balance, 8);
                    } else {
                        profit = _N(account.Stocks - initAccount.Stocks, 8);
                    }
                } else {
                    profit = _N(scope.getMoney(true) - (isUBase ? initAccount.Balance : initAccount.Stocks), 8);
                }
            }
            if (profit != null) {
                let preProfit = _G('_profit');
                if (profit != preProfit) {
                    LogProfit(profit, (isCover ? account : '&'));
                    preAccTime = accTime;
                    _G('_profit', profit)
                    _G("_accTime", accTime);
                }
            }
        }
    }
};

function main() {
    let gui = NewGUI(exchange.GetName(), exchange.GetCurrency());
    _.each(exchange.GetRecords(), function(bar, idx) {
        gui.feed(bar, idx == 10 ? [[0, {action: 'BPK',line: 20,lot: 10}], [0, {action: 'SPK',line: 20,lot: 30}]] : []);
        gui.feed(bar, idx == 18 ? [[0, {action: 'BP',line: 20,lot: 10}], [0, {action: 'SP',line: 20,lot: 30}]] : []);
    });
}