期貨反手加倍算法策略註釋詳解

該策略是好久之前適用數字貨幣796期貨交易所的一個策略,期貨合約是幣本位,即保證金扣除的是幣(例如BTC合約扣除BTC),合約下單量能夠是小數,相似幣安的幣本位合約。
該策略拿出來學習策略設計,邏輯處理仍是很不錯的,策略以學習爲主。javascript

策略代碼註釋

var FirstTradeType = [ORDER_TYPE_BUY, ORDER_TYPE_SELL][OpType];   // 首次開倉方向,根據參數OpType開倉方向肯定,全局變量FirstTradeType的值爲ORDER_TYPE_BUY或者ORDER_TYPE_SELL
var OrgAccount = null;                                            // 全局變量,記錄帳戶資產
var Counter = {s : 0, f: 0};                                      // 聲明一個變量Counter,值初始化爲一個對象(相似的結構python叫字典),s表明勝的次數,f表明負的次數
var LastProfit = 0;                                               // 最近盈虧
var AllProfit = 0;                                                // 總盈虧
var _Failed = 0;                                                  // 止損次數

// 取消掛單列表中某個ID的訂單以外的全部訂單,當不傳orderId參數時,取消當前交易對全部掛單,參數e爲交易所對象的引用,例如傳入exchange做爲參數,此刻e就是exchange的別名
function StripOrders(e, orderId) {                                
    var order = null;                               // 初始化變量 order 爲空
    if (typeof(orderId) == 'undefined') {           // 若是參數 orderId 傳入時,沒有寫,則typeof(orderId) == 'undefined'成立,執行if語句的代碼塊,orderId 賦值爲null
        orderId = null;
    }
    while (true) {                                  // 處理循環
        var dropped = 0;                            // 處理標記次數
        var orders = _C(e.GetOrders);               // 調用GetOrders獲取當前掛單(未徹底成交的訂單),賦值給orders
        for (var i = 0; i < orders.length; i++) {   // 遍歷未成交訂單列表 orders
            if (orders[i].Id == orderId) {          // 若是訂單ID和參數上傳入的訂單ID orderId 相同則給函數內局部變量order賦值orders[i],orders[i]即遍歷時當前的訂單結構
                order = orders[i];
            } else {                                // 若是ID不相同,執行撤銷操做
                var extra = "";                     // 根據部分紅交狀況,設置擴展信息extra
                if (orders[i].DealAmount > 0) {
                    extra = "成交: " + orders[i].DealAmount;
                } else {
                    extra = "未成交";
                }
                e.SetDirection(orders[i].Type == ORDER_TYPE_BUY ? "buy" : "sell");
                e.CancelOrder(orders[i].Id, orders[i].Type == ORDER_TYPE_BUY ? "買單" : "賣單", extra);    // 撤單操做,附帶輸出extra信息,在日誌上會顯示
                dropped++;                           // dropped 計數累計
            }
        }
        if (dropped == 0) {                          // 當遍歷完成時,dropped 等於0,即遍歷時沒有一次撤銷處理(沒有須要撤銷的訂單了),即爲撤銷處理工做完成,跳出while循環
            break;
        }
        Sleep(300);                                  // 防止輪轉頻率過快,每次間隔必定時間
    }
    return order;                                    // 返回要查找的訂單order
}


var preMsg = "";                                      // 記錄緩存信息的變量
function GetAccount(e, waitFrozen) {                  // 獲取帳戶資產信息,參數e亦是exchange的引用,參數waitFrozen控制是否等待凍結
    if (typeof(waitFrozen) == 'undefined') {          // 若是調用時不傳入waitFrozen參數,給參數waitFrozen賦值false,即默認不等待凍結
        waitFrozen = false;
    }
    var account = null;
    var alreadyAlert = false;                         // 標記是否已經提醒過的變量
    while (true) {                                    // 獲取當前帳戶信息,檢測凍結,若是不等待凍結,則會直接跳出while循環
        account = _C(e.GetAccount);
        if (!waitFrozen || account.FrozenStocks < MinStock) {
            break;
        }
        if (!alreadyAlert) {
            alreadyAlert = true;                      // 觸發提醒一次,就重置alreadyAlert,避免重複不停的提醒
            Log("發現帳戶有凍結的錢或幣", account);       // 輸出提醒日誌
        }
        Sleep(Interval);
    }
    msg = "成功: " + Counter.s + " 次, 失敗: " + Counter.f + " 次, 當前帳戶 幣: " + account.Stocks;
    if (account.FrozenStocks > 0) {
        msg += " 凍結的幣: " + account.FrozenStocks;
    }

    if (msg != preMsg) {                              // 檢測當前信息是否和上次信息不一樣,不一樣的話更新在狀態欄上
        preMsg = msg;
        LogStatus(msg, "#ff0000");
    }
    return account;                                   // 函數返回帳戶信息 account結構
}

function GetPosition(e, orderType) {                  // 獲取持倉,或者獲取指定方向的持倉
    var positions = _C(e.GetPosition);                // 獲取持倉
    if (typeof(orderType) == 'undefined') {           // orderType 參數爲指定要獲取的持倉類型,若是沒有傳入orderType參數,直接返回全部持倉
        return positions;
    }
    for (var i = 0; i < positions.length; i++) {      // 遍歷持倉列表
        if (positions[i].Type == orderType) {         // 若是當前遍歷的持倉數據是須要找的方向(orderType)
            return positions[i];                      // 返回 orderType 類型的持倉
        }
    }
    return null;
}

function GetTicker(e) {                               // 獲取ticker 行情數據
    while (true) {
        var ticker = _C(e.GetTicker);                 // 獲取tick行情
        if (ticker.Buy > 0 && ticker.Sell > 0 && ticker.Sell > ticker.Buy) {   // 檢查行情數據可靠性
            return ticker;                            // 返回 ticker數據
        }
        Sleep(100);
    }
}
// mode = 0 : direct buy, 1 : buy as buy1
function Trade(e, tradeType, tradeAmount, mode, slidePrice, maxSpace, retryDelay) {      // 交易函數
    // e 交易所對象引用, tradeType 交易方向(買/賣), tradeAmount 交易數量, mode 交易模式, slidePrice 滑價, maxSpace 最大掛單距離, retryDelay 重試時間間隔
    var initPosition = GetPosition(e, tradeType);      // 獲取指定方向的持倉數據,記做 initPosition
    var nowPosition = initPosition;                    // 聲明另外一個變量nowPosition 用initPosition賦值
    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;     // 下單函數,根據參數 tradeType 而定是調用 e.Buy 仍是 e.Sell
    var isBuy = tradeType == ORDER_TYPE_BUY;                          // 是不是買入的標記
    while (true) {                                                    // while循環
        var account = _C(e.GetAccount);                               // 獲取當前帳戶資產數據
        var ticker = GetTicker(e);                                    // 獲取當前行情數據
        var tradePrice = 0;                                           // 根據 mode 參數制定交易價格
        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 == null) {
            if (isFirst) {                                            // 根據 isFirst 標記變量判斷,若是是第一次執行,什麼都不作
                isFirst = false;                                      // isFirst 標記設置爲false ,表明已經不是第一次執行
            } else {                                                  // 非第一次執行,更新持倉數據
                nowPosition = GetPosition(e, tradeType);
            }
            dealAmount = _N((nowPosition ? nowPosition.Amount : 0) - (initPosition ? initPosition.Amount : 0), 6);   // 根據最初的持倉數據和當前的持倉數據,計算已經成交的數量
            var doAmount = Math.min(tradeAmount - dealAmount, account.Stocks * MarginLevel, 4);      // 根據已經成交的數量、帳戶可用資產,計算剩餘須要交易的數量
            if (doAmount < MinStock) {                                                               // 若是算出的交易數量小於最小交易數量,終止邏輯,跳出while循環
                break;
            }
            prePrice = tradePrice;                                                                   // 緩存當前循環時的交易價格
            e.SetDirection(tradeType == ORDER_TYPE_BUY ? "buy" : "sell");                            // 設置期貨交易方向
            orderId = tradeFunc(tradePrice, doAmount);                                               // 下單交易,參數爲算出的價格,本次下單數量
        } else {                                                                                     // 當記錄訂單的變量 orderId 不爲null時,則說明已經下過訂單
            if (mode == 0 || Math.abs(tradePrice - prePrice) > maxSpace) {                           // 若是是掛單模式,當前價格與上一次緩存的價格超出最大掛單區間
                orderId = null;                                                                      // 重置orderId 爲空值,就會在下一輪循環從新下單
            }
            var order = StripOrders(exchange, orderId);                                              // 調用StripOrders查找掛單列表中的ID爲orderId的訂單
            if (order == null) {                                                                     // 若是查找不到,也重置orderId 爲空值,繼續下一輪的下單操做
                orderId = null;
            }
        }
        Sleep(retryDelay);                                                                           // 暫定必定時間,起到控制循環頻率的效果
    }

    if (dealAmount <= 0) {                                                                           // 在while循環結束後,若是已經交易的量dealAmount小於等於0,說明交易失敗返回空值
        return null;
    }

    return nowPosition;                                                                              // 正常狀況返回最新的持倉數據
}

function coverFutures(e, orderType) {                               // 平倉函數
    var coverAmount = 0;                                            // 聲明一個變量coverAmount,初始賦值0,用來記錄已經平倉的數量
    while (true) {
        var positions = _C(e.GetPosition);                          // 獲取持倉
        var ticker = GetTicker(e);                                  // 獲取當前行情
        var found = 0;                                              // 查找標記
        for (var i = 0; i < positions.length; i++) {                // 遍歷持倉數組positions
            if (positions[i].Type == orderType) {                   // 找到須要的持倉
                if (coverAmount == 0) {                             
                    coverAmount = positions[i].Amount;              // 初始時記錄持倉數量,即要平倉的數量
                }
                if (positions[i].Type == ORDER_TYPE_BUY) {          // 根據持倉類型,執行平倉操做
                    e.SetDirection("closebuy");                     // 設置期貨交易方向
                    e.Sell(ticker.Buy, positions[i].Amount);        // 下單函數
                } else {
                    e.SetDirection("closesell");
                    e.Buy(ticker.Sell, positions[i].Amount);
                }
                found++;                                            // 標記累計
            }
        }
        if (found == 0) {                                           // 若是標記變量found爲0,則沒有倉位須要處理,跳出while循環
            break;
        }
        Sleep(2000);                                                // 間隔2秒
        StripOrders(e);                                             // 撤銷當前全部掛單
    }
    return coverAmount;                                             // 返回平倉的數量
}


function loop(pos) {
    var tradeType = null;                         // 初始化交易方向
    if (typeof(pos) == 'undefined' || !pos) {     // 判斷是不是首輪執行
        tradeType = FirstTradeType;
        pos = Trade(exchange, tradeType, OpAmount, OpMode, SlidePrice, MaxSpace, Interval);     // 首筆交易
        if (!pos) {
            throw "出師不利, 開倉失敗";
        } else {
            Log(tradeType == ORDER_TYPE_BUY ? "開多倉完成" : "開空倉完成", "均價:", pos.Price, "數量:", pos.Amount);
        }
    } else {
        tradeType = pos.Type;        // 根據持倉方向繼續指定交易方向
    }
    var holdPrice = pos.Price;       // 持倉價格
    var holdAmount = pos.Amount;     // 持倉數量

    var openFunc = tradeType == ORDER_TYPE_BUY ? exchange.Buy : exchange.Sell;     // 多頭持倉,開倉爲買,不然開倉爲賣
    var coverFunc = tradeType == ORDER_TYPE_BUY ? exchange.Sell : exchange.Buy;    // 多頭持倉,平倉爲賣,不然平倉位買

    var reversePrice = 0;           // 反手價格
    var coverPrice = 0;             // 平倉價格
    var canOpen = true;             // 可開倉標記

    if (tradeType == ORDER_TYPE_BUY) {                         
        reversePrice = _N(holdPrice * (1 - StopLoss), 4);     // 止損價格
        coverPrice = _N(holdPrice * (1 + StopProfit), 4);     // 止盈價格
    } else {
        reversePrice = _N(holdPrice * (1 + StopLoss), 4);
        coverPrice = _N(holdPrice * (1 - StopProfit), 4);
    }

    var coverId = null;
    var msg = "持倉價: " + holdPrice + " 止損價: " + reversePrice;

    for (var i = 0; i < 10; i++) {               // 控制最多下單10次
        if (coverId) {                           // 訂單ID爲空,不觸發break,繼續循環,直到10次
            break;
        }
        if (tradeType == ORDER_TYPE_BUY) {       // 根據方向下單,掛平倉單,即止盈的訂單
            exchange.SetDirection("closebuy");
            coverId = exchange.Sell(coverPrice, holdAmount, msg);
        } else {
            exchange.SetDirection("closesell");
            coverId = exchange.Buy(coverPrice, holdAmount, msg);
        }

        Sleep(Interval);
    }

    if (!coverId) {                // 10次下單還失敗拋出錯誤,策略中止
        StripOrders(exchange);     // 撤銷全部掛單
        Log("下單失敗", "@")        // 增長推送提醒
        throw "下單失敗";           // 拋出錯誤,讓機器人中止
    }


    while (true) {                 // 進入檢測反手的循環
        Sleep(Interval);           
        var ticker = GetTicker(exchange);                                // 獲取最新的行情
        if ((tradeType == ORDER_TYPE_BUY && ticker.Last < reversePrice) || (tradeType == ORDER_TYPE_SELL && ticker.Last > reversePrice)) {   // 檢測觸發止損即反手
            StripOrders(exchange);                                       // 掛單所有撤單
            var coverAmount = coverFutures(exchange, tradeType);         // 持倉全平
            if (_Failed >= MaxLoss) {                                    // 若是超過最大止損次數(反手次數),跳出循環從新開始
                Counter.f++;                                             // 計一次失敗
                Log("超過最大失敗次數", MaxLoss);
                break;                                                   // 跳出循環
            }
            var reverseAmount = _N(coverAmount * ReverseRate, 4);        // 根據平倉的量,進行交易量加倍

            var account = GetAccount(exchange, true);                    // 更新帳戶信息,此時不能有資產凍結
            // 檢測帳戶資產是否足夠,不足跳出循環,從新開始,如同_Failed >= MaxLoss
            if (_N(account.Stocks * MarginLevel, 4) < reverseAmount) {   // 檢測資產是否足夠
                Log("沒有幣反手開倉, 須要開倉: ", reverseAmount, "個, 只有", account.Stocks, "個幣");
                Counter.f++;
                break;
            }
            var reverseType = tradeType;                                 // 記錄反轉操做類型,默認順倉
            if (ReverseMode == 0) {                                      // 反手模式影響的調整,即若是參數設置了反倉,這裏調整
                reverseType = tradeType == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; // 反倉就是指,剛纔持倉是多,此次反手就作空,剛纔是作空,此次就作多
            }
            var pos = Trade(exchange, reverseType, reverseAmount, OpMode, SlidePrice, MaxSpace, Interval);    // 加倍的下單操做
            if (pos) {                                                                                        // 檢測交易邏輯執行以後的持倉
                Log(reverseType == ORDER_TYPE_BUY ? "多倉" : "空倉", "加倍開倉完成");
            }
            return pos;                                                                                       // 返回持倉結構
        } else {                                          // 沒有觸發反手時執行的邏輯
            var orders = _C(exchange.GetOrders);          // 當止盈掛單成交,記錄勝次數加1
            if (orders.length == 0) {
                Counter.s++;
                var account = GetAccount(exchange, true); // 更新帳戶資產
                LogProfit(account.Stocks, account);       // 打印帳戶資產
                break;
            }
        }
    }

    // 非while循環內正常return 的,返回null,例如止盈成功,超過失敗次數,資產不足
    return null;
}

function onexit() {          // 機器人中止時,執行掃尾函數onexit
    StripOrders(exchange);   // 撤銷全部掛單
    Log("Exit");
}

function main() {
    if (exchange.GetName().indexOf("Futures") == -1) {    // 檢測當前添加的第一個交易所對象是否是期貨交易所
        throw "只支持期貨, 現貨暫不支持";
    }
    // EnableLogLocal(SaveLocal);
    if (exchange.GetRate() != 1) {                        // 不啓用匯率轉換
        Log("已禁用匯率轉換");
        exchange.SetRate(1);
    }

    StopProfit /= 100;                                    // 參數處理爲小數,假設StopProfit爲1表示要1%止盈,從新計算賦值,StopProfit的值爲0.01即1%
    StopLoss /= 100;                                      // 止損(反手)同上

    var eName = exchange.GetName();
    if (eName == "Futures_CTP") {                         // 檢測添加的第一個交易所對象是否爲商品期貨,若是是,拋出錯誤信息,讓機器人中止
        throw "暫只支持數字貨幣期貨"
    }
    exchange.SetContractType(Symbol);                     // 設置數字貨幣合約代碼,即要交易、操做的合約
    exchange.SetMarginLevel(MarginLevel);                 // 設置槓桿
    Interval *= 1000;                                     // 輪詢間隔參數由秒轉換爲毫秒
    SetErrorFilter("502:|503:|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF");    // 設置屏蔽的錯誤類型
    StripOrders(exchange);                                // 取消全部掛單
    OrgAccount = GetAccount(exchange, true);              // 獲取當前帳戶信息
    LogStatus("啓動成功");                                 // 更新狀態欄信息
    var pos = null;                                       // 初始化main函數內的局部變量pos爲null,用來記錄持倉數據結構
    var positions = GetPosition(exchange);                // 獲取當前持倉,調用的是封裝後的GetPosition不帶orderType參數是要獲取所有持倉,注意調用的並不是是API接口exchange.GetPosition
    if (positions.length == 1) {                          // 若是開始時有持倉,賦值給pos變量
        pos = positions[0];
        Log("發現一個倉位, 已經自動恢復進度");
    } else if (positions.length > 1) {                    // 有多個持倉時,爲策略不可運行狀態,策略拋出錯誤讓機器人中止
        throw "發現持倉超過1個";
    }
    while (true) {                                        // 策略主循環
        pos = loop(pos);                                  // 執行交易邏輯主要的函數loop,pos做爲參數,而且返回新的持倉數據結構
        if (!pos) {                                       // 該條件觸發,返回null的狀況,例如止盈成功,超過失敗次數,資產不足
            _Failed = 0;                                  // 重置止損次數爲0,重來
        } else {
            _Failed++;                                    // 累計止損次數
        }
        Sleep(Interval);
    }
}

策略邏輯

讀完策略代碼能夠發現,策略邏輯其實並不複雜,代碼也並不算多,可是設計上可謂匠心獨運,不少地方能夠借鑑參考。
策略交易邏輯的主要函數爲loop函數,在main函數的主循環中反覆調用,當loop函數開始執行時,首先下單持倉,而後掛單止盈,等待止盈訂單成交。以後進入檢測狀態,檢測兩項內容。java

  • 檢測掛出的止盈單是否成交,止盈單成交,即盈利,退出檢測循環,重置邏輯,從新開始。
  • 檢測是否觸發止損(反手),觸發止損,即取消全部掛單,平掉倉位,而後根據參數設置是反手順倉仍是反手逆倉進行加倍反手下單交易。產生持倉,繼續掛出止盈單,而且再次進入檢測狀態(監測止盈、反手)。

策略邏輯簡單描述如此,可是仍是有一些其它細節的,好比最大反手次數的設置,帳戶資產可用的檢測,下單失敗最大次數10次的處理等。
策略中有些函數都作了根據參數不一樣而行爲差別化的設計,例如:StripOrders函數,GetAccount函數,GetPosition函數。這些函數根據參數傳入差別,有不一樣的行爲。這樣很好的複用了代碼,避免了代碼冗餘,讓策略設計簡潔易懂。python

原策略:https://www.fmz.com/strategy/3648數組

反手加倍有必定的風險,特別是在期貨上,策略僅爲學習,實盤慎用,歡迎留言討論。緩存

相關文章
相關標籤/搜索