完整使用示例

动量策略

注意:本策略仅为使用接口提供示例用法,比如查询行情,下单等,策略本身未经验证,不构成投资建议。

该策略基本思路为"过去一段时间收益较高的资产,在未来仍将延续原有趋势,可能会获得较高的收益",选股所用股票池为纳斯达克100指数成份股。

具体实现过程为:定期运行策略,每次选取股票池中周期内涨幅最高的若干股票,作为本次调仓的目标股票买入持有,对先前持仓中未入选的股票进行平仓。

代码如下:

#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <string>
#include <algorithm>
#include <numeric>
#include <cmath>
#include <thread>
#include <chrono>
#include <ctime>

#include "tigerapi/quote_client.h"
#include "tigerapi/trade_client.h"
#include "tigerapi/client_config.h"
#include "tigerapi/contract_util.h"
#include "tigerapi/order_util.h"

using namespace TIGER_API;
using namespace web::json;

// 纳斯达克100指数成分股 Components of Nasdaq-100
static const std::vector<utility::string_t> UNIVERSE_NDX = {
    U("AAPL"), U("ADBE"), U("ADI"), U("ADP"), U("ADSK"), U("AEP"), U("ALGN"), U("AMAT"), U("AMD"), U("AMGN"),
    U("AMZN"), U("ANSS"), U("ASML"), U("ATVI"), U("AVGO"), U("BIDU"), U("BIIB"), U("BKNG"), U("CDNS"), U("CDW"),
    U("CERN"), U("CHKP"), U("CHTR"), U("CMCSA"), U("COST"), U("CPRT"), U("CRWD"), U("CSCO"), U("CSX"), U("CTAS"),
    U("CTSH"), U("DLTR"), U("DOCU"), U("DXCM"), U("EA"), U("EBAY"), U("EXC"), U("FAST"), U("FB"), U("FISV"),
    U("FOX"), U("GILD"), U("GOOG"), U("HON"), U("IDXX"), U("ILMN"), U("INCY"), U("INTC"), U("INTU"), U("ISRG"),
    U("JD"), U("KDP"), U("KHC"), U("KLAC"), U("LRCX"), U("LULU"), U("MAR"), U("MCHP"), U("MDLZ"), U("MELI"),
    U("MNST"), U("MRNA"), U("MRVL"), U("MSFT"), U("MTCH"), U("MU"), U("NFLX"), U("NTES"), U("NVDA"), U("NXPI"),
    U("OKTA"), U("ORLY"), U("PAYX"), U("PCAR"), U("PDD"), U("PEP"), U("PTON"), U("PYPL"), U("QCOM"), U("REGN"),
    U("ROST"), U("SBUX"), U("SGEN"), U("SIRI"), U("SNPS"), U("SPLK"), U("SWKS"), U("TCOM"), U("TEAM"), U("TMUS"),
    U("TSLA"), U("TXN"), U("VRSK"), U("VRSN"), U("VRTX"), U("WBA"), U("WDAY"), U("XEL"), U("XLNX"), U("ZM")
};

// 持仓股票个数
static const int HOLDING_NUM = 5;
// 订单检查次数
static const int ORDERS_CHECK_MAX_TIMES = 10;
// 获取行情每次请求symbol个数
static const int REQUEST_SIZE = 50;
// 计算动量的时间周期(天数)
static const int MOMENTUM_PERIOD = 30;

// 获取当前毫秒时间戳
int64_t now_ms() {
    auto now = std::chrono::system_clock::now();
    return std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
}

// 获取指定天数前的毫秒时间戳
int64_t time_before_days(int days) {
    auto now = std::chrono::system_clock::now();
    auto target = now - std::chrono::hours(24 * days);
    return std::chrono::duration_cast<std::chrono::milliseconds>(target.time_since_epoch()).count();
}

/**
 * 动量策略类
 */
class MomentumStrategy {
public:
    MomentumStrategy(ClientConfig& config)
        : quote_client_(config), trade_client_(config), config_(config) {}

    void run() {
        // 查询行情权限
        value perms = quote_client_.grab_quote_permission();
        std::cout << "Quote permissions: " << perms.serialize() << std::endl;

        // 1. 按动量筛选股票
        screen_stocks();

        // 2. 调仓
        rebalance_portfolio();
    }

private:
    QuoteClient quote_client_;
    TradeClient trade_client_;
    ClientConfig& config_;
    std::vector<utility::string_t> selected_symbols_;

    /**
     * 按动量筛选股票
     * 选取周期内涨幅最高的若干股票
     */
    void screen_stocks() {
        std::cout << "=== 开始筛选股票 ===" << std::endl;

        // 用于存储每只股票的动量(涨幅)
        std::map<utility::string_t, double> momentum_map;

        // 分批获取历史K线数据
        for (size_t i = 0; i < UNIVERSE_NDX.size(); i += REQUEST_SIZE) {
            value symbols = value::array();
            for (size_t j = i; j < std::min(i + (size_t)REQUEST_SIZE, UNIVERSE_NDX.size()); j++) {
                symbols[j - i] = value::string(UNIVERSE_NDX[j]);
            }

            // 获取日K线数据,获取足够的天数来计算动量
            value bars = quote_client_.get_bars(symbols, U("day"), MOMENTUM_PERIOD + 5);

            // 解析K线数据,计算每只股票的动量
            if (bars.is_array()) {
                for (size_t k = 0; k < bars.size(); k++) {
                    auto& bar = bars[k];
                    if (bar.has_field(U("symbol")) && bar.has_field(U("close"))) {
                        utility::string_t symbol = bar[U("symbol")].as_string();
                        // 收集每只股票的收盘价
                        if (momentum_map.find(symbol) == momentum_map.end()) {
                            // 记录最早的收盘价(用于计算区间涨幅)
                            momentum_map[symbol] = bar[U("close")].as_double();
                        }
                    }
                }
            }

            // 控制请求频率
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
        }

        // 获取最新价格,计算涨幅
        std::vector<std::pair<utility::string_t, double>> momentum_list;
        for (size_t i = 0; i < UNIVERSE_NDX.size(); i += REQUEST_SIZE) {
            value symbols = value::array();
            for (size_t j = i; j < std::min(i + (size_t)REQUEST_SIZE, UNIVERSE_NDX.size()); j++) {
                symbols[j - i] = value::string(UNIVERSE_NDX[j]);
            }
            value briefs = quote_client_.get_brief(symbols);
            if (briefs.is_array()) {
                for (size_t k = 0; k < briefs.size(); k++) {
                    auto& item = briefs[k];
                    if (item.has_field(U("symbol")) && item.has_field(U("latestPrice"))) {
                        utility::string_t sym = item[U("symbol")].as_string();
                        double latest = item[U("latestPrice")].as_double();
                        if (momentum_map.count(sym) && momentum_map[sym] > 0) {
                            double change = (latest - momentum_map[sym]) / momentum_map[sym];
                            momentum_list.push_back({sym, change});
                        }
                    }
                }
            }
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
        }

        // 按涨幅降序排列,选取涨幅最高的 HOLDING_NUM 只股票
        std::sort(momentum_list.begin(), momentum_list.end(),
                  [](const auto& a, const auto& b) { return a.second > b.second; });

        selected_symbols_.clear();
        for (int i = 0; i < std::min(HOLDING_NUM, (int)momentum_list.size()); i++) {
            selected_symbols_.push_back(momentum_list[i].first);
            std::cout << "Selected: " << momentum_list[i].first
                      << " momentum: " << momentum_list[i].second << std::endl;
        }
    }

    /**
     * 调仓
     * 先将本次选股未选中但在持仓中的股票进行平仓,然后将选中的股票按照等权重买入
     */
    void rebalance_portfolio() {
        std::cout << "=== 开始调仓 ===" << std::endl;

        // 获取当前持仓
        value positions_json = trade_client_.get_positions(U("STK"), U("US"));
        std::map<utility::string_t, int> positions;  // symbol -> quantity
        if (positions_json.is_array()) {
            for (size_t i = 0; i < positions_json.size(); i++) {
                auto& pos = positions_json[i];
                if (pos.has_field(U("symbol")) && pos.has_field(U("quantity"))) {
                    positions[pos[U("symbol")].as_string()] = pos[U("quantity")].as_integer();
                }
            }
        }

        // 找出需要平仓的股票:在持仓中但不在本次选股结果中
        std::set<utility::string_t> selected_set(selected_symbols_.begin(), selected_symbols_.end());
        std::vector<utility::string_t> need_close_symbols;
        for (auto& [symbol, qty] : positions) {
            if (selected_set.find(symbol) == selected_set.end()) {
                need_close_symbols.push_back(symbol);
            }
        }

        // 平仓未选中的股票
        if (!need_close_symbols.empty()) {
            // 获取最新价格用于下限价单
            value close_symbols_arr = value::array();
            for (size_t i = 0; i < need_close_symbols.size(); i++) {
                close_symbols_arr[i] = value::string(need_close_symbols[i]);
            }
            value close_briefs = quote_client_.get_brief(close_symbols_arr);
            std::map<utility::string_t, double> close_prices;
            if (close_briefs.is_array()) {
                for (size_t i = 0; i < close_briefs.size(); i++) {
                    auto& item = close_briefs[i];
                    if (item.has_field(U("symbol")) && item.has_field(U("latestPrice"))) {
                        close_prices[item[U("symbol")].as_string()] = item[U("latestPrice")].as_double();
                    }
                }
            }

            std::vector<Order> sell_orders;
            for (auto& symbol : need_close_symbols) {
                int quantity = positions[symbol];
                if (quantity == 0) continue;

                Contract contract = ContractUtil::stock_contract(symbol, U("USD"));
                utility::string_t action = (quantity > 0) ? U("SELL") : U("BUY");
                Order order = OrderUtil::limit_order(
                    config_.account, contract, action,
                    std::abs(quantity), close_prices[symbol]
                );
                sell_orders.push_back(order);
                std::cout << "平仓: " << action << " " << symbol
                          << " quantity=" << std::abs(quantity)
                          << " price=" << close_prices[symbol] << std::endl;
            }
            execute_orders(sell_orders);
        }

        // 获取账户资产信息,计算可用于买入的金额
        value asset_json = trade_client_.get_prime_assets();
        double equity_with_loan = 0;
        double overnight_liquidation = 0;
        if (asset_json.has_field(U("equityWithLoan"))) {
            equity_with_loan = asset_json[U("equityWithLoan")].as_double();
        }
        if (asset_json.has_field(U("overnightLiquidation"))) {
            overnight_liquidation = asset_json[U("overnightLiquidation")].as_double();
        }

        // 调仓后目标隔夜剩余流动性
        // 隔夜剩余流动性比例 = 隔夜剩余流动性 / 含贷款价值总权益
        double target_ratio = 0.6;
        double target_overnight_liquidation = equity_with_loan * target_ratio;
        double adjust_value = overnight_liquidation - target_overnight_liquidation;
        if (adjust_value <= 0) {
            std::cout << "流动性不足,无法买入" << std::endl;
            return;
        }

        // 获取选中股票的最新价格
        value selected_arr = value::array();
        for (size_t i = 0; i < selected_symbols_.size(); i++) {
            selected_arr[i] = value::string(selected_symbols_[i]);
        }
        value buy_briefs = quote_client_.get_brief(selected_arr);
        std::map<utility::string_t, double> buy_prices;
        if (buy_briefs.is_array()) {
            for (size_t i = 0; i < buy_briefs.size(); i++) {
                auto& item = buy_briefs[i];
                if (item.has_field(U("symbol")) && item.has_field(U("latestPrice"))) {
                    buy_prices[item[U("symbol")].as_string()] = item[U("latestPrice")].as_double();
                }
            }
        }

        // 按持股数量等权重持仓 equal weight
        double weight = 1.0 / selected_symbols_.size();
        std::vector<Order> buy_orders;
        for (auto& symbol : selected_symbols_) {
            double price = buy_prices[symbol];
            if (price <= 0) continue;
            int quantity = static_cast<int>(adjust_value * weight / price);
            if (quantity <= 0) continue;

            Contract contract = ContractUtil::stock_contract(symbol, U("USD"));
            Order order = OrderUtil::limit_order(
                config_.account, contract, U("BUY"),
                quantity, price
            );
            order.time_in_force = U("GTC");  // 撤销前有效
            buy_orders.push_back(order);
            std::cout << "买入: BUY " << symbol
                      << " quantity=" << quantity
                      << " price=" << price << std::endl;
        }
        execute_orders(buy_orders);
    }

    /**
     * 执行订单列表:下单、检查成交、改单、撤单
     */
    void execute_orders(std::vector<Order>& orders) {
        std::map<int64_t, Order> local_orders;
        for (auto& order : orders) {
            try {
                value result = trade_client_.place_order(order);
                std::cout << "下单成功: " << order.contract.symbol
                          << " id=" << order.id << std::endl;
                local_orders[order.id] = order;
            } catch (const std::exception& e) {
                std::cerr << "下单失败: " << order.contract.symbol
                          << " error=" << e.what() << std::endl;
            }
        }

        // 等待订单成交
        std::this_thread::sleep_for(std::chrono::seconds(20));

        for (int i = 0; i <= ORDERS_CHECK_MAX_TIMES; i++) {
            std::cout << "检查订单状态,第 " << i << " 次" << std::endl;

            value open_orders = trade_client_.get_open_orders(
                U("STK"), U("US"),
                time_before_days(1), now_ms()
            );

            if (!open_orders.is_array() || open_orders.size() == 0) {
                std::cout << "所有订单已成交" << std::endl;
                break;
            }

            // 检查一定次数后如果还未成交,进行一次改单,修改限价为最新价格
            if (i == ORDERS_CHECK_MAX_TIMES / 2) {
                for (size_t j = 0; j < open_orders.size(); j++) {
                    auto& open_order = open_orders[j];
                    if (open_order.has_field(U("symbol")) && open_order.has_field(U("id"))) {
                        utility::string_t sym = open_order[U("symbol")].as_string();
                        int64_t order_id = open_order[U("id")].as_number().to_int64();

                        value sym_arr = value::array();
                        sym_arr[0] = value::string(sym);
                        value brief = quote_client_.get_brief(sym_arr);
                        if (brief.is_array() && brief.size() > 0 &&
                            brief[0].has_field(U("latestPrice"))) {
                            double new_price = brief[0][U("latestPrice")].as_double();
                            try {
                                trade_client_.modify_order(order_id, new_price);
                                std::cout << "改单: id=" << order_id
                                          << " symbol=" << sym
                                          << " new_price=" << new_price << std::endl;
                            } catch (const std::exception& e) {
                                std::cerr << "改单失败: id=" << order_id
                                          << " error=" << e.what() << std::endl;
                            }
                        }
                    }
                }
            }

            // 如果达到最大检查次数还未成交,则进行撤单
            if (i >= ORDERS_CHECK_MAX_TIMES) {
                for (size_t j = 0; j < open_orders.size(); j++) {
                    auto& open_order = open_orders[j];
                    if (open_order.has_field(U("id"))) {
                        int64_t order_id = open_order[U("id")].as_number().to_int64();
                        try {
                            trade_client_.cancel_order(order_id);
                            std::cout << "撤单: id=" << order_id << std::endl;
                        } catch (const std::exception& e) {
                            std::cerr << "撤单失败: id=" << order_id
                                      << " error=" << e.what() << std::endl;
                        }
                    }
                }
            }

            std::this_thread::sleep_for(std::chrono::seconds(10));
        }

        // 打印已成交订单信息
        value filled = trade_client_.get_filled_orders(
            U("STK"), U("US"),
            time_before_days(1), now_ms()
        );
        if (filled.is_array()) {
            std::cout << "已成交订单:" << std::endl;
            for (size_t i = 0; i < filled.size(); i++) {
                std::cout << "  " << filled[i].serialize() << std::endl;
            }
        }
    }
};

int main() {
    // 初始化配置
    ClientConfig config(false, U("/path/to/your/properties/"));

    // 创建策略并运行
    MomentumStrategy strategy(config);
    strategy.run();

    return 0;
}