利益集約者戦略分析 (1)

作者: リン・ハーンニナバダス作成日:2022-04-26 11:31:51 更新日:

利益集約者戦略分析 (1)

最近,FMZ Quant の微信群のユーザーは,print money議論は熱く,非常に古い戦略は,再びクォントのビジョンに入りました:利益 ハーブスターわかったわ ロボット取引の原則print money利潤の収穫戦略に基づいています. 私はその時に利潤の収穫戦略をよく理解していなかったことを自分を責めました. だから,私は再び本来の戦略を真剣に読み,また移植版を読みました:OKCoinの利益収集機を移植する- わかった この戦略を分析し,戦略のアイデアを掘り出すことで, プラットフォームのユーザーがこの戦略のアイデアを学ぶことができます. この記事では,プログラミングに関連する退屈なコンテンツを最小限に抑えるために,戦略的思考,意図等レベルからさらに分析します.

[ポート OKCoin 利益収穫者] 戦略 ソースコード:

function LeeksReaper() {
    var self = {}
    self.numTick = 0
    self.lastTradeId = 0
    self.vol = 0
    self.askPrice = 0
    self.bidPrice = 0
    self.orderBook = {Asks:[], Bids:[]}
    self.prices = []
    self.tradeOrderId = 0
    self.p = 0.5
    self.account = null
    self.preCalc = 0
    self.preNet = 0

    self.updateTrades = function() {
        var trades = _C(exchange.GetTrades)
        if (self.prices.length == 0) {
            while (trades.length == 0) {
                trades = trades.concat(_C(exchange.GetTrades))
            }
            for (var i = 0; i < 15; i++) {
                self.prices[i] = trades[trades.length - 1].Price
            }
        }
        self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {
            // Huobi not support trade.Id
            if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
                self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId)
                mem += trade.Amount
            }
            return mem
        }, 0)

    }
    self.updateOrderBook = function() {
        var orderBook = _C(exchange.GetDepth)
        self.orderBook = orderBook
        if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) {
            return
        }
        self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01
        self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01
        self.prices.shift()
        self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 +
            (orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 +
            (orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05))
    }
    self.balanceAccount = function() {
        var account = exchange.GetAccount()
        if (!account) {
            return
        }
        self.account = account
        var now = new Date().getTime()
        if (self.orderBook.Bids.length > 0 && now - self.preCalc > (CalcNetInterval * 1000)) {
            self.preCalc = now
            var net = _N(account.Balance + account.FrozenBalance + self.orderBook.Bids[0].Price * (account.Stocks + account.FrozenStocks))
            if (net != self.preNet) {
                self.preNet = net
                LogProfit(net)
            }
        }
        self.btc = account.Stocks
        self.cny = account.Balance
        self.p = self.btc * self.prices[self.prices.length-1] / (self.btc * self.prices[self.prices.length-1] + self.cny)
        var balanced = false
        
        if (self.p < 0.48) {
            Log("start to balance", self.p)
            self.cny -= 300
            if (self.orderBook.Bids.length >0) {
                exchange.Buy(self.orderBook.Bids[0].Price + 0.00, 0.01)
                exchange.Buy(self.orderBook.Bids[0].Price + 0.01, 0.01)
                exchange.Buy(self.orderBook.Bids[0].Price + 0.02, 0.01)
            }
        } else if (self.p > 0.52) {
            Log("start to balance", self.p)
            self.btc -= 0.03
            if (self.orderBook.Asks.length >0) {
                exchange.Sell(self.orderBook.Asks[0].Price - 0.00, 0.01)
                exchange.Sell(self.orderBook.Asks[0].Price - 0.01, 0.01)
                exchange.Sell(self.orderBook.Asks[0].Price - 0.02, 0.01)
            }
        }
        Sleep(BalanceTimeout)
        var orders = exchange.GetOrders()
        if (orders) {
            for (var i = 0; i < orders.length; i++) {
                if (orders[i].Id != self.tradeOrderId) {
                    exchange.CancelOrder(orders[i].Id)
                }
            }
        }
    }

    self.poll = function() {
        self.numTick++
        self.updateTrades()
        self.updateOrderBook()
        self.balanceAccount()
        
        var burstPrice = self.prices[self.prices.length-1] * BurstThresholdPct
        var bull = false
        var bear = false
        var tradeAmount = 0
        if (self.account) {
            LogStatus(self.account, 'Tick:', self.numTick, ', lastPrice:', self.prices[self.prices.length-1], ', burstPrice: ', burstPrice)
        }
        
        if (self.numTick > 2 && (
            self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -1)) > burstPrice ||
            self.prices[self.prices.length-1] - _.max(self.prices.slice(-6, -2)) > burstPrice && self.prices[self.prices.length-1] > self.prices[self.prices.length-2]
            )) {
            bull = true
            tradeAmount = self.cny / self.bidPrice * 0.99
        } else if (self.numTick > 2 && (
            self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -1)) < -burstPrice ||
            self.prices[self.prices.length-1] - _.min(self.prices.slice(-6, -2)) < -burstPrice && self.prices[self.prices.length-1] < self.prices[self.prices.length-2]
            )) {
            bear = true
            tradeAmount = self.btc
        }
        if (self.vol < BurstThresholdVol) {
            tradeAmount *= self.vol / BurstThresholdVol
        }
        
        if (self.numTick < 5) {
            tradeAmount *= 0.8
        }
        
        if (self.numTick < 10) {
            tradeAmount *= 0.8
        }
        
        if ((!bull && !bear) || tradeAmount < MinStock) {
            return
        }
        var tradePrice = bull ? self.bidPrice : self.askPrice
        while (tradeAmount >= MinStock) {
            var orderId = bull ? exchange.Buy(self.bidPrice, tradeAmount) : exchange.Sell(self.askPrice, tradeAmount)
            Sleep(200)
            if (orderId) {
                self.tradeOrderId = orderId
                var order = null
                while (true) {
                    order = exchange.GetOrder(orderId)
                    if (order) {
                        if (order.Status == ORDER_STATE_PENDING) {
                            exchange.CancelOrder(orderId)
                            Sleep(200)
                        } else {
                            break
                        }
                    }
                }
                self.tradeOrderId = 0
                tradeAmount -= order.DealAmount
                tradeAmount *= 0.9
                if (order.Status == ORDER_STATE_CANCELED) {
                    self.updateOrderBook()
                    while (bull && self.bidPrice - tradePrice > 0.1) {
                        tradeAmount *= 0.99
                        tradePrice += 0.1
                    }
                    while (bear && self.askPrice - tradePrice < -0.1) {
                        tradeAmount *= 0.99
                        tradePrice -= 0.1
                    }
                }
            }
        }
        self.numTick = 0
    }
    return self
}

function main() {
    var reaper = LeeksReaper()
    while (true) {
        reaper.poll()
        Sleep(TickInterval)
    }
}

戦略の概要

一般的に,戦略を学ぶとき,読むとき,まず全体的なプログラム構造を見てください. 戦略コードは200行未満の長さではなく,非常に簡素化されており,このバージョンと同じ元のバージョンの戦略に対して高い復元度を持っています. 戦略コードが実行されているとき,main()戦略コード全体を除くmain(), 名前で関数しか持っていませんLeeksReaper().....LeeksReaper()この関数は,プロフィットハーベスター戦略論理モジュール (オブジェクト) のコンストラクターとして理解できます.LeeksReaper()利潤採集者の取引論理を構築する責任があります

  • "第1行"の第2行は"第2行"の第2行ですmain戦略における役割:var reaper = LeeksReaper(); コードはローカル変数を宣言します.reaper, そして LeeksReaper() 関数を呼び出し,戦略論理オブジェクトを構成し,それを割り当てますreaper.

  • この項目は,main機能:

    while (true) {
        reaper.poll()
        Sleep(TickInterval)
    }
    

    A を入力してください.while処理機能を継続的に実行します.reaperオブジェクトpoll()そして,poll()戦略プログラム全体が 継続的に取引の論理を実行し始めます

    線についてSleep(TickInterval)簡単に理解できるもので,全体的な取引ロジックの実行後の休止時間を制御し,取引ロジックの回転頻度を制御することを目的としています.

建設 者 を 分析 するLeeksReaper()

この関数は,LeeksReaper()戦略論理でオブジェクトを構成します

についてLeeksReaper()function は null オブジェクトを宣言し始めます.var self = {}執行中にLeeksReaper()この null オブジェクトは,いくつかのメソッドと属性を追加することで徐々に修正されます. 最後に,オブジェクトの構築が完了し,オブジェクトが返されます (すなわち,var reaper = LeeksReaper()についてmain()返されたオブジェクトは,reaper).

オブジェクトに属性を追加するself

追加しました.self戦略を理解しやすくし,コードの堆積を見て混乱しないようにします. プログラムでは,すべての要素がコードの堆積で組み込まれています.

    self.numTick = 0         # it is used to record the number of times that the trading is not triggered when the poll function is called. When placing an order is triggered and the order logic is executed, reset self.numTick to 0
    self.lastTradeId = 0     # the trading record ID of the order that has been executed in the trading market; this variable records the current latest execution record ID in the market
    self.vol = 0             # after the weighted average calculation, the trading volume reference of the market at each inspection (the market data is obtained once per time of the loop, which can be understood as the inspection of the market once)
    self.askPrice = 0        # ask price of delivery order, which can be understood as the price of the pending sell order calculated by the strategy 
    self.bidPrice = 0        # bid price of delivery order
    self.orderBook = {Asks:[], Bids:[]}    # record the currently obtained order book data, that is, depth data (sell 1...sell n, buy 1...buy n)
    self.prices = []                       # an array that records the price in the time series after the weighted average calculation of the first three levels in the order book. Simply put, it is the weighted average price of the first three levels of the order book obtained by storing each time, and put them in an array as reference of the subsequent strategic trading signals. Therefore, the variable name is "prices", plural, which means a set of prices
    self.tradeOrderId = 0    # record the order ID after currently lading and ordering 
    self.p = 0.5             # position proportion; when the currency value is exactly half of the total asset value, the value of it is 0.5, which means the balanced state 
    self.account = null      # record the account asset data, which will be returned by the function GetAccount()
    self.preCalc = 0         # record the timestamp when calculating the return of the latest time, in milliseconds, which is used to control the frequency of triggering execution of the return calculation code
    self.preNet = 0          # record the current return value  

オブジェクトにメソッドを追加するself

この属性をselfに追加した後,メソッドを追加し始めますselfこのオブジェクトは,いくつかの操作を行い,いくつかの機能を持つことができます.

最初の追加機能:

    self.updateTrades = function() {
        var trades = _C(exchange.GetTrades)  # call the encapsulated interface "GetTrades" of FMZ, to obtain the currently latest execution data in the market
        if (self.prices.length == 0) {       # when self.prices.length == 0, you need to fill values in the array of self.prices, which is only triggered when the strategy is started 
            while (trades.length == 0) {     # if there is no latest execution record in the market, the while loop will run infinitely, until there is new execution data; the, update the variable "trades"
                trades = trades.concat(_C(exchange.GetTrades))   # "concat" is a method of JS array type, which is used to match two arrays; here we use it to match the array "trades" and the array returned by "_C(exchange.GetTrades)" into one array 
            }
            for (var i = 0; i < 15; i++) {   # fill in values for "self.prices"; fill 15 latest execution prices 
                self.prices[i] = trades[trades.length - 1].Price
            }
        }
        self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {  # _.reduce function iteratively calculates the accumulated execution volume of the latest execution record 
            // Huobi not support trade.Id
            if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
                self.lastTradeId = Math.max(trade.Id == 0 ? trade.Time : trade.Id, self.lastTradeId)
                mem += trade.Amount
            }
            return mem
        }, 0)

    }

機能についてupdateTrades最新の市場での実行データを入手し,データに基づいて計算や記録を行い,その結果を後続の戦略論理で使用します. コードに一行一行書き込みました プログラミングの基礎知識を持たない学生は_.reduce簡単に紹介しましょう_.reduceとなる.Underscore.jsFMZJS 戦略によってサポートされています. したがって,繰り返しの計算を行うためにそれを使用することは非常に便利です.Underscore.jsの情報リンク

簡単に理解できます 例えば:

function main () {
   var arr = [1, 2, 3, 4]
   var sum = _.reduce(arr, function(ret, ele){
       ret += ele
       
       return ret
   }, 0)

   Log("sum:", sum)    # sum is 10
}

配列の各数を足すと[1, 2, 3, 4]実行記録の各データのボリューム値を足すことになります.tradesこの数列は,最後の実行ボリュームの合計を表示します.self.vol = 0.7 * self.vol + 0.3 * _.reduce(...)代用してみましょう....計算のコストは,self.volまた,重度の平均値である.つまり,新たに生成された総実行量は30%を占め,以前の計算された実行量は70%を占める.この比率は,市場規則の遵守に関連して戦略開発者が人工的に設定する.

"最新実行データを取得するためのインターフェースが古いデータを重複して返した場合はどうすればいいですか? 入手したデータはすべて間違っているので,使用する意味がありますか?" 心配しないでください,戦略は,それを念頭に置いて設計されていますので,コードは以下の内容を持っています:

if ((trade.Id > self.lastTradeId) || (trade.Id == 0 && trade.Time > self.lastTradeId)) {
    ...
}

判断は,取引実行記録の実行IDに基づいて行うことができます. IDが前回のIDよりも大きい場合にのみ,蓄積が起動します. または,プラットフォームインターフェイスがIDを提供していない場合,つまり,trade.Id == 0取引実行記録のタイムスタンプを使用して判断します.self.lastTradeId実行記録のタイムスタンプです IDではありません

2番目の追加機能:

    self.updateOrderBook = function() {
        var orderBook = _C(exchange.GetDepth)
        self.orderBook = orderBook
        if (orderBook.Bids.length < 3 || orderBook.Asks.length < 3) {
            return
        }
        self.bidPrice = orderBook.Bids[0].Price * 0.618 + orderBook.Asks[0].Price * 0.382 + 0.01
        self.askPrice = orderBook.Bids[0].Price * 0.382 + orderBook.Asks[0].Price * 0.618 - 0.01
        self.prices.shift()
        self.prices.push(_N((orderBook.Bids[0].Price + orderBook.Asks[0].Price) * 0.35 +
            (orderBook.Bids[1].Price + orderBook.Asks[1].Price) * 0.1 +
            (orderBook.Bids[2].Price + orderBook.Asks[2].Price) * 0.05))
    }

次に,この2つの方法について見ていきましょう.updateOrderBookfunction. 機能名の意味から,その機能が注文簿を更新することを見ることができます. しかし,それは単に注文簿を更新するのではありません. 機能は,最初に FMZ API 関数を呼び出します.GetDepth()現在の市場オーダーブックデータ (売り1...売りn,買い1...買いn) を取得し,in self.orderBook次に,購入注文と販売注文のオーダーブックデータのレベルが3レベル未満かどうかを判断します.そうであれば,無効な関数であると判断し,直接返します.

その後,2つのデータ計算を行いました.

  • 配送注文価格を計算する 配送価格の計算は,加重平均計算にも基づいています. 購入注文の入札価格を計算する際には,購入1価格に 61.8% (0.618) というより大きな重みを与え,販売1価格は残りの重み 38.2% (0.382) を占めています. 配送注文の要求価格を計算する際にも同じことが当てはまります. 販売1価格により多くの重みを与えます. なぜそれが 0.618 になるのかについては,開発者が黄金比率を好む可能性があります. 価格の少し (0.01) を最後に追加または減算すると,それは市場の中心部に少し近づくためにオフセットされます.

  • タイムシリアルのオーダーブックの最初の3つのレベルの重度の平均価格を更新する オーダーブック内の最初の3つのレベルの購入オーダーと販売オーダーについては,重度の平均計算が行われます.最初のレベルの重量は0.7,第2レベルの重量は0.2,第3レベルの重量は0.1.一部の学生は"ああ,間違ってる,コードには0.7,0.2,または0.1はない!"と言うかもしれません.

    計算を拡大してみましょう.

    (Buy 1 + Sell 1) * 0.35 + (Buy 2 + Sell 2) * 0.1 + (Buy 3 + Sell 3) * 0.05
    ->
    (Buy 1 + Sell 1) / 2 * 2 * 0.35 + (Buy 2 + Sell 2) / 2 * 2 * 0.1 + (Buy 3 + Sell 3) / 2 * 2 * 0.05
    ->
    (Buy 1 + Sell 1) / 2 * 0.7 + (Buy 2 + Sell 2) / 2 * 0.2 + (Buy 3 + Sell 3) / 2 * 0.1
    ->
    average price of the first level * 0.7 + average price of the second level * 0.2 + average price of the third level * 0.1
    

    ここで,最終的な計算価格が,現在の市場における第3レベルの平均価格位置を反映していることがわかります. この計算した価格を使ってself.prices古いデータ (by the) をキックアウトします.shift()最新のデータで更新する (push()shiftpush は JS アレイ オブジェクトのメソッドで,詳細については JS に関連する資料で検索できます).self.pricesタイムシリーズ順序でデータフローです.

    休憩して 分析を終わらせて 次回は会おう


もっと