from hermes_mcp.decision_engine import assess_wallet_state, make_decision, normalize_strategy_snapshot, score_strategy_fit def test_assess_wallet_state_marks_one_sided_wallet_as_critically_unbalanced(): concern = {"account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} account_info = { "balances": [ {"asset_code": "XRP", "available": 1}, {"asset_code": "USD", "available": 1000}, ] } wallet = assess_wallet_state(account_info=account_info, concern=concern, price=2.0) assert wallet["inventory_state"] == "critically_unbalanced" assert wallet["rebalance_needed"] is True assert wallet["grid_ready"] is False def test_assess_wallet_state_infers_base_and_quote_from_market_symbol_when_missing(): concern = {"account_id": "a1", "market_symbol": "xrpusd", "base_currency": None, "quote_currency": None} account_info = { "balances": [ {"asset_code": "XRP", "available": 64.68158}, {"asset_code": "USD", "available": 12.64}, ] } wallet = assess_wallet_state(account_info=account_info, concern=concern, price=1.318, strategies=[]) assert wallet["base_currency"] == "XRP" assert wallet["quote_currency"] == "USD" assert wallet["base_available"] == 64.68158 assert wallet["quote_available"] == 12.64 def test_score_strategy_fit_penalizes_grid_when_wallet_unbalanced(): strategy = normalize_strategy_snapshot({ "id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {}, "config": {}, }) narrative = {"stance": "constructive_bullish", "opportunity_map": {"continuation": 0.7, "mean_reversion": 0.1, "reversal": 0.1, "wait": 0.1}} wallet_state = {"inventory_state": "critically_unbalanced", "rebalance_needed": True} fit = score_strategy_fit(strategy=strategy, narrative=narrative, wallet_state=wallet_state) assert fit["score"] < 0 assert any("grid" in block or "wallet" in block for block in fit["blocks"]) def test_assess_wallet_state_counts_reserved_orders_in_effective_inventory(): concern = {"account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} account_info = { "balances": [ {"asset_code": "XRP", "available": 0}, {"asset_code": "USD", "available": 0}, ] } strategies = [ { "id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "market_symbol": "xrpusd", "state": { "orders": [ {"side": "sell", "status": "open", "amount": "10", "price": "1.50"}, {"side": "buy", "status": "open", "amount": "10", "price": "1.40"}, ] }, } ] wallet = assess_wallet_state(account_info=account_info, concern=concern, price=1.45, strategies=strategies) assert wallet["inventory_state"] == "balanced" assert wallet["base_reserved"] == 10.0 assert wallet["quote_reserved"] == 14.0 assert wallet["base_effective"] == 10.0 assert wallet["quote_effective"] == 14.0 def test_make_decision_keeps_grid_when_imbalance_is_manageable(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.72, "opportunity_map": {"continuation": 0.75, "mean_reversion": 0.1, "reversal": 0.05, "wait": 0.1}, } wallet_state = { "inventory_state": "base_heavy", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.8, "quote_ratio": 0.2, } strategies = [ {"id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "observe" assert decision.action == "keep_grid" assert decision.target_strategy == "grid-1" def test_make_decision_does_not_replace_grid_with_rebalancer_only_because_grid_mentions_handoff_readiness(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "cautious_bullish", "confidence": 0.74, "opportunity_map": {"continuation": 0.5, "mean_reversion": 0.25, "reversal": 0.05, "wait": 0.2}, "scoped_state": { "micro": {"impulse": "mixed", "trend_bias": "mixed"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "partial_alignment", "friction": "medium"}, } wallet_state = { "inventory_state": "base_heavy", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.64, "quote_ratio": 0.36, } strategies = [ {"id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {}, "config": {}, "report": {"supervision": {"capacity_available": False, "side_capacity": {"buy": False, "sell": True}}}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.action == "keep_grid" assert decision.target_strategy == "grid-1" def test_make_decision_replaces_grid_when_breakout_pressure_is_persistent(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.78, "opportunity_map": {"continuation": 0.72, "mean_reversion": 0.08, "reversal": 0.05, "wait": 0.15}, "scoped_state": { "micro": {"impulse": "up", "trend_bias": "bullish"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "micro_meso_macro_aligned", "friction": "low"}, } wallet_state = { "inventory_state": "critically_unbalanced", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.88, "quote_ratio": 0.12, } strategies = [ {"id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.action == "replace_with_exposure_protector" assert decision.target_strategy == "protect-1" def test_make_decision_keeps_grid_when_critically_unbalanced_but_grid_still_has_working_side_capacity(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "cautious_bullish", "confidence": 0.7, "opportunity_map": {"continuation": 0.55, "mean_reversion": 0.2, "reversal": 0.05, "wait": 0.2}, "scoped_state": { "micro": {"impulse": "up", "trend_bias": "bullish", "location": "upper_half", "reversal_risk": "low"}, "meso": {"structure": "range", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "partial_alignment", "friction": "medium"}, "features_by_timeframe": {"1m": {"raw": {"price": 1.4374}}}, } wallet_state = { "inventory_state": "critically_unbalanced", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.86, "quote_ratio": 0.14, } strategies = [ { "id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": { "last_price": 1.4374, "open_order_count": 4, "orders": [ {"side": "sell", "status": "open", "price": "1.43956", "amount": "7"}, {"side": "sell", "status": "open", "price": "1.44500", "amount": "7"}, ], }, "config": {}, "report": {"supervision": {"capacity_available": False, "side_capacity": {"buy": False, "sell": True}, "inventory_pressure": "critical", "degraded": False}}, }, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "observe" assert decision.action == "keep_grid" assert decision.target_strategy == "grid-1" def test_make_decision_keeps_grid_when_trend_has_only_eaten_two_levels(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.78, "opportunity_map": {"continuation": 0.72, "mean_reversion": 0.08, "reversal": 0.05, "wait": 0.15}, "scoped_state": { "micro": {"impulse": "up", "trend_bias": "bullish", "location": "upper_half", "reversal_risk": "low"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "micro_meso_macro_aligned", "friction": "low"}, "features_by_timeframe": {"1m": {"raw": {"price": 110.0}}}, } wallet_state = { "inventory_state": "balanced", "rebalance_needed": False, "grid_ready": True, "base_ratio": 0.52, "quote_ratio": 0.48, } strategies = [ {"id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {"center_price": 100.0}, "config": {"grid_step_pct": 0.05}}, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "warn" assert decision.action == "keep_grid" assert decision.target_strategy == "grid-1" def test_make_decision_replaces_grid_when_third_level_is_sustained(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.82, "opportunity_map": {"continuation": 0.82, "mean_reversion": 0.05, "reversal": 0.03, "wait": 0.1}, "scoped_state": { "micro": {"impulse": "up", "trend_bias": "bullish", "location": "near_upper_band", "reversal_risk": "low"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "micro_meso_macro_aligned", "friction": "low"}, "features_by_timeframe": {"1m": {"raw": {"price": 116.0}}}, } wallet_state = { "inventory_state": "balanced", "rebalance_needed": False, "grid_ready": True, "base_ratio": 0.52, "quote_ratio": 0.48, } strategies = [ {"id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {"center_price": 100.0}, "config": {"grid_step_pct": 0.05}}, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "act" assert decision.action == "replace_with_trend_follower" assert decision.target_strategy == "trend-1" def test_make_decision_replaces_grid_with_trend_when_breakout_is_persistent_but_inventory_is_only_base_heavy(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.78, "opportunity_map": {"continuation": 0.72, "mean_reversion": 0.08, "reversal": 0.05, "wait": 0.15}, "scoped_state": { "micro": {"impulse": "up", "trend_bias": "bullish"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "micro_meso_macro_aligned", "friction": "low"}, } wallet_state = { "inventory_state": "base_heavy", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.64, "quote_ratio": 0.36, } strategies = [ {"id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {}, "config": {}, "report": {"supervision": {"capacity_available": False, "side_capacity": {"buy": True, "sell": False}}}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.action == "keep_grid" assert decision.target_strategy == "grid-1" def test_make_decision_prefers_active_grid_over_observe_trend_as_current_primary(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.72, "opportunity_map": {"continuation": 0.75, "mean_reversion": 0.1, "reversal": 0.05, "wait": 0.1}, } wallet_state = { "inventory_state": "base_heavy", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.81, "quote_ratio": 0.19, } strategies = [ {"id": "trend-1", "strategy_type": "trend_follower", "mode": "observe", "account_id": "a1", "state": {}, "config": {}}, {"id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.action == "keep_grid" assert decision.target_strategy == "grid-1" def test_make_decision_prefers_trend_over_rebalancer_on_bullish_breakout_with_depleted_base(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.9, "opportunity_map": {"continuation": 0.85, "mean_reversion": 0.05, "reversal": 0.02, "wait": 0.08}, "scoped_state": { "micro": {"impulse": "up", "trend_bias": "bullish"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "micro_meso_macro_aligned", "friction": "low"}, } wallet_state = { "inventory_state": "critically_unbalanced", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.0, "quote_ratio": 1.0, } strategies = [ {"id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.action == "replace_with_exposure_protector" assert decision.target_strategy == "protect-1" def test_make_decision_keeps_grid_when_next_sell_is_close_despite_persistent_breakout(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.9, "opportunity_map": {"continuation": 0.85, "mean_reversion": 0.05, "reversal": 0.02, "wait": 0.08}, "features_by_timeframe": { "1m": {"raw": {"price": 1.4374, "atr_percent": 0.11}}, }, "scoped_state": { "micro": {"impulse": "up", "trend_bias": "bullish"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "micro_meso_macro_aligned", "friction": "low"}, } wallet_state = { "inventory_state": "base_heavy", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.65, "quote_ratio": 0.35, } strategies = [ { "id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "market_symbol": "xrpusd", "state": { "last_price": 1.4374, "orders": [ {"side": "sell", "status": "open", "price": "1.43956", "amount": "7"}, {"side": "buy", "status": "open", "price": "1.42523", "amount": "7"}, ], }, "config": {}, }, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "observe", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.action == "keep_grid" assert decision.target_strategy == "grid-1" def test_normalize_strategy_snapshot_uses_live_report_contract_and_supervision(): normalized = normalize_strategy_snapshot({ "id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "report": { "fit": { "role": "primary", "inventory_behavior": "balanced", "safe_when_unbalanced": False, "can_run_with": ["exposure_protector"], }, "state": {"last_action": "hold", "open_order_count": 12}, "supervision": {"inventory_pressure": "base_heavy", "capacity_available": False, "side_capacity": {"buy": True, "sell": False}, "degraded": False}, }, }) assert normalized["contract"]["inventory_behavior"] == "balanced" assert normalized["supervision"]["capacity_available"] is False assert normalized["open_order_count"] == 12 def test_make_decision_keeps_trend_during_directional_regime_even_if_wallet_is_skewed(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.7, "opportunity_map": {"continuation": 0.8, "mean_reversion": 0.1, "reversal": 0.05, "wait": 0.05}, } wallet_state = { "inventory_state": "critically_unbalanced", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.88, "quote_ratio": 0.12, } strategies = [ {"id": "trend-1", "strategy_type": "trend_follower", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "observe" assert decision.action == "keep_trend" assert decision.target_strategy == "trend-1" def test_make_decision_replaces_trend_with_rebalancer_after_trend_cools_and_wallet_needs_repair(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "neutral_rotational", "confidence": 0.65, "opportunity_map": {"continuation": 0.15, "mean_reversion": 0.25, "reversal": 0.2, "wait": 0.4}, } wallet_state = { "inventory_state": "critically_unbalanced", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.88, "quote_ratio": 0.12, } strategies = [ {"id": "trend-1", "strategy_type": "trend_follower", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "warn" assert decision.action == "keep_trend" assert decision.target_strategy == "trend-1" def test_make_decision_replaces_trend_with_rebalancer_on_edge_cooling_even_before_full_rotational_stance(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.74, "opportunity_map": {"continuation": 0.58, "mean_reversion": 0.12, "reversal": 0.08, "wait": 0.22}, "scoped_state": { "micro": {"impulse": "mixed", "trend_bias": "mixed", "location": "near_upper_band"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "partial_alignment", "friction": "medium"}, } wallet_state = { "inventory_state": "base_heavy", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.74, "quote_ratio": 0.26, } strategies = [ {"id": "trend-1", "strategy_type": "trend_follower", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "warn" assert decision.action == "keep_trend" assert decision.target_strategy == "trend-1" def test_make_decision_replaces_trend_with_rebalancer_when_micro_reversal_risk_spikes(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.76, "opportunity_map": {"continuation": 0.62, "mean_reversion": 0.1, "reversal": 0.18, "wait": 0.1}, "scoped_state": { "micro": {"impulse": "up", "trend_bias": "bullish", "location": "near_upper_band", "reversal_risk": "high"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "partial_alignment", "friction": "medium"}, } wallet_state = { "inventory_state": "base_heavy", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.72, "quote_ratio": 0.28, } strategies = [ {"id": "trend-1", "strategy_type": "trend_follower", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "warn" assert decision.action == "keep_trend" assert decision.target_strategy == "trend-1" def test_make_decision_replaces_rebalancer_with_grid_when_balanced_and_rotational(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "neutral_rotational", "confidence": 0.68, "opportunity_map": {"continuation": 0.15, "mean_reversion": 0.72, "reversal": 0.05, "wait": 0.08}, } wallet_state = { "inventory_state": "balanced", "rebalance_needed": False, "grid_ready": True, "base_ratio": 0.49, "quote_ratio": 0.51, } strategies = [ {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "grid-1", "strategy_type": "grid_trader", "mode": "off", "account_id": "a1", "state": {}, "config": {}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "act" assert decision.action == "replace_with_grid" assert decision.target_strategy == "grid-1" def test_make_decision_replaces_rebalancer_with_grid_when_trend_is_directional_but_not_sustained(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.72, "opportunity_map": {"continuation": 0.4, "mean_reversion": 0.4, "reversal": 0.08, "wait": 0.12}, "scoped_state": { "micro": {"impulse": "mixed", "trend_bias": "mixed", "location": "centered", "reversal_risk": "low"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "partial_alignment", "friction": "medium"}, } wallet_state = { "inventory_state": "balanced", "rebalance_needed": False, "grid_ready": True, "base_ratio": 0.51, "quote_ratio": 0.49, } strategies = [ {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "grid-1", "strategy_type": "grid_trader", "mode": "off", "account_id": "a1", "state": {}, "config": {}, "report": {"supervision": {"capacity_available": True, "side_capacity": {"buy": True, "sell": True}, "inventory_pressure": "balanced", "degraded": False}}}, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {}, "report": {"supervision": {"capacity_available": True, "trend_strength": 0.58, "inventory_pressure": "balanced", "degraded": False}}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "act" assert decision.action == "replace_with_grid" assert decision.target_strategy == "grid-1" def test_make_decision_replaces_rebalancer_with_trend_when_breakout_is_still_strong(): concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"} narrative = { "stance": "constructive_bullish", "confidence": 0.84, "opportunity_map": {"continuation": 0.82, "mean_reversion": 0.08, "reversal": 0.03, "wait": 0.07}, "scoped_state": { "micro": {"impulse": "up", "trend_bias": "bullish", "location": "near_upper_band", "reversal_risk": "low"}, "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"}, "macro": {"bias": "bullish"}, }, "cross_scope_summary": {"alignment": "micro_meso_macro_aligned", "friction": "low"}, } wallet_state = { "inventory_state": "base_heavy", "rebalance_needed": True, "grid_ready": False, "base_ratio": 0.74, "quote_ratio": 0.26, } strategies = [ {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "active", "account_id": "a1", "state": {}, "config": {}}, {"id": "grid-1", "strategy_type": "grid_trader", "mode": "off", "account_id": "a1", "state": {}, "config": {}, "report": {"supervision": {"capacity_available": True, "side_capacity": {"buy": True, "sell": True}, "inventory_pressure": "balanced", "degraded": False}}}, {"id": "trend-1", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {}, "report": {"supervision": {"capacity_available": True, "trend_strength": 0.92, "inventory_pressure": "balanced", "degraded": False}}}, ] decision = make_decision(concern=concern, narrative_payload=narrative, wallet_state=wallet_state, strategies=strategies) assert decision.mode == "observe" assert decision.action == "keep_rebalancer" assert decision.target_strategy == "protect-1"