최근에는print money
FMZ Quant WeChat 그룹에 있는 로봇입니다. 아주 오래된 전략이 다시 퀀트의 눈에 들어왔습니다.
로봇 거래 원칙print money
그래서, 저는 원래 전략을 다시 신중하게 읽고 FMZ Quant에서 이식된 OKCoin leeksreaper의 이식된 버전을 보았습니다.
FMZ 퀀트 플랫폼을 기반으로 한 이식 류리퍼의 전략은 전략의 아이디어를 탐구하기 위해 분석됩니다. 플랫폼 사용자가이 전략의 아이디어를 배울 수 있도록.
이 기사에서는 프로그래밍과 관련된 지루한 콘텐츠를 최소화하기 위해 전략 아이디어와 의도의 측면에서 더 많이 분석 할 것입니다.
[OKCoin LeeksReaper 이식] 전략의 소스 코드:
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 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 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 전략 논리 모듈 (물질) 의 구성 요소로 이해할 수 있습니다. 간단히 말해서,LeeksReaper()
키워드:
· 전략의 첫 번째 라인main
기능:var reaper = LeeksReaper()
, 코드는 로컬 변수를 선언합니다.reaper
그리고 나서 LeeksReaper() 함수를 호출하여 전략 논리 객체를 구성합니다.reaper
.
전략의 다음 단계main
기능:
while (true) {
reaper.poll()
Sleep(TickInterval)
}
a를 입력합니다while
끝없는 루프 및 처리 기능을 실행 유지poll()
의reaper
대상은,poll()
이 함수는 바로 거래 전략의 주요 논리가 있는 곳이고, 전체 전략 프로그램은 거래 논리를 반복적으로 실행하기 시작합니다.
라인에 대해서는Sleep(TickInterval)
, 그것은 이해하기 쉽다, 그것은 전체 거래 논리의 실행 후 휴식 시간을 제어하기 위해 거래 논리의 회전 주파수를 제어하는 것입니다.
LeeksReaper()
제작자어떻게LeeksReaper()
함수는 전략 논리 객체를 구성합니다.
이LeeksReaper()
함수는 빈 객체를 선언함으로써 시작됩니다.var self = {}
, 그리고 그 집행 동안LeeksReaper()
함수는 점진적으로 이 빈 객체에 몇 가지 메소드와 속성을 추가하고, 마침내 이 객체의 구축을 완료하고 반환합니다 (즉,main()
함수 내부var reaper = LeeksReaper()
, 반환된 객체는reaper
).
속성을 추가합니다self
물체
다음으로, 저는 많은 속성을 추가했습니다.self
각 속성을 아래와 같이 설명하겠습니다. 이 속성과 변수의 목적과 의도를 빠르게 이해할 수 있고, 전략에 대한 이해를 용이하게 할 수 있으며, 코드를 볼 때 혼란을 피할 수 있습니다.
self.numTick = 0 # It is used to record the number of transactions not triggered when the poll function is called. When the order is triggered and the order logic is executed, self.numTick is reset to 0
self.lastTradeId = 0 # The transaction record ID of the order that has been transacted in the transaction market. This variable records the current transaction record ID of the market
self.vol = 0 # Reference to the trading volume of each market inspection after weighted average calculation (market data is obtained once per loop, which can be interpreted as a time of market inspection)
self.askPrice = 0 # The bill of lading price of the sales order can be understood as the price of the listing order after the strategy is calculated
self.bidPrice = 0 # Purchase order bill of lading price
self.orderBook = {Asks:[], Bids:[]} # Record the currently obtained order book data, that is, depth data (sell one... sell n, buy one... buy n)
self.prices = [] # An array that records the prices on the time series after the calculation of the first three weighted averages in the order book, which means that each time the first three weighted averages of the order book are stored, they are placed in an array and used as a reference for subsequent strategy trading signals, so the variable name is prices, in plural form, indicating a set of prices
self.tradeOrderId = 0 # Record the order ID after the current bill of lading is placed
self.p = 0.5 # Position proportion: when the value of currency accounts for exactly half of the total asset value, the value is 0.5, that is, the equilibrium state
self.account = null # Record the account asset data, which is returned by the GetAccount() function
self.preCalc = 0 # Record the timestamp of the last time when the revenue was calculated, in milliseconds, to control the frequency of triggering the execution of the revenue calculation code
self.preNet = 0 # Record current return values
이 속성을 self에 추가한 후,self
이 객체가 어떤 작업을 수행하고 어떤 기능을 가질 수 있도록
첫 번째 함수는 이렇게 덧붙였습니다.
self.updateTrades = function() {
var trades = _C(exchange.GetTrades) # Call the FMZ encapsulated interface GetTrades to obtain the latest market transaction data
if (self.prices.length == 0) { # When self.prices.length == 0, the self.prices array needs to be filled with numeric values, which will be triggered only when the strategy starts running
while (trades.length == 0) { # If there is no recent transaction record in the market, the while loop will keep executing until the latest transaction data is available and update the trades variable
trades = trades.concat(_C(exchange.GetTrades)) # concat is a method of JS array type, which is used to concatenate two arrays, here is to concatenate the "trades" array and the array data returned by "_C(exchange.GetTrades)" into one array
}
for (var i = 0; i < 15; i++) { # Fill in data to self.prices, and fill in 15 pieces of latest transaction prices
self.prices[i] = trades[trades.length - 1].Price
}
}
self.vol = 0.7 * self.vol + 0.3 * _.reduce(trades, function(mem, trade) { # _. Reduce function is used for iterative calculation to accumulate the amount of the latest transaction records
// 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.js 라이브러리의 함수입니다. FMZJS 전략은 이 라이브러리를 지원하므로 반복 계산에 매우 편리합니다. Underscore.js 데이터 링크 (https://underscorejs.net/#reduce)
그 의미는 또한 매우 간단합니다. 예를 들어:
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) 에 대해, 그것은 개척의 중심으로 약간 오프셋을 해야 합니다.
· 시간 계열에 대한 주문 포크의 첫 세 레벨의 가중 평균 가격을 업데이트합니다. 주문책의 첫 세 단계의 구매 및 판매 주문 가격에 대해 가중된 평균을 계산합니다. 첫 번째 레벨의 무게는 0.7, 두 번째 레벨의 무게는 0.2, 세 번째 레벨의 무게는 0.1. 누군가가 말할 수 있습니다. "오, 아니, 코드에 0.7, 0.2, 0.1가 있습니다". 계산을 확장해보죠.
(Buy one+Sell one) * 0.35+(Buy two+Sell two) * 0.1+(Buy three+Sell three) * 0.05
->
(Buy one+sell one)/2 * 2 * 0.35+(Buy two+sell two)/2 * 2 * 0.1+(Buy three+sell three)/2 * 2 * 0.05
->
(Buy one+sell one)/2 * 0.7+(Buy two+sell two)/2 * 0.2+(Buy three+sell three)/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
여기서 볼 수 있듯이, 최종 계산된 가격은 실제로 현재 시장에서 세 번째 개방의 중간 가격 위치에 대한 반응입니다.
이 계산된 가격을 사용하여 배열을 업데이트합니다.self.prices
, 가장 오래된 데이터 중 하나를 shift()
기능) 를 통해 최신 데이터 중 하나를 업데이트하고push()
함수, 이동 및 푸시 함수는 JS 언어 배열 객체의 방법입니다, 당신은 세부 사항에 대한 JS 데이터를 확인할 수 있습니다.) 따라서 배열을 형성self.prices
, 시간 계열 순서를 가진 데이터 스트림입니다.
그럼 여기서 쉬자 다음 번 보자~