Type/to search
8
Follow
1369
Followers
Projeto de sistema de simulação de negociação baseado na plataforma quantitativa FMZ
Discussions
Created 2025-04-30 16:59:01  Updated 2025-04-30 17:53:06
 1
 1352

img

Prefácio

Este artigo apresenta o design e a implementação do PaperTrader, um sistema de simulação de negociação baseado na plataforma quantitativa FMZ e orientado por condições reais de mercado. O sistema combina ordens por meio de condições de mercado detalhadas em tempo real, simula completamente processos de negociação, como colocação de ordens de estratégia, transações, alterações de ativos e processamento de taxas, oferece suporte a ordens de mercado/limite, congelamento de ativos e arquivamento de cancelamento, e é adequado para testes de estratégia e verificação de comportamento real antes de negociações reais. Este artigo explicará em detalhes seu conceito de design e implementação principal a partir das perspectivas de arquitetura do sistema, mecanismo de correspondência, compatibilidade de interface, etc., e fornecerá casos de uso de demonstração prática completa para ajudar estratégias quantitativas a construir uma "caixa de areia intermediária" segura e confiável antes de entrar online.

Análise e Design de Requisitos do PaperTrader

Pontos problemáticos da demanda:

  • A simulação de negociação de câmbio é caótica e irrealista.
  • Solicitar uma conta simulada em uma bolsa é trabalhoso, assim como obter fundos de teste.
  • Muitas bolsas não oferecem um ambiente de teste.

O problema da “área cinzenta” entre o backtesting e a negociação real

Por que você precisa de um sistema de negociação simulado?

Em todo o processo de desenvolvimento de estratégia quantitativa, geralmente passamos pelas etapas de "backtesting histórico → teste ambiental → negociação real". No entanto, o backtesting histórico usa dados estatísticos e não consegue processar a eficácia das estratégias em condições reais de mercado. No entanto, a negociação real significa fuga de fundos, e a falta de um ambiente de teste intermediário se tornou um ponto problemático em nossa exploração.
Para resolver esse problema, precisamos projetar um sistema de simulação de negociação leve - PaperTrader, que pode usar condições de mercado em tempo real (profundidade, preço de mercado) para simular todo o processo de negociação, incluindo colocação de ordens, ordens pendentes, transações, retiradas de ordens, alterações de ativos e deduções de comissão e, finalmente, concluir a verificação da estratégia próxima ao nível de negociação real.

Objetivos de design e principais recursos

    1. Movimentação do mercado em tempo real
      Use o FMZ para acessar cotações de câmbio reais, incluindo GetTicker(), GetDepth() e dezenas de outras interfaces.
    1. Simulação de colocação de pedidos e dedução
      Suporta ordens de limite/mercado, dedução separada de taxas de fabricante/tomador e usa o valor de entrada calculado real para ordens de compra de mercado.
    1. Gestão de ativos/posições/ordens
      Ele suporta o congelamento de ativos ao fazer pedidos e a devolução deles ao cancelar pedidos, além de suportar a manutenção de múltiplos ativos/posições/pedidos com base no nível do símbolo.
    1. Ciclo de pedido completo
      Os pedidos são gerenciados de forma clara, desde a criação, espera, execução até o cancelamento. Após a execução, eles são arquivados automaticamente no banco de dados local para dar suporte a consultas e análises futuras.
    1. Experiência do usuário
      A estratégia não precisa alterar nenhuma chamada de ordem e pode implementar negociações simuladas simplesmente substituindo o objeto de troca pelo PaperTrader.

Visão geral do design da biblioteca

O sistema consiste principalmente em três partes:

  • Classe PaperTrader
    A conta de simulação principal inclui manutenção de dados como ativos, ordens, posições, condições de mercado e configurações.

  • [mecanismo de correspondência simEngine]:
    Thread de fundo, verifica a ordem atual de acordo com a profundidade do mercado e executa operações

  • 【Arquivo de banco de dados】:
    Grave pedidos concluídos/cancelados no banco de dados local para análise e revisão posteriores

Design de motor correspondente

simEngine(data, lock) é o núcleo de todo o sistema de simulação. Ele combina os pedidos pendentes atuais em um loop de acordo com os dados reais de profundidade do mercado para fornecer resultados de simulação precisos para transações.

O processo principal inclui:

  • Obtenha ordens pendentes, posições, ativos e condições de mercado atuais;
  • Obter a profundidade de todos os símbolos usados ​​GetDepth;
  • Percorrer as ordens pendentes e selecionar a profundidade da operação (asks/bids) de acordo com a direção (compra/venda);
  • Determinar se a transação é concluída com base na conformidade do preço com as condições operacionais;
  • Se a transação for concluída, atualize e conte as informações do pedido, como AvgPrice / DealAmount;
  • Deduza taxas de manuseio com base no emissor/tomador.
  • Se todas as ordens forem executadas, a ordem será arquivada; caso contrário, permanecerá pendente.

Compatibilidade de informações de interface

O PaperTrader foi projetado para se alinhar o máximo possível com a interface de negociação real da plataforma FMZ, incluindo, mas não se limitando a:

Classificaçãointerfacedescrever
Interface de pedidoBuy(price, amount) / Sell(price, amount) / CreateOrder(symbol, side, price, amount)Operação de ordem
Interface de mercadoGetTicker() / GetDepth() / GetRecords() / GetTrades()Solicite diretamente o preço real de mercado da troca
Interface de pedidoGetOrders() / CancelOrder(id) / GetOrder(id)Para operações de pedidos
Interface de conta e posiçãoGetAccount() / GetAssets() / GetPositions()Para operações de conta
Outras configurações de interfaceSetCurrency() / SetDirection()Outras configurações

Este design permite que a lógica da estratégia seja executada diretamente em um ambiente de negociação simulado sem modificações. Ao substituir a bolsa pelo PaperTrader com um clique, você pode migrar a estratégia para a "camada intermediária" entre o backtesting e a negociação real.

Código-fonte do design do 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) }

Demonstração prática e casos de teste

Oferta Firme

O código acima pode ser salvo como a "Biblioteca de Modelos" da plataforma FMZ.mainA função é a função de teste:

img

Dessa forma, quando você estiver realmente negociando, você pode escrever uma string API KEY ao configurar o objeto de troca. Neste momento, operações como a colocação de ordens não acessarão de fato a interface de câmbio, mas usarão os ativos, ordens, posições e outros dados do sistema de simulação para simulação. Mas as condições de mercado são as condições reais de mercado da troca.

Direção de expansão e otimização

O valor dos sistemas de simulação no desenvolvimento de estratégias
O PaperTrader fornece um ambiente de testes muito próximo do mercado real, permitindo que os desenvolvedores verifiquem o comportamento de execução, a lógica da ordem, o desempenho de correspondência e as alterações de capital da estratégia sem nenhum risco. É particularmente adequado para os seguintes cenários:

  • Teste simultâneo de depuração multiestratégia
  • Verifique rapidamente o desempenho das estratégias em diferentes condições de mercado
  • Evite ordens diretas em tempo real durante a depuração para evitar perdas
  • Substituir alguns métodos tradicionais de verificação de backtesting histórico

Diferença do backtesting puro

O backtesting tradicional é baseado em dados históricos e executado K por K, ignorando detalhes reais de transações, como pedidos pendentes, transações parciais, slippage correspondente e estrutura de taxas. O sistema de simulação:

  • Use cotações em tempo real (não dados históricos estáticos)
  • Simule o ciclo de vida real do pedido (criar → fazer pedido → corresponder → executar → cancelar)
  • Calcule com precisão taxas, slippage e preço médio de transação
  • Melhor em testar "comportamento estratégico" em vez de apenas "modelo estratégico"
  • Atuando como uma ponte entre a implantação em tempo real

Notas sobre o PaperTrader
O PaperTrader acima é apenas um projeto preliminar (apenas revisão preliminar do código e testes foram feitos), e o objetivo é fornecer uma ideia de projeto e uma referência de solução. O PaperTrader também precisa ser testado para verificar se a lógica de correspondência, o sistema de ordens, o sistema de posições, o sistema de capital e outros designs são razoáveis. Devido a restrições de tempo, apenas a negociação à vista foi implementada de forma relativamente completa, e algumas funções de contratos futuros ainda estão em fase de conclusão.

Possíveis problemas potenciais:

  • Erro de cálculo de ponto flutuante.
  • Limites de processamento lógico.
  • O suporte para contratos de entrega é mais complicado
  • O desenho do mecanismo de liquidação é mais complicado

A próxima direção da evolução

Para aumentar ainda mais o valor da aplicação do PaperTrader, as seguintes direções podem ser consideradas para expansão na próxima etapa:

  • Melhorar o suporte para simulação de contratos (parte inacabada do código).
  • Oferece suporte à posição do contrato e à gestão de fundos de alavancagem (posição por posição, posição completa).
  • Introduzir cálculo de lucros e perdas flutuantes e mecanismo de liquidação forçada.

Por meio do PaperTrader, não só podemos fornecer um ambiente de teste mais seguro para estratégias, mas também promover ainda mais o vínculo fundamental das estratégias entre "modelos de pesquisa" e "produtividade real".

Os leitores são bem-vindos para deixar mensagens, obrigado pela leitura.

Comment
All comments (1)

    牛逼啊

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