Sumber daya yang dimuat... Pemuatan...

Bagaimana Membangun Strategi Perdagangan Multi-Valuta Universal dengan Cepat Setelah Peningkatan FMZ

Penulis:FMZ~Lydia, Dibuat: 2024-10-08 10:01:41, Diperbarui: 2024-10-15 08:55:09

img

Pengantar

FMZ Quant Trading Platform didirikan terlalu awal. Pada saat itu, ada pertukaran dan mata uang yang sangat terbatas, dan ada beberapa mode perdagangan. Oleh karena itu, desain API awal sederhana dan berfokus pada strategi perdagangan mata uang tunggal. Setelah bertahun-tahun iterasi, terutama versi terbaru, itu relatif lengkap, dan API pertukaran yang umum digunakan dapat diselesaikan dengan fungsi yang terkapsul. Terutama untuk strategi multi-mata uang, mendapatkan kondisi pasar, akun, dan transaksi jauh lebih sederhana dari sebelumnya, dan fungsi IO tidak lagi diperlukan untuk mengakses antarmuka API exchanges. Untuk backtesting, itu juga telah ditingkatkan agar kompatibel dengan perdagangan langsung. Singkatnya, jika strategi Anda awalnya memiliki banyak metode khusus dan tidak dapat digunakan untuk beberapa pertukaran dan backtesting, Anda dapat memperbaharunya.

Informasi tentang fitur baru antarmuka API telah diperbarui ke dokumentasi API dari FMZ Quant Trading Platform:

Panduan sintaksis:https://www.fmz.com/syntax-guidePanduan Pengguna:https://www.fmz.com/user-guide

Dapatkan Keakuratan

Saat ini, API memiliki fungsi terpadu untuk mendapatkan akurasi, yang diperkenalkan di sini menggunakan kontrak abadi sebagai contoh.

//Global variables store data. SYMBOLS represents the currency to be traded, and the format is "BTC,ETH,LTC". QUOTO is the base currency. Common perpetual contracts include USDT and USDC. INTERVAL represents the interval of the cycle.
var Info = { trade_symbols: SYMBOLS.split(","), base_coin: QUOTO, ticker: {}, order: {}, account: {}, precision: {}, 
            position: {}, time:{}, count:{}, interval:INTERVAL}
function InitInfo() {
    //Initialization strategy
    if (!IsVirtual() && Version() < 3.7){
        throw "FMZ platform upgrades API, you need to download the latest docker";
    }
    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] =  {};
    }
}
//Get accuracy
function GetPrecision() {
    let exchange_info = exchange.GetMarkets();
    for (let pair in exchange_info) {
        let symbol = pair.split('_')[0]; //The format of perpetual contract trading pairs is 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; //Contract value, for example, 1 piece represents 0.01 coin
            if (exchange_info[pair].CtValCcy != symbol){ //The currency used to denominate the value. This does not include currency-based situations, for example, 1 note worth 100 USD.
                throw "No support for currency-based"
            }
        }
    }
}

Dapatkan Tickers

Untuk merancang strategi multi-produk, Anda perlu mendapatkan informasi pasar dari seluruh pasar. antarmuka informasi pasar agregat ini sangat penting. fungsi GetTickers mendukung sebagian besar bursa arus utama.

function UpdateTicker() {
    //Updated prices
    let ticker = exchange.GetTickers();
    if (!ticker) {
        Log("Failed to obtain market information", 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) {
            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);
    }
}

Dapatkan Informasi Posisi Akun

Bidang Ekuitas dan bidang UPnL telah ditambahkan ke informasi akun berjangka, menghilangkan kebutuhan akan ketidakcocokan yang disebabkan oleh pemrosesan tambahan. Fungsi GetPositions juga mendukung untuk mendapatkan semua posisi. Satu detail adalah bahwa jumlah posisi harus dikalikan dengan nilai kontrak untuk mendapatkan jumlah yang sebenarnya.

function UpdateAccount() {
    //Update account
    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("Failed to update account");
        return;
    }
    Info.account.margin_used = _N(account.Equity - account.Balance, 2);
    Info.account.margin_balance = _N(account.Equity, 2); //Current balance
    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("Timeout for updating 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
        }; //Some exchanges have no positions and return empty
    }
    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 "One-way position required";
        }
        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",
                ", transaction price:",
                deal_price,
                ", cost 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);
}

Transaksi

Transaksi juga perlu menangani masalah jumlah kontrak. Fungsi CreateOrder terbaru digunakan di sini untuk memproses pesanan, yang jauh lebih nyaman.

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, "abnormal 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) { //The order will be cancelled only if there is a price difference between the two orders.
        new_order = true;
    }
    if (amount <= 0 || Info.order[symbol][direction].id == 0) { //The amount passed in is 0 to cancel the order
        new_order = true;
    }
    if (new_order) {
        if (Info.order[symbol][direction].id) { //Cancellation of existing order
            CancelOrder(symbol, direction, Info.order[symbol][direction].id);
            Info.order[symbol][direction].id = 0;
        }
        if ( //The delay is too high and the order is not placed
            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, "the order quantity is too small", price * amount);
            return;
        }
        Order(symbol, direction, price, amount, msg);
    }
}

Tampilan Status

Umumnya dua tabel ditampilkan: informasi akun dan informasi pasangan perdagangan.

function 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",
            "Total profit",
            "Profit rate",
            "Unrealised profit",
            "Total position",
            "Leverage-used",
            "Loop 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: "Trading pair information",
        cols: [
            "Symbol",
            "Direction",
            "Amount",
            "Position price",
            "Position value",
            "Current price",
            "Buy price",
            "Sell price",
            "Unrealised profit / loss",
        ],
        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 execution time: " + _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();
    }
}

Logika Perdagangan

Setelah perancah diatur, kode logika perdagangan inti sederhana.

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

Daur Utama

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

Ringkasan

Artikel ini menyediakan kerangka kerja perdagangan multi-mata uang kontrak abadi yang sederhana. Dengan menggunakan API terbaru, Anda dapat membangun strategi yang kompatibel dengan lebih nyaman dan cepat.


Lebih banyak