← Назад к главному
# step.rs - Модуль оркестрации LIVE движка ## 📋 Назначение модуля `step.rs` оркестрирует выполнение LIVE движка, вызывая стратегии и исполняя их действия через Gate.io API. **Основная логика:** 1. **Обновление цены** из TradingMeta 2. **Risk check** - проверка рисков 3. **Вызов стратегий** (Core, Single, Both) 4. **Исполнение действий** через Gate.io API 5. **Логирование** в Redis и файлы 6. **Обновление состояния позиции** **Используется в:** - `main.rs` - главный цикл сессий (вызывает live_step) --- ## 🏗️ Структура модуля ### Структура `LiveStepContext` **Строки:** 22-38 ```rust pub struct LiveStepContext { pub machine: PositionMachine, // Фазовая машина позиций pub core: CoreStrategy, // Стратегия первого входа pub single: SingleStrategy, // Стратегия односторонней позиции pub both: BothStrategy, // Стратегия BOTH позиции } ``` **Назначение:** Хранение всех стратегий и фазовой машины **Поля и их описание:** | Поле | Тип | Описание | |------|-----|----------| | `machine` | PositionMachine | Фазовая машина (Flat, Single, Both, Boosted, WindDown, ExitReady) | | `core` | CoreStrategy | Стратегия первого входа (пустая позиция) | | `single` | SingleStrategy | Стратегия односторонней позиции | | `both` | BothStrategy | Стратегия BOTH позиции | --- ## 🔄 Основные функции ### Функция `LiveStepContext::new()` **Строки:** 30-37 **Сигнатура:** ```rust pub fn new(cfg: &Config) -> Self ``` **Возвращает:** Новый `LiveStepContext` с инициализированными стратегиями **Алгоритм:** ```rust pub fn new(cfg: &Config) -> Self { Self { machine: PositionMachine::new(), core: CoreStrategy::new(cfg), single: SingleStrategy::new(), both: BothStrategy::new(), } } ``` **Пример:** ```rust let cfg = Config::from_file("config.toml").unwrap(); let ctx = LiveStepContext::new(&cfg); ctx = LiveStepContext { machine: PositionMachine { phase: Single, cut_count: 0, boosted_once: false }, core: CoreStrategy { }, single: SingleStrategy { did_cut_once: false, first_rebuild_price: 0.0 }, both: BothStrategy { }, } ``` --- ### Функция `live_step()` **Строки:** 43-145 **Сигнатура:** ```rust pub async fn live_step( redis: Arc, ctx: &mut LiveStepContext, pos: &mut PositionSnapshot, meta_prev: &TradingMeta, meta_now: &TradingMeta, current_balance: f64, logger: &mut crate::logger::Logger, ) -> Result<()> ``` **Возвращает:** - `Ok(())` - шаг выполнен успешно - `Err` - ошибка **Алгоритм (8 этапов):** --- #### Этап 1: Обновление цены **Строки:** 55-56 ```rust pos.update_price(meta_now.price); pos.update_price(meta_now.price); ``` **Назначение:** Обновление цены дважды (возможно для синхронизации) **Пример:** ``` meta_now.price = 0.002060 pos.update_price(0.002060); // pos.last_price = 0.002060 ``` --- #### Этап 2: Risk check **Строка:** 58 ```rust let _risk = risk_check(pos, meta_prev, meta_now); ``` **Назначение:** Проверка рисков (критичные изменения цены, маржа и т.д.) **Пример:** ```rust risk_check(pos, meta_prev, meta_now); // Проверяет: резкое изменение цены, недостаточная маржа, и т.д. // Если риск → возвращает ошибку → сессия завершается ``` --- #### Этап 3: CoreStrategy - первый вход **Строки:** 60-67 ```rust // FIX: CORE работает только если позиция полностью пустая if pos.long_qty == 0.0 && pos.short_qty == 0.0 { let gate = GateClient::new(); let core_actions = ctx.core.process_with_real_limits(redis.clone(), &gate, pos, meta_now, current_balance).await; create_real_orders(redis.clone(), pos, &core_actions, &gate, logger).await; } ``` **Условия:** - `long_qty == 0` **и** `short_qty == 0` (пустая позиция) **Действия:** 1. Создать Gate.io клиент 2. Вызвать `CoreStrategy::process_with_real_limits()` 3. Вызвать `create_real_orders()` для исполнения **Пример:** ``` long_qty = 0, short_qty = 0 ✅ ctx.core.process_with_real_limits(...) -> Vec: - SetBaseQty(1000) - SetContractValue(100) - SetLeverage(50.0) - OpenLong(1000, 0.002000) create_real_orders(...) → исполнение через Gate.io API ``` --- #### Этап 4: SingleStrategy - управление односторонней позицией **Строки:** 69-94 ```rust // 🔥 SingleStrategy теперь возвращает actions! let gate = GateClient::new(); let single_actions = ctx.single.process(pos, meta_now); if !single_actions.is_empty() { create_real_orders(redis.clone(), pos, &single_actions, &gate, logger).await; } ``` **Условия:** - Односторонняя позиция (LONG или SHORT) **Действия:** 1. Вызвать `SingleStrategy::process()` 2. Если есть действия → `create_real_orders()` **Пример:** ``` long_qty = 1000, short_qty = 0 ctx.single.process(pos, meta_now) -> Vec: - CloseLong(500, 0.002060, true) - OpenLong(500, 0.002060) create_real_orders(...) → исполнение через Gate.io API ``` --- #### Этап 5: Определение entry_price для хеджа **Строки:** 76-84 ```rust let entry_for_hedge = if pos.initial_entry_price > 0.0 { pos.initial_entry_price } else if pos.long_qty > 0.0 { pos.entry_long } else if pos.short_qty > 0.0 { pos.entry_short } else { 0.0 }; ``` **Логика:** 1. Если `initial_entry_price > 0` → используем `initial_entry_price` 2. Иначе если `long_qty > 0` → используем `entry_long` 3. Иначе если `short_qty > 0` → используем `entry_short` 4. Иначе → `0.0` **Примеры:** ``` Сценарий 1 (первый вход): initial_entry_price = 0.002000 entry_for_hedge = 0.002000 Сценарий 2 (LONG уже открыт): long_qty = 1000, short_qty = 0 entry_long = 0.002000 entry_for_hedge = 0.002000 Сценарий 3 (SHORT уже открыт): long_qty = 0, short_qty = 1000 entry_short = 0.002000 entry_for_hedge = 0.002000 ``` --- #### Этап 6: HedgeGuard - экстренный хедж **Строки:** 86-94 ```rust // 🔥 HedgeGuard теперь возвращает actions! if entry_for_hedge > 0.0 { if let Some(sig) = HedgeGuard::check(pos, meta_now, entry_for_hedge) { let hedge_actions = HedgeGuard::execute(pos, sig, meta_now.price); if !hedge_actions.is_empty() { create_real_orders(redis.clone(), pos, &hedge_actions, &gate, logger).await; } } } ``` **Условия:** - `entry_for_hedge > 0` (есть позиция) **Действия:** 1. Вызвать `HedgeGuard::check()` 2. Если есть сигнал → `HedgeGuard::execute()` 3. Если есть действия → `create_real_orders()` **Пример:** ``` long_qty = 1000, short_qty = 0 entry_long = 0.002000 current_price = 0.001970 (delta = -1.5%) HedgeGuard::check(...) -> Some(HedgeSignal): - long_qty: 1000 - short_qty: 1000 - add_long: 0.0 - add_short: 1000 - reason: "Auto-hedge LONG: delta -1.5%, add SHORT 1000" HedgeGuard::execute(...) -> Vec: - OpenShort(1000, 0.001970) create_real_orders(...) → исполнение через Gate.io API ``` --- #### Этап 7: BothStrategy - управление BOTH позицией **Строки:** 96-140 ```rust ctx.machine.update_phase(pos); let both_actions = ctx.both.process(pos, meta_now, &mut ctx.machine); if !both_actions.is_empty() { create_real_orders(redis.clone(), pos, &both_actions, &gate, logger).await; // 🔥 Логируем детальное состояние ПОСЛЕ выполнения действий if pos.long_qty > 0.0 && pos.short_qty > 0.0 { let cv = pos.contract_value.max(1.0); let price = meta_now.price; let pnl_long = if pos.entry_long > 0.0 { pos.long_qty * (price - pos.entry_long) * cv } else { 0.0 }; let pnl_short = if pos.entry_short > 0.0 { pos.short_qty * (pos.entry_short - price) * cv } else { 0.0 }; let notional_long = pos.long_qty * pos.entry_long * cv; let notional_short = pos.short_qty * pos.entry_short * cv; let pnl_long_pct = if notional_long > 0.0 { (pnl_long / notional_long) * 100.0 } else { 0.0 }; let pnl_short_pct = if notional_short > 0.0 { (pnl_short / notional_short) * 100.0 } else { 0.0 }; let notional_long = pos.long_qty * pos.entry_long * cv; let notional_short = pos.short_qty * pos.entry_short * cv; let pnl_long_pct = if notional_long > 0.0 { (pnl_long / notional_long) * 100.0 } else { 0.0 }; let pnl_short_pct = if notional_short > 0.0 { (pnl_short / notional_short) * 100.0 } else { 0.0 }; println!("📊 СТАТУС ПОСЛЕ ДЕЙСТВИЙ:"); println!(" LONG: {:+.2}% (qty={:.0}, entry={:.6}, realized={:.4})", pnl_long_pct, pos.long_qty, pos.entry_long, pos.realized_pnl); println!(" SHORT: {:+.2}% (qty={:.0}, entry={:.6})", pnl_short_pct, pos.short_qty, pos.entry_short); println!(" mode={:?}", pos.mode); } else { println!("📊 СТАТУС ПОСЛЕ ДЕЙСТВИЙ: LONG={} SHORT={} mode={:?} realized_pnl={:.4}", pos.long_qty, pos.short_qty, pos.mode, pos.realized_pnl); } } ``` **Условия:** Обе стороны активны (или одна после закрытия другой) **Действия:** 1. Обновить фазу: `machine.update_phase(pos)` 2. Вызвать `BothStrategy::process()` 3. Если есть действия → `create_real_orders()` 4. Логировать детальное состояние **Пример:** ``` long_qty = 1000, short_qty = 1000 ctx.both.process(pos, meta_now, &mut machine) -> Vec: - CloseLong(1000, 0.002060, true) - CloseShort(500, 0.002060, false) - OpenLong(1000, 0.002060) create_real_orders(...) → исполнение через Gate.io API После: long_qty = 1000, short_qty = 500 Лог: 📊 СТАТУС ПОСЛЕ ДЕЙСТВИЙ: LONG: +2.50% (qty=1000.0, entry=0.002060, realized=0.0300) SHORT: -2.50% (qty=500.0, entry=0.002000) mode=Both ``` --- #### Этап 8: Запись лога в Redis **Строка:** 142 ```rust write_live_log(redis, pos, meta_now, &ctx.machine).await?; ``` **Назначение:** Запись текущего состояния в Redis **Пример:** ```rust write_live_log(redis, pos, meta_now, &machine).await; // Записывает состояние позиции в Redis: // - hb:log:{symbol}: JSON с деталями ``` --- ### Функция `create_real_orders()` **Строки:** 150-480 **Сигнатура:** ```rust async fn create_real_orders( _redis: Arc, pos: &mut PositionSnapshot, actions: &[LiveAction], gate: &GateClient, logger: &mut crate::logger::Logger, ) ``` **Назначение:** Исполнение действий через Gate.io API (MARKET ордера) **Алгоритм (4 фазы):** --- #### Фаза 0: Rate limiting **Строки:** 160-170 ```rust static LAST_ORDER_TIME: Mutex> = Mutex::new(None); { let mut last_time = LAST_ORDER_TIME.lock().unwrap(); if let Some(prev) = *last_time { if prev.elapsed() < Duration::from_millis(200) { return; } } *last_time = Some(Instant::now()); } ``` **Назначение:** Rate limiting - не более 1 ордера каждые 200мс **Логика:** 1. Читаем `LAST_ORDER_TIME` 2. Если прошло меньше 200мс → выходим 3. Иначе → обновляем `LAST_ORDER_TIME` **Пример:** ``` 00:00:00.100 → LAST_ORDER_TIME = None ✅ 00:00:00.100 → LAST_ORDER_TIME = Some(00:00:00.100) ✅ 00:00:00.250 → elapsed = 150мс < 200мс ❌ return 00:00:00.300 → elapsed = 200мс >= 200мс ✅ LAST_ORDER_TIME = Some(00:00:00.300) ``` --- #### Фаза 1: Закрытие ПРИБЫЛЬНОЙ стороны **Строки:** 174-261 ```rust println!("🎯🎯 ФАЗА 1: Закрытие ПРИБЫЛЬНОЙ стороны..."); for action in actions { match action { LiveAction::CloseLong(qty, _, true) => { if *qty <= 0.0 { continue; } println!("🔴 CLOSE LONG (ПРИБЫЛЬ) {} {}", qty, contract); match gate.create_reduce_only_order(&contract, false, *qty).await { Ok(resp) => { println!("✅ CLOSE LONG исполнен: {:?}", resp); let fill_price = resp["fill_price"] .as_str() .and_then(|s| s.parse::().ok()) .unwrap_or(pos.last_price); let close_qty = qty.min(pos.long_qty); if close_qty >= pos.long_qty { pos.long_qty = 0.0; pos.entry_long = 0.0; pos.long_margin = 0.0; } else { let prev_qty = pos.long_qty; let new_qty = prev_qty - close_qty; let ratio = if prev_qty > 0.0 { new_qty / prev_qty } else { 0.0 }; pos.long_qty = new_qty.max(0.0); pos.long_margin *= ratio.max(0.0); } pos.recalc_margins(); println!("📊 ПРИБЫЛЬНАЯ сторона закрыта: LONG {} по {}", close_qty, fill_price); let _ = logger.log_action( "CLOSE_LONG", fill_price, close_qty, &format!("Закрыт LONG {} по {}", close_qty, fill_price), ); } Err(e) => { println!("❌ Ошибка CLOSE LONG (ПРИБЫЛЬ): {:?}", e); } } } LiveAction::CloseShort(qty, _, true) => { if *qty <= 0.0 { continue; } println!("🔴 CLOSE SHORT (ПРИБЫЛЬ) {} {}", qty, contract); match gate.create_reduce_only_order(&contract, true, *qty).await { Ok(resp) => { println!("✅ CLOSE SHORT исполнен: {:?}", resp); let fill_price = resp["fill_price"] .as_str() .and_then(|s| s.parse::().ok()) .unwrap_or(pos.last_price); let close_qty = qty.min(pos.short_qty); if close_qty >= pos.short_qty { pos.short_qty = 0.0; pos.entry_short = 0.0; pos.short_margin = 0.0; } else { let prev_qty = pos.short_qty; let new_qty = prev_qty - close_qty; let ratio = if prev_qty > 0.0 { new_qty / prev_qty } else { 0.0 }; pos.short_qty = new_qty.max(0.0); pos.short_margin *= ratio.max(0.0); } pos.recalc_margins(); println!("📊 ПРИБЫЛЬНАЯ сторона закрыта: SHORT {} по {}", close_qty, fill_price); let _ = logger.log_action( "CLOSE_SHORT", fill_price, close_qty, &format!("Закрыт SHORT {} по {}", close_qty, fill_price), ); } Err(e) => { println!("❌ Ошибка CLOSE SHORT (ПРИБЫЛЬ): {:?}", e); } } } _ => {} } } ``` **Назначение:** Закрытие ПРИБЫЛЬНОЙ стороны (is_profit_side=true) **Логика:** 1. Перебираем actions 2. Если `CloseLong(qty, _, true)` → закрыть LONG 3. Если `CloseShort(qty, _, true)` → закрыть SHORT 4. Остальные действия → пропускаем (будут в следующих фазах) **Gate.io API:** - `create_reduce_only_order(&contract, false, qty)` - закрыть LONG - `create_reduce_only_order(&contract, true, qty)` - закрыть SHORT **Обновление позиции:** - Полное закрытие: `qty = 0, entry = 0, margin = 0` - Частичное закрытие: `qty -= close_qty, margin *= ratio` **Пример:** ``` LONG = 1000, entry = 0.002000 CloseLong(1000, 0.002060, true): Gate.io API: create_reduce_only_order(contract, false, 1000) Ордер исполнен: fill_price = 0.002060 Обновление: pos.long_qty = 0 pos.entry_long = 0 pos.long_margin = 0 ``` --- #### Фаза 2: Закрытие УБЫТОЧНОЙ стороны **Строки:** 268-358 ```rust println!("🎯🎯 ФАЗА 2: Закрытие УБЫТОЧНОЙ стороны..."); for action in actions { match action { LiveAction::CloseLong(qty, _, false) => { if *qty <= 0.0 || pos.long_qty <= 0.0 { continue; } println!("🔴 CLOSE LONG (УБЫТОК) {} {}", qty, contract); match gate.create_reduce_only_order(&contract, false, *qty).await { Ok(resp) => { println!("✅ CLOSE LONG исполнен: {:?}", resp); let fill_price = resp["fill_price"] .as_str() .and_then(|s| s.parse::().ok()) .unwrap_or(pos.last_price); let close_qty = qty.min(pos.long_qty); if close_qty >= pos.long_qty { pos.long_qty = 0.0; pos.entry_long = 0.0; pos.long_margin = 0.0; } else { let prev_qty = pos.long_qty; let new_qty = prev_qty - close_qty; let ratio = if prev_qty > 0.0 { new_qty / prev_qty } else { 0.0 }; pos.long_qty = new_qty.max(0.0); pos.long_margin *= ratio.max(0.0); } pos.recalc_margins(); println!("📊 УБЫТОЧНАЯ сторона закрыта: LONG {} по {}", close_qty, fill_price); let _ = logger.log_action( "CLOSE_LONG", fill_price, close_qty, &format!("Закрыт LONG {} по {}", close_qty, fill_price), ); } Err(e) => { println!("❌ Ошибка CLOSE LONG (УБЫТОК): {:?}", e); } } } LiveAction::CloseShort(qty, _, false) => { if *qty <= 0.0 || pos.short_qty <= 0.0 { continue; } println!("🔴 CLOSE SHORT (УБЫТОК) {} {}", qty, contract); match gate.create_reduce_only_order(&contract, true, *qty).await { Ok(resp) => { println!("✅ CLOSE SHORT исполнен: {:?}", resp); let fill_price = resp["fill_price"] .as_str() .and_then(|s| s.parse::().ok()) .unwrap_or(pos.last_price); let close_qty = qty.min(pos.short_qty); if close_qty >= pos.short_qty { pos.short_qty = 0.0; pos.entry_short = 0.0; pos.short_margin = 0.0; } else { let prev_qty = pos.short_qty; let new_qty = prev_qty - close_qty; let ratio = if prev_qty > 0.0 { new_qty / prev_qty } else { 0.0 }; pos.short_qty = new_qty.max(0.0); pos.short_margin *= ratio.max(0.0); } pos.recalc_margins(); println!("📊 УБЫТОЧНАЯ сторона закрыта: SHORT {} по {}", close_qty, fill_price); let _ = logger.log_action( "CLOSE_SHORT", fill_price, close_qty, &format!("Закрыт SHORT {} по {}", close_qty, fill_price), ); } Err(e) => { println!("❌ Ошибка CLOSE SHORT (УБЫТОК): {:?}", e); } } } _ => {} } } ``` **Назначение:** Закрытие УБЫТОЧНОЙ стороны (is_profit_side=false) **Логика:** Аналогично фазе 1, но для убыточной стороны **Пример:** ``` SHORT = 500, entry = 0.002000 CloseShort(500, 0.002060, false): Gate.io API: create_reduce_only_order(contract, true, 500) Ордер исполнен: fill_price = 0.002060 Обновление: pos.short_qty = 0 pos.entry_short = 0 pos.short_margin = 0 ``` --- #### Фаза 3: Открытие позиции **Строки:** 360-457 ```rust println!("🎯🎯 ФАЗА 3: Остальные действия..."); for action in actions { match action { LiveAction::OpenLong(qty, _) => { if *qty <= 0.0 { continue; } println!("🚀 MARKET LONG {} {}", qty, contract); match gate.create_market_order(&contract, true, *qty).await { Ok(resp) => { println!("✅ MARKET LONG исполнен: {:?}", resp); let fill_price = resp["fill_price"] .as_str() .and_then(|s| s.parse::().ok()) .unwrap_or(pos.last_price); pos.open_long(*qty, fill_price); println!("📊 Позиция обновлена: LONG {} по {}", *qty, fill_price); let _ = logger.log_action( "OPEN_LONG", fill_price, *qty, &format!("Открыт LONG по {}", fill_price), ); } Err(e) => { println!("❌ Ошибка MARKET LONG: {:?}", e); } } } LiveAction::OpenShort(qty, _) => { if *qty <= 0.0 { continue; } println!("🚀 MARKET SHORT {} {}", qty, contract); match gate.create_market_order(&contract, false, *qty).await { Ok(resp) => { println!("✅ MARKET SHORT исполнен: {:?}", resp); let fill_price = resp["fill_price"] .as_str() .and_then(|s| s.parse::().ok()) .unwrap_or(pos.last_price); pos.open_short(*qty, fill_price); println!("📊 Позиция обновлена: SHORT {} по {}", *qty, fill_price); let _ = logger.log_action( "OPEN_SHORT", fill_price, *qty, &format!("Открыт SHORT по {}", fill_price), ); } Err(e) => { println!("❌ Ошибка MARKET SHORT: {:?}", e); } } } LiveAction::SetBaseQty(qty) => { pos.base_qty = (*qty).max(0.0); println!("✅ Установлён базовый размер: {}", pos.base_qty); } LiveAction::SetLeverage(lev) => { match gate.update_leverage(&contract, *lev).await { Ok(_) => { pos.entry_leverage = *lev; pos.recalc_margins(); println!("✅ Плечо установлено {}x", pos.entry_leverage); } Err(e) => { println!("⚠️ Плечо (уже установлено или ошибка): {}", e); pos.entry_leverage = *lev; pos.recalc_margins(); } } } LiveAction::SetContractValue(val) => { pos.contract_value = if *val > 0.0 { *val } else { 1.0 }; pos.recalc_margins(); println!("✅ Стоимость контракта: {}", pos.contract_value); } LiveAction::Realize(pnl) => { pos.realized_pnl += pnl; println!("✅ Реализован PnL: {}", pnl); } LiveAction::SetMode(mode) => { pos.mode = mode.clone(); println!("✅ Режим изменён на {:?}", mode); } LiveAction::ResetCutStreak => { pos.cut_streak = 0; println!("✅ Сброс счётчика резок"); } LiveAction::CloseLong(_, _, _) | LiveAction::CloseShort(_, _, _) => { // Close actions уже обработаны в фазах 1 и 2 } } } ``` **Назначение:** Исполнение остальных действий (OPEN, SET, REALIZE) **Логика:** 1. `OpenLong` / `OpenShort` → MARKET ордер 2. `SetBaseQty` → установить базовое количество 3. `SetLeverage` → установить плечо 4. `SetContractValue` → установить значение контракта 5. `Realize` → добавить в realized_pnl 6. `SetMode` → сменить режим 7. `ResetCutStreak` → сбросить счётчик резок 8. `CloseLong` / `CloseShort` → пропускаем (уже обработано) **Пример:** ``` OpenLong(1000, 0.002000): Gate.io API: create_market_order(contract, true, 1000) Ордер исполнен: fill_price = 0.002000 pos.open_long(1000, 0.002000); ``` --- #### Фаза 4: Финал **Строки:** 478-480 ```rust println!("🎯🎯 ВСЕ ДЕЙСТВИЯ ВЫПОЛНЕНЫ"); ``` **Назначение:** Логирование завершения --- ## 📊 Полный граф выполнения ``` live_step() ↓ ├─→ Обновление цены (pos.update_price) ├─→ Risk check ├─→ CoreStrategy (если пустая позиция) │ ├─→ process_with_real_limits() │ └─→ create_real_orders() │ ├─→ SingleStrategy (односторонняя позиция) │ ├─→ process() │ └─→ create_real_orders() │ ├─→ HedgeGuard (экстренный хедж) │ ├─→ check() │ ├─→ execute() │ └─→ create_real_orders() │ ├─→ BothStrategy (BOTH позиция) │ ├─→ update_phase() │ ├─→ process() │ ├─→ create_real_orders() │ └─→ Логирование статуса │ └─→ write_live_log() create_real_orders() ↓ ├─→ Rate limiting (200мс) │ ├─→ ФАЗА 1: Закрытие ПРИБЫЛЬНОЙ стороны │ ├─→ CloseLong(_, _, true) → create_reduce_only_order(false, qty) │ └─→ CloseShort(_, _, true) → create_reduce_only_order(true, qty) │ ├─→ ФАЗА 2: Закрытие УБЫТОЧНОЙ стороны │ ├─→ CloseLong(_, _, false) → create_reduce_only_order(false, qty) │ └─→ CloseShort(_, _, false) → create_reduce_only_order(true, qty) │ ├─→ ФАЗА 3: Остальные действия │ ├─→ OpenLong → create_market_order(true, qty) │ ├─→ OpenShort → create_market_order(false, qty) │ ├─→ SetBaseQty → pos.base_qty = qty │ ├─→ SetLeverage → update_leverage(lev) │ ├─→ SetContractValue → pos.contract_value = val │ ├─→ Realize → pos.realized_pnl += pnl │ ├─→ SetMode → pos.mode = mode │ └─→ ResetCutStreak → pos.cut_streak = 0 │ └─→ ВСЕ ДЕЙСТВИЯ ВЫПОЛНЕНЫ ``` --- ## ⚠️ Критические моменты ### 1. Rate limiting (200мс) **Строки:** 160-170 ```rust static LAST_ORDER_TIME: Mutex> = Mutex::new(None); { let mut last_time = LAST_ORDER_TIME.lock().unwrap(); if let Some(prev) = *last_time { if prev.elapsed() < Duration::from_millis(200) { return; } } *last_time = Some(Instant::now()); } ``` **Назначение:** Защита от превышения лимитов биржи **Пример:** ``` 00:00:00.100 → ордер 1 ✅ (LAST_ORDER_TIME = 00:00:00.100) 00:00:00.200 → ордер 2 ❌ (150мс < 200мс) 00:00:00.300 → ордер 3 ✅ (200мс >= 200мс) ``` --- ### 2. Порядок выполнения фаз **Фазы выполняются последовательно:** 1. Закрытие ПРИБЫЛЬНОЙ стороны 2. Закрытие УБЫТОЧНОЙ стороны 3. Открытие позиций **Почему это важно:** - Сначала фиксируем прибыль - Потом закрываем убыток (если нужно) - Потом открываем новые позиции --- ### 3. CoreStrategy работает только если позиция пустая **Строка:** 61 ```rust if pos.long_qty == 0.0 && pos.short_qty == 0.0 { ``` **Почему это важно:** - CoreStrategy возвращает действия для входа - Не должен работать если позиция уже открыта --- ## 📚 Связанные файлы | Файл | Связь | |------|-------| | `src/live/state.rs` | PositionSnapshot | | `src/live/meta.rs` | TradingMeta | | `src/live/position_machine.rs` | PositionMachine | | `src/live/strategy_core.rs` | CoreStrategy | | `src/live/strategy_single.rs` | SingleStrategy | | `src/live/strategy_both.rs` | BothStrategy | | `src/live/hedge_guard.rs` | HedgeGuard | | `src/live/risk.rs` | risk_check | | `src/live/logger.rs` | Logger | | `src/gate.rs` | GateClient | --- ## 🎯 Резюме **Что делает step.rs:** 1. ✅ Оркестрирует LIVE движок 2. ✅ Обновляет цену из TradingMeta 3. ✅ Проверяет риски 4. ✅ Вызывает стратегии (Core, Single, Both, Hedge) 5. ✅ Исполняет действия через Gate.io API 6. ✅ Логирует действия в Redis и файлы 7. ✅ Обновляет состояние позиции **Порядок выполнения:** 1. Обновление цены 2. Risk check 3. CoreStrategy (пустая позиция) 4. SingleStrategy (односторонняя) 5. HedgeGuard (экстренный хедж) 6. BothStrategy (BOTH) 7. create_real_orders (4 фазы) 8. write_live_log **Фазы create_real_orders:** 1. Rate limiting (200мс) 2. Закрытие ПРИБЫЛЬНОЙ стороны 3. Закрытие УБЫТОЧНОЙ стороны 4. Открытие позиций + SET + REALIZE **Rate limiting:** - Не более 1 ордера каждые 200мс --- **Дата создания:** 2026-02-22 **Автор:** Claude Code Assistant **Версия:** 1.0