← Назад к документации
actions
Исходный код Rust - Trading AI
📄 Rust
📦 Модуль
🔧 Исходный код
// ===========================
// File: src/live/actions.rs
// ===========================
//
// LiveAction + применение к PositionSnapshot
//

use crate::live::state::{PositionSnapshot, LiveMode};

/// Действия LIVE-движка.
///
/// Важное разделение:
/// - CloseLong/CloseShort НИЧЕГО НЕ ПИШУТ в realized_pnl
///   (PNL добавляется отдельным действием Realize(pnl)).
/// - SetBaseQty — новое действие: запоминаем базовый юнит позиции
///
#[derive(Clone, Debug)]
pub enum LiveAction {
    /// Базовое количество контрактов для всей сессии
    SetBaseQty(f64),

    /// Задать плечо на сессию (20x, 50x, ...)
    SetLeverage(f64),

    /// Задать contract_value (quanto_multiplier)
    SetContractValue(f64),

    /// Открыть/докупить лонг (qty контрактов по цене price)
    OpenLong(f64, f64),

    /// Открыть/докупить шорт
    OpenShort(f64, f64),

    /// Закрыть часть/весь лонг (qty, price, is_profit_side)
    CloseLong(f64, f64, bool),

    /// Закрыть часть/весь шорт (qty, price, is_profit_side)
    CloseShort(f64, f64, bool),

    /// Добавить в реализованный PnL
    Realize(f64),

    /// Сменить режим (Flat/Single/Both/Recovery)
    SetMode(LiveMode),

    /// Сбросить счётчик резок
    ResetCutStreak,
}

/// Применение списка действий к позиции.
pub fn apply_to_position(pos: &mut PositionSnapshot, actions: &[LiveAction]) {
    for act in actions {
        match act {
            // === Новое действие: Запоминаем базовый юнит ===
            LiveAction::SetBaseQty(q) => {
                pos.base_qty = (*q).max(0.0);
            }

            LiveAction::SetLeverage(lev) => {
                pos.entry_leverage = *lev;
                pos.recalc_margins();
            }

            LiveAction::SetContractValue(v) => {
                pos.contract_value = if *v > 0.0 { *v } else { 1.0 };
                pos.recalc_margins();
            }

            LiveAction::OpenLong(qty, price) => {
                if *qty <= 0.0 {
                    continue;
                }
                // Используем PositionSnapshot::open_long, чтобы
                // корректно поддерживать first_side, initial_entry_price,
                // base_qty и mode.
                pos.open_long(*qty, *price);
            }

            LiveAction::OpenShort(qty, price) => {
                if *qty <= 0.0 {
                    continue;
                }
                pos.open_short(*qty, *price);
            }

            LiveAction::CloseLong(qty, _price, _is_profit) => {
                if *qty <= 0.0 || pos.long_qty <= 0.0 {
                    continue;
                }

                // Сколько реально можем закрыть (не больше текущего объёма)
                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();

                pos.mode = if pos.long_qty > 0.0 && pos.short_qty > 0.0 {
                    LiveMode::Both
                } else if pos.long_qty > 0.0 || pos.short_qty > 0.0 {
                    LiveMode::Single
                } else {
                    LiveMode::Flat
                };
            }

            LiveAction::CloseShort(qty, _price, _is_profit) => {
                if *qty <= 0.0 || pos.short_qty <= 0.0 {
                    continue;
                }

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

                pos.mode = if pos.long_qty > 0.0 && pos.short_qty > 0.0 {
                    LiveMode::Both
                } else if pos.long_qty > 0.0 || pos.short_qty > 0.0 {
                    LiveMode::Single
                } else {
                    LiveMode::Flat
                };
            }

            LiveAction::Realize(pnl) => {
                pos.realized_pnl += pnl;
            }

            LiveAction::SetMode(m) => {
                pos.mode = m.clone();
            }

            LiveAction::ResetCutStreak => {
                pos.cut_streak = 0;
            }
        }
    }
}