#include namespace mira { namespace marketmaking { MexcSpotMarketMaking::MexcSpotMarketMaking(strategy_config_t config) : Strategy(config) { using conv_fn = std::function; config_.symbol = Strategy::config_.symbol; config_.execution = Strategy::config_.execution; config_.data = Strategy::config_.data; std::unordered_map cnv = { {"api", [this](const auto& v) { config_.api_key = v; }}, {"secret", [this](const auto& v) { config_.secret_key = v; }}, {"available_base_balance_to_total", [this](const auto& v) { config_.available_base_balance_to_total = std::stod(v); }}, {"available_quote_balance_to_total", [this](const auto& v) { config_.available_quote_balance_to_total = std::stod(v); }}, {"base_size_to_total", [this](const auto& v) { config_.base_size_to_total = std::stod(v); }}, {"cancel_on_signal_event", [this](const auto& v) { config_.cancel_on_signal_event = std::stoi(v); }}, {"first_order_size", [this](const auto& v) { config_.first_order_size = std::stod(v); }}, {"min_order_size", [this](const auto& v) { config_.min_order_size = std::stod(v); }}, {"n_base", [this](const auto& v) { config_.n_base = std::stoi(v); }}, {"n_lvl", [this](const auto& v) { config_.n_lvl = std::stoi(v); }}, {"price_step", [this](const auto& v) { config_.price_step = std::stoi(v); }}, {"react_on_trade", [this](const auto& v) { config_.react_on_trade = std::stoi(v); }}, {"spread_size", [this](const auto& v) { config_.spread_size = std::stod(v); }}, {"symbol", [this](const auto& v) { config_.symbol_str = v; }}, {"target_price", [this](const auto& v) { config_.target.price = std::stod(v); }}, {"use_grid", [this](const auto& v) { config_.use_grid = std::stoi(v); }}, {"debug", [this](const auto& v) { config_.debug = std::stoi(v); }}, {"enable_batch_orders", [this](const auto& v) { config_.enable_batch_orders = std::stoi(v); }}, {"target_type", [this](const auto& v) { if (v == "up") { config_.target.type = target::targetType::Up; } else if (v == "down") { config_.target.type = target::targetType::Down; } else if (v == "stable") { config_.target.type = target::targetType::Stable; } } }, }; for (const auto &[key, fn] : cnv) { try { if (config_.init_args.count(key)) { fn(config_.init_args[key]); } } catch (const std::exception& e) { std::cerr << "Error for key " << key << ": " << e.what() << '\n'; std::exit(EXIT_FAILURE); } } // Validate some parameters if (config_.target.type == target::targetType::Stable && !config_.target.price.has_value()) { std::cerr << "Wrong target type: \"Stable\", but no targetPrice specified."; exit(EXIT_FAILURE); } if (!config_.debug) { std::string fname = "/mnt/logs/" + config_.symbol_str + "-" + get_date(std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count()) + ".log"; std::cout << "***\n" << fname << "\n***\n"; std::freopen(fname.c_str(), "w", stderr); } } order_event MexcSpotMarketMaking::__process(const market_event& event) { order_event res; if (event.type == e_market_event::ORDER_BOOK) { auto orderbook_event = event.get(); if (orderbook_event.event_time - config_hidden_.upd_time < 1000) { // 0-момент по-идее сюда не попадёт return res; } __update_mid_price(orderbook_event); int spread_ticks = std::max( (config_hidden_.mid_price * config_.spread_size).to_scaled(), SIZE_T_ONE ); auto [asks, bids] = calculate_orders(config_hidden_.mid_price, spread_ticks); res.set(order_limit_grid_event(std::move(asks), std::move(bids))); res.symbol = config_.symbol; } return res; } void MexcSpotMarketMaking::__fill_order_preprocessing(const fill_order_event& event) { // config_.logger.get()->log(event, e_severity_level::INFO); std::cerr << "Fill event:\n" << event << "\n"; order_t order = event.order; double filled_qty = event.filled_quantity_piece; // Note: Позиция уже обновлена в execution, тепер ММ не берёт обязанности риск-чекера if (event.order.status == e_order_status::FILLED) { // т.е. когда снесли уровень полностью, иначе нам неитересно реагировать if (config_.target.type == target::targetType::Stable) { // thought: case_1 return; } int trade_sgn = (event.order.side == e_side::SELL ? 1 : -1); int target_sgn; switch (config_.target.type) { case target::targetType::Up: target_sgn = 1; break; case target::targetType::Down: target_sgn = -1; break; default: target_sgn = 0; break; } if (trade_sgn != target_sgn) { if (config_.react_on_trade) { if (!target_sgn) { config_.target.type = target::targetType::Adaptive; } else { config_.target.type = (target_sgn == 1 ? target::targetType::Down : target::targetType::Up); } } } } } order_event MexcSpotMarketMaking::__process_fill_order(const fill_order_event& event) { __fill_order_preprocessing(event); // режим работы пересчитываем return order_event(); // mock } order_event MexcSpotMarketMaking::__process(const fill_event& event) { // get it from execution order_event res; switch(event.type) { case e_fill_event::ORDER: { auto fill_event = event.get(); res.set(__process_fill_order(fill_event)); res.symbol = config_.symbol; break; } case e_fill_event::NONE: break; default: throw std::runtime_error("unknown e_fill_event type"); } return res; } std::pair MexcSpotMarketMaking::calculate_orders(precised_float mid_price, int spread_ticks) // TODO залогировать { auto& balance_getter = config_.shared_state.get()->get_balance_getter(config_.market); auto base_balance_res = balance_getter.get_balance(config_.symbol->base); auto quote_balance_res = balance_getter.get_balance(config_.symbol->quote); auto base_balance = (base_balance_res.free + base_balance_res.locked) * config_.available_base_balance_to_total; auto quote_balance = (quote_balance_res.free + quote_balance_res.locked) * config_.available_quote_balance_to_total; const auto& bid_and_asks_levels = config_.shared_state->get_data_getter(config_.symbol).get_order_book()[0]; // TODO: это же не факт, что органика auto ask_price = std::max(mid_price, config_.symbol->make_price(bid_and_asks_levels.bid_price) + 1) + (spread_ticks + 1) / 2; auto bid_price = std::min(mid_price, config_.symbol->make_price(bid_and_asks_levels.ask_price) - 1) - spread_ticks / 2; double base_amount_per_level = base_balance / config_.n_base; double quote_amount_per_level = quote_balance / config_.n_base; grids asks, bids; for (int i = 0; i < config_.n_lvl; ++i) { // SELL orders auto sell_price = ask_price + i * config_.price_step; asks[sell_price] = config_.symbol->make_quantity(base_amount_per_level / sell_price.to_double()); // BUY orders auto buy_price = bid_price - i * config_.price_step; bids[buy_price] = config_.symbol->make_quantity(quote_amount_per_level / buy_price.to_double()); } return std::make_pair(std::move(asks), std::move(bids)); } // Обновляет мидпрайс и время её последнего изменения void MexcSpotMarketMaking::__update_mid_price(const market_order_book_event& event) { // config_.logger.get()->log(event, e_severity_level::INFO); std::cerr << "Order book event:\n" << event << "\n"; if (config_.target.price.has_value()) { // ветка для таргетированной цены: config_hidden_.mid_price = config_.symbol->make_price(config_.target.price.value()); } else { const auto& bid_and_asks_levels = event.order_book[0]; double mid_price; if (bid_and_asks_levels.ask_price && bid_and_asks_levels.bid_price) { // в стакане есть и бид и аск mid_price = (bid_and_asks_levels.ask_price + bid_and_asks_levels.bid_price) / 2; } else if (bid_and_asks_levels.ask_price + bid_and_asks_levels.bid_price != 0) { // в стакане есть либо бид, либо аск if (bid_and_asks_levels.ask_price) { // есть аск mid_price = bid_and_asks_levels.ask_price * (1 - config_.spread_size / 2); } else { // есть бид mid_price = bid_and_asks_levels.bid_price * (1 + config_.spread_size / 2); } } else { // пустой стакан mid_price = 1; } config_hidden_.mid_price = config_.symbol->make_price(mid_price); } config_hidden_.upd_time = event.event_time; } } // namespace marketmaking } // namespace mira