4
Подписаться
1271
Подписчики

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Создано: 2021-06-18 18:24:23, Обновлено: 2024-12-04 21:16:56
comments   6
hits   4042

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

В предыдущей статье мы разработали стратегию мониторинга спреда контрактов с несколькими вариантами. В этой статье мы продолжим совершенствовать эту идею. Давайте посмотрим, осуществим ли эту идею, и запустим ее на платформе моделирования OKEX V5, чтобы проверить дизайн стратегии. Эти процессы также необходимо пройти в процессе программной торговли цифровой валютой и количественной торговли. Я надеюсь, что новички смогут накопить ценный опыт.

Позвольте мне сначала рассказать вам спойлер: стратегия работает, и я немного взволнован!

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Общая схема стратегии реализована максимально просто. Хотя чрезмерных требований к деталям нет, из кода все равно можно почерпнуть некоторые подсказки. Весь код стратегии занимает менее 400 строк, поэтому его нескучно читать и понимать. Конечно, это всего лишь тестовая ДЕМО-версия, и вам нужно запустить ее на некоторое время, чтобы протестировать. Итак, что я хочу сказать: текущая стратегия предназначена только для успешного открытия позиции, а различные ситуации, такие как закрытие позиции, должны быть фактически протестированы и проверены. Ошибки неизбежны в разработке программ, поэтому тестирование и отладка очень важны!

Возвращаясь к разработке стратегии, основанной на коде из предыдущей статьи, стратегия добавляется с помощью:

  • Проектирование сохранения данных (использование функции _G для сохранения данных и восстановления данных после перезапуска)
  • Добавлена ​​структура данных сетки для каждой отслеживаемой пары контрактных спредов (используется для управления открытием и закрытием хеджирования)
  • Реализована простая функция хеджирования для хеджирования открытия и закрытия позиций.
  • Добавлена ​​функция приобретения общего капитала для расчета плавающей прибыли и убытка.
  • Добавлено отображение вывода данных в строке состояния.

Выше приведены дополнительные функции. Для упрощения конструкции стратегия предназначена только для положительного хеджирования (короткие форвардные контракты, длинные краткосрочные контракты). В настоящее время бессрочный контракт (в ближайшей перспективе) имеет отрицательную ставку, поэтому сейчас самое время открыть длинную позицию по бессрочному контракту, чтобы посмотреть, можно ли увеличить доход от ставки.

Дайте стратегии поработать некоторое время~

После трехдневного тестирования разница в цене оказалась вполне приемлемой.

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Вы видите, что ставка финансирования приносит определенный доход.

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Давайте поделимся исходным кодом стратегии:

var arrNearContractType = strNearContractType.split(",")
var arrFarContractType = strFarContractType.split(",")

var nets = null
var initTotalEquity = null 
var OPEN_PLUS = 1
var COVER_PLUS = 2


function createNet(begin, diff, initAvgPrice, diffUsagePercentage) {
    if (diffUsagePercentage) {
        diff = diff * initAvgPrice
    }
    var oneSideNums = 3
    var up = []
    var down = []
    for (var i = 0 ; i < oneSideNums ; i++) {
        var upObj = {
            sell : false, 
            price : begin + diff / 2 + i * diff
        }
        up.push(upObj)

        var j = (oneSideNums - 1) - i
        var downObj = {
            sell : false,
            price : begin - diff / 2 - j * diff
        }
        if (downObj.price <= 0) {  // 价格不能小于等于0 
            continue
        }
        down.push(downObj)
    }
    return down.concat(up)
}

function createCfg(symbol) {
    var cfg = {
        extension: {
            layout: 'single', 
            height: 300,
            col: 6
        },
        title: {
            text: symbol
        },
        xAxis: {
            type: 'datetime'
        },
        series: [{
            name: 'plus',
            data: []
        }]
    }
    return cfg
}

function formatSymbol(originalSymbol) {
    var arr = originalSymbol.split("-")
    return [arr[0] + "_" + arr[1], arr[0], arr[1]]
}

function main() {	
    if (isSimulate) {
    	exchange.IO("simulate", true)  // 切换为模拟环境
    	Log("仅支持OKEX V5 API,切换为OKEX V5 模拟盘:")
    } else {
    	exchange.IO("simulate", false)  // 切换为实盘
    	Log("仅支持OKEX V5 API,切换为OKEX V5 实盘:")
    }    
    if (exchange.GetName() != "Futures_OKCoin") {
    	throw "支持OKEX期货"
    }

    // 初始化
    if (isReset) {
        _G(null)
        LogReset(1)
        LogProfitReset()
        LogVacuum()
        Log("重置所有数据", "#FF0000")
    }

    // 初始化标记
    var isFirst = true 

    // 收益打印周期
    var preProfitPrintTS = 0
    // 总权益
    var totalEquity = 0
    var posTbls = []   // 持仓表格数组

    // 声明arrCfg
    var arrCfg = []
    _.each(arrNearContractType, function(ct) {
        arrCfg.push(createCfg(formatSymbol(ct)[0]))
    })
    var objCharts = Chart(arrCfg)
    objCharts.reset()
    
    // 创建对象
    var exName = exchange.GetName() + "_V5"
    var nearConfigureFunc = $.getConfigureFunc()[exName]
    var farConfigureFunc = $.getConfigureFunc()[exName]
    var nearEx = $.createBaseEx(exchange, nearConfigureFunc)
    var farEx = $.createBaseEx(exchange, farConfigureFunc)

    // 预先写入需要订阅的合约
    _.each(arrNearContractType, function(ct) {
        nearEx.pushSubscribeSymbol(ct)
    })
    _.each(arrFarContractType, function(ct) {
        farEx.pushSubscribeSymbol(ct)
    })

    while (true) {
        var ts = new Date().getTime()
        // 获取行情数据
        nearEx.goGetTickers()
        farEx.goGetTickers()
        var nearTickers = nearEx.getTickers()
        var farTickers = farEx.getTickers()  
        if (!farTickers || !nearTickers) {
            Sleep(2000)
            continue
        }

        var tbl = {
            type : "table",
            title : "远期-近期差价",
            cols : ["交易对", "远期", "近期", "正对冲", "反对冲"],
            rows : []
        }        
        
        var subscribeFarTickers = []
        var subscribeNearTickers = []
        _.each(farTickers, function(farTicker) {
            _.each(arrFarContractType, function(symbol) {
                if (farTicker.originalSymbol == symbol) {
                    subscribeFarTickers.push(farTicker)
                }
            })
        })

        _.each(nearTickers, function(nearTicker) {
            _.each(arrNearContractType, function(symbol) {
                if (nearTicker.originalSymbol == symbol) {
                    subscribeNearTickers.push(nearTicker)
                }
            })
        })

        var pairs = []        
        _.each(subscribeFarTickers, function(farTicker) {
            _.each(subscribeNearTickers, function(nearTicker) {                
                if (farTicker.symbol == nearTicker.symbol) {
                    var pair = {symbol: nearTicker.symbol, nearTicker: nearTicker, farTicker: farTicker, plusDiff: farTicker.bid1 - nearTicker.ask1, minusDiff: farTicker.ask1 - nearTicker.bid1}
                    pairs.push(pair)
                    tbl.rows.push([pair.symbol, farTicker.originalSymbol, nearTicker.originalSymbol, pair.plusDiff, pair.minusDiff])
                    for (var i = 0 ; i < arrCfg.length ; i++) {
                        if (arrCfg[i].title.text == pair.symbol) {
                            objCharts.add([i, [ts, pair.plusDiff]])
                        }                        
                    }
                }
            })
        })

        // 初始化
        if (isFirst) {
            isFirst = false 
            var recoveryNets = _G("nets")
            var recoveryInitTotalEquity = _G("initTotalEquity")
            if (!recoveryNets) {
                // 检查持仓
                _.each(subscribeFarTickers, function(farTicker) {
                    var pos = farEx.getFuPos(farTicker.originalSymbol, ts)
                    if (pos.length != 0) {
                        Log(farTicker.originalSymbol, pos)
                        throw "初始化时有持仓"
                    }
                })
                _.each(subscribeNearTickers, function(nearTicker) {
                    var pos = nearEx.getFuPos(nearTicker.originalSymbol, ts)
                    if (pos.length != 0) {
                        Log(nearTicker.originalSymbol, pos)
                        throw "初始化时有持仓"
                    }
                })                
                // 构造nets
                nets = []
                _.each(pairs, function (pair) {
                    farEx.goGetAcc(pair.farTicker.originalSymbol, ts)
                    nearEx.goGetAcc(pair.nearTicker.originalSymbol, ts)
                    var obj = {
                        "symbol" : pair.symbol, 
                        "farSymbol" : pair.farTicker.originalSymbol,
                        "nearSymbol" : pair.nearTicker.originalSymbol,
                        "initPrice" : (pair.nearTicker.ask1 + pair.farTicker.bid1) / 2, 
                        "prePlus" : pair.farTicker.bid1 - pair.nearTicker.ask1,                        
                        "net" : createNet((pair.farTicker.bid1 - pair.nearTicker.ask1), diff, (pair.nearTicker.ask1 + pair.farTicker.bid1) / 2, true), 
                        "initFarAcc" : farEx.getAcc(pair.farTicker.originalSymbol, ts), 
                        "initNearAcc" : nearEx.getAcc(pair.nearTicker.originalSymbol, ts),
                        "farTicker" : pair.farTicker,
                        "nearTicker" : pair.nearTicker,
                        "farPos" : null, 
                        "nearPos" : null,
                    }
                    nets.push(obj)
                })
                var currTotalEquity = getTotalEquity()
                if (currTotalEquity) {
                	initTotalEquity = currTotalEquity
                } else {
                	throw "初始化获取总权益失败!"
                }                
            } else {
                // 恢复
                nets = recoveryNets
                initTotalEquity = recoveryInitTotalEquity
            }
        }

        // 检索网格,检查是否触发交易
        _.each(nets, function(obj) {
            var currPlus = null
            _.each(pairs, function(pair) {
                if (pair.symbol == obj.symbol) {
                    currPlus = pair.plusDiff
                    obj.farTicker = pair.farTicker
                    obj.nearTicker = pair.nearTicker
                }
            })
            if (!currPlus) {
                Log("没有查询到", obj.symbol, "的差价")
                return 
            }

            // 检查网格,动态添加
            while (currPlus >= obj.net[obj.net.length - 1].price) {
                obj.net.push({
                    sell : false,
                    price : obj.net[obj.net.length - 1].price + diff * obj.initPrice,
                })
            }
            while (currPlus <= obj.net[0].price) {
                var price = obj.net[0].price - diff * obj.initPrice
                if (price <= 0) {
                    break
                }
                obj.net.unshift({
                    sell : false,
                    price : price,
                })
            }
            
            // 检索网格
            for (var i = 0 ; i < obj.net.length - 1 ; i++) {
                var p = obj.net[i]
                var upP = obj.net[i + 1]
                if (obj.prePlus <= p.price && currPlus > p.price && !p.sell) {
                    if (hedge(nearEx, farEx, obj.nearSymbol, obj.farSymbol, obj.nearTicker, obj.farTicker, hedgeAmount, OPEN_PLUS)) {   // 正对冲开仓
                        p.sell = true 
                    }
                } else if (obj.prePlus >= p.price && currPlus < p.price && upP.sell) {
                    if (hedge(nearEx, farEx, obj.nearSymbol, obj.farSymbol, obj.nearTicker, obj.farTicker, hedgeAmount, COVER_PLUS)) {   // 正对冲平仓
                        upP.sell = false 
                    }
                }
            }
            obj.prePlus = currPlus  // 记录本次差价,作为缓存,下次用于判断上穿下穿
            // 增加其它表格输出
        })        

        if (ts - preProfitPrintTS > 1000 * 60 * 5) {   // 5分钟打印一次       
        	var currTotalEquity = getTotalEquity()
        	if (currTotalEquity) {
        		totalEquity = currTotalEquity
        		LogProfit(totalEquity - initTotalEquity, "&")   // 打印动态权益收益
        	}

        	// 检查持仓
        	posTbls = []  // 重置,更新
            _.each(nets, function(obj) {
                var currFarPos = farEx.getFuPos(obj.farSymbol)
                var currNearPos = nearEx.getFuPos(obj.nearSymbol)
                if (currFarPos && currNearPos) {
                	obj.farPos = currFarPos
                	obj.nearPos = currNearPos
                }
                var posTbl = {
                	"type" : "table", 
                	"title" : obj.symbol, 
                	"cols" : ["合约代码", "数量", "价格"], 
                	"rows" : [] 
                }
                _.each(obj.farPos, function(pos) {
                    posTbl.rows.push([pos.symbol, pos.amount, pos.price])
                })  
                _.each(obj.nearPos, function(pos) {
                	posTbl.rows.push([pos.symbol, pos.amount, pos.price])
                })
                posTbls.push(posTbl)
            })

            preProfitPrintTS = ts
        }

        // 显示网格
        var netTbls = []
        _.each(nets, function(obj) {
            var netTbl = {
            	"type" : "table",
            	"title" : obj.symbol,
            	"cols" : ["网格"],
            	"rows" : []
            }
            _.each(obj.net, function(p) {
            	var color = ""
            	if (p.sell) {
            		color = "#00FF00"
            	}
            	netTbl.rows.push([JSON.stringify(p) + color])
            })
            netTbl.rows.reverse()
            netTbls.push(netTbl)
        })

        LogStatus(_D(), "总权益:", totalEquity, "初始总权益:", initTotalEquity, " 浮动盈亏:", totalEquity - initTotalEquity, 
        	"\n`" + JSON.stringify(tbl) + "`" + "\n`" + JSON.stringify(netTbls) + "`" + "\n`" + JSON.stringify(posTbls) + "`")
        Sleep(interval)
    }
}

function getTotalEquity() {
    var totalEquity = null 
    var ret = exchange.IO("api", "GET", "/api/v5/account/balance", "ccy=USDT")
    if (ret) {
        try {
        	totalEquity = parseFloat(ret.data[0].details[0].eq)
        } catch(e) {
        	Log("获取账户总权益失败!")
        	return null
        }
    }
    return totalEquity
}

function hedge(nearEx, farEx, nearSymbol, farSymbol, nearTicker, farTicker, amount, tradeType) {
    var farDirection = null
    var nearDirection = null
    if (tradeType == OPEN_PLUS) {
        farDirection = farEx.OPEN_SHORT
        nearDirection = nearEx.OPEN_LONG
    } else {
        farDirection = farEx.COVER_SHORT
        nearDirection = nearEx.COVER_LONG
    }
    var nearSymbolInfo = nearEx.getSymbolInfo(nearSymbol) 
    var farSymbolInfo = farEx.getSymbolInfo(farSymbol)
    nearAmount = nearEx.calcAmount(nearSymbol, nearDirection, nearTicker.ask1, amount * nearSymbolInfo.multiplier)
    farAmount = farEx.calcAmount(farSymbol, farDirection, farTicker.bid1, amount * farSymbolInfo.multiplier)
    if (!nearAmount || !farAmount) {
        Log(nearSymbol, farSymbol, "下单量计算错误:", nearAmount, farAmount)
        return 
    }
    nearEx.goGetTrade(nearSymbol, nearDirection, nearTicker.ask1, nearAmount[0])
    farEx.goGetTrade(farSymbol, farDirection, farTicker.bid1, farAmount[0])
    var nearIdMsg = nearEx.getTrade()
    var farIdMsg = farEx.getTrade()
    return [nearIdMsg, farIdMsg]
}

function onexit() {
	Log("执行扫尾函数", "#FF0000")
    _G("nets", nets)
    _G("initTotalEquity", initTotalEquity)
    Log("保存数据:", _G("nets"), _G("initTotalEquity"))
}

Новички в количественной торговле в криптовалютных кругах, пожалуйста, ознакомьтесь с этим - Приближаемся к количественной торговле в криптовалютных кругах (VIII)

Публичный адрес стратегии: https://www.fmz.com/strategy/288559

Стратегия использует библиотеку шаблонов, которую я написал сам. Поскольку она не очень хорошо написана, я не буду ее публиковать. Исходный код стратегии выше можно изменить, чтобы не использовать этот шаблон.

Если вам интересно, вы можете настроить диск моделирования OKEX V5 для тестирования. ой! Кстати, эту стратегию невозможно протестировать на исторических данных.