3
tập trung vào
1444
Người theo dõi

Cách nhanh chóng xây dựng chiến lược giao dịch đa tiền tệ phổ biến sau khi nâng cấp FMZ

Được tạo ra trong: 2024-09-30 15:22:55, cập nhật trên: 2024-11-05 17:46:43
comments   4
hits   1439

Cách nhanh chóng xây dựng chiến lược giao dịch đa tiền tệ phổ biến sau khi nâng cấp FMZ

Lời nói đầu

Nền tảng giao dịch định lượng Inventor được thành lập quá sớm. Vào thời điểm đó, có rất ít sàn giao dịch và tiền tệ, và không có nhiều mô hình giao dịch. Do đó, thiết kế API ban đầu tương đối đơn giản và tập trung vào các chiến lược giao dịch một loại tiền tệ. Sau nhiều năm lặp lại, đặc biệt là phiên bản mới nhất, nó đã trở nên tương đối hoàn thiện và các API trao đổi thường dùng có thể được hoàn thiện bằng các hàm được đóng gói. Đặc biệt đối với các chiến lược đa tiền tệ, việc thu thập thông tin thị trường, tài khoản và giao dịch đơn giản hơn nhiều so với trước đây và không còn yêu cầu các chức năng IO để truy cập vào giao diện API của sàn giao dịch nữa. Tính năng kiểm tra ngược cũng đã được nâng cấp để tương thích với giao dịch thực tế. Tóm lại, nếu chiến lược ban đầu của bạn có nhiều phương pháp chuyên dụng và không thể sử dụng cho nhiều sàn giao dịch và thử nghiệm ngược, bạn có thể muốn nâng cấp nó. Bài viết này sẽ giới thiệu khuôn khổ chiến lược hiện tại kết hợp với các chiến lược.

Người giám hộ cần nâng cấp lên 3.7 để hỗ trợ đầy đủ. Thông tin về các tính năng mới của giao diện API đã được cập nhật vào tài liệu API của Nền tảng giao dịch định lượng Inventor:

Hướng dẫn cú pháp: https://www.fmz.com/syntax-guide Hướng dẫn sử dụng: https://www.fmz.com/user-guide

Nhận độ chính xác

Hiện tại, API có một chức năng thống nhất để có được độ chính xác, được giới thiệu ở đây bằng cách sử dụng hợp đồng vĩnh viễn làm ví dụ.

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

Nhận báo giá

Để thiết kế một chiến lược đa dạng, cần phải có đầy đủ các điều kiện thị trường. Giao diện thị trường tổng hợp này rất cần thiết và chức năng GetTickers hỗ trợ hầu hết các sàn giao dịch chính thống.

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

Nhận thông tin vị trí tài khoản

Trường Equity và trường UPnL đã được thêm vào thông tin tài khoản tương lai để tránh tình trạng không tương thích do quá trình xử lý bổ sung gây ra. Hàm GetPositions cũng hỗ trợ việc lấy tất cả các vị trí. Một chi tiết là số lượng vị trí phải được nhân với giá trị của một hợp đồng để có được số lượng thực tế. Ví dụ, mặc dù trang web hợp đồng vĩnh viễn OKX cho phép bạn chọn giao dịch theo số lượng, API dựa trên số lượng hợp đồng.

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

buôn bán

Giao dịch cũng cần giải quyết vấn đề về số lượng hợp đồng. Chức năng CreateOrder mới nhất được sử dụng ở đây để xử lý đơn hàng, tiện lợi hơn nhiều.

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

Hiển thị trạng thái

Thông thường có hai bảng được hiển thị: thông tin tài khoản và thông tin cặp giao dịch.

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

Logic giao dịch

Sau khi thiết lập xong giàn giáo, mã logic giao dịch cốt lõi rất đơn giản. Sau đây là chiến lược lệnh tảng băng trôi đơn giản nhất.

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

Vòng lặp chính

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

Tóm tắt

Bài viết này cung cấp một khuôn khổ giao dịch đa tiền tệ hợp đồng vĩnh viễn đơn giản. Bằng cách sử dụng API mới nhất, bạn có thể xây dựng các chiến lược tương thích thuận tiện và nhanh chóng hơn. Rất đáng để thử.