3
konzentrieren Sie sich auf
1444
Anhänger

So erstellen Sie nach dem FMZ-Upgrade schnell eine universelle Multi-Währungs-Handelsstrategie

Erstellt in: 2024-09-30 15:22:55, aktualisiert am: 2024-11-05 17:46:43
comments   4
hits   1439

So erstellen Sie nach dem FMZ-Upgrade schnell eine universelle Multi-Währungs-Handelsstrategie

Vorwort

Die Inventor Quantitative Trading Platform wurde zu früh eingerichtet. Zu dieser Zeit gab es nur sehr wenige Börsen und Währungen und nicht viele Handelsmodelle. Daher war das anfängliche API-Design relativ einfach und konzentrierte sich auf Handelsstrategien für einzelne Währungen. Nach Jahren der Iteration ist es, insbesondere die neueste Version, relativ vollständig geworden und häufig verwendete Exchange-APIs können mit gekapselten Funktionen vervollständigt werden. Insbesondere bei Mehrwährungsstrategien ist das Abrufen von Marktinformationen, Konten und Transaktionen viel einfacher als zuvor und für den Zugriff auf die API-Schnittstelle der Börse sind keine IO-Funktionen mehr erforderlich. Das Backtesting wurde ebenfalls verbessert, um mit dem realen Handel kompatibel zu sein. Kurz gesagt: Wenn Ihre Strategie ursprünglich über viele dedizierte Methoden verfügt und nicht für mehrere Börsen und Backtestings verwendet werden kann, möchten Sie sie möglicherweise aktualisieren. In diesem Artikel wird der aktuelle Strategierahmen in Kombination mit Strategien vorgestellt.

Depotbanken müssen auf 3.7 aktualisieren, um dies vollständig unterstützen zu können. Informationen zu neuen Funktionen der API-Schnittstelle wurden in die API-Dokumentation der Inventor Quantitative Trading Platform aktualisiert:

Syntaxhandbuch: https://www.fmz.com/syntax-guide Benutzerhandbuch: https://www.fmz.com/user-guide

Holen Sie sich Genauigkeit

Aktuell verfügt die API über eine einheitliche Funktion zur Ermittlung der Genauigkeit, die hier am Beispiel eines unbefristeten Vertrags vorgestellt wird.

//全局的变量,储存数据,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"
            }
        }
    }
}

Angebote einholen

Für die Entwicklung einer Mehrsortenstrategie ist es erforderlich, alle Marktbedingungen zu berücksichtigen. Diese aggregierte Marktschnittstelle ist unerlässlich und die GetTickers-Funktion unterstützt die meisten gängigen Börsen.

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);
    }
}

Erhalten Sie Informationen zur Kontoposition

Um Inkompatibilitäten durch zusätzliche Verarbeitung zu vermeiden, wurden den Futures-Kontoinformationen die Felder „Equity“ und „UPnL“ hinzugefügt. Die Funktion GetPositions unterstützt auch das Abrufen aller Positionen. Ein Detail ist, dass die Anzahl der Positionen mit dem Wert eines Kontrakts multipliziert werden muss, um die tatsächliche Anzahl zu erhalten. Obwohl Sie beispielsweise auf der OKX-Website für unbefristete Kontrakte den Handel nach Menge wählen können, die API basiert auf der Anzahl der Verträge.

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);
}

Handel

Die Transaktion muss sich auch mit der Anzahl der Verträge befassen. Hier wird die neueste Funktion CreateOrder verwendet, um Bestellungen abzuwickeln, was viel bequemer ist.

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);
    }
}

Statusanzeige

Im Allgemeinen werden zwei Tabellen angezeigt: Kontoinformationen und Transaktionspaarinformationen.

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();
    }
}

Transaktionslogik

Nachdem das Gerüst aufgebaut ist, ist der Kerncode der Handelslogik einfach. Hier ist die einfachste Eisberg-Order-Strategie.

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);
        }
    }
}

Hauptschleife

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);
    }
}

Zusammenfassen

Dieser Artikel bietet ein einfaches Framework für den Handel mit unbefristeten Verträgen in mehreren Währungen. Durch die Verwendung der neuesten API können Sie kompatible Strategien bequemer und schneller erstellen. Es ist einen Versuch wert.