Type/to search
8
Follow
1369
Followers
Разработка реальной рыночной имитационной торговой системы на основе количественной платформы FMZ
Discussions
Created 2025-04-30 16:59:01  Updated 2025-04-30 17:53:06
 1
 1352

img

Предисловие

В данной статье описывается разработка и реализация PaperTrader — системы имитационной торговли, основанной на количественной платформе FMZ и управляемой реальными рыночными условиями. Система сопоставляет заказы с учетом рыночных условий в режиме реального времени, полностью имитирует торговые процессы, такие как размещение стратегического заказа, транзакции, изменение активов и обработка комиссий, поддерживает рыночные/лимитные заказы, заморозку активов и архивацию аннулирований, а также подходит для тестирования стратегий и проверки реального поведения перед реальной торговлей. В этой статье подробно описывается концепция дизайна и ключевая реализация с точки зрения архитектуры системы, механизма сопоставления, совместимости интерфейсов и т. д., а также приводятся полные практические демонстрационные примеры использования, которые помогут количественным стратегиям создать безопасную и надежную «промежуточную песочницу» перед выходом в Интернет.

Анализ и проектирование требований PaperTrader

Болевые точки спроса:

  • Биржевая имитация торговли хаотична и нереалистична.
  • Подача заявки на создание имитационного счета на бирже обременительна, как и получение тестовых средств.
  • Многие биржи не предоставляют тестовую среду.

Проблема «серой зоны» между бэктестингом и реальной торговлей

Зачем вам нужна имитационная торговая система?

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

Цели дизайна и ключевые особенности

    1. Движение рынка в реальном времени
      Используйте FMZ для доступа к реальным биржевым котировкам, включая GetTicker(), GetDepth() и десятки других интерфейсов.
    1. Размещение и вычет заказа моделирования
      Поддерживает лимитные/рыночные ордера, отдельный вычет комиссий мейкера/тейкера и использует фактическую рассчитанную сумму ввода для рыночных ордеров на покупку.
    1. Управление активами/позициями/заказами
      Он поддерживает заморозку активов при размещении ордеров и их возврат при отмене ордеров, а также поддерживает поддержание нескольких активов/позиций/ордеров на основе уровня символа.
    1. Полный цикл заказа
      Заказы четко управляются с момента создания, ожидания, исполнения до отмены. После выполнения они автоматически архивируются в локальной базе данных для поддержки будущих запросов и анализа.
    1. Пользовательский опыт
      Стратегия не требует изменения вызовов ордеров и может реализовывать имитацию торговли, просто заменив объект биржи на PaperTrader.

Обзор дизайна библиотеки

Система в основном состоит из трех частей:

  • Класс PaperTrader
    Основной счет моделирования включает в себя ведение данных, таких как активы, заказы, позиции, рыночные условия и конфигурации.

  • [движок сопоставления simEngine]:
    Фоновый поток, сканирует текущий ордер в соответствии с глубиной рынка и выполняет операции

  • 【Архив базы данных】:
    Запись выполненных/отмененных заказов в локальную базу данных для последующего анализа и проверки.

Соответствующая конструкция двигателя

simEngine(data, lock) — это ядро ​​всей системы моделирования. Он сопоставляет текущие отложенные ордера в цикле в соответствии с фактическими данными о глубине рынка, обеспечивая точные результаты моделирования для транзакций.

Основной процесс включает в себя:

  • Получайте текущие отложенные ордера, позиции, активы и рыночные условия;
  • Получить глубину всех используемых символов GetDepth;
  • Просматривать отложенные ордера и выбирать глубину операции (аски/биды) в зависимости от направления (покупка/продажа);
  • Определить, завершена ли сделка, на основании того, соответствует ли цена условиям эксплуатации;
  • Если транзакция завершена, обновите и посчитайте информацию о заказе, такую ​​как AvgPrice / DealAmount;
  • Вычтите комиссию за обработку в зависимости от производителя/получателя.
  • Если все заказы выполнены, заказ будет заархивирован; в противном случае он останется нерассмотренным.

Совместимость информации об интерфейсе

PaperTrader разработан таким образом, чтобы максимально соответствовать реальному торговому интерфейсу платформы FMZ, включая, помимо прочего:

Классификацияинтерфейсописывать
Интерфейс заказаBuy(price, amount) / Sell(price, amount) / CreateOrder(symbol, side, price, amount)Заказать операцию
Интерфейс рынкаGetTicker() / GetDepth() / GetRecords() / GetTrades()Напрямую запросите реальную рыночную цену обмена
Интерфейс заказаGetOrders() / CancelOrder(id) / GetOrder(id)Для заказных операций
Интерфейс учетной записи и позицииGetAccount() / GetAssets() / GetPositions()Для операций по счету
Другие настройки интерфейсаSetCurrency() / SetDirection()Другие настройки

Такая конструкция позволяет запускать логику стратегии непосредственно в имитируемой торговой среде без каких-либо изменений. Заменив биржу на PaperTrader одним щелчком мыши, вы можете перенести стратегию на «средний уровень» между бэктестингом и реальной торговлей.

Исходный код дизайна PaperTrader

javascript
class PaperTrader { constructor(exIdx, realExchange, assets, fee) { this.exIdx = exIdx this.e = realExchange this.name = realExchange.GetName() + "_PaperTrader" this.currency = realExchange.GetCurrency() this.baseCurrency = this.currency.split("_")[0] this.quoteCurrency = this.currency.split("_")[1] this.period = realExchange.GetPeriod() this.fee = fee // 数据同步锁 this.data = threading.Dict() this.dataLock = threading.Lock() // 初始化this.data this.data.set("assets", assets) this.data.set("orders", []) this.data.set("positions", []) // exchangeData let exchangeData = { "exIdx": this.exIdx, "fee": this.fee } // exchange Type if (this.name.includes("Futures_")) { this.exchangeType = "Futures" this.direction = "buy" this.marginLevel = 10 this.contractType = "swap" this.e.SetContractType(this.contractType) // set exchangeData exchangeData["exchangeType"] = this.exchangeType exchangeData["marginLevel"] = this.marginLevel } else { this.exchangeType = "Spot" // set exchangeData exchangeData["exchangeType"] = this.exchangeType } // 记录交易所相关信息,用于传入撮合引擎 this.data.set("exchangeData", exchangeData) // database this.historyOrdersTblName = "HISTORY_ORDER" this.data.set("historyOrdersTblName", this.historyOrdersTblName) // init this.init() } // export SetCurrency(currency) { let arrCurrency = currency.split("_") if (arrCurrency.length != 2) { this.e.Log(3, null, null, `invalid currency: ${currency}`) return } this.currency = currency this.baseCurrency = arrCurrency[0] this.quoteCurrency = arrCurrency[1] return this.e.SetCurrency(currency) } SetContractType(contractType) { if (this.exchangeType == "Spot") { this.e.Log(3, null, null, `not support`) return } if (!this.isValidContractType(contractType)) { this.e.Log(3, null, null, `invalid contractType: ${contractType}`) return } this.contractType = contractType return this.e.SetContractType(contractType) } SetDirection(direction) { if (this.exchangeType == "Spot") { this.e.Log(3, null, null, `not support`) return } if (direction != "buy" && direction != "sell" && direction != "closebuy" && direction != "closesell") { this.e.Log(3, null, null, `invalid direction: ${direction}`) return } this.direction = direction return this.e.SetDirection(direction) } GetTicker(...args) { return this.e.GetTicker(...args) } GetDepth(...args) { return this.e.GetDepth(...args) } GetTrades(...args) { return this.e.GetTrades(...args) } GetRecords(...args) { return this.e.GetRecords(...args) } GetMarkets() { return this.e.GetMarkets() } GetTickers() { return this.e.GetTickers() } GetFundings(...args) { if (this.exchangeType == "Spot") { this.e.Log(3, null, null, `not support`) return } return this.e.GetFundings(...args) } GetAccount() { let assets = this.data.get("assets") let acc = {"Balance": 0, "FrozenBalance": 0, "Stocks": 0, "FrozenStocks": 0} for (let asset of assets) { if (this.exchangeType == "Futures") { if (this.quoteCurrency == "USDT" || this.quoteCurrency == "USDC") { if (asset["Currency"] == this.quoteCurrency) { return {"Balance": asset["Amount"], "FrozenBalance": asset["FrozenAmount"], "Stocks": 0, "FrozenStocks": 0} } } else if (this.quoteCurrency == "USD") { if (asset["Currency"] == this.baseCurrency) { return {"Balance": 0, "FrozenBalance": 0, "Stocks": asset["Amount"], "FrozenStocks": asset["FrozenAmount"]} } } } else if (this.exchangeType == "Spot") { if (asset["Currency"] == this.baseCurrency) { // Stocks acc["Stocks"] = asset["Amount"] acc["FrozenStocks"] = asset["FrozenAmount"] } else if (asset["Currency"] == this.quoteCurrency) { // Balance acc["Balance"] = asset["Amount"] acc["FrozenBalance"] = asset["FrozenAmount"] } } } return acc } GetAssets() { let assets = this.data.get("assets") return assets } GetOrders(symbol) { let ret = [] let orders = this.data.get("orders") if (this.exchangeType == "Spot") { if (typeof(symbol) == "undefined") { return orders } else { let arrCurrency = symbol.split("_") if (arrCurrency.length != 2) { this.e.Log(3, null, null, `invalid symbol: ${symbol}`) return null } for (let o of orders) { if (o.Symbol == symbol) { ret.push(o) } } return ret } } else if (this.exchangeType == "Futures") { if (typeof(symbol) == "undefined") { for (let o of orders) { if (o.Symbol.includes(`${this.quoteCurrency}.${this.contractType}`)) { ret.push(o) } } return ret } else { let arr = symbol.split(".") if (arr.length != 2) { this.e.Log(3, null, null, `invalid symbol: ${symbol}`) return null } let currency = arr[0] let contractType = arr[1] let arrCurrency = currency.split("_") if (arrCurrency.length != 2) { for (let o of orders) { if (o.Symbol.includes(`${arrCurrency[0]}.${contractType}`)) { ret.push(o) } } } else { for (let o of orders) { if (o.Symbol == symbol) { ret.push(o) } } } return ret } } else { this.e.Log(3, null, null, `invalid exchangeType: ${this.exchangeType}`) return null } } GetOrder(orderId) { let data = DBExec(`SELECT ORDERDATA FROM ${this.historyOrdersTblName} WHERE ID = ?`, orderId) // {"columns":["ORDERDATA"],"values":[]} if (!data) { this.e.Log(3, null, null, `Order not found: ${orderId}`) return null } if (data && Array.isArray(data["values"]) && data["values"].length <= 0) { this.e.Log(3, null, null, `Order not found: ${orderId}`) return null } else if (data["values"].length != 1) { this.e.Log(3, null, null, `invalid data: ${data["values"]}`) return null } else { let ret = this.parseJSON(data["values"][0]) if (!ret) { this.e.Log(3, null, null, `invalid data: ${data["values"]}`) return null } return ret } } Buy(price, amount) { return this.trade("Buy", price, amount) } Sell(price, amount) { return this.trade("Sell", price, amount) } trade(tradeType, price, amount) { if (this.exchangeType == "Spot") { let side = "" if (tradeType == "Buy") { side = "buy" } else if (tradeType == "Sell") { side = "sell" } else { this.e.Log(3, null, null, `invalid tradeType: ${tradeType}`) return null } let symbol = this.currency return this.createOrder(symbol, side, price, amount) } else if (this.exchangeType == "Futures") { let compose = `${tradeType}_${this.direction}` if (compose != "Sell_closebuy" && compose != "Sell_sell" && compose != "Buy_buy" && compose != "Buy_closesell") { this.e.Log(3, null, null, `${tradeType}, invalid direction: ${this.direction}`) return null } let side = this.direction let symbol = `${this.currency}.${this.contractType}` return this.createOrder(symbol, side, price, amount) } else { this.e.Log(3, null, null, `invalid exchangeType: ${this.exchangeType}`) return } } CreateOrder(symbol, side, price, amount) { if (side != "buy" && side != "sell" && side != "closebuy" && side != "closesell") { this.e.Log(3, null, null, `invalid direction: ${side}`) return null } if (this.exchangeType == "Spot") { if (side == "closebuy") { side = "sell" } else if (side == "closesell") { side = "buy" } } return this.createOrder(symbol, side, price, amount) } createOrder(symbol, side, price, amount) { this.dataLock.acquire() let isError = false let orders = this.data.get("orders") let positions = this.data.get("positions") let assets = this.data.get("assets") // 检查amount if (amount <= 0) { this.e.Log(3, null, null, `invalid amount: ${amount}`) return null } // 构造订单 let order = { "Info": null, "Symbol": symbol, "Price": price, "Amount": amount, "DealAmount": 0, "AvgPrice": 0, "Status": ORDER_STATE_PENDING, "ContractType": symbol.split(".").length == 2 ? symbol.split(".")[1] : "" } let logType = null switch (side) { case "buy": order["Type"] = ORDER_TYPE_BUY order["Offset"] = ORDER_OFFSET_OPEN logType = LOG_TYPE_BUY break case "sell": order["Type"] = ORDER_TYPE_SELL order["Offset"] = ORDER_OFFSET_OPEN logType = LOG_TYPE_SELL break case "closebuy": order["Type"] = ORDER_TYPE_SELL order["Offset"] = ORDER_OFFSET_CLOSE logType = LOG_TYPE_SELL break case "closesell": order["Type"] = ORDER_TYPE_BUY order["Offset"] = ORDER_OFFSET_CLOSE logType = LOG_TYPE_BUY break default: this.e.Log(3, null, null, `invalid direction: ${side}`) isError = true } if (isError) { return null } // 检查资产/持仓,资产/持仓不足报错 let needAssetName = "" let needAsset = 0 if (this.exchangeType == "Futures") { // 检查资产、持仓 // to do } else if (this.exchangeType == "Spot") { // 检查资产 let arr = symbol.split(".") if (arr.length == 2) { this.e.Log(3, null, null, `invalid symbol: ${symbol}`) return null } let currency = arr[0] let arrCurrency = currency.split("_") if (arrCurrency.length != 2) { this.e.Log(3, null, null, `invalid symbol: ${symbol}`) return null } let baseCurrency = arrCurrency[0] let quoteCurrency = arrCurrency[1] needAssetName = side == "buy" ? quoteCurrency : baseCurrency if (side == "buy" && price <= 0) { // market order of buy, amount is quantity by quoteCurrency needAsset = amount } else { // limit order, amount is quantity by baseCurrency needAsset = side == "buy" ? price * amount : amount } let canPostOrder = false for (let asset of assets) { if (asset["Currency"] == needAssetName && asset["Amount"] >= needAsset) { canPostOrder = true } } if (!canPostOrder) { this.e.Log(3, null, null, `insufficient balance for ${needAssetName}, need: ${needAsset}, Account: ${JSON.stringify(assets)}`) return null } } else { this.e.Log(3, null, null, `invalid exchangeType: ${this.exchangeType}`) return null } // 生成订单ID, UnixNano() 使用纳秒时间戳 let orderId = this.generateOrderId(symbol, UnixNano()) order["Id"] = orderId // 更新pending中的订单记录 orders.push(order) this.data.set("orders", orders) // 输出日志记录 if (this.exchangeType == "Futures") { this.e.SetDirection(side) } this.e.Log(logType, price, amount, `orderId: ${orderId}`) // 更新资产 for (let asset of assets) { if (asset["Currency"] == needAssetName) { asset["Amount"] -= needAsset asset["FrozenAmount"] += needAsset } } this.data.set("assets", assets) this.dataLock.release() return orderId } CancelOrder(orderId) { this.dataLock.acquire() let orders = this.data.get("orders") let assets = this.data.get("assets") let positions = this.data.get("positions") let targetIdx = orders.findIndex(item => item.Id == orderId) if (targetIdx != -1) { // 目标订单 let targetOrder = orders[targetIdx] // 更新资产 if (this.exchangeType == "Futures") { // 合约交易所资产更新 // to do } else if (this.exchangeType == "Spot") { let arrCurrency = targetOrder.Symbol.split("_") let baseCurrency = arrCurrency[0] let quoteCurrency = arrCurrency[1] let needAsset = 0 let needAssetName = "" if (targetOrder.Type == ORDER_TYPE_BUY && targetOrder.Price <= 0) { needAssetName = quoteCurrency needAsset = targetOrder.Amount - targetOrder.DealAmount } else { needAssetName = targetOrder.Type == ORDER_TYPE_BUY ? quoteCurrency : baseCurrency needAsset = targetOrder.Type == ORDER_TYPE_BUY ? targetOrder.Price * (targetOrder.Amount - targetOrder.DealAmount) : (targetOrder.Amount - targetOrder.DealAmount) } for (let asset of assets) { if (asset["Currency"] == needAssetName) { asset["FrozenAmount"] -= needAsset asset["Amount"] += needAsset } } // 更新 assets this.data.set("assets", assets) } else { this.e.Log(3, null, null, `invalid exchangeType: ${this.exchangeType}`) return false } // 更新撤销状态 orders.splice(targetIdx, 1) targetOrder.Status = ORDER_STATE_CANCELED // 归档,写入数据库 let strSql = [ `INSERT INTO ${this.historyOrdersTblName} (ID, ORDERDATA)`, `VALUES ('${targetOrder.Id}', '${JSON.stringify(targetOrder)}');` ].join("") let ret = DBExec(strSql) if (!ret) { e.Log(3, null, null, `Order matched successfully, but failed to archive to database: ${JSON.stringify(o)}`) } } else { // 撤单失败 this.e.Log(3, null, null, `Order not found: ${orderId}`) this.dataLock.release() return false } this.data.set("orders", orders) this.e.Log(LOG_TYPE_CANCEL, orderId) this.dataLock.release() return true } GetHistoryOrders(symbol, since, limit) { // 查询历史订单 // to do } SetMarginLevel(symbol) { // 设置杠杆值 // 同步 this.marginLevel 和 this.data 中的 exchangeData["marginLevel"] // to do } GetPositions(symbol) { // 查询持仓 // to do /* if (this.exchangeType == "Spot") { this.e.Log(3, null, null, `not support`) return } let pos = this.data.get("positions") */ } // engine simEngine(data, lock) { while (true) { lock.acquire() // get orders / positions / assets / exchangeData let orders = data.get("orders") let positions = data.get("positions") let assets = data.get("assets") let exchangeData = data.get("exchangeData") let historyOrdersTblName = data.get("historyOrdersTblName") // get exchange idx and fee let exIdx = exchangeData["exIdx"] let fee = exchangeData["fee"] let e = exchanges[exIdx] // get exchangeType let exchangeType = exchangeData["exchangeType"] let marginLevel = 0 if (exchangeType == "Futures") { marginLevel = exchangeData["marginLevel"] } // get Depth let dictTick = {} for (let order of orders) { dictTick[order.Symbol] = {} } for (let position of positions) { dictTick[position.Symbol] = {} } // 更新行情 for (let symbol in dictTick) { dictTick[symbol] = e.GetDepth(symbol) } // 撮合 let newPendingOrders = [] for (let o of orders) { // 只处理pending订单 if (o.Status != ORDER_STATE_PENDING) { continue } // 盘口无数据 let depth = dictTick[o.Symbol] if (!depth) { e.Log(3, null, null, `Order canceled due to invalid order book data: ${JSON.stringify(o)}`) continue } // 根据订单方向,确定订单薄撮合方向 let matchSide = o.Type == ORDER_TYPE_BUY ? depth.Asks : depth.Bids if (!matchSide || matchSide.length == 0) { e.Log(3, null, null, `Order canceled due to invalid order book data: ${JSON.stringify(o)}`) continue } let remain = o.Amount - o.DealAmount let filledValue = 0 let filledAmount = 0 for (let level of matchSide) { let levelAmount = level.Amount let levelPrice = level.Price if ((o.Price > 0 && ((o.Type == ORDER_TYPE_BUY && o.Price >= levelPrice) || (o.Type == ORDER_TYPE_SELL && o.Price <= levelPrice))) || o.Price <= 0) { if (exchangeType == "Spot" && o.Type == ORDER_TYPE_BUY && o.Price <= 0) { // 现货市价单买单 let currentFilledQty = Math.min(levelAmount * levelPrice, remain) remain -= currentFilledQty filledValue += currentFilledQty filledAmount += currentFilledQty / levelPrice } else { // 限价单,价格符合撮合;市价单,直接盘口撮合 let currentFilledAmount = Math.min(levelAmount, remain) remain -= currentFilledAmount filledValue += currentFilledAmount * levelPrice filledAmount += currentFilledAmount } // 初次判断,如果直接撮合,判定为 taker if (typeof(o.isMaker) == "undefined") { o.isMaker = false } } else { // 价格不符合撮合,初次判断,判定为 maker if (typeof(o.isMaker) == "undefined") { o.isMaker = true } break } if (remain <= 0) { // 订单成交完成 break } } // 订单有变动 if (filledAmount > 0) { // 更新订单变动 if (exchangeType == "Spot" && o.Type == ORDER_TYPE_BUY && o.Price <= 0) { if (o.AvgPrice == 0) { o.AvgPrice = filledValue / filledAmount o.DealAmount += filledValue } else { o.AvgPrice = (o.DealAmount + filledValue) / (filledAmount + o.DealAmount / o.AvgPrice) o.DealAmount += filledValue } } else { o.AvgPrice = (o.DealAmount * o.AvgPrice + filledValue) / (filledAmount + o.DealAmount) o.DealAmount += filledAmount } // 处理持仓更新 if (exchangeType == "Futures") { // 期货,查找对应订单方向上的持仓,更新 // to do /* if () { // 查到对应持仓,更新 } else { // 没有对应持仓,新建 let pos = { "Info": null, "Symbol": o.Symbol, "MarginLevel": marginLevel, "Amount": o.Amount, "FrozenAmount": 0, "Price": o.Price, "Profit": 0, "Type": o.Type == ORDER_TYPE_BUY ? PD_LONG : PD_SHORT, "ContractType": o.Symbol.split(".")[1], "Margin": o.Amount * o.Price / marginLevel // to do USDT/USD contract Multiplier } positions.push(pos) } */ } // 处理资产更新 if (exchangeType == "Futures") { // 处理期货资产更新 // to do } else if (exchangeType == "Spot") { // 处理现货资产更新 let arrCurrency = o.Symbol.split("_") let baseCurrency = arrCurrency[0] let quoteCurrency = arrCurrency[1] let minusAssetName = o.Type == ORDER_TYPE_BUY ? quoteCurrency : baseCurrency let minusAsset = o.Type == ORDER_TYPE_BUY ? filledValue : filledAmount let plusAssetName = o.Type == ORDER_TYPE_BUY ? baseCurrency : quoteCurrency let plusAsset = o.Type == ORDER_TYPE_BUY ? filledAmount : filledValue // 手续费扣除 if (o.isMaker) { plusAsset = (1 - fee["maker"]) * plusAsset } else { plusAsset = (1 - fee["taker"]) * plusAsset } for (let asset of assets) { if (asset["Currency"] == minusAssetName) { // asset["FrozenAmount"] -= minusAsset asset["FrozenAmount"] = Math.max(0, asset["FrozenAmount"] - minusAsset) } else if (asset["Currency"] == plusAssetName) { asset["Amount"] += plusAsset } } } } // 检测remain更新订单状态 if (remain <= 0) { // 订单完成,更新订单状态,更新均价,更新完成量 o.Status = ORDER_STATE_CLOSED // 完成的订单归档,记录到数据库 let strSql = [ `INSERT INTO ${historyOrdersTblName} (ID, ORDERDATA)`, `VALUES ('${o.Id}', '${JSON.stringify(o)}');` ].join("") let ret = DBExec(strSql) if (!ret) { e.Log(3, null, null, `Order matched successfully, but failed to archive to database: ${JSON.stringify(o)}`) } } else { newPendingOrders.push(o) } } // 更新当前挂单数据 data.set("orders", newPendingOrders) data.set("assets", assets) lock.release() Sleep(1000) } } // other isValidContractType(contractType) { // only support swap let contractTypes = ["swap"] if (contractTypes.includes(contractType)) { return true } else { return false } } generateOrderId(symbol, ts) { let uuid = '', i, random for (i = 0; i < 36; i++) { if (i === 8 || i === 13 || i === 18 || i === 23) { uuid += '-' } else if (i === 14) { // 固定为4 uuid += '4' } else if (i === 19) { // 高2位固定为10 random = (Math.random() * 16) | 0 uuid += ((random & 0x3) | 0x8).toString(16) } else { random = (Math.random() * 16) | 0 uuid += random.toString(16) } } return `${symbol},${uuid}-${ts}` } parseJSON(strData) { let ret = null try { ret = JSON.parse(strData) } catch (err) { Log("err.name:", err.name, ", err.stack:", err.stack, ", err.message:", err.message, ", strData:", strData) } return ret } init() { threading.Thread(this.simEngine, this.data, this.dataLock) // 删除数据库 历史订单表 DBExec(`DROP TABLE IF EXISTS ${this.historyOrdersTblName};`) // 重建 历史订单表 let strSql = [ `CREATE TABLE IF NOT EXISTS ${this.historyOrdersTblName} (`, "ID VARCHAR(255) NOT NULL PRIMARY KEY,", "ORDERDATA TEXT NOT NULL", ")" ].join(""); DBExec(strSql) } } // extport $.CreatePaperTrader = function(exIdx, realExchange, assets, fee) { return new PaperTrader(exIdx, realExchange, assets, fee) } // 用真实行情打造高效 Paper Trader function main() { // create PaperTrader let simulateAssets = [{"Currency": "USDT", "Amount": 10000, "FrozenAmount": 0}] let fee = {"taker": 0.001, "maker": 0.0005} paperTraderEx = $.CreatePaperTrader(0, exchange, simulateAssets, fee) Log(paperTraderEx) // test GetTicker Log("GetTicker:", paperTraderEx.GetTicker()) // test GetOrders Log("GetOrders:", paperTraderEx.GetOrders()) // test Buy/Sell let orderId = paperTraderEx.Buy(-1, 0.1) Log("orderId:", orderId) // test GetOrder Sleep(1000) Log(paperTraderEx.GetOrder(orderId)) Sleep(6000) }

Практическая демонстрация и тестовые случаи

Твердое предложение

Приведенный выше код можно сохранить как «Библиотеку шаблонов» платформы FMZ.mainФункция является тестовой функцией:

img

Таким образом, когда вы фактически торгуете, вы можете записать строку API KEY при настройке объекта обмена. В настоящее время такие операции, как размещение заказов, фактически не будут иметь доступа к интерфейсу биржи, но будут использовать активы, заказы, позиции и другие данные системы моделирования для моделирования. Но рыночные условия — это реальные рыночные условия обмена.

Направление расширения и оптимизации

Значение систем моделирования в разработке стратегии
PaperTrader предоставляет среду тестирования, максимально приближенную к реальному рынку, что позволяет разработчикам проверять поведение исполнения, логику ордеров, эффективность сопоставления и изменения капитала стратегии без какого-либо риска. Он особенно подходит для следующих сценариев:

  • Параллельное тестирование с несколькими стратегиями отладки
  • Быстро проверяйте эффективность стратегий в различных рыночных условиях
  • Избегайте прямых заказов в режиме реального времени во время отладки, чтобы избежать потерь.
  • Заменить некоторые традиционные методы проверки на основе исторического бэктестинга

Отличие от чистого бэктестинга

Традиционное бэктестирование основано на исторических данных и выполняется по принципу K за K, игнорируя реальные детали транзакций, такие как отложенные ордера, частичные транзакции, проскальзывание и структура комиссий. Система моделирования:

  • Используйте котировки в реальном времени (а не статические исторические данные)
  • Имитация реального жизненного цикла заказа (создание → размещение заказа → сопоставление → выполнение → отмена)
  • Точно рассчитывайте комиссии, проскальзывание и среднюю цену транзакции
  • Лучше подходит для тестирования «стратегического поведения», а не просто «стратегической модели»
  • Выступая в качестве моста между развертыванием в реальном времени

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

Возможные потенциальные проблемы:

  • Ошибка вычисления чисел с плавающей точкой.
  • Логические границы обработки.
  • Поддержка контрактов на поставку стала сложнее
  • Конструкция механизма ликвидации более сложная

Следующее направление эволюции

Для дальнейшего повышения ценности приложения PaperTrader на следующем этапе можно рассмотреть следующие направления расширения:

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

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

Читатели могут оставлять сообщения, спасибо за чтение.

Comment
All comments (1)

    牛逼啊

    a year ago
  • 1
Forums
PINE Language
Get the app
iPhone Download
© 2015 - ∞ INVENTOR PTE LTD (SG)