use std::cell::{Cell, RefCell};
use std::collections::{BTreeMap, BTreeSet};

use chrono::{Duration, NaiveDate, NaiveDateTime, NaiveTime};

use crate::cost::CostModel;
use crate::data::{DataSet, IntradayExecutionQuote, PriceField};
use crate::engine::BacktestError;
use crate::events::{
    AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent,
    ProcessEventKind,
};
use crate::portfolio::PortfolioState;
use crate::rules::EquityRuleHooks;
use crate::strategy::{OrderIntent, StrategyDecision};

#[derive(Debug, Default)]
pub struct BrokerExecutionReport {
    pub order_events: Vec<OrderEvent>,
    pub fill_events: Vec<FillEvent>,
    pub position_events: Vec<PositionEvent>,
    pub account_events: Vec<AccountEvent>,
    pub process_events: Vec<ProcessEvent>,
    pub diagnostics: Vec<String>,
}

#[derive(Debug, Clone, Copy)]
struct ExecutionLeg {
    price: f64,
    quantity: u32,
}

#[derive(Debug, Clone)]
struct ExecutionFill {
    quantity: u32,
    next_cursor: NaiveDateTime,
    legs: Vec<ExecutionLeg>,
    unfilled_reason: Option<&'static str>,
}

#[derive(Debug, Clone)]
struct OpenOrder {
    order_id: u64,
    symbol: String,
    side: OrderSide,
    requested_quantity: u32,
    filled_quantity: u32,
    remaining_quantity: u32,
    limit_price: f64,
    reason: String,
}

#[derive(Debug, Clone)]
struct TargetConstraint {
    symbol: String,
    current_qty: u32,
    desired_qty: u32,
    min_target_qty: u32,
    max_target_qty: u32,
    provisional_target_qty: u32,
    price: f64,
    minimum_order_quantity: u32,
    order_step_size: u32,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MatchingType {
    OpenAuction,
    CurrentBarClose,
    NextBarOpen,
    NextTickLast,
    NextTickBestOwn,
    NextTickBestCounterparty,
    CounterpartyOffer,
    Vwap,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SlippageModel {
    None,
    PriceRatio(f64),
    TickSize(f64),
    LimitPrice,
}

pub struct BrokerSimulator<C, R> {
    cost_model: C,
    rules: R,
    board_lot_size: u32,
    matching_type: MatchingType,
    execution_price_field: PriceField,
    slippage_model: SlippageModel,
    volume_percent: f64,
    volume_limit: bool,
    inactive_limit: bool,
    liquidity_limit: bool,
    intraday_execution_start_time: Option<NaiveTime>,
    next_order_id: Cell<u64>,
    open_orders: RefCell<Vec<OpenOrder>>,
}

impl<C, R> BrokerSimulator<C, R> {
    pub fn new(cost_model: C, rules: R) -> Self {
        Self {
            cost_model,
            rules,
            board_lot_size: 100,
            matching_type: matching_type_from_price_field(PriceField::Open),
            execution_price_field: PriceField::Open,
            slippage_model: SlippageModel::None,
            volume_percent: 0.25,
            volume_limit: true,
            inactive_limit: true,
            liquidity_limit: true,
            intraday_execution_start_time: None,
            next_order_id: Cell::new(1),
            open_orders: RefCell::new(Vec::new()),
        }
    }

    pub fn new_with_execution_price(
        cost_model: C,
        rules: R,
        execution_price_field: PriceField,
    ) -> Self {
        Self {
            cost_model,
            rules,
            board_lot_size: 100,
            matching_type: matching_type_from_price_field(execution_price_field),
            execution_price_field,
            slippage_model: SlippageModel::None,
            volume_percent: 0.25,
            volume_limit: true,
            inactive_limit: true,
            liquidity_limit: true,
            intraday_execution_start_time: None,
            next_order_id: Cell::new(1),
            open_orders: RefCell::new(Vec::new()),
        }
    }

    pub fn with_volume_limit(mut self, enabled: bool) -> Self {
        self.volume_limit = enabled;
        self
    }

    pub fn with_inactive_limit(mut self, enabled: bool) -> Self {
        self.inactive_limit = enabled;
        self
    }

    pub fn with_liquidity_limit(mut self, enabled: bool) -> Self {
        self.liquidity_limit = enabled;
        self
    }

    pub fn with_volume_percent(mut self, volume_percent: f64) -> Self {
        self.volume_percent = volume_percent;
        self
    }

    pub fn with_intraday_execution_start_time(mut self, start_time: NaiveTime) -> Self {
        self.intraday_execution_start_time = Some(start_time);
        self
    }

    pub fn with_matching_type(mut self, matching_type: MatchingType) -> Self {
        self.matching_type = matching_type;
        self
    }

    pub fn with_slippage_model(mut self, slippage_model: SlippageModel) -> Self {
        self.slippage_model = slippage_model;
        self
    }
}

impl<C, R> BrokerSimulator<C, R>
where
    C: CostModel,
    R: EquityRuleHooks,
{
    fn buy_price(&self, snapshot: &crate::data::DailyMarketSnapshot) -> f64 {
        snapshot.buy_price(self.execution_price_field)
    }

    fn sell_price(&self, snapshot: &crate::data::DailyMarketSnapshot) -> f64 {
        snapshot.sell_price(self.execution_price_field)
    }

    fn sizing_price(&self, snapshot: &crate::data::DailyMarketSnapshot) -> f64 {
        snapshot.price(self.execution_price_field)
    }

    fn snapshot_execution_price(
        &self,
        snapshot: &crate::data::DailyMarketSnapshot,
        side: OrderSide,
    ) -> f64 {
        let raw_price = if self.execution_price_field == PriceField::Last
            && self.intraday_execution_start_time.is_some()
        {
            let _ = side;
            snapshot.price(PriceField::Last)
        } else {
            match side {
                OrderSide::Buy => self.buy_price(snapshot),
                OrderSide::Sell => self.sell_price(snapshot),
            }
        };

        self.apply_slippage(snapshot, side, raw_price)
    }

    fn is_open_auction_matching(&self) -> bool {
        self.execution_price_field == PriceField::DayOpen
    }

    fn apply_slippage(
        &self,
        snapshot: &crate::data::DailyMarketSnapshot,
        side: OrderSide,
        raw_price: f64,
    ) -> f64 {
        if !raw_price.is_finite() || raw_price <= 0.0 {
            return raw_price;
        }

        if self.is_open_auction_matching() {
            return self.clamp_execution_price(snapshot, side, raw_price);
        }

        let adjusted = match self.slippage_model {
            SlippageModel::None => raw_price,
            SlippageModel::PriceRatio(ratio) => {
                let ratio = ratio.max(0.0);
                match side {
                    OrderSide::Buy => raw_price * (1.0 + ratio),
                    OrderSide::Sell => raw_price * (1.0 - ratio),
                }
            }
            SlippageModel::TickSize(ticks) => {
                let tick = snapshot.effective_price_tick();
                let ticks = ticks.max(0.0);
                match side {
                    OrderSide::Buy => raw_price + tick * ticks,
                    OrderSide::Sell => raw_price - tick * ticks,
                }
            }
            SlippageModel::LimitPrice => raw_price,
        };

        self.clamp_execution_price(snapshot, side, adjusted)
    }

    fn clamp_execution_price(
        &self,
        snapshot: &crate::data::DailyMarketSnapshot,
        side: OrderSide,
        adjusted_price: f64,
    ) -> f64 {
        if !adjusted_price.is_finite() {
            return adjusted_price;
        }

        let mut bounded = adjusted_price.max(snapshot.effective_price_tick());
        match side {
            OrderSide::Buy => {
                if snapshot.upper_limit.is_finite() && snapshot.upper_limit > 0.0 {
                    bounded = bounded.min(snapshot.upper_limit);
                }
            }
            OrderSide::Sell => {
                if snapshot.lower_limit.is_finite() && snapshot.lower_limit > 0.0 {
                    bounded = bounded.max(snapshot.lower_limit);
                }
            }
        }
        bounded
    }

    fn quote_execution_price(
        &self,
        snapshot: &crate::data::DailyMarketSnapshot,
        side: OrderSide,
        raw_price: f64,
    ) -> f64 {
        self.apply_slippage(snapshot, side, raw_price)
    }

    fn select_quote_reference_price(
        &self,
        snapshot: &crate::data::DailyMarketSnapshot,
        quote: &IntradayExecutionQuote,
        side: OrderSide,
    ) -> Option<f64> {
        let raw_price = match self.matching_type {
            MatchingType::NextTickBestOwn => match side {
                OrderSide::Buy => {
                    if quote.bid1.is_finite() && quote.bid1 > 0.0 {
                        Some(quote.bid1)
                    } else {
                        quote
                            .last_price
                            .is_finite()
                            .then_some(quote.last_price)
                            .filter(|price| *price > 0.0)
                    }
                }
                OrderSide::Sell => {
                    if quote.ask1.is_finite() && quote.ask1 > 0.0 {
                        Some(quote.ask1)
                    } else {
                        quote
                            .last_price
                            .is_finite()
                            .then_some(quote.last_price)
                            .filter(|price| *price > 0.0)
                    }
                }
            },
            MatchingType::NextTickBestCounterparty | MatchingType::CounterpartyOffer => {
                match side {
                    OrderSide::Buy => quote.buy_price(),
                    OrderSide::Sell => quote.sell_price(),
                }
            }
            MatchingType::NextTickLast | MatchingType::Vwap => {
                if quote.last_price.is_finite() && quote.last_price > 0.0 {
                    Some(quote.last_price)
                } else {
                    match side {
                        OrderSide::Buy => quote.buy_price(),
                        OrderSide::Sell => quote.sell_price(),
                    }
                }
            }
            _ => match side {
                OrderSide::Buy => quote.buy_price(),
                OrderSide::Sell => quote.sell_price(),
            },
        }?;
        let execution_price = self.quote_execution_price(snapshot, side, raw_price);
        if execution_price.is_finite() && execution_price > 0.0 {
            Some(execution_price)
        } else {
            None
        }
    }

    pub fn execute(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        decision: &StrategyDecision,
    ) -> Result<BrokerExecutionReport, BacktestError> {
        let mut report = BrokerExecutionReport::default();
        let mut intraday_turnover = BTreeMap::<String, u32>::new();
        let mut execution_cursors = BTreeMap::<String, NaiveDateTime>::new();
        let mut global_execution_cursor = None::<NaiveDateTime>;
        let mut commission_state = BTreeMap::<u64, f64>::new();
        self.process_open_orders(
            date,
            portfolio,
            data,
            &mut intraday_turnover,
            &mut execution_cursors,
            &mut global_execution_cursor,
            &mut commission_state,
            &mut report,
        )?;
        if !decision.order_intents.is_empty() {
            for intent in &decision.order_intents {
                self.process_order_intent(
                    date,
                    portfolio,
                    data,
                    intent,
                    &mut intraday_turnover,
                    &mut execution_cursors,
                    &mut global_execution_cursor,
                    &mut commission_state,
                    &mut report,
                )?;
            }
            portfolio.prune_flat_positions();
            return Ok(report);
        }

        let (target_quantities, rebalance_diagnostics) = if decision.rebalance {
            self.target_quantities(date, portfolio, data, &decision.target_weights)?
        } else {
            (BTreeMap::new(), Vec::new())
        };
        report.diagnostics.extend(rebalance_diagnostics);

        let mut sell_symbols = BTreeSet::new();
        sell_symbols.extend(portfolio.positions().keys().cloned());
        sell_symbols.extend(decision.exit_symbols.iter().cloned());
        sell_symbols.extend(target_quantities.keys().cloned());

        for symbol in sell_symbols {
            let current_qty = portfolio
                .position(&symbol)
                .map(|pos| pos.quantity)
                .unwrap_or(0);
            if current_qty == 0 {
                continue;
            }

            let target_qty = if decision.exit_symbols.contains(&symbol) {
                0
            } else if decision.rebalance {
                *target_quantities.get(&symbol).unwrap_or(&0)
            } else {
                current_qty
            };

            if current_qty > target_qty {
                let requested_qty = current_qty - target_qty;
                self.process_sell(
                    date,
                    portfolio,
                    data,
                    &symbol,
                    requested_qty,
                    self.reserve_order_id(),
                    sell_reason(decision, &symbol),
                    &mut intraday_turnover,
                    &mut execution_cursors,
                    &mut global_execution_cursor,
                    &mut commission_state,
                    None,
                    false,
                    true,
                    &mut report,
                )?;
            }
        }

        if decision.rebalance {
            for (symbol, target_qty) in target_quantities {
                let current_qty = portfolio
                    .position(&symbol)
                    .map(|pos| pos.quantity)
                    .unwrap_or(0);
                if target_qty > current_qty {
                    let requested_qty = target_qty - current_qty;
                    self.process_buy(
                        date,
                        portfolio,
                        data,
                        &symbol,
                        requested_qty,
                        self.reserve_order_id(),
                        "rebalance_buy",
                        &mut intraday_turnover,
                        &mut execution_cursors,
                        &mut global_execution_cursor,
                        &mut commission_state,
                        None,
                        None,
                        false,
                        true,
                        &mut report,
                    )?;
                }
            }
        }

        portfolio.prune_flat_positions();
        Ok(report)
    }

    fn process_order_intent(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        intent: &OrderIntent,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        match intent {
            OrderIntent::Shares {
                symbol,
                quantity,
                reason,
            } => self.process_shares(
                date,
                portfolio,
                data,
                symbol,
                *quantity,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::LimitShares {
                symbol,
                quantity,
                limit_price,
                reason,
            } => self.process_limit_shares(
                date,
                portfolio,
                data,
                symbol,
                *quantity,
                *limit_price,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::Lots {
                symbol,
                lots,
                reason,
            } => self.process_lots(
                date,
                portfolio,
                data,
                symbol,
                *lots,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::LimitLots {
                symbol,
                lots,
                limit_price,
                reason,
            } => self.process_limit_lots(
                date,
                portfolio,
                data,
                symbol,
                *lots,
                *limit_price,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::TargetValue {
                symbol,
                target_value,
                reason,
            } => self.process_target_value(
                date,
                portfolio,
                data,
                symbol,
                *target_value,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::LimitTargetValue {
                symbol,
                target_value,
                limit_price,
                reason,
            } => self.process_limit_target_value(
                date,
                portfolio,
                data,
                symbol,
                *target_value,
                *limit_price,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::Value {
                symbol,
                value,
                reason,
            } => self.process_value(
                date,
                portfolio,
                data,
                symbol,
                *value,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::LimitValue {
                symbol,
                value,
                limit_price,
                reason,
            } => self.process_limit_value(
                date,
                portfolio,
                data,
                symbol,
                *value,
                *limit_price,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::Percent {
                symbol,
                percent,
                reason,
            } => self.process_percent(
                date,
                portfolio,
                data,
                symbol,
                *percent,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::LimitPercent {
                symbol,
                percent,
                limit_price,
                reason,
            } => self.process_limit_percent(
                date,
                portfolio,
                data,
                symbol,
                *percent,
                *limit_price,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::TargetPercent {
                symbol,
                target_percent,
                reason,
            } => self.process_target_percent(
                date,
                portfolio,
                data,
                symbol,
                *target_percent,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::LimitTargetPercent {
                symbol,
                target_percent,
                limit_price,
                reason,
            } => self.process_limit_target_percent(
                date,
                portfolio,
                data,
                symbol,
                *target_percent,
                *limit_price,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            ),
            OrderIntent::CancelOrder { order_id, reason } => {
                self.cancel_open_order(date, *order_id, reason, report);
                Ok(())
            }
            OrderIntent::CancelSymbol { symbol, reason } => {
                self.cancel_open_orders_for_symbol(date, symbol, reason, report);
                Ok(())
            }
            OrderIntent::CancelAll { reason } => {
                self.cancel_all_open_orders(date, reason, report);
                Ok(())
            }
        }
    }

    fn process_limit_shares(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        quantity: i32,
        limit_price: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        self.process_limit_shares_internal(
            date,
            portfolio,
            data,
            symbol,
            quantity,
            limit_price,
            reason,
            None,
            true,
            intraday_turnover,
            execution_cursors,
            global_execution_cursor,
            commission_state,
            report,
        )
    }

    fn process_limit_shares_internal(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        quantity: i32,
        limit_price: f64,
        reason: &str,
        existing_order_id: Option<u64>,
        emit_creation_events: bool,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        if quantity == 0 {
            return Ok(());
        }
        let order_id = existing_order_id.unwrap_or_else(|| self.reserve_order_id());
        if quantity > 0 {
            let requested_qty = self.round_buy_quantity(
                quantity as u32,
                self.minimum_order_quantity(data, symbol),
                self.order_step_size(data, symbol),
            );
            if requested_qty == 0 {
                return Ok(());
            }
            self.process_buy(
                date,
                portfolio,
                data,
                symbol,
                requested_qty,
                order_id,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                None,
                Some(limit_price),
                true,
                emit_creation_events,
                report,
            )
        } else {
            self.process_sell(
                date,
                portfolio,
                data,
                symbol,
                quantity.unsigned_abs(),
                order_id,
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                Some(limit_price),
                true,
                emit_creation_events,
                report,
            )
        }
    }

    fn process_limit_lots(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        lots: i32,
        limit_price: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let round_lot = self.round_lot(data, symbol);
        let requested_quantity = lots.saturating_abs() as u32 * round_lot;
        let signed_quantity = if lots >= 0 {
            requested_quantity as i32
        } else {
            -(requested_quantity as i32)
        };
        self.process_limit_shares(
            date,
            portfolio,
            data,
            symbol,
            signed_quantity,
            limit_price,
            reason,
            intraday_turnover,
            execution_cursors,
            global_execution_cursor,
            commission_state,
            report,
        )
    }

    fn reserve_order_id(&self) -> u64 {
        let order_id = self.next_order_id.get();
        self.next_order_id.set(order_id.saturating_add(1));
        order_id
    }

    fn upsert_open_order(&self, open_order: OpenOrder) {
        let mut open_orders = self.open_orders.borrow_mut();
        open_orders.retain(|existing| existing.order_id != open_order.order_id);
        open_orders.push(open_order);
    }

    fn clear_open_order(&self, order_id: u64) {
        self.open_orders
            .borrow_mut()
            .retain(|existing| existing.order_id != order_id);
    }

    fn reserved_open_sell_quantity(&self, symbol: &str, exclude_order_id: Option<u64>) -> u32 {
        self.open_orders
            .borrow()
            .iter()
            .filter(|order| {
                order.side == OrderSide::Sell
                    && order.symbol == symbol
                    && exclude_order_id.is_none_or(|order_id| order.order_id != order_id)
            })
            .map(|order| order.remaining_quantity)
            .sum()
    }

    fn process_open_orders(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let pending_orders = {
            let mut open_orders = self.open_orders.borrow_mut();
            std::mem::take(&mut *open_orders)
        };
        for order in pending_orders {
            let signed_quantity = if order.side == OrderSide::Buy {
                order.remaining_quantity as i32
            } else {
                -(order.remaining_quantity as i32)
            };
            self.process_limit_shares_internal(
                date,
                portfolio,
                data,
                &order.symbol,
                signed_quantity,
                order.limit_price,
                &order.reason,
                Some(order.order_id),
                false,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                report,
            )?;
        }
        Ok(())
    }

    fn cancel_open_order(
        &self,
        date: NaiveDate,
        order_id: u64,
        reason: &str,
        report: &mut BrokerExecutionReport,
    ) {
        let canceled = {
            let mut open_orders = self.open_orders.borrow_mut();
            if let Some(index) = open_orders
                .iter()
                .position(|order| order.order_id == order_id)
            {
                Some(open_orders.remove(index))
            } else {
                None
            }
        };
        if let Some(order) = canceled {
            self.emit_user_canceled_open_order(date, order, reason, report);
        } else {
            report.process_events.push(ProcessEvent {
                date,
                kind: ProcessEventKind::OrderCancellationReject,
                order_id: Some(order_id),
                symbol: None,
                side: None,
                detail: format!("reason={reason} status=not_found"),
            });
        }
    }

    fn cancel_open_orders_for_symbol(
        &self,
        date: NaiveDate,
        symbol: &str,
        reason: &str,
        report: &mut BrokerExecutionReport,
    ) {
        let canceled = {
            let mut open_orders = self.open_orders.borrow_mut();
            let mut canceled = Vec::new();
            let mut retained = Vec::with_capacity(open_orders.len());
            for order in open_orders.drain(..) {
                if order.symbol == symbol {
                    canceled.push(order);
                } else {
                    retained.push(order);
                }
            }
            *open_orders = retained;
            canceled
        };
        if canceled.is_empty() {
            report.process_events.push(ProcessEvent {
                date,
                kind: ProcessEventKind::OrderCancellationReject,
                order_id: None,
                symbol: Some(symbol.to_string()),
                side: None,
                detail: format!("reason={reason} status=no_open_orders_for_symbol"),
            });
        }
        for order in canceled {
            self.emit_user_canceled_open_order(date, order, reason, report);
        }
    }

    fn cancel_all_open_orders(
        &self,
        date: NaiveDate,
        reason: &str,
        report: &mut BrokerExecutionReport,
    ) {
        let canceled = {
            let mut open_orders = self.open_orders.borrow_mut();
            std::mem::take(&mut *open_orders)
        };
        if canceled.is_empty() {
            report.process_events.push(ProcessEvent {
                date,
                kind: ProcessEventKind::OrderCancellationReject,
                order_id: None,
                symbol: None,
                side: None,
                detail: format!("reason={reason} status=no_open_orders"),
            });
        }
        for order in canceled {
            self.emit_user_canceled_open_order(date, order, reason, report);
        }
    }

    fn emit_user_canceled_open_order(
        &self,
        date: NaiveDate,
        order: OpenOrder,
        reason: &str,
        report: &mut BrokerExecutionReport,
    ) {
        Self::emit_order_process_event(
            report,
            date,
            ProcessEventKind::OrderPendingCancel,
            order.order_id,
            &order.symbol,
            order.side,
            format!("reason={reason}"),
        );
        report.order_events.push(OrderEvent {
            date,
            order_id: Some(order.order_id),
            symbol: order.symbol.clone(),
            side: order.side,
            requested_quantity: order.requested_quantity,
            filled_quantity: order.filled_quantity,
            status: OrderStatus::Canceled,
            reason: format!("{reason}: canceled by user"),
        });
        Self::emit_order_process_event(
            report,
            date,
            ProcessEventKind::OrderCancellationPass,
            order.order_id,
            &order.symbol,
            order.side,
            format!(
                "status=Canceled requested_quantity={} filled_quantity={}",
                order.requested_quantity, order.filled_quantity
            ),
        );
    }

    pub fn after_trading(&self, date: NaiveDate) -> BrokerExecutionReport {
        let mut report = BrokerExecutionReport::default();
        let pending = {
            let mut open_orders = self.open_orders.borrow_mut();
            std::mem::take(&mut *open_orders)
        };
        for order in pending {
            let market_close_reason = format!(
                "Order Rejected: {} can not match. Market close.",
                order.symbol
            );
            report.order_events.push(OrderEvent {
                date,
                order_id: Some(order.order_id),
                symbol: order.symbol.clone(),
                side: order.side,
                requested_quantity: order.requested_quantity,
                filled_quantity: order.filled_quantity,
                status: OrderStatus::Rejected,
                reason: market_close_reason.clone(),
            });
            Self::emit_order_process_event(
                &mut report,
                date,
                ProcessEventKind::OrderUnsolicitedUpdate,
                order.order_id,
                &order.symbol,
                order.side,
                format!(
                    "status=Rejected requested_quantity={} filled_quantity={} reason={market_close_reason}",
                    order.requested_quantity, order.filled_quantity
                ),
            );
        }
        report
    }

    fn emit_order_process_event(
        report: &mut BrokerExecutionReport,
        date: NaiveDate,
        kind: ProcessEventKind,
        order_id: u64,
        symbol: &str,
        side: OrderSide,
        detail: impl Into<String>,
    ) {
        report.process_events.push(ProcessEvent {
            date,
            kind,
            order_id: Some(order_id),
            symbol: Some(symbol.to_string()),
            side: Some(side),
            detail: detail.into(),
        });
    }

    fn creation_reject_kind(emit_creation_events: bool) -> ProcessEventKind {
        if emit_creation_events {
            ProcessEventKind::OrderCreationReject
        } else {
            ProcessEventKind::OrderUnsolicitedUpdate
        }
    }

    fn target_quantities(
        &self,
        date: NaiveDate,
        portfolio: &PortfolioState,
        data: &DataSet,
        target_weights: &BTreeMap<String, f64>,
    ) -> Result<(BTreeMap<String, u32>, Vec<String>), BacktestError> {
        let equity = self.rebalance_total_equity_at(date, portfolio, data)?;
        let target_weight_sum = target_weights.values().copied().sum::<f64>();
        let mut desired_targets = BTreeMap::new();
        let mut diagnostics = Vec::new();
        for (symbol, weight) in target_weights {
            let price = self.rebalance_valuation_price(date, symbol, data)?;
            let raw_qty = ((equity * weight) / price).floor() as u32;
            desired_targets.insert(
                symbol.clone(),
                self.round_buy_quantity(
                    raw_qty,
                    self.minimum_order_quantity(data, symbol),
                    self.order_step_size(data, symbol),
                ),
            );
        }

        let mut symbols = BTreeSet::new();
        symbols.extend(portfolio.positions().keys().cloned());
        symbols.extend(desired_targets.keys().cloned());

        let mut constraints = Vec::new();
        let mut projected_cash = portfolio.cash();
        for symbol in symbols {
            let current_qty = portfolio
                .position(&symbol)
                .map(|pos| pos.quantity)
                .unwrap_or(0);
            let desired_qty = *desired_targets.get(&symbol).unwrap_or(&0);
            let price = self.rebalance_valuation_price(date, &symbol, data)?;
            let minimum_order_quantity = self.minimum_order_quantity(data, &symbol);
            let order_step_size = self.order_step_size(data, &symbol);
            let min_target_qty = self.minimum_target_quantity(
                date,
                portfolio,
                data,
                &symbol,
                current_qty,
                minimum_order_quantity,
                order_step_size,
            );
            let max_target_qty = self.maximum_target_quantity(
                date,
                portfolio,
                data,
                &symbol,
                current_qty,
                minimum_order_quantity,
                order_step_size,
            );
            let provisional_target_qty = desired_qty.clamp(min_target_qty, max_target_qty);
            if desired_qty < current_qty
                && min_target_qty >= current_qty
                && diagnostics.len() < 16
                && let Some(reason) = self.sell_target_denial_reason(
                    date,
                    portfolio,
                    data,
                    &symbol,
                    current_qty,
                    minimum_order_quantity,
                    order_step_size,
                )
            {
                diagnostics.push(format!(
                    "rebalance_target_denied symbol={} side=sell reason={}",
                    symbol, reason
                ));
            }
            if desired_qty > current_qty
                && max_target_qty <= current_qty
                && diagnostics.len() < 16
                && let Some(reason) = self.buy_target_denial_reason(
                    date,
                    portfolio,
                    data,
                    &symbol,
                    current_qty,
                    minimum_order_quantity,
                    order_step_size,
                )
            {
                diagnostics.push(format!(
                    "rebalance_target_denied symbol={} side=buy reason={}",
                    symbol, reason
                ));
            }
            if provisional_target_qty != desired_qty && diagnostics.len() < 16 {
                diagnostics.push(format!(
                    "rebalance_target_clipped symbol={} desired={} min={} max={} provisional={}",
                    symbol, desired_qty, min_target_qty, max_target_qty, provisional_target_qty
                ));
            }
            if current_qty > provisional_target_qty {
                projected_cash += self.estimated_sell_net_cash(
                    date,
                    price,
                    current_qty.saturating_sub(provisional_target_qty),
                );
            }
            constraints.push(TargetConstraint {
                symbol: symbol.clone(),
                current_qty,
                desired_qty,
                min_target_qty,
                max_target_qty,
                provisional_target_qty,
                price,
                minimum_order_quantity,
                order_step_size,
            });
        }

        let mut targets = BTreeMap::new();
        for constraint in &constraints {
            if constraint.provisional_target_qty > constraint.current_qty {
                continue;
            }
            if constraint.provisional_target_qty > 0 {
                targets.insert(constraint.symbol.clone(), constraint.provisional_target_qty);
            }
        }

        let buy_constraints = constraints
            .iter()
            .filter(|constraint| constraint.provisional_target_qty > constraint.current_qty)
            .collect::<Vec<_>>();
        if buy_constraints.is_empty() {
            return Ok((targets, diagnostics));
        }

        let mut best_targets = targets.clone();
        let mut best_proportion_diff = f64::INFINITY;
        let initial_safety = if target_weight_sum > 0.95 { 1.2 } else { 1.0 };
        let mut safety = initial_safety;
        loop {
            let mut candidate_targets = targets.clone();
            let mut buy_cash_out = 0.0;
            for constraint in &buy_constraints {
                let scaled_desired_qty = ((constraint.desired_qty as f64) * safety).floor() as u32;
                let mut target_qty = self
                    .round_buy_quantity(
                        scaled_desired_qty,
                        constraint.minimum_order_quantity,
                        constraint.order_step_size,
                    )
                    .clamp(constraint.min_target_qty, constraint.max_target_qty)
                    .max(constraint.current_qty);
                if target_qty < constraint.current_qty {
                    target_qty = constraint.current_qty;
                }
                if target_qty > constraint.current_qty {
                    buy_cash_out += self.estimated_buy_cash_out(
                        date,
                        constraint.price,
                        target_qty - constraint.current_qty,
                    );
                }
                if target_qty > 0 {
                    candidate_targets.insert(constraint.symbol.clone(), target_qty);
                }
            }

            let total_target_value = constraints
                .iter()
                .map(|constraint| {
                    candidate_targets
                        .get(&constraint.symbol)
                        .copied()
                        .unwrap_or(0) as f64
                        * constraint.price
                })
                .sum::<f64>();
            let proportion_diff = if equity > 0.0 {
                ((total_target_value / equity) - target_weight_sum).abs()
            } else {
                0.0
            };
            if buy_cash_out <= projected_cash + 1e-6 {
                if proportion_diff <= best_proportion_diff + 1e-12 {
                    best_targets = candidate_targets;
                    best_proportion_diff = proportion_diff;
                } else if best_proportion_diff.is_finite() {
                    break;
                }
            }

            if safety <= 0.0 {
                break;
            }
            let step = (proportion_diff / 10.0).clamp(0.0001, 0.002);
            let next_safety = (safety - step).max(0.0);
            if (next_safety - safety).abs() < f64::EPSILON {
                break;
            }
            safety = next_safety;
        }

        if safety < initial_safety && diagnostics.len() < 16 {
            diagnostics.push(format!(
                "rebalance_safety_scaled final_safety={:.4} target_weight_sum={:.4} projected_cash={:.2}",
                safety, target_weight_sum, projected_cash
            ));
        }

        for constraint in &buy_constraints {
            let final_target_qty = best_targets
                .get(&constraint.symbol)
                .copied()
                .unwrap_or(constraint.current_qty);
            if final_target_qty < constraint.provisional_target_qty && diagnostics.len() < 16 {
                diagnostics.push(format!(
                    "rebalance_buy_reduced symbol={} provisional={} final={} current={}",
                    constraint.symbol,
                    constraint.provisional_target_qty,
                    final_target_qty,
                    constraint.current_qty
                ));
            }
        }

        Ok((best_targets, diagnostics))
    }

    fn minimum_target_quantity(
        &self,
        date: NaiveDate,
        portfolio: &PortfolioState,
        data: &DataSet,
        symbol: &str,
        current_qty: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
    ) -> u32 {
        if current_qty == 0 {
            return 0;
        }
        let Some(position) = portfolio.position(symbol) else {
            return 0;
        };
        let Ok(snapshot) = data.require_market(date, symbol) else {
            return current_qty;
        };
        let Ok(candidate) = data.require_candidate(date, symbol) else {
            return current_qty;
        };
        let rule = self.rules.can_sell(
            date,
            snapshot,
            candidate,
            position,
            self.execution_price_field,
        );
        if !rule.allowed {
            return current_qty;
        }
        let sellable = position
            .sellable_qty(date)
            .saturating_sub(self.reserved_open_sell_quantity(symbol, None));
        let sell_limit = match self.market_fillable_quantity(
            snapshot,
            OrderSide::Sell,
            sellable.min(current_qty),
            minimum_order_quantity,
            order_step_size,
            0,
            sellable >= current_qty,
        ) {
            Ok(quantity) => quantity.min(sellable).min(current_qty),
            Err(_) => 0,
        };
        current_qty.saturating_sub(sell_limit)
    }

    fn maximum_target_quantity(
        &self,
        date: NaiveDate,
        _portfolio: &PortfolioState,
        data: &DataSet,
        symbol: &str,
        current_qty: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
    ) -> u32 {
        let Ok(snapshot) = data.require_market(date, symbol) else {
            return current_qty;
        };
        let Ok(candidate) = data.require_candidate(date, symbol) else {
            return current_qty;
        };
        let rule = self
            .rules
            .can_buy(date, snapshot, candidate, self.execution_price_field);
        if !rule.allowed {
            return current_qty;
        }
        let additional_limit = match self.market_fillable_quantity(
            snapshot,
            OrderSide::Buy,
            u32::MAX,
            minimum_order_quantity,
            order_step_size,
            0,
            false,
        ) {
            Ok(quantity) => quantity,
            Err(_) => 0,
        };
        current_qty.saturating_add(additional_limit)
    }

    fn estimated_sell_net_cash(&self, date: NaiveDate, price: f64, quantity: u32) -> f64 {
        if quantity == 0 {
            return 0.0;
        }
        let gross = price * quantity as f64;
        let cost = self.cost_model.calculate(date, OrderSide::Sell, gross);
        gross - cost.total()
    }

    fn sell_target_denial_reason(
        &self,
        date: NaiveDate,
        portfolio: &PortfolioState,
        data: &DataSet,
        symbol: &str,
        current_qty: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
    ) -> Option<String> {
        if current_qty == 0 {
            return None;
        }
        let position = portfolio.position(symbol)?;
        let snapshot = data.require_market(date, symbol).ok()?;
        let candidate = data.require_candidate(date, symbol).ok()?;
        let rule = self.rules.can_sell(
            date,
            snapshot,
            candidate,
            position,
            self.execution_price_field,
        );
        if !rule.allowed {
            return rule.reason;
        }
        let sellable = position
            .sellable_qty(date)
            .saturating_sub(self.reserved_open_sell_quantity(symbol, None));
        match self.market_fillable_quantity(
            snapshot,
            OrderSide::Sell,
            sellable.min(current_qty),
            minimum_order_quantity,
            order_step_size,
            0,
            sellable >= current_qty,
        ) {
            Ok(quantity) => {
                let quantity = quantity.min(sellable).min(current_qty);
                if quantity == 0 {
                    Some("no sellable quantity".to_string())
                } else {
                    None
                }
            }
            Err(reason) => Some(reason),
        }
    }

    fn buy_target_denial_reason(
        &self,
        date: NaiveDate,
        _portfolio: &PortfolioState,
        data: &DataSet,
        symbol: &str,
        current_qty: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
    ) -> Option<String> {
        let snapshot = data.require_market(date, symbol).ok()?;
        let candidate = data.require_candidate(date, symbol).ok()?;
        let rule = self
            .rules
            .can_buy(date, snapshot, candidate, self.execution_price_field);
        if !rule.allowed {
            return rule.reason;
        }
        match self.market_fillable_quantity(
            snapshot,
            OrderSide::Buy,
            u32::MAX,
            minimum_order_quantity,
            order_step_size,
            0,
            false,
        ) {
            Ok(quantity) => {
                if current_qty.saturating_add(quantity) <= current_qty {
                    Some("no fillable buy quantity".to_string())
                } else {
                    None
                }
            }
            Err(reason) => Some(reason),
        }
    }

    fn estimated_buy_cash_out(&self, date: NaiveDate, price: f64, quantity: u32) -> f64 {
        if quantity == 0 {
            return 0.0;
        }
        let gross = price * quantity as f64;
        let cost = self.cost_model.calculate(date, OrderSide::Buy, gross);
        gross + cost.total()
    }

    fn process_sell(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        requested_qty: u32,
        order_id: u64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        limit_price: Option<f64>,
        allow_pending_limit: bool,
        emit_creation_events: bool,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let snapshot = data.require_market(date, symbol)?;
        let candidate = data.require_candidate(date, symbol)?;
        let Some(position) = portfolio.position(symbol) else {
            return Ok(());
        };

        if emit_creation_events {
            Self::emit_order_process_event(
                report,
                date,
                ProcessEventKind::OrderPendingNew,
                order_id,
                symbol,
                OrderSide::Sell,
                format!("requested_quantity={requested_qty} reason={reason}"),
            );
        }

        let rule = self.rules.can_sell(
            date,
            snapshot,
            candidate,
            position,
            self.execution_price_field,
        );
        if !rule.allowed {
            let rule_reason = rule.reason.as_deref().unwrap_or_default().to_string();
            let status = match rule.reason.as_deref() {
                Some("paused")
                | Some("sell disabled by eligibility flags")
                | Some("open at or below lower limit") => OrderStatus::Canceled,
                _ => OrderStatus::Rejected,
            };
            report.order_events.push(OrderEvent {
                date,
                order_id: Some(order_id),
                symbol: symbol.to_string(),
                side: OrderSide::Sell,
                requested_quantity: requested_qty,
                filled_quantity: 0,
                status,
                reason: format!("{reason}: {rule_reason}"),
            });
            Self::emit_order_process_event(
                report,
                date,
                Self::creation_reject_kind(emit_creation_events),
                order_id,
                symbol,
                OrderSide::Sell,
                format!("status={status:?} reason={rule_reason}"),
            );
            self.clear_open_order(order_id);
            return Ok(());
        }

        if emit_creation_events {
            Self::emit_order_process_event(
                report,
                date,
                ProcessEventKind::OrderCreationPass,
                order_id,
                symbol,
                OrderSide::Sell,
                "sell order passed rule checks",
            );
        }

        let sellable = position
            .sellable_qty(date)
            .saturating_sub(self.reserved_open_sell_quantity(symbol, Some(order_id)));
        let mut partial_fill_reason = if sellable < requested_qty {
            Some("sellable quantity limit".to_string())
        } else {
            None
        };
        let market_limited_qty = self.market_fillable_quantity(
            snapshot,
            OrderSide::Sell,
            requested_qty.min(sellable),
            self.minimum_order_quantity(data, symbol),
            self.order_step_size(data, symbol),
            *intraday_turnover.get(symbol).unwrap_or(&0),
            requested_qty >= position.quantity && sellable >= position.quantity,
        );
        let fillable_qty = match market_limited_qty {
            Ok(quantity) => {
                let quantity = quantity.min(sellable);
                if quantity < requested_qty.min(sellable) {
                    partial_fill_reason = merge_partial_fill_reason(
                        partial_fill_reason,
                        Some("market liquidity or volume limit"),
                    );
                }
                quantity
            }
            Err(limit_reason) => {
                if allow_pending_limit {
                    self.upsert_open_order(OpenOrder {
                        order_id,
                        symbol: symbol.to_string(),
                        side: OrderSide::Sell,
                        requested_quantity: requested_qty,
                        filled_quantity: 0,
                        remaining_quantity: requested_qty,
                        limit_price: limit_price.expect("limit price for pending limit sell"),
                        reason: reason.to_string(),
                    });
                    report.order_events.push(OrderEvent {
                        date,
                        order_id: Some(order_id),
                        symbol: symbol.to_string(),
                        side: OrderSide::Sell,
                        requested_quantity: requested_qty,
                        filled_quantity: 0,
                        status: OrderStatus::Pending,
                        reason: format!("{reason}: pending due to {limit_reason}"),
                    });
                    Self::emit_order_process_event(
                        report,
                        date,
                        ProcessEventKind::OrderUnsolicitedUpdate,
                        order_id,
                        symbol,
                        OrderSide::Sell,
                        format!("status=Pending reason={limit_reason}"),
                    );
                    return Ok(());
                }
                report.order_events.push(OrderEvent {
                    date,
                    order_id: Some(order_id),
                    symbol: symbol.to_string(),
                    side: OrderSide::Sell,
                    requested_quantity: requested_qty,
                    filled_quantity: 0,
                    status: zero_fill_status_for_reason(&limit_reason),
                    reason: format!("{reason}: {limit_reason}"),
                });
                Self::emit_order_process_event(
                    report,
                    date,
                    Self::creation_reject_kind(emit_creation_events),
                    order_id,
                    symbol,
                    OrderSide::Sell,
                    format!(
                        "status={:?} reason={limit_reason}",
                        zero_fill_status_for_reason(&limit_reason)
                    ),
                );
                return Ok(());
            }
        };
        if fillable_qty == 0 {
            if allow_pending_limit {
                let detail = partial_fill_reason
                    .as_deref()
                    .unwrap_or("no sellable quantity");
                self.upsert_open_order(OpenOrder {
                    order_id,
                    symbol: symbol.to_string(),
                    side: OrderSide::Sell,
                    requested_quantity: requested_qty,
                    filled_quantity: 0,
                    remaining_quantity: requested_qty,
                    limit_price: limit_price.expect("limit price for pending limit sell"),
                    reason: reason.to_string(),
                });
                report.order_events.push(OrderEvent {
                    date,
                    order_id: Some(order_id),
                    symbol: symbol.to_string(),
                    side: OrderSide::Sell,
                    requested_quantity: requested_qty,
                    filled_quantity: 0,
                    status: OrderStatus::Pending,
                    reason: format!("{reason}: pending due to {detail}"),
                });
                Self::emit_order_process_event(
                    report,
                    date,
                    ProcessEventKind::OrderUnsolicitedUpdate,
                    order_id,
                    symbol,
                    OrderSide::Sell,
                    format!("status=Pending reason={detail}"),
                );
                return Ok(());
            }
            report.order_events.push(OrderEvent {
                date,
                order_id: Some(order_id),
                symbol: symbol.to_string(),
                side: OrderSide::Sell,
                requested_quantity: requested_qty,
                filled_quantity: 0,
                status: OrderStatus::Rejected,
                reason: format!("{reason}: no sellable quantity"),
            });
            Self::emit_order_process_event(
                report,
                date,
                Self::creation_reject_kind(emit_creation_events),
                order_id,
                symbol,
                OrderSide::Sell,
                "status=Rejected reason=no sellable quantity",
            );
            return Ok(());
        }

        let fill = self.resolve_execution_fill(
            date,
            symbol,
            OrderSide::Sell,
            snapshot,
            data,
            fillable_qty,
            self.round_lot(data, symbol),
            self.minimum_order_quantity(data, symbol),
            self.order_step_size(data, symbol),
            fillable_qty >= position.quantity,
            execution_cursors,
            None,
            None,
            None,
            limit_price,
        );
        let (filled_qty, execution_legs) = if let Some(fill) = fill {
            execution_cursors.insert(symbol.to_string(), fill.next_cursor);
            if self.uses_serial_execution_cursor(reason) {
                *global_execution_cursor = Some(fill.next_cursor);
            }
            partial_fill_reason =
                merge_partial_fill_reason(partial_fill_reason, fill.unfilled_reason);
            (fill.quantity, fill.legs)
        } else {
            let execution_price = self.snapshot_execution_price(snapshot, OrderSide::Sell);
            if !self.price_satisfies_limit(
                OrderSide::Sell,
                execution_price,
                limit_price,
                snapshot.effective_price_tick(),
            ) {
                partial_fill_reason = merge_partial_fill_reason(
                    partial_fill_reason,
                    Some("limit price not marketable yet"),
                );
                (0, Vec::new())
            } else {
                let execution_price =
                    self.execution_price_with_limit_slippage(execution_price, limit_price);
                (
                    fillable_qty,
                    vec![ExecutionLeg {
                        price: execution_price,
                        quantity: fillable_qty,
                    }],
                )
            }
        };
        if filled_qty == 0 {
            let detail = partial_fill_reason
                .as_deref()
                .unwrap_or("limit price not marketable yet");
            if allow_pending_limit && Self::limit_order_can_remain_open(Some(detail)) {
                self.upsert_open_order(OpenOrder {
                    order_id,
                    symbol: symbol.to_string(),
                    side: OrderSide::Sell,
                    requested_quantity: requested_qty,
                    filled_quantity: 0,
                    remaining_quantity: requested_qty,
                    limit_price: limit_price.expect("limit price for pending limit sell"),
                    reason: reason.to_string(),
                });
                report.order_events.push(OrderEvent {
                    date,
                    order_id: Some(order_id),
                    symbol: symbol.to_string(),
                    side: OrderSide::Sell,
                    requested_quantity: requested_qty,
                    filled_quantity: 0,
                    status: OrderStatus::Pending,
                    reason: format!("{reason}: pending due to {detail}"),
                });
                Self::emit_order_process_event(
                    report,
                    date,
                    ProcessEventKind::OrderUnsolicitedUpdate,
                    order_id,
                    symbol,
                    OrderSide::Sell,
                    format!("status=Pending reason={detail}"),
                );
                return Ok(());
            }
            report.order_events.push(OrderEvent {
                date,
                order_id: Some(order_id),
                symbol: symbol.to_string(),
                side: OrderSide::Sell,
                requested_quantity: requested_qty,
                filled_quantity: 0,
                status: zero_fill_status_for_reason(detail),
                reason: format!("{reason}: {detail}"),
            });
            Self::emit_order_process_event(
                report,
                date,
                Self::creation_reject_kind(emit_creation_events),
                order_id,
                symbol,
                OrderSide::Sell,
                format!(
                    "status={:?} reason={detail}",
                    zero_fill_status_for_reason(detail)
                ),
            );
            self.clear_open_order(order_id);
            return Ok(());
        }
        if execution_legs.len() > 1 {
            report.diagnostics.push(format!(
                "order_split_fill symbol={symbol} side=sell order_id={order_id} fills={}",
                execution_legs.len()
            ));
        }
        for leg in &execution_legs {
            let leg_cash_before = portfolio.cash();
            let gross_amount = leg.price * leg.quantity as f64;
            let cost = self.cost_model.calculate_with_order_state(
                date,
                OrderSide::Sell,
                gross_amount,
                Some(order_id),
                commission_state,
            );
            let net_cash = gross_amount - cost.total();
            let realized_pnl = portfolio
                .position_mut(symbol)
                .sell(leg.quantity, leg.price)
                .map_err(BacktestError::Execution)?;
            portfolio.apply_cash_delta(net_cash);

            report.fill_events.push(FillEvent {
                date,
                order_id: Some(order_id),
                symbol: symbol.to_string(),
                side: OrderSide::Sell,
                quantity: leg.quantity,
                price: leg.price,
                gross_amount,
                commission: cost.commission,
                stamp_tax: cost.stamp_tax,
                net_cash_flow: net_cash,
                reason: reason.to_string(),
            });
            Self::emit_order_process_event(
                report,
                date,
                ProcessEventKind::Trade,
                order_id,
                symbol,
                OrderSide::Sell,
                format!("filled_quantity={} price={}", leg.quantity, leg.price),
            );
            report.position_events.push(PositionEvent {
                date,
                symbol: symbol.to_string(),
                delta_quantity: -(leg.quantity as i32),
                quantity_after: portfolio
                    .position(symbol)
                    .map(|pos| pos.quantity)
                    .unwrap_or(0),
                average_cost: portfolio
                    .position(symbol)
                    .map(|pos| pos.average_cost)
                    .unwrap_or(0.0),
                realized_pnl_delta: realized_pnl,
                reason: reason.to_string(),
            });
            report.account_events.push(AccountEvent {
                date,
                cash_before: leg_cash_before,
                cash_after: portfolio.cash(),
                total_equity: self.total_equity_at(
                    date,
                    portfolio,
                    data,
                    self.account_mark_price_field(),
                )?,
                note: format!("sell {symbol} {reason}"),
            });
        }
        portfolio.prune_flat_positions();
        *intraday_turnover.entry(symbol.to_string()).or_default() += filled_qty;

        let remaining_qty = requested_qty.saturating_sub(filled_qty);
        let keep_open = allow_pending_limit
            && remaining_qty > 0
            && Self::limit_order_can_remain_open(partial_fill_reason.as_deref());
        if keep_open {
            self.upsert_open_order(OpenOrder {
                order_id,
                symbol: symbol.to_string(),
                side: OrderSide::Sell,
                requested_quantity: requested_qty,
                filled_quantity: filled_qty,
                remaining_quantity: remaining_qty,
                limit_price: limit_price.expect("limit price for pending limit sell"),
                reason: reason.to_string(),
            });
        } else {
            self.clear_open_order(order_id);
        }

        let status = if keep_open {
            OrderStatus::PartiallyFilled
        } else if filled_qty < requested_qty {
            final_partial_fill_status(partial_fill_reason.as_deref())
        } else {
            OrderStatus::Filled
        };
        let order_reason = if keep_open {
            let detail = partial_fill_reason
                .as_deref()
                .unwrap_or("remaining quantity could not be filled");
            report.diagnostics.push(format!(
                "order_partial_fill symbol={symbol} side=sell requested={requested_qty} filled={filled_qty} reason={detail}; remaining open"
            ));
            format!("{reason}: partial fill due to {detail}; remaining quantity pending")
        } else if status == OrderStatus::PartiallyFilled {
            let detail = partial_fill_reason
                .as_deref()
                .unwrap_or("remaining quantity could not be filled");
            report.diagnostics.push(format!(
                "order_partial_fill symbol={symbol} side=sell requested={requested_qty} filled={filled_qty} reason={detail}"
            ));
            format!("{reason}: partial fill due to {detail}")
        } else if status == OrderStatus::Canceled && filled_qty < requested_qty {
            let detail = partial_fill_reason
                .as_deref()
                .unwrap_or("remaining quantity could not be filled");
            report.diagnostics.push(format!(
                "order_remainder_canceled symbol={symbol} side=sell requested={requested_qty} filled={filled_qty} reason={detail}"
            ));
            format!("{reason}: partial fill due to {detail}; remaining quantity canceled")
        } else {
            reason.to_string()
        };

        report.order_events.push(OrderEvent {
            date,
            order_id: Some(order_id),
            symbol: symbol.to_string(),
            side: OrderSide::Sell,
            requested_quantity: requested_qty,
            filled_quantity: filled_qty,
            status,
            reason: order_reason,
        });
        if matches!(status, OrderStatus::Canceled | OrderStatus::Rejected) {
            Self::emit_order_process_event(
                report,
                date,
                ProcessEventKind::OrderUnsolicitedUpdate,
                order_id,
                symbol,
                OrderSide::Sell,
                format!("status={status:?} filled_quantity={filled_qty}"),
            );
        }
        Ok(())
    }

    fn process_target_value(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        target_value: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let price = data
            .market(date, symbol)
            .map(|snapshot| self.sizing_price(snapshot))
            .ok_or_else(|| BacktestError::MissingPrice {
                date,
                symbol: symbol.to_string(),
                field: price_field_name(self.execution_price_field),
            })?;
        let current_qty = portfolio
            .position(symbol)
            .map(|pos| pos.quantity)
            .unwrap_or(0);
        let current_value = price * current_qty as f64;
        let target_qty = self.round_buy_quantity(
            ((target_value.max(0.0)) / price).floor() as u32,
            self.minimum_order_quantity(data, symbol),
            self.order_step_size(data, symbol),
        );

        if current_qty > target_qty {
            self.process_sell(
                date,
                portfolio,
                data,
                symbol,
                current_qty - target_qty,
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                None,
                false,
                true,
                report,
            )?;
        } else if target_qty > current_qty {
            self.process_buy(
                date,
                portfolio,
                data,
                symbol,
                target_qty - current_qty,
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                None,
                None,
                false,
                true,
                report,
            )?;
        } else if (current_value - target_value).abs() <= f64::EPSILON {
            report.order_events.push(OrderEvent {
                date,
                order_id: None,
                symbol: symbol.to_string(),
                side: if current_qty > 0 {
                    OrderSide::Sell
                } else {
                    OrderSide::Buy
                },
                requested_quantity: 0,
                filled_quantity: 0,
                status: OrderStatus::Filled,
                reason: format!("{reason}: already at target value"),
            });
        }

        Ok(())
    }

    fn process_limit_target_value(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        target_value: f64,
        limit_price: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let price = data
            .market(date, symbol)
            .map(|snapshot| self.sizing_price(snapshot))
            .ok_or_else(|| BacktestError::MissingPrice {
                date,
                symbol: symbol.to_string(),
                field: price_field_name(self.execution_price_field),
            })?;
        let current_qty = portfolio
            .position(symbol)
            .map(|pos| pos.quantity)
            .unwrap_or(0);
        let target_qty = self.round_buy_quantity(
            ((target_value.max(0.0)) / price).floor() as u32,
            self.minimum_order_quantity(data, symbol),
            self.order_step_size(data, symbol),
        );

        if current_qty > target_qty {
            self.process_sell(
                date,
                portfolio,
                data,
                symbol,
                current_qty - target_qty,
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                Some(limit_price),
                true,
                true,
                report,
            )?;
        } else if target_qty > current_qty {
            self.process_buy(
                date,
                portfolio,
                data,
                symbol,
                target_qty - current_qty,
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                None,
                Some(limit_price),
                true,
                true,
                report,
            )?;
        }

        Ok(())
    }

    fn process_target_percent(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        target_percent: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let total_equity = self.rebalance_total_equity_at(date, portfolio, data)?;
        self.process_target_value(
            date,
            portfolio,
            data,
            symbol,
            total_equity * target_percent.max(0.0),
            reason,
            intraday_turnover,
            execution_cursors,
            global_execution_cursor,
            commission_state,
            report,
        )
    }

    fn process_limit_target_percent(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        target_percent: f64,
        limit_price: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let total_equity = self.rebalance_total_equity_at(date, portfolio, data)?;
        self.process_limit_target_value(
            date,
            portfolio,
            data,
            symbol,
            total_equity * target_percent.max(0.0),
            limit_price,
            reason,
            intraday_turnover,
            execution_cursors,
            global_execution_cursor,
            commission_state,
            report,
        )
    }

    fn process_value(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        value: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        if value.abs() <= f64::EPSILON {
            return Ok(());
        }
        let snapshot = data
            .market(date, symbol)
            .ok_or_else(|| BacktestError::MissingPrice {
                date,
                symbol: symbol.to_string(),
                field: price_field_name(self.execution_price_field),
            })?;
        if value > 0.0 {
            let round_lot = self.round_lot(data, symbol);
            let minimum_order_quantity = self.minimum_order_quantity(data, symbol);
            let order_step_size = self.order_step_size(data, symbol);
            let price = self.sizing_price(snapshot);
            let snapshot_requested_qty = self.round_buy_quantity(
                ((value.abs()) / price).floor() as u32,
                minimum_order_quantity,
                order_step_size,
            );
            let requested_qty = self.maybe_expand_periodic_value_buy_quantity(
                date,
                portfolio,
                data,
                symbol,
                snapshot_requested_qty,
                round_lot,
                value.abs(),
                reason,
                execution_cursors,
                *global_execution_cursor,
            );
            self.process_buy(
                date,
                portfolio,
                data,
                symbol,
                requested_qty,
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                Some(value.abs()),
                None,
                false,
                true,
                report,
            )
        } else {
            let price = self.sizing_price(snapshot);
            let requested_qty = self.round_buy_quantity(
                ((value.abs()) / price).floor() as u32,
                self.minimum_order_quantity(data, symbol),
                self.order_step_size(data, symbol),
            );
            self.process_sell(
                date,
                portfolio,
                data,
                symbol,
                requested_qty,
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                None,
                false,
                true,
                report,
            )
        }
    }

    fn process_limit_value(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        value: f64,
        limit_price: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        if value.abs() <= f64::EPSILON {
            return Ok(());
        }
        let snapshot = data
            .market(date, symbol)
            .ok_or_else(|| BacktestError::MissingPrice {
                date,
                symbol: symbol.to_string(),
                field: price_field_name(self.execution_price_field),
            })?;
        if value > 0.0 {
            let round_lot = self.round_lot(data, symbol);
            let minimum_order_quantity = self.minimum_order_quantity(data, symbol);
            let order_step_size = self.order_step_size(data, symbol);
            let price = self.sizing_price(snapshot);
            let snapshot_requested_qty = self.round_buy_quantity(
                ((value.abs()) / price).floor() as u32,
                minimum_order_quantity,
                order_step_size,
            );
            let requested_qty = self.maybe_expand_periodic_value_buy_quantity(
                date,
                portfolio,
                data,
                symbol,
                snapshot_requested_qty,
                round_lot,
                value.abs(),
                reason,
                execution_cursors,
                *global_execution_cursor,
            );
            self.process_buy(
                date,
                portfolio,
                data,
                symbol,
                requested_qty,
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                Some(value.abs()),
                Some(limit_price),
                true,
                true,
                report,
            )
        } else {
            let price = self.sizing_price(snapshot);
            let requested_qty = self.round_buy_quantity(
                ((value.abs()) / price).floor() as u32,
                self.minimum_order_quantity(data, symbol),
                self.order_step_size(data, symbol),
            );
            self.process_sell(
                date,
                portfolio,
                data,
                symbol,
                requested_qty,
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                Some(limit_price),
                true,
                true,
                report,
            )
        }
    }

    fn process_percent(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        percent: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let total_equity = self.rebalance_total_equity_at(date, portfolio, data)?;
        self.process_value(
            date,
            portfolio,
            data,
            symbol,
            total_equity * percent,
            reason,
            intraday_turnover,
            execution_cursors,
            global_execution_cursor,
            commission_state,
            report,
        )
    }

    fn process_limit_percent(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        percent: f64,
        limit_price: f64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let total_equity = self.rebalance_total_equity_at(date, portfolio, data)?;
        self.process_limit_value(
            date,
            portfolio,
            data,
            symbol,
            total_equity * percent,
            limit_price,
            reason,
            intraday_turnover,
            execution_cursors,
            global_execution_cursor,
            commission_state,
            report,
        )
    }

    fn process_shares(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        quantity: i32,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        if quantity == 0 {
            return Ok(());
        }
        if quantity > 0 {
            let requested_qty = self.round_buy_quantity(
                quantity as u32,
                self.minimum_order_quantity(data, symbol),
                self.order_step_size(data, symbol),
            );
            self.process_buy(
                date,
                portfolio,
                data,
                symbol,
                requested_qty,
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                None,
                None,
                false,
                true,
                report,
            )
        } else {
            self.process_sell(
                date,
                portfolio,
                data,
                symbol,
                quantity.unsigned_abs(),
                self.reserve_order_id(),
                reason,
                intraday_turnover,
                execution_cursors,
                global_execution_cursor,
                commission_state,
                None,
                false,
                true,
                report,
            )
        }
    }

    fn process_lots(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        lots: i32,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let round_lot = self.round_lot(data, symbol);
        let requested_quantity = lots.saturating_abs() as u32 * round_lot;
        let signed_quantity = if lots >= 0 {
            requested_quantity as i32
        } else {
            -(requested_quantity as i32)
        };
        self.process_shares(
            date,
            portfolio,
            data,
            symbol,
            signed_quantity,
            reason,
            intraday_turnover,
            execution_cursors,
            global_execution_cursor,
            commission_state,
            report,
        )
    }

    fn maybe_expand_periodic_value_buy_quantity(
        &self,
        _date: NaiveDate,
        _portfolio: &PortfolioState,
        _data: &DataSet,
        _symbol: &str,
        requested_qty: u32,
        _round_lot: u32,
        _value_budget: f64,
        _reason: &str,
        _execution_cursors: &BTreeMap<String, NaiveDateTime>,
        _global_execution_cursor: Option<NaiveDateTime>,
    ) -> u32 {
        requested_qty
    }

    fn process_buy(
        &self,
        date: NaiveDate,
        portfolio: &mut PortfolioState,
        data: &DataSet,
        symbol: &str,
        requested_qty: u32,
        order_id: u64,
        reason: &str,
        intraday_turnover: &mut BTreeMap<String, u32>,
        execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        global_execution_cursor: &mut Option<NaiveDateTime>,
        commission_state: &mut BTreeMap<u64, f64>,
        value_budget: Option<f64>,
        limit_price: Option<f64>,
        allow_pending_limit: bool,
        emit_creation_events: bool,
        report: &mut BrokerExecutionReport,
    ) -> Result<(), BacktestError> {
        let snapshot = data.require_market(date, symbol)?;
        let candidate = data.require_candidate(date, symbol)?;

        if emit_creation_events {
            Self::emit_order_process_event(
                report,
                date,
                ProcessEventKind::OrderPendingNew,
                order_id,
                symbol,
                OrderSide::Buy,
                format!("requested_quantity={requested_qty} reason={reason}"),
            );
        }

        let rule = self
            .rules
            .can_buy(date, snapshot, candidate, self.execution_price_field);
        if !rule.allowed {
            let rule_reason = rule.reason.as_deref().unwrap_or_default().to_string();
            let status = match rule.reason.as_deref() {
                Some("paused")
                | Some("buy disabled by eligibility flags")
                | Some("open at or above upper limit") => OrderStatus::Canceled,
                _ => OrderStatus::Rejected,
            };
            report.order_events.push(OrderEvent {
                date,
                order_id: Some(order_id),
                symbol: symbol.to_string(),
                side: OrderSide::Buy,
                requested_quantity: requested_qty,
                filled_quantity: 0,
                status,
                reason: format!("{reason}: {rule_reason}"),
            });
            Self::emit_order_process_event(
                report,
                date,
                Self::creation_reject_kind(emit_creation_events),
                order_id,
                symbol,
                OrderSide::Buy,
                format!("status={status:?} reason={rule_reason}"),
            );
            self.clear_open_order(order_id);
            return Ok(());
        }

        if emit_creation_events {
            Self::emit_order_process_event(
                report,
                date,
                ProcessEventKind::OrderCreationPass,
                order_id,
                symbol,
                OrderSide::Buy,
                "buy order passed rule checks",
            );
        }

        let mut partial_fill_reason = None;
        let market_limited_qty = self.market_fillable_quantity(
            snapshot,
            OrderSide::Buy,
            requested_qty,
            self.minimum_order_quantity(data, symbol),
            self.order_step_size(data, symbol),
            *intraday_turnover.get(symbol).unwrap_or(&0),
            false,
        );
        let constrained_qty = match market_limited_qty {
            Ok(quantity) => {
                if quantity < requested_qty {
                    partial_fill_reason = Some("market liquidity or volume limit".to_string());
                }
                quantity
            }
            Err(limit_reason) => {
                if allow_pending_limit {
                    self.upsert_open_order(OpenOrder {
                        order_id,
                        symbol: symbol.to_string(),
                        side: OrderSide::Buy,
                        requested_quantity: requested_qty,
                        filled_quantity: 0,
                        remaining_quantity: requested_qty,
                        limit_price: limit_price.expect("limit price for pending limit buy"),
                        reason: reason.to_string(),
                    });
                    report.order_events.push(OrderEvent {
                        date,
                        order_id: Some(order_id),
                        symbol: symbol.to_string(),
                        side: OrderSide::Buy,
                        requested_quantity: requested_qty,
                        filled_quantity: 0,
                        status: OrderStatus::Pending,
                        reason: format!("{reason}: pending due to {limit_reason}"),
                    });
                    Self::emit_order_process_event(
                        report,
                        date,
                        ProcessEventKind::OrderUnsolicitedUpdate,
                        order_id,
                        symbol,
                        OrderSide::Buy,
                        format!("status=Pending reason={limit_reason}"),
                    );
                    return Ok(());
                }
                report.order_events.push(OrderEvent {
                    date,
                    order_id: Some(order_id),
                    symbol: symbol.to_string(),
                    side: OrderSide::Buy,
                    requested_quantity: requested_qty,
                    filled_quantity: 0,
                    status: zero_fill_status_for_reason(&limit_reason),
                    reason: format!("{reason}: {limit_reason}"),
                });
                Self::emit_order_process_event(
                    report,
                    date,
                    Self::creation_reject_kind(emit_creation_events),
                    order_id,
                    symbol,
                    OrderSide::Buy,
                    format!(
                        "status={:?} reason={limit_reason}",
                        zero_fill_status_for_reason(&limit_reason)
                    ),
                );
                return Ok(());
            }
        };

        let fill = self.resolve_execution_fill(
            date,
            symbol,
            OrderSide::Buy,
            snapshot,
            data,
            constrained_qty,
            self.round_lot(data, symbol),
            self.minimum_order_quantity(data, symbol),
            self.order_step_size(data, symbol),
            false,
            execution_cursors,
            None,
            Some(portfolio.cash()),
            value_budget.map(|budget| budget + 400.0),
            limit_price,
        );
        let (filled_qty, execution_legs) = if let Some(fill) = fill {
            execution_cursors.insert(symbol.to_string(), fill.next_cursor);
            if self.uses_serial_execution_cursor(reason) {
                *global_execution_cursor = Some(fill.next_cursor);
            }
            partial_fill_reason =
                merge_partial_fill_reason(partial_fill_reason, fill.unfilled_reason);
            (fill.quantity, fill.legs)
        } else {
            let execution_price = self.snapshot_execution_price(snapshot, OrderSide::Buy);
            if !self.price_satisfies_limit(
                OrderSide::Buy,
                execution_price,
                limit_price,
                snapshot.effective_price_tick(),
            ) {
                partial_fill_reason = merge_partial_fill_reason(
                    partial_fill_reason,
                    Some("limit price not marketable yet"),
                );
                (0, Vec::new())
            } else {
                let execution_price =
                    self.execution_price_with_limit_slippage(execution_price, limit_price);
                let filled_qty = self.affordable_buy_quantity(
                    date,
                    portfolio.cash(),
                    value_budget.map(|budget| budget + 400.0),
                    execution_price,
                    constrained_qty,
                    self.minimum_order_quantity(data, symbol),
                    self.order_step_size(data, symbol),
                );
                if filled_qty < constrained_qty {
                    partial_fill_reason = merge_partial_fill_reason(
                        partial_fill_reason,
                        self.buy_reduction_reason(
                            portfolio.cash(),
                            value_budget.map(|budget| budget + 400.0),
                            execution_price,
                            constrained_qty,
                            filled_qty,
                        ),
                    );
                }
                (
                    filled_qty,
                    vec![ExecutionLeg {
                        price: execution_price,
                        quantity: filled_qty,
                    }],
                )
            }
        };
        if filled_qty == 0 {
            let detail = partial_fill_reason
                .as_deref()
                .unwrap_or("insufficient cash after fees");
            if allow_pending_limit && Self::limit_order_can_remain_open(Some(detail)) {
                self.upsert_open_order(OpenOrder {
                    order_id,
                    symbol: symbol.to_string(),
                    side: OrderSide::Buy,
                    requested_quantity: requested_qty,
                    filled_quantity: 0,
                    remaining_quantity: requested_qty,
                    limit_price: limit_price.expect("limit price for pending limit buy"),
                    reason: reason.to_string(),
                });
                report.order_events.push(OrderEvent {
                    date,
                    order_id: Some(order_id),
                    symbol: symbol.to_string(),
                    side: OrderSide::Buy,
                    requested_quantity: requested_qty,
                    filled_quantity: 0,
                    status: OrderStatus::Pending,
                    reason: format!("{reason}: pending due to {detail}"),
                });
                Self::emit_order_process_event(
                    report,
                    date,
                    ProcessEventKind::OrderUnsolicitedUpdate,
                    order_id,
                    symbol,
                    OrderSide::Buy,
                    format!("status=Pending reason={detail}"),
                );
                return Ok(());
            }
            report.order_events.push(OrderEvent {
                date,
                order_id: Some(order_id),
                symbol: symbol.to_string(),
                side: OrderSide::Buy,
                requested_quantity: requested_qty,
                filled_quantity: 0,
                status: OrderStatus::Rejected,
                reason: format!("{reason}: {detail}"),
            });
            Self::emit_order_process_event(
                report,
                date,
                Self::creation_reject_kind(emit_creation_events),
                order_id,
                symbol,
                OrderSide::Buy,
                format!("status=Rejected reason={detail}"),
            );
            self.clear_open_order(order_id);
            return Ok(());
        }

        if execution_legs.len() > 1 {
            report.diagnostics.push(format!(
                "order_split_fill symbol={symbol} side=buy order_id={order_id} fills={}",
                execution_legs.len()
            ));
        }
        for leg in &execution_legs {
            let leg_cash_before = portfolio.cash();
            let gross_amount = leg.price * leg.quantity as f64;
            let cost = self.cost_model.calculate_with_order_state(
                date,
                OrderSide::Buy,
                gross_amount,
                Some(order_id),
                commission_state,
            );
            let cash_out = gross_amount + cost.total();

            portfolio.apply_cash_delta(-cash_out);
            portfolio
                .position_mut(symbol)
                .buy(date, leg.quantity, leg.price);

            report.fill_events.push(FillEvent {
                date,
                order_id: Some(order_id),
                symbol: symbol.to_string(),
                side: OrderSide::Buy,
                quantity: leg.quantity,
                price: leg.price,
                gross_amount,
                commission: cost.commission,
                stamp_tax: cost.stamp_tax,
                net_cash_flow: -cash_out,
                reason: reason.to_string(),
            });
            Self::emit_order_process_event(
                report,
                date,
                ProcessEventKind::Trade,
                order_id,
                symbol,
                OrderSide::Buy,
                format!("filled_quantity={} price={}", leg.quantity, leg.price),
            );
            report.position_events.push(PositionEvent {
                date,
                symbol: symbol.to_string(),
                delta_quantity: leg.quantity as i32,
                quantity_after: portfolio
                    .position(symbol)
                    .map(|pos| pos.quantity)
                    .unwrap_or(0),
                average_cost: portfolio
                    .position(symbol)
                    .map(|pos| pos.average_cost)
                    .unwrap_or(0.0),
                realized_pnl_delta: 0.0,
                reason: reason.to_string(),
            });
            report.account_events.push(AccountEvent {
                date,
                cash_before: leg_cash_before,
                cash_after: portfolio.cash(),
                total_equity: self.total_equity_at(
                    date,
                    portfolio,
                    data,
                    self.account_mark_price_field(),
                )?,
                note: format!("buy {symbol} {reason}"),
            });
        }
        *intraday_turnover.entry(symbol.to_string()).or_default() += filled_qty;

        let remaining_qty = requested_qty.saturating_sub(filled_qty);
        let keep_open = allow_pending_limit
            && remaining_qty > 0
            && Self::limit_order_can_remain_open(partial_fill_reason.as_deref());
        if keep_open {
            self.upsert_open_order(OpenOrder {
                order_id,
                symbol: symbol.to_string(),
                side: OrderSide::Buy,
                requested_quantity: requested_qty,
                filled_quantity: filled_qty,
                remaining_quantity: remaining_qty,
                limit_price: limit_price.expect("limit price for pending limit buy"),
                reason: reason.to_string(),
            });
        } else {
            self.clear_open_order(order_id);
        }

        let status = if keep_open {
            OrderStatus::PartiallyFilled
        } else if filled_qty < requested_qty {
            final_partial_fill_status(partial_fill_reason.as_deref())
        } else {
            OrderStatus::Filled
        };
        let order_reason = if keep_open {
            let detail = partial_fill_reason
                .as_deref()
                .unwrap_or("remaining quantity could not be filled");
            report.diagnostics.push(format!(
                "order_partial_fill symbol={symbol} side=buy requested={requested_qty} filled={filled_qty} reason={detail}; remaining open"
            ));
            format!("{reason}: partial fill due to {detail}; remaining quantity pending")
        } else if status == OrderStatus::PartiallyFilled {
            let detail = partial_fill_reason
                .as_deref()
                .unwrap_or("remaining quantity could not be filled");
            report.diagnostics.push(format!(
                "order_partial_fill symbol={symbol} side=buy requested={requested_qty} filled={filled_qty} reason={detail}"
            ));
            format!("{reason}: partial fill due to {detail}")
        } else if status == OrderStatus::Canceled && filled_qty < requested_qty {
            let detail = partial_fill_reason
                .as_deref()
                .unwrap_or("remaining quantity could not be filled");
            report.diagnostics.push(format!(
                "order_remainder_canceled symbol={symbol} side=buy requested={requested_qty} filled={filled_qty} reason={detail}"
            ));
            format!("{reason}: partial fill due to {detail}; remaining quantity canceled")
        } else {
            reason.to_string()
        };

        report.order_events.push(OrderEvent {
            date,
            order_id: Some(order_id),
            symbol: symbol.to_string(),
            side: OrderSide::Buy,
            requested_quantity: requested_qty,
            filled_quantity: filled_qty,
            status,
            reason: order_reason,
        });
        if matches!(status, OrderStatus::Canceled | OrderStatus::Rejected) {
            Self::emit_order_process_event(
                report,
                date,
                ProcessEventKind::OrderUnsolicitedUpdate,
                order_id,
                symbol,
                OrderSide::Buy,
                format!("status={status:?} filled_quantity={filled_qty}"),
            );
        }
        Ok(())
    }

    fn total_equity_at(
        &self,
        date: NaiveDate,
        portfolio: &PortfolioState,
        data: &DataSet,
        field: PriceField,
    ) -> Result<f64, BacktestError> {
        let mut market_value = 0.0;
        for position in portfolio.positions().values() {
            let price = data.price(date, &position.symbol, field).ok_or_else(|| {
                BacktestError::MissingPrice {
                    date,
                    symbol: position.symbol.clone(),
                    field: match field {
                        PriceField::DayOpen => "day_open",
                        PriceField::Open => "open",
                        PriceField::Close => "close",
                        PriceField::Last => "last",
                    },
                }
            })?;
            market_value += price * position.quantity as f64;
        }

        Ok(portfolio.cash() + market_value)
    }

    fn round_lot(&self, data: &DataSet, symbol: &str) -> u32 {
        data.instruments()
            .get(symbol)
            .map(|instrument| instrument.effective_round_lot())
            .unwrap_or(self.board_lot_size.max(1))
    }

    fn minimum_order_quantity(&self, data: &DataSet, symbol: &str) -> u32 {
        data.instruments()
            .get(symbol)
            .map(|instrument| instrument.minimum_order_quantity())
            .unwrap_or(self.board_lot_size.max(1))
    }

    fn order_step_size(&self, data: &DataSet, symbol: &str) -> u32 {
        data.instruments()
            .get(symbol)
            .map(|instrument| instrument.order_step_size())
            .unwrap_or(self.board_lot_size.max(1))
    }

    fn account_mark_price_field(&self) -> PriceField {
        if self.is_open_auction_matching() {
            PriceField::DayOpen
        } else {
            PriceField::Open
        }
    }

    fn rebalance_valuation_price_field_name(&self) -> &'static str {
        if self.is_open_auction_matching() {
            "prev_close"
        } else {
            price_field_name(self.execution_price_field)
        }
    }

    fn rebalance_valuation_price_for_snapshot(
        &self,
        snapshot: &crate::data::DailyMarketSnapshot,
    ) -> Option<f64> {
        let price = if self.is_open_auction_matching() {
            snapshot.prev_close
        } else {
            snapshot.price(self.execution_price_field)
        };
        if price.is_finite() && price > 0.0 {
            Some(price)
        } else {
            None
        }
    }

    fn rebalance_valuation_price(
        &self,
        date: NaiveDate,
        symbol: &str,
        data: &DataSet,
    ) -> Result<f64, BacktestError> {
        let snapshot = data
            .market(date, symbol)
            .ok_or_else(|| BacktestError::MissingPrice {
                date,
                symbol: symbol.to_string(),
                field: self.rebalance_valuation_price_field_name(),
            })?;
        self.rebalance_valuation_price_for_snapshot(snapshot)
            .ok_or_else(|| BacktestError::MissingPrice {
                date,
                symbol: symbol.to_string(),
                field: self.rebalance_valuation_price_field_name(),
            })
    }

    fn rebalance_total_equity_at(
        &self,
        date: NaiveDate,
        portfolio: &PortfolioState,
        data: &DataSet,
    ) -> Result<f64, BacktestError> {
        let mut market_value = 0.0;
        for position in portfolio.positions().values() {
            let snapshot =
                data.market(date, &position.symbol)
                    .ok_or_else(|| BacktestError::MissingPrice {
                        date,
                        symbol: position.symbol.clone(),
                        field: self.rebalance_valuation_price_field_name(),
                    })?;
            let price = self
                .rebalance_valuation_price_for_snapshot(snapshot)
                .ok_or_else(|| BacktestError::MissingPrice {
                    date,
                    symbol: position.symbol.clone(),
                    field: self.rebalance_valuation_price_field_name(),
                })?;
            market_value += price * position.quantity as f64;
        }
        Ok(portfolio.cash() + market_value)
    }

    fn round_buy_quantity(
        &self,
        quantity: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
    ) -> u32 {
        let step = order_step_size.max(1);
        let normalized = (quantity / step) * step;
        if normalized < minimum_order_quantity.max(1) {
            0
        } else {
            normalized
        }
    }

    fn decrement_order_quantity(
        &self,
        quantity: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
    ) -> u32 {
        let minimum = minimum_order_quantity.max(1);
        if quantity <= minimum {
            return 0;
        }
        let next = quantity.saturating_sub(order_step_size.max(1));
        if next < minimum { 0 } else { next }
    }

    fn affordable_buy_quantity(
        &self,
        date: NaiveDate,
        cash: f64,
        gross_limit: Option<f64>,
        price: f64,
        requested_qty: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
    ) -> u32 {
        let mut quantity =
            self.round_buy_quantity(requested_qty, minimum_order_quantity, order_step_size);
        while quantity > 0 {
            let gross = price * quantity as f64;
            if gross_limit.is_some_and(|limit| gross > limit + 1e-6) {
                quantity = self.decrement_order_quantity(
                    quantity,
                    minimum_order_quantity,
                    order_step_size,
                );
                continue;
            }
            let cost = self.cost_model.calculate(date, OrderSide::Buy, gross);
            if gross + cost.total() <= cash + 1e-6 {
                return quantity;
            }
            quantity =
                self.decrement_order_quantity(quantity, minimum_order_quantity, order_step_size);
        }
        0
    }

    fn buy_reduction_reason(
        &self,
        cash_limit: f64,
        gross_limit: Option<f64>,
        price: f64,
        requested_qty: u32,
        filled_qty: u32,
    ) -> Option<&'static str> {
        if filled_qty >= requested_qty {
            return None;
        }
        if gross_limit.is_some_and(|limit| price * requested_qty as f64 > limit + 1e-6) {
            Some("value budget limit")
        } else if cash_limit.is_finite() {
            Some("insufficient cash after fees")
        } else {
            None
        }
    }

    fn market_fillable_quantity(
        &self,
        snapshot: &crate::data::DailyMarketSnapshot,
        side: OrderSide,
        requested_qty: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
        consumed_turnover: u32,
        allow_odd_lot_sell: bool,
    ) -> Result<u32, String> {
        if requested_qty == 0 {
            return Ok(0);
        }

        if self.inactive_limit && snapshot.tick_volume == 0 {
            return Err("tick no volume".to_string());
        }

        let mut max_fill = requested_qty;
        if self.liquidity_limit && !self.is_open_auction_matching() {
            let top_level_liquidity = match side {
                OrderSide::Buy => snapshot.liquidity_for_buy(),
                OrderSide::Sell => snapshot.liquidity_for_sell(),
            }
            .min(u32::MAX as u64) as u32;
            if top_level_liquidity == 0 {
                return Err("no quote liquidity".to_string());
            }
            let top_level_limit = if side == OrderSide::Sell && allow_odd_lot_sell {
                top_level_liquidity
            } else {
                self.round_buy_quantity(
                    top_level_liquidity,
                    minimum_order_quantity,
                    order_step_size,
                )
            };
            max_fill = max_fill.min(top_level_limit);
        }

        if self.volume_limit {
            let raw_limit = ((snapshot.tick_volume as f64) * self.volume_percent).round() as i64
                - consumed_turnover as i64;
            if raw_limit <= 0 {
                return Err("tick volume limit".to_string());
            }
            let volume_limited = if side == OrderSide::Sell && allow_odd_lot_sell {
                raw_limit as u32
            } else {
                self.round_buy_quantity(raw_limit as u32, minimum_order_quantity, order_step_size)
            };
            if volume_limited == 0 {
                return Err("tick volume limit".to_string());
            }
            max_fill = max_fill.min(volume_limited);
        }

        Ok(max_fill)
    }

    fn price_satisfies_limit(
        &self,
        side: OrderSide,
        execution_price: f64,
        limit_price: Option<f64>,
        price_tick: f64,
    ) -> bool {
        let Some(limit_price) = limit_price else {
            return execution_price.is_finite() && execution_price > 0.0;
        };
        if !execution_price.is_finite() || execution_price <= 0.0 {
            return false;
        }
        let tolerance = price_tick.abs().max(1e-9);
        match side {
            OrderSide::Buy => execution_price <= limit_price + tolerance,
            OrderSide::Sell => execution_price + tolerance >= limit_price,
        }
    }

    fn execution_price_with_limit_slippage(
        &self,
        execution_price: f64,
        limit_price: Option<f64>,
    ) -> f64 {
        match (self.slippage_model, limit_price) {
            (SlippageModel::LimitPrice, Some(limit_price)) => limit_price,
            _ => execution_price,
        }
    }

    fn limit_order_can_remain_open(partial_reason: Option<&str>) -> bool {
        !partial_reason.is_some_and(|reason| {
            reason.contains("insufficient cash") || reason.contains("value budget")
        })
    }

    fn resolve_execution_fill(
        &self,
        date: NaiveDate,
        symbol: &str,
        side: OrderSide,
        snapshot: &crate::data::DailyMarketSnapshot,
        data: &DataSet,
        requested_qty: u32,
        round_lot: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
        allow_odd_lot_sell: bool,
        _execution_cursors: &mut BTreeMap<String, NaiveDateTime>,
        _global_execution_cursor: Option<NaiveDateTime>,
        cash_limit: Option<f64>,
        gross_limit: Option<f64>,
        limit_price: Option<f64>,
    ) -> Option<ExecutionFill> {
        if self.execution_price_field != PriceField::Last {
            return None;
        }

        let start_cursor = self
            .intraday_execution_start_time
            .map(|start_time| date.and_time(start_time));
        let quotes = data.execution_quotes_on(date, symbol);

        if let Some(fill) = self.select_execution_fill(
            snapshot,
            quotes,
            side,
            start_cursor,
            requested_qty,
            round_lot,
            minimum_order_quantity,
            order_step_size,
            allow_odd_lot_sell,
            cash_limit,
            gross_limit,
            limit_price,
        ) {
            return Some(fill);
        }

        if self.intraday_execution_start_time.is_some() {
            let execution_price = self.snapshot_execution_price(snapshot, side);
            if !self.price_satisfies_limit(
                side,
                execution_price,
                limit_price,
                snapshot.effective_price_tick(),
            ) {
                return None;
            }
            let execution_price =
                self.execution_price_with_limit_slippage(execution_price, limit_price);
            let quantity = match side {
                OrderSide::Buy => self.affordable_buy_quantity(
                    date,
                    cash_limit.unwrap_or(f64::INFINITY),
                    gross_limit,
                    execution_price,
                    requested_qty,
                    minimum_order_quantity,
                    order_step_size,
                ),
                OrderSide::Sell => requested_qty,
            };
            if quantity == 0 {
                return None;
            }
            let next_cursor = self
                .intraday_execution_start_time
                .map(|start_time| date.and_time(start_time) + Duration::seconds(1))
                .unwrap_or_else(|| date.and_hms_opt(0, 0, 1).expect("valid midnight"));
            return Some(ExecutionFill {
                quantity,
                next_cursor,
                legs: vec![ExecutionLeg {
                    price: execution_price,
                    quantity,
                }],
                unfilled_reason: self.buy_reduction_reason(
                    cash_limit.unwrap_or(f64::INFINITY),
                    gross_limit,
                    execution_price,
                    requested_qty,
                    quantity,
                ),
            });
        }

        None
    }

    fn select_execution_fill(
        &self,
        snapshot: &crate::data::DailyMarketSnapshot,
        quotes: &[IntradayExecutionQuote],
        side: OrderSide,
        start_cursor: Option<NaiveDateTime>,
        requested_qty: u32,
        round_lot: u32,
        minimum_order_quantity: u32,
        order_step_size: u32,
        allow_odd_lot_sell: bool,
        cash_limit: Option<f64>,
        gross_limit: Option<f64>,
        limit_price: Option<f64>,
    ) -> Option<ExecutionFill> {
        if requested_qty == 0 {
            return None;
        }

        let lot = round_lot.max(1);
        let mut filled_qty = 0_u32;
        let mut gross_amount = 0.0_f64;
        let mut last_timestamp = None;
        let mut legs = Vec::new();
        let mut budget_block_reason = None;
        let mut saw_quote_after_cursor = false;

        for quote in quotes {
            if start_cursor.is_some_and(|cursor| quote.timestamp < cursor) {
                continue;
            }
            saw_quote_after_cursor = true;

            // Approximate JoinQuant market-order fills with the evolving L1 book after
            // the decision time instead of trade VWAP. This keeps quantities/prices
            // closer to the observed 10:18 execution logs.
            if quote.volume_delta == 0 {
                continue;
            }
            let Some(quote_price) = self.select_quote_reference_price(snapshot, quote, side) else {
                continue;
            };
            if !self.price_satisfies_limit(
                side,
                quote_price,
                limit_price,
                snapshot.effective_price_tick(),
            ) {
                continue;
            }
            let quote_price = self.execution_price_with_limit_slippage(quote_price, limit_price);
            let top_level_liquidity = match side {
                OrderSide::Buy => quote.ask1_volume,
                OrderSide::Sell => quote.bid1_volume,
            };
            let available_qty = top_level_liquidity
                .saturating_mul(lot as u64)
                .min(u32::MAX as u64) as u32;
            if available_qty == 0 {
                continue;
            }

            let remaining_qty = requested_qty.saturating_sub(filled_qty);
            if remaining_qty == 0 {
                break;
            }
            let mut take_qty = remaining_qty.min(available_qty);
            if !(side == OrderSide::Sell && allow_odd_lot_sell && take_qty == remaining_qty) {
                take_qty =
                    self.round_buy_quantity(take_qty, minimum_order_quantity, order_step_size);
            }
            if take_qty == 0 {
                continue;
            }

            if let Some(cash) = cash_limit {
                while take_qty > 0 {
                    let candidate_gross = gross_amount + quote_price * take_qty as f64;
                    if gross_limit.is_some_and(|limit| candidate_gross > limit + 1e-6) {
                        budget_block_reason = Some("value budget limit");
                        take_qty = self.decrement_order_quantity(
                            take_qty,
                            minimum_order_quantity,
                            order_step_size,
                        );
                        continue;
                    }
                    if candidate_gross <= cash + 1e-6 {
                        break;
                    }
                    budget_block_reason = Some("insufficient cash after fees");
                    take_qty = self.decrement_order_quantity(
                        take_qty,
                        minimum_order_quantity,
                        order_step_size,
                    );
                }
                if take_qty == 0 {
                    break;
                }
            }

            gross_amount += quote_price * take_qty as f64;
            filled_qty += take_qty;
            last_timestamp = Some(quote.timestamp);
            legs.push(ExecutionLeg {
                price: quote_price,
                quantity: take_qty,
            });

            if filled_qty >= requested_qty {
                break;
            }
        }

        if filled_qty == 0 {
            return None;
        }

        Some(ExecutionFill {
            quantity: filled_qty,
            next_cursor: last_timestamp.unwrap() + Duration::seconds(1),
            legs: if self.matching_type == MatchingType::Vwap {
                vec![ExecutionLeg {
                    price: gross_amount / filled_qty as f64,
                    quantity: filled_qty,
                }]
            } else {
                legs
            },
            unfilled_reason: if filled_qty < requested_qty {
                budget_block_reason.or(if saw_quote_after_cursor {
                    Some("intraday quote liquidity exhausted")
                } else {
                    Some("no execution quotes after start")
                })
            } else {
                None
            },
        })
    }

    fn uses_serial_execution_cursor(&self, reason: &str) -> bool {
        let _ = reason;
        false
    }
}

fn matching_type_from_price_field(field: PriceField) -> MatchingType {
    match field {
        PriceField::DayOpen => MatchingType::OpenAuction,
        PriceField::Open => MatchingType::NextBarOpen,
        PriceField::Close => MatchingType::CurrentBarClose,
        PriceField::Last => MatchingType::NextTickLast,
    }
}

fn merge_partial_fill_reason(current: Option<String>, next: Option<&str>) -> Option<String> {
    match (current, next) {
        (Some(existing), Some(next_reason)) if !existing.contains(next_reason) => {
            Some(format!("{existing}; {next_reason}"))
        }
        (Some(existing), _) => Some(existing),
        (None, Some(next_reason)) => Some(next_reason.to_string()),
        (None, None) => None,
    }
}

fn zero_fill_status_for_reason(reason: &str) -> OrderStatus {
    match reason {
        "tick no volume" | "tick volume limit" => OrderStatus::Canceled,
        _ => OrderStatus::Rejected,
    }
}

fn final_partial_fill_status(partial_reason: Option<&str>) -> OrderStatus {
    match partial_reason {
        Some(reason)
            if reason.contains("market liquidity or volume limit")
                || reason.contains("intraday quote liquidity exhausted")
                || reason.contains("no execution quotes after start") =>
        {
            OrderStatus::Canceled
        }
        _ => OrderStatus::PartiallyFilled,
    }
}

fn price_field_name(field: PriceField) -> &'static str {
    match field {
        PriceField::DayOpen => "day_open",
        PriceField::Open => "open",
        PriceField::Close => "close",
        PriceField::Last => "last",
    }
}

fn sell_reason(decision: &StrategyDecision, symbol: &str) -> &'static str {
    if decision.exit_symbols.contains(symbol) {
        "exit_hook_sell"
    } else {
        "rebalance_sell"
    }
}
