| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408 |
- 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_depleted_base_side():
- 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"] == "depleted_base_side"
- 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_assess_wallet_state_marks_one_sided_wallet_as_depleted_quote_side():
- concern = {"account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- account_info = {
- "balances": [
- {"asset_code": "XRP", "available": 1000},
- {"asset_code": "USD", "available": 1},
- ]
- }
- wallet = assess_wallet_state(account_info=account_info, concern=concern, price=2.0)
- assert wallet["inventory_state"] == "depleted_quote_side"
- assert wallet["rebalance_needed"] is True
- assert wallet["grid_ready"] is False
- 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_score_strategy_fit_rewards_trend_when_breakout_is_confirmed():
- strategy = normalize_strategy_snapshot({
- "id": "trend-1",
- "strategy_type": "trend_follower",
- "mode": "off",
- "account_id": "a1",
- "state": {},
- "config": {},
- })
- base_narrative = {
- "stance": "constructive_bullish",
- "opportunity_map": {"continuation": 0.72, "mean_reversion": 0.08, "reversal": 0.05, "wait": 0.15},
- "grid_breakout_pressure": {"phase": "developing"},
- }
- confirmed_narrative = {
- **base_narrative,
- "grid_breakout_pressure": {"phase": "confirmed", "persistent": True},
- }
- wallet_state = {"inventory_state": "balanced", "rebalance_needed": False}
- base_fit = score_strategy_fit(strategy=strategy, narrative=base_narrative, wallet_state=wallet_state)
- confirmed_fit = score_strategy_fit(strategy=strategy, narrative=confirmed_narrative, wallet_state=wallet_state)
- assert confirmed_fit["score"] > base_fit["score"]
- assert any("confirmed breakout" in reason for reason in confirmed_fit["reasons"])
- def test_score_strategy_fit_prefers_matching_trade_side():
- bullish_narrative = {
- "stance": "constructive_bullish",
- "opportunity_map": {"continuation": 0.75, "mean_reversion": 0.08, "reversal": 0.05, "wait": 0.12},
- "grid_breakout_pressure": {"phase": "confirmed", "persistent": True, "micro_bias": "bullish", "meso_bias": "bullish"},
- }
- bearish_narrative = {
- "stance": "constructive_bearish",
- "opportunity_map": {"continuation": 0.75, "mean_reversion": 0.08, "reversal": 0.05, "wait": 0.12},
- "grid_breakout_pressure": {"phase": "confirmed", "persistent": True, "micro_bias": "bearish", "meso_bias": "bearish"},
- }
- wallet_state = {"inventory_state": "balanced", "rebalance_needed": False}
- buy_strategy = normalize_strategy_snapshot({"id": "trend-long", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {"trade_side": "buy"}})
- sell_strategy = normalize_strategy_snapshot({"id": "trend-short", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {"trade_side": "sell"}})
- buy_fit = score_strategy_fit(strategy=buy_strategy, narrative=bullish_narrative, wallet_state=wallet_state)
- sell_fit = score_strategy_fit(strategy=sell_strategy, narrative=bullish_narrative, wallet_state=wallet_state)
- assert buy_fit["score"] > sell_fit["score"]
- buy_fit_bear = score_strategy_fit(strategy=buy_strategy, narrative=bearish_narrative, wallet_state=wallet_state)
- sell_fit_bear = score_strategy_fit(strategy=sell_strategy, narrative=bearish_narrative, wallet_state=wallet_state)
- assert sell_fit_bear["score"] > buy_fit_bear["score"]
- 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"
- assert decision.payload["grid_breakout_pressure"]["phase"] == "confirmed"
- def test_make_decision_exits_grid_early_on_fast_bearish_alignment():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "generated_at": "2026-04-18T20:15:00+00:00",
- "stance": "constructive_bearish",
- "confidence": 0.84,
- "opportunity_map": {"continuation": 0.8, "mean_reversion": 0.05, "reversal": 0.05, "wait": 0.1},
- "features_by_timeframe": {
- "1m": {"raw": {"price": 96.0, "atr_percent": 0.22, "rsi": 28.0, "macd_histogram": -0.02, "vwap": 97.2, "ema_fast": 96.8, "ema_slow": 97.6, "sma_long": 98.0, "bands": {"bollinger": {"middle": 97.0, "upper": 98.0, "lower": 95.5}}}},
- },
- "scoped_state": {
- "micro": {"impulse": "down", "trend_bias": "bearish", "location": "near_lower_band", "reversal_risk": "low"},
- "meso": {"structure": "trend_continuation", "momentum_bias": "bearish"},
- "macro": {"bias": "bearish"},
- },
- "cross_scope_summary": {"alignment": "micro_meso_macro_aligned", "friction": "low"},
- }
- wallet_state = {
- "inventory_state": "balanced",
- "rebalance_needed": False,
- "grid_ready": True,
- "base_ratio": 0.5,
- "quote_ratio": 0.5,
- }
- 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": {"trade_side": "sell"}},
- {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}},
- ]
- history_window = {
- "window_seconds": 1800,
- "recent_states": [
- {"created_at": "2026-04-18T19:55:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":100.0}}}}'},
- {"created_at": "2026-04-18T20:00:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":99.2}}}}'},
- {"created_at": "2026-04-18T20:05:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":97.8}}}}'},
- {"created_at": "2026-04-18T20:10:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":96.8}}}}'},
- ],
- }
- decision = make_decision(
- concern=concern,
- narrative_payload=narrative,
- wallet_state=wallet_state,
- strategies=strategies,
- history_window=history_window,
- )
- assert decision.mode == "act"
- assert decision.action == "replace_with_trend_follower"
- assert decision.target_strategy == "trend-1"
- assert decision.payload["decision_audit"]["rapid_downside_pressure"] is True
- def test_make_decision_prefers_exposure_protector_when_downside_hits_skewed_wallet():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "generated_at": "2026-04-18T20:15:00+00:00",
- "stance": "constructive_bearish",
- "confidence": 0.84,
- "opportunity_map": {"continuation": 0.8, "mean_reversion": 0.05, "reversal": 0.05, "wait": 0.1},
- "features_by_timeframe": {
- "1m": {"raw": {"price": 96.0, "atr_percent": 0.22, "rsi": 28.0, "macd_histogram": -0.02, "vwap": 97.2, "ema_fast": 96.8, "ema_slow": 97.6, "sma_long": 98.0}},
- },
- "scoped_state": {
- "micro": {"impulse": "down", "trend_bias": "bearish", "location": "near_lower_band", "reversal_risk": "low"},
- "meso": {"structure": "trend_continuation", "momentum_bias": "bearish"},
- "macro": {"bias": "bearish"},
- },
- "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.8,
- "quote_ratio": 0.2,
- }
- 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": {"trade_side": "sell"}},
- {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}},
- ]
- history_window = {
- "window_seconds": 1800,
- "recent_states": [
- {"created_at": "2026-04-18T19:55:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":100.0}}}}'},
- {"created_at": "2026-04-18T20:00:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":99.2}}}}'},
- {"created_at": "2026-04-18T20:05:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":97.8}}}}'},
- {"created_at": "2026-04-18T20:10:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":96.8}}}}'},
- ],
- }
- decision = make_decision(
- concern=concern,
- narrative_payload=narrative,
- wallet_state=wallet_state,
- strategies=strategies,
- history_window=history_window,
- )
- assert decision.mode == "act"
- assert decision.action == "replace_with_exposure_protector"
- assert decision.target_strategy == "protect-1"
- def test_make_decision_prefers_exposure_protector_when_upside_hits_skewed_wallet():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "generated_at": "2026-04-18T20:15:00+00:00",
- "stance": "constructive_bullish",
- "confidence": 0.84,
- "opportunity_map": {"continuation": 0.8, "mean_reversion": 0.05, "reversal": 0.05, "wait": 0.1},
- "features_by_timeframe": {
- "1m": {"raw": {"price": 104.0, "atr_percent": 0.22, "rsi": 72.0, "macd_histogram": 0.02, "vwap": 102.8, "ema_fast": 103.2, "ema_slow": 102.4, "sma_long": 102.0}},
- },
- "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": "quote_heavy",
- "rebalance_needed": True,
- "grid_ready": False,
- "base_ratio": 0.2,
- "quote_ratio": 0.8,
- }
- 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": {"trade_side": "buy"}},
- {"id": "protect-1", "strategy_type": "exposure_protector", "mode": "off", "account_id": "a1", "state": {}, "config": {}},
- ]
- history_window = {
- "window_seconds": 1800,
- "recent_states": [
- {"created_at": "2026-04-18T19:55:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":100.0}}}}'},
- {"created_at": "2026-04-18T20:00:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":100.8}}}}'},
- {"created_at": "2026-04-18T20:05:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":102.2}}}}'},
- {"created_at": "2026-04-18T20:10:00+00:00", "payload_json": '{"features_by_timeframe":{"1m":{"raw":{"price":103.4}}}}'},
- ],
- }
- decision = make_decision(
- concern=concern,
- narrative_payload=narrative,
- wallet_state=wallet_state,
- strategies=strategies,
- history_window=history_window,
- )
- assert decision.mode == "act"
- assert decision.action == "replace_with_exposure_protector"
- assert decision.target_strategy == "protect-1"
- def test_make_decision_targets_the_trade_side_that_matches_direction():
- 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-long", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {"trade_side": "buy"}},
- {"id": "trend-short", "strategy_type": "trend_follower", "mode": "off", "account_id": "a1", "state": {}, "config": {"trade_side": "sell"}},
- ]
- 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-long"
- def test_make_decision_marks_breakout_as_developing_under_partial_alignment():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "stance": "cautious_bullish",
- "confidence": 0.76,
- "opportunity_map": {"continuation": 0.62, "mean_reversion": 0.12, "reversal": 0.06, "wait": 0.2},
- "scoped_state": {
- "micro": {"impulse": "up", "trend_bias": "bullish", "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": "base_heavy",
- "rebalance_needed": True,
- "grid_ready": False,
- "base_ratio": 0.74,
- "quote_ratio": 0.26,
- }
- 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": "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 == "keep_grid"
- assert decision.payload["grid_breakout_pressure"]["phase"] == "developing"
- assert decision.reason_summary == "breakout pressure is developing, but grid can still work and should not be abandoned yet"
- def test_make_decision_argus_compression_stays_context_only():
- 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", "reversal_risk": "low"},
- "meso": {"structure": "trend_continuation", "momentum_bias": "bullish"},
- "macro": {"bias": "bullish"},
- },
- "cross_scope_summary": {"alignment": "micro_meso_macro_aligned", "friction": "low"},
- "argus_context": {
- "regime": "compression",
- "regime_confidence": 0.72,
- "regime_components": {"compression": 0.81},
- },
- "features_by_timeframe": {"1m": {"raw": {"price": 112.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.action == "keep_grid"
- assert decision.payload["grid_breakout_pressure"]["argus_compression_active"] is True
- assert decision.payload["grid_breakout_pressure"]["phase"] == "confirmed"
- assert decision.payload["argus_decision_context"]["compression_active"] is True
- def test_make_decision_keeps_grid_when_1m_and_5m_trend_is_only_partial_confirmation():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "stance": "constructive_bullish",
- "confidence": 0.91,
- "opportunity_map": {"continuation": 0.9, "mean_reversion": 0.03, "reversal": 0.02, "wait": 0.05},
- "features_by_timeframe": {
- "1m": {
- "raw": {"price": 1.4334},
- "trend": {"alignment": "mixed", "strength": 0.12, "bias_score": 0.49},
- "momentum": {"impulse": "high"},
- },
- "5m": {
- "raw": {"price": 1.4334},
- "trend": {"alignment": "bullish_pullback", "strength": 0.32, "bias_score": 1.30},
- "momentum": {"impulse": "medium"},
- },
- "15m": {
- "raw": {"price": 1.4334},
- "trend": {"alignment": "bullish_pullback", "strength": 0.38, "bias_score": 1.51},
- "momentum": {"impulse": "medium"},
- },
- "1h": {
- "raw": {"price": 1.4312},
- "trend": {"alignment": "fully_bullish", "strength": 0.71, "bias_score": 2.83},
- "momentum": {"impulse": "medium"},
- },
- "4h": {
- "raw": {"price": 1.4308},
- "trend": {"alignment": "fully_bullish", "strength": 1.0, "bias_score": 4.64},
- "momentum": {"impulse": "low"},
- },
- "1d": {
- "raw": {"price": 1.4311},
- "trend": {"alignment": "bearish_pullback", "strength": 0.28, "bias_score": -1.13},
- "momentum": {"impulse": "medium"},
- },
- },
- "scoped_state": {
- "micro": {"impulse": "mixed", "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"},
- "decision_inputs": {
- "structural_direction": "bullish",
- "structural_trend_strength": 0.9,
- "tactical_direction": "bullish",
- "tactical_trend_strength": 0.55,
- "tactical_range_quality": 0.0,
- "tactical_easing": False,
- "micro_location": "upper_half",
- "micro_atr_percent": 0.0606,
- "micro_bollinger_width_pct": 0.2976,
- },
- }
- wallet_state = {
- "inventory_state": "base_heavy",
- "rebalance_needed": True,
- "grid_ready": False,
- "base_ratio": 0.7481,
- "quote_ratio": 0.2519,
- }
- strategies = [
- {"id": "grid-1", "strategy_type": "grid_trader", "mode": "active", "account_id": "a1", "state": {"center_price": 1.4293}, "config": {"grid_step_pct": 0.0125}},
- {"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 == "keep_grid"
- assert decision.target_strategy == "grid-1"
- assert decision.payload["grid_switch_tradeoff"]["short_term_trend_score"] < 0.68
- def test_make_decision_promotes_developing_breakout_from_time_window_memory():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "generated_at": "2026-04-18T20:15:00+00:00",
- "stance": "cautious_bullish",
- "confidence": 0.76,
- "opportunity_map": {"continuation": 0.62, "mean_reversion": 0.12, "reversal": 0.06, "wait": 0.2},
- "scoped_state": {
- "micro": {"impulse": "up", "trend_bias": "bullish", "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": "base_heavy",
- "rebalance_needed": True,
- "grid_ready": False,
- "base_ratio": 0.74,
- "quote_ratio": 0.26,
- }
- 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": "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": {}},
- ]
- history_window = {
- "window_seconds": 15 * 60,
- "recent_states": [
- {
- "created_at": "2026-04-18T20:06:00+00:00",
- "payload_json": '{"scoped_state":{"micro":{"impulse":"up","trend_bias":"bullish"},"meso":{"structure":"trend_continuation","momentum_bias":"bullish"},"macro":{"bias":"bullish"}},"cross_scope_summary":{"alignment":"partial_alignment","friction":"medium"}}',
- },
- {
- "created_at": "2026-04-18T20:10:30+00:00",
- "payload_json": '{"scoped_state":{"micro":{"impulse":"up","trend_bias":"bullish"},"meso":{"structure":"trend_continuation","momentum_bias":"bullish"},"macro":{"bias":"bullish"}},"cross_scope_summary":{"alignment":"partial_alignment","friction":"medium"}}',
- },
- ],
- }
- decision = make_decision(
- concern=concern,
- narrative_payload=narrative,
- wallet_state=wallet_state,
- strategies=strategies,
- history_window=history_window,
- )
- assert decision.payload["grid_breakout_pressure"]["phase"] == "confirmed"
- assert decision.payload["grid_breakout_pressure"]["time_window_memory"]["promoted_to_confirmed"] is True
- assert decision.payload["grid_breakout_pressure"]["time_window_memory"]["same_direction_seconds"] >= 540
- 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_replaces_grid_when_next_sell_is_close_but_confirmed_trend_handoff_is_ready():
- 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,
- "center_price": 1.24,
- "orders": [
- {"side": "sell", "status": "open", "price": "1.43956", "amount": "7"},
- {"side": "buy", "status": "open", "price": "1.42523", "amount": "7"},
- ],
- },
- "config": {"grid_step_pct": 0.05},
- },
- {"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 == "replace_with_trend_follower"
- assert decision.target_strategy == "trend-1"
- assert decision.payload["grid_fill_context"]["near_fill_side"] == "sell"
- def test_make_decision_replaces_grid_when_time_promoted_confirmation_clears_lower_handoff_threshold():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "generated_at": "2026-04-18T20:15:00+00:00",
- "stance": "breakout_watch",
- "confidence": 0.78,
- "opportunity_map": {"continuation": 0.7, "mean_reversion": 0.1, "reversal": 0.04, "wait": 0.16},
- "features_by_timeframe": {
- "1m": {"raw": {"price": 111.0, "atr_percent": 0.35}},
- },
- "scoped_state": {
- "micro": {"impulse": "up", "trend_bias": "bullish", "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": "base_heavy",
- "rebalance_needed": True,
- "grid_ready": False,
- "base_ratio": 0.66,
- "quote_ratio": 0.34,
- }
- strategies = [
- {
- "id": "grid-1",
- "strategy_type": "grid_trader",
- "mode": "active",
- "account_id": "a1",
- "market_symbol": "xrpusd",
- "state": {"last_price": 111.0, "center_price": 100.0},
- "config": {"grid_step_pct": 0.05},
- },
- {"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": {}},
- ]
- history_window = {
- "window_seconds": 15 * 60,
- "recent_states": [
- {
- "created_at": "2026-04-18T20:06:00+00:00",
- "payload_json": '{"scoped_state":{"micro":{"impulse":"up","trend_bias":"bullish"},"meso":{"structure":"trend_continuation","momentum_bias":"bullish"},"macro":{"bias":"bullish"}},"cross_scope_summary":{"alignment":"partial_alignment","friction":"medium"}}',
- },
- {
- "created_at": "2026-04-18T20:10:30+00:00",
- "payload_json": '{"scoped_state":{"micro":{"impulse":"up","trend_bias":"bullish"},"meso":{"structure":"trend_continuation","momentum_bias":"bullish"},"macro":{"bias":"bullish"}},"cross_scope_summary":{"alignment":"partial_alignment","friction":"medium"}}',
- },
- ],
- }
- decision = make_decision(
- concern=concern,
- narrative_payload=narrative,
- wallet_state=wallet_state,
- strategies=strategies,
- history_window=history_window,
- )
- assert decision.action == "replace_with_trend_follower"
- assert decision.target_strategy == "trend-1"
- assert decision.payload["grid_breakout_pressure"]["time_window_memory"]["promoted_to_confirmed"] is True
- 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_strong_directional_regime():
- 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": "balanced",
- "rebalance_needed": False,
- "grid_ready": True,
- "base_ratio": 0.52,
- "quote_ratio": 0.48,
- }
- 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 == "act"
- assert decision.action == "replace_with_exposure_protector"
- assert decision.target_strategy == "protect-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 == "act"
- assert decision.action == "replace_with_exposure_protector"
- assert decision.target_strategy == "protect-1"
- def test_make_decision_replaces_trend_with_rebalancer_when_1m_and_5m_dislocate_from_higher_trends():
- 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.6, "mean_reversion": 0.12, "reversal": 0.08, "wait": 0.2},
- "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": "partial_alignment", "friction": "medium"},
- "features_by_timeframe": {
- "1m": {"trend": {"bias_score": 0.82, "alignment": "fully_bullish"}, "momentum": {"impulse": "up"}},
- "5m": {"trend": {"bias_score": -0.74, "alignment": "fully_bearish"}, "momentum": {"impulse": "down"}},
- "15m": {"trend": {"bias_score": 0.66, "alignment": "fully_bullish"}, "momentum": {"impulse": "up"}},
- "1h": {"trend": {"bias_score": 0.7, "alignment": "fully_bullish"}, "momentum": {"impulse": "up"}},
- },
- }
- 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 == "act"
- assert decision.action == "replace_with_exposure_protector"
- assert decision.target_strategy == "protect-1"
- def test_make_decision_replaces_trend_with_rebalancer_when_short_tape_is_mixed_and_inventory_is_base_heavy():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "stance": "constructive_bullish",
- "confidence": 0.77,
- "opportunity_map": {"continuation": 0.61, "mean_reversion": 0.12, "reversal": 0.08, "wait": 0.19},
- "scoped_state": {
- "micro": {"impulse": "mixed", "trend_bias": "mixed", "location": "lower_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": {"trend": {"bias_score": -0.12, "alignment": "mixed"}, "momentum": {"impulse": "medium"}},
- "5m": {"trend": {"bias_score": 0.88, "alignment": "bullish_pullback"}, "momentum": {"impulse": "low"}},
- "15m": {"trend": {"bias_score": 0.95, "alignment": "bullish_pullback"}, "momentum": {"impulse": "low"}},
- "1h": {"trend": {"bias_score": 2.6, "alignment": "fully_bullish"}, "momentum": {"impulse": "medium"}},
- },
- }
- wallet_state = {
- "inventory_state": "depleted_quote_side",
- "rebalance_needed": True,
- "grid_ready": False,
- "base_ratio": 0.99,
- "quote_ratio": 0.01,
- }
- 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 == "act"
- assert decision.action == "replace_with_exposure_protector"
- assert decision.target_strategy == "protect-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 == "act"
- assert decision.action == "replace_with_exposure_protector"
- assert decision.target_strategy == "protect-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_wallet_is_rebalanced_even_if_trend_is_still_hot():
- 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": "balanced",
- "rebalance_needed": False,
- "grid_ready": True,
- "base_ratio": 0.61,
- "quote_ratio": 0.39,
- }
- 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.94, "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"
- assert "rebalanced" in decision.reason_summary
- def test_make_decision_replaces_rebalancer_with_grid_when_within_tolerance_even_before_perfect_balance():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "stance": "neutral_rotational",
- "confidence": 0.7,
- "opportunity_map": {"continuation": 0.18, "mean_reversion": 0.68, "reversal": 0.05, "wait": 0.09},
- }
- wallet_state = {
- "inventory_state": "base_heavy",
- "rebalance_needed": True,
- "grid_ready": True,
- "base_ratio": 0.71,
- "quote_ratio": 0.29,
- }
- 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_grid_when_micro_easing_restores_harvestability():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "stance": "constructive_bearish",
- "confidence": 0.73,
- "opportunity_map": {"continuation": 0.42, "mean_reversion": 0.38, "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": "bearish"},
- "macro": {"bias": "bearish"},
- },
- "cross_scope_summary": {"alignment": "partial_alignment", "friction": "medium"},
- "features_by_timeframe": {
- "1m": {
- "raw": {"price": 1.01, "atr_percent": 0.42},
- "volatility": {"bollinger_width_pct": 1.35},
- },
- },
- }
- wallet_state = {
- "inventory_state": "quote_heavy",
- "rebalance_needed": True,
- "grid_ready": False,
- "base_ratio": 0.29,
- "quote_ratio": 0.71,
- }
- 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": {"grid_step_pct": 0.005}},
- {"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 == "act"
- assert decision.action == "replace_with_grid"
- assert decision.target_strategy == "grid-1"
- assert decision.payload["decision_audit"]["tactical_easing"] is True
- assert decision.payload["decision_audit"]["grid_harvestable_now"] is True
- assert decision.payload["decision_audit"]["rebalancer_release_ready"] is True
- def test_make_decision_replaces_rebalancer_with_grid_near_local_bottom_when_noise_exceeds_grid_size():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "stance": "cautious_bearish",
- "confidence": 0.69,
- "opportunity_map": {"continuation": 0.34, "mean_reversion": 0.44, "reversal": 0.1, "wait": 0.12},
- "scoped_state": {
- "micro": {"impulse": "mixed", "trend_bias": "mixed", "location": "near_lower_band", "reversal_risk": "medium"},
- "meso": {"structure": "trend_continuation", "momentum_bias": "bearish"},
- "macro": {"bias": "bearish"},
- },
- "cross_scope_summary": {"alignment": "partial_alignment", "friction": "medium"},
- "features_by_timeframe": {
- "1m": {
- "raw": {"price": 0.992, "atr_percent": 0.48},
- "volatility": {"bollinger_width_pct": 1.6},
- },
- },
- }
- wallet_state = {
- "inventory_state": "quote_heavy",
- "rebalance_needed": True,
- "grid_ready": False,
- "base_ratio": 0.31,
- "quote_ratio": 0.69,
- }
- 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": {"grid_step_pct": 0.005}},
- ]
- 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"
- assert decision.payload["decision_audit"]["pullback_to_grid_ratio"] is not None
- assert decision.payload["decision_audit"]["pullback_to_grid_ratio"] > 2.0
- def test_make_decision_replaces_grid_with_trend_when_pullbacks_are_too_shallow_for_grid_step():
- concern = {"id": "c1", "account_id": "a1", "market_symbol": "xrpusd", "base_currency": "XRP", "quote_currency": "USD"}
- narrative = {
- "generated_at": "2026-04-19T19:05:00+00:00",
- "stance": "constructive_bullish",
- "confidence": 0.88,
- "opportunity_map": {"continuation": 0.84, "mean_reversion": 0.05, "reversal": 0.03, "wait": 0.08},
- "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": 104.2, "atr_percent": 0.11},
- "volatility": {"bollinger_width_pct": 0.24},
- },
- },
- }
- wallet_state = {
- "inventory_state": "base_heavy",
- "rebalance_needed": True,
- "grid_ready": False,
- "base_ratio": 0.67,
- "quote_ratio": 0.33,
- }
- strategies = [
- {
- "id": "grid-1",
- "strategy_type": "grid_trader",
- "mode": "active",
- "account_id": "a1",
- "market_symbol": "xrpusd",
- "state": {
- "last_price": 104.2,
- "center_price": 100.0,
- "orders": [
- {"side": "sell", "status": "open", "price": "104.7", "amount": "5"},
- {"side": "buy", "status": "open", "price": "103.7", "amount": "5"},
- ],
- },
- "config": {"grid_step_pct": 0.005},
- },
- {"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": {}},
- ]
- history_window = {
- "window_seconds": 15 * 60,
- "recent_states": [
- {
- "created_at": "2026-04-19T18:55:00+00:00",
- "payload_json": '{"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"}}',
- },
- {
- "created_at": "2026-04-19T19:00:30+00:00",
- "payload_json": '{"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"}}',
- },
- ],
- }
- decision = make_decision(
- concern=concern,
- narrative_payload=narrative,
- wallet_state=wallet_state,
- strategies=strategies,
- history_window=history_window,
- )
- assert decision.mode == "act"
- assert decision.action == "replace_with_trend_follower"
- assert decision.target_strategy == "trend-1"
- assert decision.payload["decision_audit"]["pullback_to_grid_ratio"] < 1.0
- assert decision.payload["decision_audit"]["trend_following_pressure"] is True
- 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"
|