Type/to search
3
Follow
1505
Followers
FMZ アップグレード後にユニバーサルなマルチ通貨取引戦略を迅速に構築する方法
Original
Created 2024-09-30 15:22:55  Updated 2024-11-05 17:46:43
 4
 1762

img

序文

Inventor Quantitative Trading Platform の設立時期が早すぎました。当時は取引所や通貨が非常に限られており、取引モデルも多くありませんでした。そのため、初期の API 設計は比較的シンプルで、単一通貨の取引戦略に重点を置いていました。長年の反復を経て、特に最新バージョンでは、比較的完成度が高くなり、よく使用される交換 API はカプセル化された関数で完成できるようになりました。特に、複数通貨戦略の場合、市場情報、アカウント、取引の取得が以前よりもはるかに簡単になり、取引所の API インターフェースにアクセスするために IO 機能が不要になりました。バックテストも実際の取引と互換性を持つようにアップグレードされました。つまり、戦略に元々多くの専用メソッドがあり、複数の取引所やバックテストに使用できない場合は、アップグレードすることをお勧めします。この記事では、現在の戦略フレームワークを戦略と組み合わせて紹介します。

カストディアンはこれを完全にサポートするために 3.7 にアップグレードする必要があります。API インターフェースの新機能に関する情報は、Inventor Quantitative Trading Platform の API ドキュメントに更新されています。

構文ガイド: https://www.fmz.com/syntax-guide
ユーザーガイド: https://www.fmz.com/user-guide

正確性を得る

現在、API には精度を取得するための統一された機能があり、ここでは永久契約を例に紹介します。

//全局的变量,储存数据,SYMBOLS代表要交易的币种,格式如"BTC,ETH,LTC", QUOTO为基础货币,永续合约常见的有USDT,USDC,INTERVAL代表循环的间隔。 var Info = { trade_symbols: SYMBOLS.split(","), base_coin: QUOTO, ticker: {}, order: {}, account: {}, precision: {}, position: {}, time:{}, count:{}, interval:INTERVAL} function InitInfo() { //初始化策略 if (!IsVirtual() && Version() < 3.7){ throw "FMZ平台升级API,需要下载最新托管者|Update to neweset docekr"; } Info.account = {init_balance:0}; Info.time = { update_ticker_time: 0, update_pos_time: 0, update_profit_time: 0, update_account_time: 0, update_status_time: 0, last_loop_time:0, loop_delay:0, }; for (let i = 0; i < Info.trade_symbols.length; i++) { let symbol = Info.trade_symbols[i]; Info.ticker[symbol] = { last: 0, ask: 0, bid: 0 }; Info.order[symbol] = { buy: { id: 0, price: 0, amount: 0 }, sell: { id: 0, price: 0, amount: 0 } }; Info.position[symbol] = { amount: 0, hold_price: 0, unrealised_profit: 0, open_time: 0, value: 0 }; Info.precision[symbol] = {}; } } //获取精度 function GetPrecision() { let exchange_info = exchange.GetMarkets(); for (let pair in exchange_info) { let symbol = pair.split('_')[0]; //永续合约交易对的格式为 BTC_USDT.swap if (Info.trade_symbols.indexOf(symbol) > -1 && pair.split('.')[0].endsWith(Info.base_coin) && pair.endsWith("swap")) { Info.precision[symbol].tick_size = exchange_info[pair].TickSize; Info.precision[symbol].amount_size = exchange_info[pair].AmountSize; Info.precision[symbol].price_precision = exchange_info[pair].PricePrecision Info.precision[symbol].amount_precision = exchange_info[pair].AmountPrecision Info.precision[symbol].min_qty = exchange_info[pair].MinQty Info.precision[symbol].max_qty = exchange_info[pair].MaxQty Info.precision[symbol].min_notional = exchange_info[pair].MinNotional Info.precision[symbol].ctVal = exchange_info[pair].CtVal; //合约价值,如1张代表0.01个币 if (exchange_info[pair].CtValCcy != symbol){ //价值的计价货币,这里不处理币本位的情况,如1张价值100美元 throw "不支持币本位|Don't support coin margin type" } } } }

見積もりを取得

多品種戦略を設計するには、完全な市場状況を把握する必要があります。この集約された市場インターフェースは不可欠であり、GetTickers 機能はほとんどの主流の取引所をサポートしています。

function UpdateTicker() { //更新价格 let ticker = exchange.GetTickers(); if (!ticker) { Log("获取行情失败|Fail to get market", GetLastError()); return; } Info.time.update_ticker_time = Date.now(); for (let i = 0; i < ticker.length; i++) { let symbol = ticker[i].Symbol.split('_')[0]; if (!ticker[i].Symbol.split('.')[0].endsWith(Info.base_coin) || Info.trade_symbols.indexOf(symbol) < 0 || !ticker[i].Symbol.endsWith('swap')) { continue; } Info.ticker[symbol].ask = parseFloat(ticker[i].Sell); Info.ticker[symbol].bid = parseFloat(ticker[i].Buy); Info.ticker[symbol].last = parseFloat(ticker[i].Last); } }

アカウントポジション情報を取得する

追加処理による非互換性を回避するために、先物口座情報に Equity フィールドと UPnL フィールドが追加されました。 GetPositions関数は、すべてのポジションの取得もサポートしています。1つの詳細は、実際の数を取得するには、ポジションの数に1つの契約の価値を掛け算する必要があることです。たとえば、OKX無期限契約はWebページで数量で取引できますが、 API は契約数に基づいています。

function UpdateAccount() { //更新账户 if (Date.now() - Info.time.update_account_time < 60 * 1000) { return; } Info.time.update_account_time = Date.now(); let account = exchange.GetAccount(); if (account === null) { Log("更新账户失败|Fail to get account"); return; } Info.account.margin_used = _N(account.Equity - account.Balance, 2); Info.account.margin_balance = _N(account.Equity, 2); //当前余额 Info.account.margin_free = _N(account.Balance, 2); Info.account.wallet_balance = _N(account.Equity - account.UPnL, 2); Info.account.unrealised_profit = _N(account.UPnL, 2); if (!Info.account.init_balance) { if (_G("init_balance") && _G("init_balance") > 0) { Info.account.init_balance = _N(_G("init_balance"), 2); } else { Info.account.init_balance = Info.account.margin_balance; _G("init_balance", Info.account.init_balance); } } Info.account.profit = _N(Info.account.margin_balance - Info.account.init_balance, 2); Info.account.profit_rate = _N((100 * Info.account.profit) / init_balance, 2); } function UpdatePosition() { let pos = exchange.GetPositions(Info.base_coin + ".swap"); if (!pos) { Log("更新仓位超时|Fail to get position"); return; } Info.time.update_pos_time = Date.now(); let position_info = {}; for (let symbol of Info.trade_symbols) { position_info[symbol] = { amount: 0, hold_price: 0, unrealised_profit: 0 }; //有的交易所没有仓位返回空 } for (let k = 0; k < pos.length; k++) { let symbol = pos[k].Symbol.split("_")[0]; if (!pos[k].Symbol.split(".")[0].endsWith(Info.base_coin) || Info.trade_symbols.indexOf(symbol) < 0) { continue; } if (position_info[symbol].amount != 0){ throw "需要单向持仓|Position need net Mode:"; } position_info[symbol] = { amount: pos[k].Type == 0 ? pos[k].Amount * Info.precision[symbol].ctVal : -pos[k].Amount * Info.precision[symbol].ctVal, hold_price: pos[k].Price, unrealised_profit: pos[k].Profit }; } Info.count = { long: 0, short: 0, total: 0, leverage: 0 }; for (let symbol in position_info) { let deal_volume = Math.abs(position_info[symbol].amount - Info.position[symbol].amount); let direction = position_info[symbol].amount - Info.position[symbol].amount > 0 ? 1 : -1; if (deal_volume) { let deal_price = direction == 1 ? Info.order[symbol].buy.price : Info.order[symbol].sell.price; Log( symbol, "仓位更新:|Position update:", _N(Info.position[symbol].value, 1), " -> ", _N(position_info[symbol].amount * Info.ticker[symbol].last, 1), direction == 1 ? ", 买|. Buy" : ", 卖|, Sell", ", 成交价:| Deal price: ", deal_price, ", 成本价:| Hold price: ", _N(Info.position[symbol].hold_price, Info.precision[symbol].price_precision), ); } Info.position[symbol].amount = position_info[symbol].amount; Info.position[symbol].hold_price = position_info[symbol].hold_price; Info.position[symbol].value = _N(Info.position[symbol].amount * Info.ticker[symbol].last, 2); Info.position[symbol].unrealised_profit = position_info[symbol].unrealised_profit; Info.count.long += Info.position[symbol].amount > 0 ? Math.abs(Info.position[symbol].value) : 0; Info.count.short += Info.position[symbol].amount < 0 ? Math.abs(Info.position[symbol].value) : 0; } Info.count.total = _N(Info.count.long + Info.count.short, 2); Info.count.leverage = _N(Info.count.total / Info.account.margin_balance, 2); }

貿易

トランザクションでは、契約数の問題にも対処する必要があります。ここでは、最新の CreateOrder 関数を使用して注文を処理します。これは、はるかに便利です。

function Order(symbol, direction, price, amount, msg) { let ret = null; let pair = symbol + "_" + Info.base_coin + ".swap" ret = exchange.CreateOrder(pair, direction, price, amount, msg) if (ret) { Info.order[symbol][direction].id = ret; Info.order[symbol][direction].price = price; }else { Log(symbol, direction, price, amount, "下单异常|Error on order"); } } function Trade(symbol, direction, price, amount, msg) { price = _N(price - (price % Info.precision[symbol].tick_size), Info.precision[symbol].price_precision); amount = amount / Info.precision[symbol].ctVal; amount = _N(amount - (amount % Info.precision[symbol].amount_size), Info.precision[symbol].amount_precision); amount = Info.precision[symbol].max_qty > 0 ? Math.min(amount, Info.precision[symbol].max_qty) : amount; let new_order = false; if (price > 0 && Math.abs(price - Info.order[symbol][direction].price) / price > 0.0001) { //两次订单有差价了才撤单 new_order = true; } if (amount <= 0 || Info.order[symbol][direction].id == 0) { //传入amount为0 撤单 new_order = true; } if (new_order) { if (Info.order[symbol][direction].id) { //原有订单撤销 CancelOrder(symbol, direction, Info.order[symbol][direction].id); Info.order[symbol][direction].id = 0; } if ( //延时过高不下单 Date.now() - Info.time.update_pos_time > 2 * Info.interval * 1000 || Date.now() - Info.time.update_ticker_time > 2 * Info.interval * 1000 || ) { return; } if (price * amount <= Info.precision[symbol].min_notional || amount < Info.precision[symbol].min_qty) { Log(symbol, "下单量太低|amount is too small", price * amount); return; } Order(symbol, direction, price, amount, msg); } }

ステータス表示

通常、アカウント情報と取引ペア情報の 2 つのテーブルが表示されます。

unction UpdateStatus() { if (Date.now() - Info.time.update_status_time < 4000) { return; } Info.time.update_status_time = Date.now(); let table1 = { type: "table", title: "账户信息|Account info", cols: [ "初始余额|Initial Balance", "钱包余额|Wallet balance", "保证金余额|Margin balance", "已用保证金|Used margin", "可用保证金|Avaiable margin", "总收益|Profit", "收益率|Profit rate", "未实现收益|Unrealised profit", "总持仓|Total value", "已用杠杆|Leverage-used", "循环延时|Delay", ], rows: [ [ Info.account.init_balance, Info.account.wallet_balance, Info.account.margin_balance, Info.account.margin_used, Info.account.margin_free, Info.account.profit, Info.account.profit_rate + "%", _N(Info.account.unrealised_profit, 2), _N(Info.count.total, 2), Info.count.leverage, Info.time.loop_delay + "ms", ], ], }; let table2 = { type: "table", title: "交易对信息|Symbol info", cols: [ "币种|Symbol", "方向|Direction", "数量|Amount", "持仓价格|Hold price", "持仓价值|Value", "现价|Price", "挂单买价|Buy price", "挂单卖价|Sell price", "未实现盈亏|Unrealised profit", ], rows: [], }; for (let i in Info.trade_symbols) { let symbol = Info.trade_symbols[i]; table2.rows.push([ symbol, Info.position[symbol].amount > 0 ? "LONG" : "SHORT", _N(Info.position[symbol].amount, Info.precision[symbol].amount_precision+2), _N(Info.position[symbol].hold_price, Info.precision[symbol].price_precision), _N(Info.position[symbol].value, 2), _N(Info.ticker[symbol].last, Info.precision[symbol].price_precision), Info.order[symbol].buy.price, Info.order[symbol].sell.price, _N(Info.position[symbol].unrealised_profit, 2), ]); } LogStatus( "初始化时间: | Initial date: " + _D(new Date(Info.time.start_time)) + "\n", "`" + JSON.stringify(table1) + "`" + "\n" + "`" + JSON.stringify(table2) + "`\n", "最后执行时间: |Last run date: " + _D() + "\n", ); if (Date.now() - Info.time.update_profit_time > 5 * 60 * 1000) { UpdateAccount(); LogProfit(_N(Info.account.profit, 3)); Info.time.update_profit_time = Date.now(); } }

トランザクションロジック

足場がセットアップされると、コア取引ロジック コードはシンプルになります。以下は、最もシンプルなアイスバーグ注文戦略です。

function MakeOrder() { for (let i in Info.trade_symbols) { let symbol = Info.trade_symbols[i]; let buy_price = Info.ticker[symbol].bid; let buy_amount = 50 / buy_price; if (Info.position[symbol].value < 2000){ Trade(symbol, "buy", buy_price, buy_amount, symbol); } } }

メインループ

function OnTick() { try { UpdateTicker(); UpdatePosition(); MakeOrder(); UpdateStatus(); } catch (error) { Log("循环出错: | Loop error: " + error); } } function main() { InitInfo(); while (true) { let loop_start_time = Date.now(); if (Date.now() - Info.time.last_loop_time > Info.interval * 1000) { OnTick(); Info.time.last_loop_time = Date.now(); Info.time.loop_delay = Date.now() - loop_start_time; } Sleep(5); } }

要約する

この記事では、シンプルな永久契約の多通貨取引フレームワークを紹介します。最新の API を使用すると、互換性のある戦略をより便利かつ迅速に構築できます。試してみる価値があります。

Comment
All comments (4)

    小草老师,能不能出个Python版本的源码,方便新手学习修改

    a year ago

    img 你好请问这个问题,是由于我托管者等级低的原因吗?还是什么问题呢?

    2 years ago

    SYMBOLS是策略里定义的全局变量,需要在策略参数中定义好。

    2 years ago

    感谢,了解了这里面的门道有多深!

    2 years ago
  • 1
iPhone Download
Forums
PINE Language
© 2015 - ∞ INVENTOR PTE LTD (SG)