avatar of 发明者量化-小小梦 发明者量化-小小梦
집중하다 사신
4
집중하다
1271
수행원

파 수확 전략 분석 (1)

만든 날짜: 2020-11-12 22:11:32, 업데이트 날짜: 2024-12-06 22:20:54
comments   26
hits   10639

파 수확 전략 분석 (1)

리크 수확기 전략 분석

최근, 양적 WeChat 그룹 토론의 발명가print money로봇에 대한 논의는 매우 격렬했으며, 매우 오래된 전략이 양적 분석의 비전에 다시 등장했습니다.파 수확기print money로봇의 거래 원리는 파 수확기 전략을 기반으로 합니다. 당시 파 수확기 전략을 명확하게 이해하지 못한 것을 스스로 탓합니다. 그래서 저는 원래 전략을 다시 주의 깊게 검토했고, Inventor Quant에 이식된 버전도 검토했습니다.OKCoin 리크 수확기 이식。 Inventor Quantitative Platform의 파 수확 전략의 이식 버전을 사용하여 전략을 분석하고 그 이면에 있는 아이디어를 살펴보겠습니다. 이를 통해 플랫폼 사용자들은 이러한 전략적 아이디어를 배울 수 있습니다. 이 글에서는 전략적 사고, 의도 등의 관점에서 더 많은 분석을 실시하고, 프로그래밍과 관련된 지루한 내용을 줄이도록 노력하겠습니다.

[[이식 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("开始平衡", 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("开始平衡", 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) 파 수확 전략 분석 (1)

  • 전략main함수의 첫 번째 줄: var reaper = LeeksReaper(), 코드는 지역 변수를 선언합니다.reaper그런 다음 LeeksReaper() 함수를 호출하여 전략 논리 객체를 구성하고 이를 할당합니다.reaper

  • 전략main기능은 다음과 같습니다.

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

입력하세요while데드 루프, 끝없는 실행reaper객체 처리 기능poll()poll()이 기능은 거래 전략의 주요 논리이며, 전체 전략 프로그램은 거래 논리를 지속적으로 실행하기 시작합니다. 에 관해서는Sleep(TickInterval)이 줄은 이해하기 쉽습니다. 전체 트랜잭션 로직의 각 실행 후 일시 정지 시간을 제어하여 트랜잭션 로직의 회전 빈도를 제어하기 위한 것입니다.

프로파일링LeeksReaper()건설자

한번 보세요LeeksReaper()함수가 전략 논리 객체를 구성하는 방법.

LeeksReaper()함수의 시작부분에서 빈 객체가 선언됩니다.var self = {},존재하다LeeksReaper()함수 실행 중에 일부 메서드와 속성이 이 빈 객체에 점진적으로 추가되고, 마침내 이 객체의 생성이 완료되고, 마침내 이 객체가 반환됩니다(즉,main()함수 내부var reaper = LeeksReaper()이 단계에서는 반환된 객체가 다음에 할당됩니다.reaper)。

주다self객체에 속성 추가

다음에 주세요self많은 속성이 추가되었습니다. 아래에서 각 속성을 설명하여 이러한 속성과 변수의 목적과 의도를 빠르게 이해하고 전략을 쉽게 이해하여 이 코드 더미를 볼 때 혼란스러워하지 않도록 하겠습니다.

    self.numTick = 0         # 用来记录poll函数调用时未触发交易的次数,当触发下单并且下单逻辑执行完时,self.numTick重置为0
    self.lastTradeId = 0     # 交易市场已经成交的订单交易记录ID,这个变量记录市场当前最新的成交记录ID
    self.vol = 0             # 通过加权平均计算之后的市场每次考察时成交量参考(每次循环获取一次市场行情数据,可以理解为考察了行情一次)
    self.askPrice = 0        # 卖单提单价格,可以理解为策略通过计算后将要挂卖单的价格
    self.bidPrice = 0        # 买单提单价格
    self.orderBook = {Asks:[], Bids:[]}    # 记录当前获取的订单薄数据,即深度数据(卖一...卖n,买一...买n)
    self.prices = []                       # 一个数组,记录订单薄中前三档加权平均计算之后的时间序列上的价格,简单说就是每次储存计算得到的订单薄前三档加权平均价格,放在一个数组中,用于后续策略交易信号参考,所以该变量名是prices,复数形式,表示一组价格
    self.tradeOrderId = 0    # 记录当前提单下单后的订单ID
    self.p = 0.5             # 仓位比重,币的价值正好占总资产价值的一半时,该值为0.5,即平衡状态
    self.account = null      # 记录账户资产数据,由GetAccount()函数返回数据
    self.preCalc = 0         # 记录最近一次计算收益时的时间戳,单位毫秒,用于控制收益计算部分代码触发执行的频率
    self.preNet = 0          # 记录当前收益数值

주다self객체 추가 방법

이러한 속성을 자기 자신에게 추가한 후 시작하십시오.self객체에 메서드를 추가하면 객체가 일부 작업을 수행하고 일부 기능을 가질 수 있습니다.

첫 번째로 추가된 기능은 다음과 같습니다.

    self.updateTrades = function() {
        var trades = _C(exchange.GetTrades)  # 调用FMZ封装的接口GetTrades,获取当前最新的市场成交数据
        if (self.prices.length == 0) {       # 当self.prices.length == 0时,需要给self.prices数组填充数值,只有策略启动运行时才会触发
            while (trades.length == 0) {     # 如果近期市场上没有更新的成交记录,这个while循环会一直执行,直到有最新成交数据,更新trades变量
                trades = trades.concat(_C(exchange.GetTrades))   # concat 是JS数组类型的一个方法,用来拼接两个数组,这里就是把“trades”数组和“_C(exchange.GetTrades)”返回的数组数据拼接成一个数组
            }
            for (var i = 0; i < 15; i++) {   # 给self.prices填充数据,填充15个最新成交价格
                self.prices[i] = trades[trades.length - 1].Price
            }
        }
        self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) {  # _.reduce 函数迭代计算,累计最新成交记录的成交量
            // 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프로그래밍 기초가 없는 학생들은 혼란스러울 수 있습니다. 간단한 설명은 다음과 같습니다._.reduceUnderscore.js이 라이브러리의 기능은 FMZJS 전략에 의해 지원되므로 반복 계산에 사용하기 매우 편리합니다.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 等于 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가 아닌 거래 기록의 타임스탬프입니다.

두 번째로 추가된 기능:

    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))
    }

다음 시계updateOrderBook이름에서 알 수 있듯이 이 기능은 주문장을 업데이트하는 데 사용됩니다. 하지만 단순히 주문서를 업데이트하는 것 이상입니다. 이 함수는 FMZ의 API 함수를 호출하기 시작합니다.GetDepth()현재 시장 주문장 데이터(1개 판매…n개 판매, 1개 구매…n개 구매)를 가져오고 주문장 데이터를 기록합니다.self.orderBook가운데. 다음으로, 주문장 데이터에 매수, 매도 주문이 3개 미만인 경우, 해당 함수는 유효하지 않은 것으로 간주되어 바로 반환됩니다.

그 후, 두 가지 데이터가 계산되었습니다.

  • 운송장 가격 계산 선하증권 가격도 가중 평균을 사용하여 계산합니다. 매수 주문을 계산할 때 매수 주문은 61.8%(0.618)의 더 큰 가중치를 부여받고 매도 주문은 나머지 38.2%(0.382)의 가중치를 차지합니다. 동일한 원칙이 운송장 판매 가격을 계산할 때도 적용되며, 판매 가격이 더 큰 가중치를 갖습니다. 0.618인 이유는 저자가 황금 비율을 선호하기 때문일 수 있습니다. 최종적으로 가격이 약간 상승하거나 하락하는 경우(0.01)는 가격을 시장의 중앙으로 약간 이동시키는 것입니다.

  • 주문서의 첫 번째 3개 계층의 가중 평균 가격을 시간 시리즈에서 업데이트합니다. 주문서의 매수 및 매도 주문 가격의 처음 세 계층에 대해 가중 평균 계산이 수행되며, 첫 번째 계층의 가중치는 0.7, 두 번째 계층의 가중치는 0.2, 세 번째 계층의 가중치는 0.1입니다. . 일부 학생들은 “아, 그건 틀렸네요. 코드에 0.7, 0.2, 0.1이 없어요”라고 말할 수 있습니다. 계산을 확장해 보겠습니다.

  (买一 + 卖一) * 0.35 + (买二 + 卖二) * 0.1 + (买三 + 卖三) * 0.05
  ->
  (买一 + 卖一) / 2 * 2 * 0.35 + (买二 + 卖二) / 2 * 2 * 0.1 + (买三 + 卖三) / 2 * 2 * 0.05
  ->
  (买一 + 卖一) / 2 * 0.7 + (买二 + 卖二) / 2 * 0.2 + (买三 + 卖三) / 2 * 0.1
  ->
  第一档平均的价格 * 0.7 + 第二档平均的价格 * 0.2 + 第三档平均的价格 * 0.1

여기서 최종 계산된 가격은 실제로 현재 시장의 세 가지 가격 수준 중 중간 가격 위치를 반영한다는 것을 알 수 있습니다. 그런 다음 계산된 가격을 사용하여 업데이트합니다.self.prices배열, 가장 오래된 데이터 제거(shift()기능), 최신 데이터 업데이트(push()함수, 시프트, 푸시 함수는 JS 언어 배열 객체의 메서드입니다. 자세한 내용은 JS 정보를 확인할 수 있습니다. 그리하여 형성하다self.prices배열은 시계열로 정렬된 데이터 스트림입니다.

어휴, 물 좀 마시세요. 저는 여기서 멈추고 다음에 봐요~