"""Unit tests for MarginAccountPolicy."""

from datetime import datetime

import pytest

from ml4t.backtest import Position
from ml4t.backtest.accounting.policy import UnifiedAccountPolicy


class TestMarginAccountPolicyInitialization:
    """Tests for MarginAccountPolicy initialization."""

    def test_default_initialization(self):
        """Test initialization with default Reg T parameters."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        assert policy.initial_margin == 0.5  # 50% = Reg T standard
        assert policy.long_maintenance_margin == 0.25  # 25% = Reg T standard

    def test_custom_initialization(self):
        """Test initialization with custom margin parameters."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            initial_margin=0.3,
            long_maintenance_margin=0.15,
            short_maintenance_margin=0.15,
        )
        assert policy.initial_margin == 0.3
        assert policy.long_maintenance_margin == 0.15

    def test_conservative_margin(self):
        """Test initialization with conservative (no leverage) parameters."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            initial_margin=1.0,
            long_maintenance_margin=0.5,
            short_maintenance_margin=0.5,
        )
        assert policy.initial_margin == 1.0
        assert policy.long_maintenance_margin == 0.5

    def test_invalid_initial_margin_too_low(self):
        """Test that initial_margin must be > 0."""
        with pytest.raises(ValueError, match="Initial margin must be in"):
            UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True, initial_margin=0.0)

    def test_invalid_initial_margin_too_high(self):
        """Test that initial_margin must be <= 1.0."""
        with pytest.raises(ValueError, match="Initial margin must be in"):
            UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True, initial_margin=1.5)

    def test_invalid_maintenance_margin_too_low(self):
        """Test that long_maintenance_margin must be > 0."""
        with pytest.raises(ValueError, match="maintenance margin must be in"):
            UnifiedAccountPolicy(
                allow_short_selling=True, allow_leverage=True, long_maintenance_margin=0.0
            )

    def test_invalid_maintenance_margin_too_high(self):
        """Test that long_maintenance_margin must be <= 1.0."""
        with pytest.raises(ValueError, match="maintenance margin must be in"):
            UnifiedAccountPolicy(
                allow_short_selling=True, allow_leverage=True, long_maintenance_margin=1.5
            )

    def test_invalid_maintenance_greater_than_initial(self):
        """Test that long_maintenance_margin must be < initial_margin."""
        with pytest.raises(ValueError, match="maintenance margin.*must be <"):
            UnifiedAccountPolicy(
                allow_short_selling=True,
                allow_leverage=True,
                initial_margin=0.25,
                long_maintenance_margin=0.5,
            )

    def test_invalid_maintenance_equal_to_initial(self):
        """Test that long_maintenance_margin cannot equal initial_margin."""
        with pytest.raises(ValueError, match="maintenance margin.*must be <"):
            UnifiedAccountPolicy(
                allow_short_selling=True,
                allow_leverage=True,
                initial_margin=0.5,
                long_maintenance_margin=0.5,
            )


class TestMarginAccountPolicyBuyingPower:
    """Tests for buying power calculation using correct initial margin formula."""

    def test_margin_buying_power_correct_max_leverage_2x(self):
        """Test that margin buying power enforces max 2x leverage, not 4x.

        This test verifies Bug #1 fix: buying power must use initial margin
        requirement, not maintenance margin.

        With Reg T (50% initial margin):
        - Cash $10k should allow max $20k purchase (2x leverage)
        - After buying $20k, BP should be $0
        - Attempting to buy $1 more should fail

        Old (buggy) formula allowed 4x leverage.
        New (correct) formula enforces 2x leverage.
        """
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )

        # Step 1: Start with $10k cash, no positions
        # BP = $10k / 0.5 = $20k (can buy $20k worth)
        bp = policy.calculate_buying_power(cash=10_000.0, positions={})
        assert bp == 20_000.0, "With $10k cash and 50% IM, should have $20k buying power"

        # Step 2: Buy $20k of stock (max leverage)
        # Now: cash=-$10k, position=$20k
        # NLV = -$10k + $20k = $10k
        # Required IM = $20k × 0.5 = $10k
        # Excess Equity = $10k - $10k = $0
        # BP = $0 / 0.5 = $0 (at limit, no more buying power)
        positions = {
            "AAPL": Position(
                asset="AAPL",
                quantity=200.0,
                entry_price=100.0,
                current_price=100.0,
                entry_time=datetime.now(),
            )
        }
        bp = policy.calculate_buying_power(cash=-10_000.0, positions=positions)
        assert bp == 0.0, (
            "After buying $20k with $10k cash, should have $0 buying power (at 2x limit)"
        )

        # Step 3: Verify we CANNOT buy more (validates 2x limit)
        valid, reason = policy.validate_new_position(
            asset="MSFT",
            quantity=0.01,  # Try to buy $1 worth
            price=100.0,
            current_positions=positions,
            cash=-10_000.0,
        )
        assert valid is False, "Should not allow buying more when at 2x leverage limit"
        assert "Insufficient buying power" in reason

    def test_cash_only_no_positions(self):
        """Test buying power with cash only (no positions).

        Example from docstring:
        cash=$100k, positions={}
        NLV = $100k, MM = $0
        BP = ($100k - $0) / 0.5 = $200k (2x leverage)
        """
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        bp = policy.calculate_buying_power(cash=100_000.0, positions={})
        assert bp == 200_000.0  # 2x leverage

    def test_long_position_with_cash(self):
        """Test buying power with long position.

        cash=$50k, long 1000 shares @ $100 = $100k market value
        NLV = $50k + $100k = $150k
        Required IM = $100k × 0.5 = $50k
        Excess Equity = $150k - $50k = $100k
        BP = $100k / 0.5 = $200k
        """
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        positions = {
            "AAPL": Position(
                asset="AAPL",
                quantity=1000.0,
                entry_price=100.0,
                current_price=100.0,
                entry_time=datetime.now(),
            )
        }
        bp = policy.calculate_buying_power(cash=50_000.0, positions=positions)
        assert bp == 200_000.0

    def test_short_position_with_cash(self):
        """Test buying power with short position.

        cash=$150k, short 1000 shares @ $100 = -$100k market value
        NLV = $150k + (-$100k) = $50k
        Required IM = $100k × 0.5 = $50k
        Excess Equity = $50k - $50k = $0
        BP = max(0, $0 / 0.5) = $0 (at limit)
        """
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        positions = {
            "AAPL": Position(
                asset="AAPL",
                quantity=-1000.0,  # Short position
                entry_price=100.0,
                current_price=100.0,
                entry_time=datetime.now(),
            )
        }
        bp = policy.calculate_buying_power(cash=150_000.0, positions=positions)
        assert bp == 0.0  # At initial margin limit

    def test_underwater_account_negative_nlv(self):
        """Test buying power when account is underwater (negative equity).

        cash=-$10k, long 1000 shares @ $50 = $50k market value
        NLV = -$10k + $50k = $40k
        Required IM = $50k × 0.5 = $25k
        Excess Equity = $40k - $25k = $15k
        BP = $15k / 0.5 = $30k
        """
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        positions = {
            "AAPL": Position(
                asset="AAPL",
                quantity=1000.0,
                entry_price=100.0,  # Bought at $100
                current_price=50.0,  # Now at $50 (down 50%)
                entry_time=datetime.now(),
            )
        }
        bp = policy.calculate_buying_power(cash=-10_000.0, positions=positions)
        assert bp == 30_000.0

    def test_multiple_positions_long_and_short(self):
        """Test buying power with multiple positions (long and short).

        cash=$100k
        Long AAPL 1000 @ $100 = +$100k market value
        Short MSFT 500 @ $200 = -$100k market value
        NLV = $100k + $100k + (-$100k) = $100k
        Required IM = (|$100k| + |-$100k|) × 0.5 = $200k × 0.5 = $100k
        Excess Equity = $100k - $100k = $0
        BP = max(0, $0 / 0.5) = $0 (at limit)
        """
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        positions = {
            "AAPL": Position(
                asset="AAPL",
                quantity=1000.0,
                entry_price=100.0,
                current_price=100.0,
                entry_time=datetime.now(),
            ),
            "MSFT": Position(
                asset="MSFT",
                quantity=-500.0,  # Short
                entry_price=200.0,
                current_price=200.0,
                entry_time=datetime.now(),
            ),
        }
        bp = policy.calculate_buying_power(cash=100_000.0, positions=positions)
        assert bp == 0.0  # At initial margin limit

    def test_no_leverage_margin_account(self):
        """Test margin account with no leverage (100% initial margin).

        cash=$100k, positions={}
        BP = ($100k - $0) / 1.0 = $100k (no leverage)
        """
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            initial_margin=1.0,
            long_maintenance_margin=0.5,
            short_maintenance_margin=0.5,
        )
        bp = policy.calculate_buying_power(cash=100_000.0, positions={})
        assert bp == 100_000.0  # No leverage

    def test_high_leverage_margin_account(self):
        """Test margin account with high leverage (25% initial margin).

        cash=$100k, positions={}
        BP = ($100k - $0) / 0.25 = $400k (4x leverage)
        """
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            initial_margin=0.25,
            long_maintenance_margin=0.15,
            short_maintenance_margin=0.15,
        )
        bp = policy.calculate_buying_power(cash=100_000.0, positions={})
        assert bp == 400_000.0  # 4x leverage

    def test_margin_call_scenario_negative_buying_power(self):
        """Test buying power when account is severely underwater.

        cash=-$50k, long 1000 shares @ $40 = $40k market value
        NLV = -$50k + $40k = -$10k (negative equity!)
        Required IM = $40k × 0.5 = $20k
        Excess Equity = -$10k - $20k = -$30k
        BP = max(0, -$30k / 0.5) = $0 (clamped to zero)
        """
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        positions = {
            "AAPL": Position(
                asset="AAPL",
                quantity=1000.0,
                entry_price=100.0,  # Bought at $100
                current_price=40.0,  # Now at $40 (down 60%)
                entry_time=datetime.now(),
            )
        }
        bp = policy.calculate_buying_power(cash=-50_000.0, positions=positions)
        assert bp == 0.0  # Clamped to zero (margin call)


class TestMarginAccountPolicyShortSelling:
    """Tests for short selling permissions."""

    def test_allows_short_selling_returns_true(self):
        """Test that margin accounts allow short selling."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        assert policy.allows_short_selling() is True


class TestMarginAccountPolicyNewPositionValidation:
    """Tests for validate_new_position method."""

    def test_valid_long_position_with_sufficient_buying_power(self):
        """Test approving long position with sufficient buying power."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # BP = $100k / 0.5 = $200k, order cost = 100 × $150 = $15k
        valid, reason = policy.validate_new_position(
            asset="AAPL",
            quantity=100.0,
            price=150.0,
            current_positions={},
            cash=100_000.0,
        )
        assert valid is True
        assert reason == ""

    def test_valid_short_position(self):
        """Test approving short position (margin accounts allow shorts)."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # BP = $100k / 0.5 = $200k, order cost = 100 × $150 = $15k
        valid, reason = policy.validate_new_position(
            asset="AAPL",
            quantity=-100.0,  # Short position
            price=150.0,
            current_positions={},
            cash=100_000.0,
        )
        assert valid is True
        assert reason == ""

    def test_reject_position_insufficient_buying_power(self):
        """Test rejecting position with insufficient buying power."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # BP = $10k / 0.5 = $20k, order cost = 1000 × $100 = $100k (too much)
        valid, reason = policy.validate_new_position(
            asset="AAPL",
            quantity=1000.0,
            price=100.0,
            current_positions={},
            cash=10_000.0,
        )
        assert valid is False
        assert "Insufficient buying power" in reason
        assert "need $100000.00" in reason
        assert "have $20000.00" in reason

    def test_valid_position_with_existing_positions(self):
        """Test approving position with existing positions affecting BP."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        existing = {
            "AAPL": Position(
                asset="AAPL",
                quantity=1000.0,
                entry_price=100.0,
                current_price=100.0,
                entry_time=datetime.now(),
            )
        }
        # cash=$50k, long $100k AAPL
        # NLV = $150k, Required IM = $100k × 0.5 = $50k
        # Excess Equity = $150k - $50k = $100k
        # BP = $100k / 0.5 = $200k
        # New order: 100 × $200 = $20k (OK)
        valid, reason = policy.validate_new_position(
            asset="MSFT",
            quantity=100.0,
            price=200.0,
            current_positions=existing,
            cash=50_000.0,
        )
        assert valid is True
        assert reason == ""


class TestMarginAccountPolicyPositionChange:
    """Tests for validate_position_change method."""

    def test_valid_add_to_long_position(self):
        """Test adding to existing long position."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # current=100, delta=+50, cash=$100k -> BP = $200k
        # Risk increase = 50 × $150 = $7.5k
        valid, reason = policy.validate_position_change(
            asset="AAPL",
            current_quantity=100.0,
            quantity_delta=50.0,
            price=150.0,
            current_positions={},
            cash=100_000.0,
        )
        assert valid is True
        assert reason == ""

    def test_valid_partial_close_long(self):
        """Test partial close of long position (always allowed)."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # current=100, delta=-50 (partial close)
        valid, reason = policy.validate_position_change(
            asset="AAPL",
            current_quantity=100.0,
            quantity_delta=-50.0,
            price=150.0,
            current_positions={},
            cash=10_000.0,  # Doesn't matter for closes
        )
        assert valid is True
        assert reason == ""

    def test_valid_full_close_long(self):
        """Test full close of long position (always allowed)."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # current=100, delta=-100 (full close)
        valid, reason = policy.validate_position_change(
            asset="AAPL",
            current_quantity=100.0,
            quantity_delta=-100.0,
            price=150.0,
            current_positions={},
            cash=10_000.0,  # Doesn't matter for closes
        )
        assert valid is True
        assert reason == ""

    def test_valid_position_reversal_long_to_short(self):
        """Test position reversal from long to short (allowed in margin accounts)."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # current=100, delta=-200 -> new=-100 (reversed to short)
        # BP = $100k / 0.5 = $200k
        # Risk = |-100| × $150 = $15k
        valid, reason = policy.validate_position_change(
            asset="AAPL",
            current_quantity=100.0,
            quantity_delta=-200.0,
            price=150.0,
            current_positions={},
            cash=100_000.0,
        )
        assert valid is True
        assert reason == ""

    def test_valid_position_reversal_short_to_long(self):
        """Test position reversal from short to long."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # current=-100, delta=+200 -> new=+100 (reversed to long)
        # BP = $100k / 0.5 = $200k
        # Risk = |100| × $150 = $15k
        valid, reason = policy.validate_position_change(
            asset="AAPL",
            current_quantity=-100.0,
            quantity_delta=200.0,
            price=150.0,
            current_positions={},
            cash=100_000.0,
        )
        assert valid is True
        assert reason == ""

    def test_valid_add_to_short_position(self):
        """Test adding to existing short position."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # current=-100, delta=-50 (adding to short)
        # BP = $100k / 0.5 = $200k
        # Risk = |-50| × $150 = $7.5k
        valid, reason = policy.validate_position_change(
            asset="AAPL",
            current_quantity=-100.0,
            quantity_delta=-50.0,
            price=150.0,
            current_positions={},
            cash=100_000.0,
        )
        assert valid is True
        assert reason == ""

    def test_reject_position_change_insufficient_buying_power(self):
        """Test rejecting position change with insufficient buying power."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # current=100, delta=+1000
        # BP = $10k / 0.5 = $20k
        # Risk = 1000 × $100 = $100k (too much)
        valid, reason = policy.validate_position_change(
            asset="AAPL",
            current_quantity=100.0,
            quantity_delta=1000.0,
            price=100.0,
            current_positions={},
            cash=10_000.0,
        )
        assert valid is False
        assert "Insufficient buying power" in reason

    def test_reject_reversal_insufficient_buying_power(self):
        """Test rejecting position reversal when insufficient BP for new side."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True, allow_leverage=True, initial_margin=0.5
        )
        # current=100, delta=-1000 -> new=-900 (large short)
        # BP = $10k / 0.5 = $20k
        # Risk = |-900| × $100 = $90k (too much)
        valid, reason = policy.validate_position_change(
            asset="AAPL",
            current_quantity=100.0,
            quantity_delta=-1000.0,
            price=100.0,
            current_positions={},
            cash=10_000.0,
        )
        assert valid is False
        assert "Insufficient buying power" in reason


class TestMarginAccountPolicyAsymmetricMaintenance:
    """Tests for asymmetric maintenance margins (shorts have higher maintenance)."""

    def test_default_asymmetric_maintenance(self):
        """Test default maintenance margins: 25% long, 30% short."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        assert policy.initial_margin == 0.5
        assert policy.long_maintenance_margin == 0.25
        assert policy.short_maintenance_margin == 0.30

    def test_get_margin_requirement_long_maintenance(self):
        """Test maintenance margin requirement for long position."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        # Long 100 @ $100 = $10,000 market value
        # Long maintenance = 25% = $2,500
        margin = policy.get_margin_requirement("AAPL", 100, 100.0, for_initial=False)
        assert margin == 2_500.0

    def test_get_margin_requirement_short_maintenance(self):
        """Test maintenance margin requirement for short position (higher than long)."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        # Short 100 @ $100 = $10,000 market value
        # Short maintenance = 30% = $3,000
        margin = policy.get_margin_requirement("AAPL", -100, 100.0, for_initial=False)
        assert margin == 3_000.0

    def test_get_margin_requirement_initial_same_for_both(self):
        """Test initial margin is the same for longs and shorts."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        # Both should be 50% of $10,000 = $5,000
        long_margin = policy.get_margin_requirement("AAPL", 100, 100.0, for_initial=True)
        short_margin = policy.get_margin_requirement("AAPL", -100, 100.0, for_initial=True)
        assert long_margin == 5_000.0
        assert short_margin == 5_000.0


class TestMarginAccountPolicyFuturesMargin:
    """Tests for futures fixed-dollar margin support."""

    def test_futures_fixed_margin_initial(self):
        """Test fixed dollar margin for futures (initial)."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            fixed_margin_schedule={"ES": (12_000.0, 6_000.0)},
        )
        # 2 ES contracts @ $12,000 per contract
        margin = policy.get_margin_requirement("ES", 2, 5000.0, for_initial=True)
        assert margin == 24_000.0

    def test_futures_fixed_margin_maintenance(self):
        """Test fixed dollar margin for futures (maintenance)."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            fixed_margin_schedule={"ES": (12_000.0, 6_000.0)},
        )
        # 2 ES contracts @ $6,000 maintenance per contract
        margin = policy.get_margin_requirement("ES", 2, 5000.0, for_initial=False)
        assert margin == 12_000.0

    def test_futures_margin_ignores_price(self):
        """Test that futures margin is per-contract, not price-based."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            fixed_margin_schedule={"ES": (12_000.0, 6_000.0)},
        )
        # Margin should be same regardless of price
        margin_low = policy.get_margin_requirement("ES", 1, 4000.0, for_initial=True)
        margin_high = policy.get_margin_requirement("ES", 1, 6000.0, for_initial=True)
        assert margin_low == margin_high == 12_000.0

    def test_futures_margin_short_same_as_long(self):
        """Test futures margin is same for long and short."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            fixed_margin_schedule={"ES": (12_000.0, 6_000.0)},
        )
        long_margin = policy.get_margin_requirement("ES", 2, 5000.0, for_initial=True)
        short_margin = policy.get_margin_requirement("ES", -2, 5000.0, for_initial=True)
        assert long_margin == short_margin == 24_000.0

    def test_equity_uses_percentage_not_fixed(self):
        """Test equities not in schedule use percentage margin."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            fixed_margin_schedule={"ES": (12_000.0, 6_000.0)},
        )
        # AAPL not in schedule, should use percentage
        # 100 shares @ $150 = $15,000 × 50% = $7,500
        margin = policy.get_margin_requirement("AAPL", 100, 150.0, for_initial=True)
        assert margin == 7_500.0

    def test_mixed_portfolio_buying_power(self):
        """Test buying power with mixed equities and futures."""
        policy = UnifiedAccountPolicy(
            allow_short_selling=True,
            allow_leverage=True,
            fixed_margin_schedule={"ES": (12_000.0, 6_000.0)},
        )
        # Cash $100k, holding 2 ES contracts
        # ES doesn't contribute market_value in traditional sense (it's a derivative)
        # But we need margin for it
        positions = {
            "ES": Position(
                asset="ES",
                quantity=2,
                entry_price=5000.0,
                entry_time=None,
                current_price=5000.0,
                multiplier=50.0,  # ES multiplier
            )
        }
        # NLV = $100k + market_value
        # market_value = 2 × 5000 × 50 = $500k (for futures with multiplier)
        # Required margin = 2 × $12k = $24k
        # Excess = $600k - $24k = $576k
        # BP = $576k / 0.5 = $1.152M
        bp = policy.calculate_buying_power(100_000.0, positions)
        # This is a complex case - let's just verify it's positive and reasonable
        assert bp > 0


class TestMarginAccountPolicyMarginCall:
    """Tests for margin call detection."""

    def test_no_margin_call_with_excess_equity(self):
        """Test no margin call when equity exceeds maintenance requirement."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        positions = {
            "AAPL": Position(
                asset="AAPL",
                quantity=100,
                entry_price=100.0,
                entry_time=None,
                current_price=100.0,
            )
        }
        # NLV = $50k + $10k market value = $60k
        # Long maintenance = $10k × 25% = $2.5k
        # $60k >> $2.5k, no margin call
        assert policy.is_margin_call(50_000.0, positions) is False

    def test_margin_call_when_underwater(self):
        """Test margin call triggered when equity below maintenance."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        positions = {
            "AAPL": Position(
                asset="AAPL",
                quantity=1000,
                entry_price=100.0,
                entry_time=None,
                current_price=100.0,
            )
        }
        # Position = 1000 × $100 = $100k market value
        # NLV = -$80k cash + $100k = $20k
        # Long maintenance = $100k × 25% = $25k
        # $20k < $25k, MARGIN CALL
        assert policy.is_margin_call(-80_000.0, positions) is True

    def test_short_margin_call_higher_threshold(self):
        """Test short positions have higher maintenance (30% vs 25%)."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        positions = {
            "AAPL": Position(
                asset="AAPL",
                quantity=-1000,
                entry_price=100.0,
                entry_time=None,
                current_price=100.0,
            )
        }
        # Short position = -1000 × $100 = -$100k market value
        # NLV = $128k cash + (-$100k) = $28k
        # Short maintenance = $100k × 30% = $30k
        # $28k < $30k, MARGIN CALL
        # Note: If this were a long, 25% = $25k would be OK
        assert policy.is_margin_call(128_000.0, positions) is True

    def test_no_margin_call_empty_positions(self):
        """Test no margin call with no positions."""
        policy = UnifiedAccountPolicy(allow_short_selling=True, allow_leverage=True)
        assert policy.is_margin_call(100_000.0, {}) is False


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
