|
@@ -235,8 +235,9 @@ def score_strategy_fit(*, strategy: dict[str, Any], narrative: dict[str, Any], w
|
|
|
|
|
|
|
|
strategy_type = strategy["strategy_type"]
|
|
strategy_type = strategy["strategy_type"]
|
|
|
supervision = strategy.get("supervision") if isinstance(strategy.get("supervision"), dict) else {}
|
|
supervision = strategy.get("supervision") if isinstance(strategy.get("supervision"), dict) else {}
|
|
|
- switch_readiness = str(supervision.get("switch_readiness") or "")
|
|
|
|
|
inventory_pressure = str(supervision.get("inventory_pressure") or "")
|
|
inventory_pressure = str(supervision.get("inventory_pressure") or "")
|
|
|
|
|
+ capacity_available = bool(supervision.get("capacity_available"))
|
|
|
|
|
+ side_capacity = supervision.get("side_capacity") if isinstance(supervision.get("side_capacity"), dict) else {}
|
|
|
score = 0.0
|
|
score = 0.0
|
|
|
reasons: list[str] = []
|
|
reasons: list[str] = []
|
|
|
blocks: list[str] = []
|
|
blocks: list[str] = []
|
|
@@ -254,9 +255,12 @@ def score_strategy_fit(*, strategy: dict[str, Any], narrative: dict[str, Any], w
|
|
|
blocks.append(f"wallet is not grid-ready: {inventory_state}")
|
|
blocks.append(f"wallet is not grid-ready: {inventory_state}")
|
|
|
else:
|
|
else:
|
|
|
reasons.append("wallet is balanced enough for two-sided harvesting")
|
|
reasons.append("wallet is balanced enough for two-sided harvesting")
|
|
|
- if switch_readiness == "ready_for_handoff":
|
|
|
|
|
- score -= 0.35
|
|
|
|
|
- blocks.append("grid reports handoff readiness")
|
|
|
|
|
|
|
+ if not capacity_available:
|
|
|
|
|
+ score -= 0.25
|
|
|
|
|
+ blocks.append("grid report shows one-sided capacity")
|
|
|
|
|
+ if side_capacity and not (bool(side_capacity.get("buy", True)) and bool(side_capacity.get("sell", True))):
|
|
|
|
|
+ score -= 0.25
|
|
|
|
|
+ blocks.append("grid side capacity is asymmetric")
|
|
|
elif strategy_type == "trend_follower":
|
|
elif strategy_type == "trend_follower":
|
|
|
score += continuation * 1.9
|
|
score += continuation * 1.9
|
|
|
if stance in {"constructive_bullish", "cautious_bullish", "constructive_bearish", "cautious_bearish"}:
|
|
if stance in {"constructive_bullish", "cautious_bullish", "constructive_bearish", "cautious_bearish"}:
|
|
@@ -271,6 +275,9 @@ def score_strategy_fit(*, strategy: dict[str, Any], narrative: dict[str, Any], w
|
|
|
if inventory_pressure in {"base_heavy", "quote_heavy"}:
|
|
if inventory_pressure in {"base_heavy", "quote_heavy"}:
|
|
|
score -= 0.1
|
|
score -= 0.1
|
|
|
blocks.append("trend report shows rising inventory pressure")
|
|
blocks.append("trend report shows rising inventory pressure")
|
|
|
|
|
+ if not capacity_available:
|
|
|
|
|
+ score -= 0.1
|
|
|
|
|
+ blocks.append("trend strength is below its own capacity threshold")
|
|
|
elif strategy_type == "exposure_protector":
|
|
elif strategy_type == "exposure_protector":
|
|
|
score += reversal * 0.4 + wait * 0.5
|
|
score += reversal * 0.4 + wait * 0.5
|
|
|
if wallet_state.get("rebalance_needed"):
|
|
if wallet_state.get("rebalance_needed"):
|
|
@@ -366,24 +373,26 @@ def _trend_cooling_edge(narrative_payload: dict[str, Any], wallet_state: dict[st
|
|
|
micro_impulse = str(micro.get("impulse") or "mixed")
|
|
micro_impulse = str(micro.get("impulse") or "mixed")
|
|
|
micro_bias = str(micro.get("trend_bias") or "mixed")
|
|
micro_bias = str(micro.get("trend_bias") or "mixed")
|
|
|
micro_location = str(micro.get("location") or "unknown")
|
|
micro_location = str(micro.get("location") or "unknown")
|
|
|
|
|
+ micro_reversal_risk = str(micro.get("reversal_risk") or "low")
|
|
|
meso_bias = str(meso.get("momentum_bias") or "neutral")
|
|
meso_bias = str(meso.get("momentum_bias") or "neutral")
|
|
|
meso_structure = str(meso.get("structure") or "rotation")
|
|
meso_structure = str(meso.get("structure") or "rotation")
|
|
|
inventory_state = str(wallet_state.get("inventory_state") or "unknown")
|
|
inventory_state = str(wallet_state.get("inventory_state") or "unknown")
|
|
|
|
|
+ early_reversal_warning = micro_reversal_risk in {"medium", "high"}
|
|
|
|
|
|
|
|
bullish_cooling = (
|
|
bullish_cooling = (
|
|
|
inventory_state in {"base_heavy", "critically_unbalanced"}
|
|
inventory_state in {"base_heavy", "critically_unbalanced"}
|
|
|
and meso_structure == "trend_continuation"
|
|
and meso_structure == "trend_continuation"
|
|
|
and meso_bias == "bullish"
|
|
and meso_bias == "bullish"
|
|
|
- and micro_impulse == "mixed"
|
|
|
|
|
- and micro_bias in {"mixed", "bearish"}
|
|
|
|
|
|
|
+ and (micro_impulse == "mixed" or early_reversal_warning)
|
|
|
|
|
+ and micro_bias in {"mixed", "bearish", "bullish"}
|
|
|
and micro_location in {"near_upper_band", "upper_half", "centered"}
|
|
and micro_location in {"near_upper_band", "upper_half", "centered"}
|
|
|
)
|
|
)
|
|
|
bearish_cooling = (
|
|
bearish_cooling = (
|
|
|
inventory_state in {"quote_heavy", "critically_unbalanced"}
|
|
inventory_state in {"quote_heavy", "critically_unbalanced"}
|
|
|
and meso_structure == "trend_continuation"
|
|
and meso_structure == "trend_continuation"
|
|
|
and meso_bias == "bearish"
|
|
and meso_bias == "bearish"
|
|
|
- and micro_impulse == "mixed"
|
|
|
|
|
- and micro_bias in {"mixed", "bullish"}
|
|
|
|
|
|
|
+ and (micro_impulse == "mixed" or early_reversal_warning)
|
|
|
|
|
+ and micro_bias in {"mixed", "bullish", "bearish"}
|
|
|
and micro_location in {"near_lower_band", "lower_half", "centered"}
|
|
and micro_location in {"near_lower_band", "lower_half", "centered"}
|
|
|
)
|
|
)
|
|
|
return bullish_cooling or bearish_cooling
|
|
return bullish_cooling or bearish_cooling
|
|
@@ -446,6 +455,16 @@ def make_decision(*, concern: dict[str, Any], narrative_payload: dict[str, Any],
|
|
|
stance = str(narrative_payload.get("stance") or "neutral_rotational")
|
|
stance = str(narrative_payload.get("stance") or "neutral_rotational")
|
|
|
inventory_state = str(wallet_state.get("inventory_state") or "unknown")
|
|
inventory_state = str(wallet_state.get("inventory_state") or "unknown")
|
|
|
breakout = _grid_breakout_pressure(narrative_payload)
|
|
breakout = _grid_breakout_pressure(narrative_payload)
|
|
|
|
|
+ scoped = narrative_payload.get("scoped_state") if isinstance(narrative_payload.get("scoped_state"), dict) else {}
|
|
|
|
|
+ micro = scoped.get("micro") if isinstance(scoped.get("micro"), dict) else {}
|
|
|
|
|
+ micro_impulse = str(micro.get("impulse") or "mixed")
|
|
|
|
|
+ micro_bias = str(micro.get("trend_bias") or "mixed")
|
|
|
|
|
+ micro_reversal_risk = str(micro.get("reversal_risk") or "low")
|
|
|
|
|
+ bullish_micro_clear = micro_impulse == "up" and micro_bias == "bullish" and micro_reversal_risk != "high"
|
|
|
|
|
+ bearish_micro_clear = micro_impulse == "down" and micro_bias == "bearish" and micro_reversal_risk != "high"
|
|
|
|
|
+ stance_is_bullish = "bullish" in stance
|
|
|
|
|
+ stance_is_bearish = "bearish" in stance
|
|
|
|
|
+ directional_micro_clear = bullish_micro_clear if stance_is_bullish else bearish_micro_clear if stance_is_bearish else False
|
|
|
grid_fill = _grid_fill_proximity(current_primary, narrative_payload) if current_primary and current_primary["strategy_type"] == "grid_trader" else {"near_fill": False}
|
|
grid_fill = _grid_fill_proximity(current_primary, narrative_payload) if current_primary and current_primary["strategy_type"] == "grid_trader" else {"near_fill": False}
|
|
|
|
|
|
|
|
action = "hold"
|
|
action = "hold"
|
|
@@ -463,7 +482,7 @@ def make_decision(*, concern: dict[str, Any], narrative_payload: dict[str, Any],
|
|
|
if severe_imbalance and breakout["persistent"]:
|
|
if severe_imbalance and breakout["persistent"]:
|
|
|
reasons.append("grid imbalance now coincides with persistent breakout pressure")
|
|
reasons.append("grid imbalance now coincides with persistent breakout pressure")
|
|
|
directional_inventory = _inventory_breakout_is_directionally_compatible(inventory_state, breakout)
|
|
directional_inventory = _inventory_breakout_is_directionally_compatible(inventory_state, breakout)
|
|
|
- if trend and trend["score"] > 0.45 and (
|
|
|
|
|
|
|
+ if trend and trend["score"] > 0.45 and directional_micro_clear and (
|
|
|
not wallet_state.get("rebalance_needed")
|
|
not wallet_state.get("rebalance_needed")
|
|
|
or directional_inventory
|
|
or directional_inventory
|
|
|
or not rebalance
|
|
or not rebalance
|
|
@@ -495,7 +514,7 @@ def make_decision(*, concern: dict[str, Any], narrative_payload: dict[str, Any],
|
|
|
elif not grid_friendly_stance and breakout["persistent"]:
|
|
elif not grid_friendly_stance and breakout["persistent"]:
|
|
|
reasons.append("grid should yield because directional pressure is persistent across scopes")
|
|
reasons.append("grid should yield because directional pressure is persistent across scopes")
|
|
|
trend = next((r for r in ranked if r["strategy_type"] == "trend_follower"), None)
|
|
trend = next((r for r in ranked if r["strategy_type"] == "trend_follower"), None)
|
|
|
- if trend and trend["score"] > 0.45:
|
|
|
|
|
|
|
+ if trend and trend["score"] > 0.45 and directional_micro_clear:
|
|
|
action = "replace_with_trend_follower"
|
|
action = "replace_with_trend_follower"
|
|
|
target_strategy = trend["strategy_id"]
|
|
target_strategy = trend["strategy_id"]
|
|
|
mode = "act"
|
|
mode = "act"
|
|
@@ -503,7 +522,7 @@ def make_decision(*, concern: dict[str, Any], narrative_payload: dict[str, Any],
|
|
|
action = "keep_grid"
|
|
action = "keep_grid"
|
|
|
target_strategy = current_primary["id"]
|
|
target_strategy = current_primary["id"]
|
|
|
mode = "warn"
|
|
mode = "warn"
|
|
|
- blocks.append("directional pressure is rising but no strong trend handoff is ready")
|
|
|
|
|
|
|
+ blocks.append("directional pressure is rising but the micro layer is not clear enough for a trend handoff")
|
|
|
else:
|
|
else:
|
|
|
action = "keep_grid"
|
|
action = "keep_grid"
|
|
|
mode = "observe"
|
|
mode = "observe"
|
|
@@ -544,31 +563,42 @@ def make_decision(*, concern: dict[str, Any], narrative_payload: dict[str, Any],
|
|
|
mode = "observe"
|
|
mode = "observe"
|
|
|
reasons.append("trend strategy still fits the directional narrative")
|
|
reasons.append("trend strategy still fits the directional narrative")
|
|
|
elif current_primary and current_primary["strategy_type"] == "exposure_protector":
|
|
elif current_primary and current_primary["strategy_type"] == "exposure_protector":
|
|
|
- if wallet_state.get("grid_ready") and stance == "neutral_rotational":
|
|
|
|
|
- if grid and grid["score"] >= 0.5:
|
|
|
|
|
|
|
+ trend = next((r for r in ranked if r["strategy_type"] == "trend_follower"), None)
|
|
|
|
|
+ trend_is_sustained = bool(trend and trend["score"] > 0.85 and breakout["persistent"])
|
|
|
|
|
+ if trend_is_sustained and stance in {"constructive_bullish", "cautious_bullish", "constructive_bearish", "cautious_bearish"}:
|
|
|
|
|
+ action = "replace_with_trend_follower"
|
|
|
|
|
+ target_strategy = trend["strategy_id"]
|
|
|
|
|
+ mode = "act"
|
|
|
|
|
+ reasons.append("trend is sustained strongly enough that the rebalancer should hand back to trend immediately")
|
|
|
|
|
+ elif str(wallet_state.get("inventory_state") or "").lower() == "balanced":
|
|
|
|
|
+ if grid:
|
|
|
action = "replace_with_grid"
|
|
action = "replace_with_grid"
|
|
|
target_strategy = grid["strategy_id"]
|
|
target_strategy = grid["strategy_id"]
|
|
|
mode = "act"
|
|
mode = "act"
|
|
|
- reasons.append("rebalance is complete and rotational conditions support grid again")
|
|
|
|
|
|
|
+ reasons.append("wallet is balanced, so grid should run until a strong trend is detected")
|
|
|
else:
|
|
else:
|
|
|
action = "keep_rebalancer"
|
|
action = "keep_rebalancer"
|
|
|
mode = "observe"
|
|
mode = "observe"
|
|
|
- blocks.append("wallet is ready but grid fit is still too weak")
|
|
|
|
|
- elif not wallet_state.get("rebalance_needed") and stance in {"constructive_bullish", "cautious_bullish", "constructive_bearish", "cautious_bearish"}:
|
|
|
|
|
- trend = next((r for r in ranked if r["strategy_type"] == "trend_follower"), None)
|
|
|
|
|
- if trend and trend["score"] > 0.45:
|
|
|
|
|
- action = "replace_with_trend_follower"
|
|
|
|
|
- target_strategy = trend["strategy_id"]
|
|
|
|
|
|
|
+ blocks.append("wallet is balanced but no grid candidate is available")
|
|
|
|
|
+ elif wallet_state.get("grid_ready") and stance == "neutral_rotational":
|
|
|
|
|
+ if grid and grid["score"] >= 0.5:
|
|
|
|
|
+ action = "replace_with_grid"
|
|
|
|
|
+ target_strategy = grid["strategy_id"]
|
|
|
mode = "act"
|
|
mode = "act"
|
|
|
- reasons.append("rebalance is done and directional conditions favor trend capture")
|
|
|
|
|
|
|
+ reasons.append("rebalance is complete and rotational conditions support grid again")
|
|
|
else:
|
|
else:
|
|
|
action = "keep_rebalancer"
|
|
action = "keep_rebalancer"
|
|
|
mode = "observe"
|
|
mode = "observe"
|
|
|
- blocks.append("trend candidate is not strong enough yet")
|
|
|
|
|
|
|
+ blocks.append("wallet is ready but grid fit is still too weak")
|
|
|
|
|
+ elif grid and grid["score"] >= 0.5:
|
|
|
|
|
+ action = "replace_with_grid"
|
|
|
|
|
+ target_strategy = grid["strategy_id"]
|
|
|
|
|
+ mode = "act"
|
|
|
|
|
+ reasons.append("trend is directional but not yet sustained, so grid can resume first")
|
|
|
else:
|
|
else:
|
|
|
action = "keep_rebalancer"
|
|
action = "keep_rebalancer"
|
|
|
mode = "observe"
|
|
mode = "observe"
|
|
|
- reasons.append("rebalancing should continue until wallet posture improves")
|
|
|
|
|
|
|
+ blocks.append("trend candidate is not strong enough yet and grid fit is not ready")
|
|
|
else:
|
|
else:
|
|
|
if best and best["score"] >= 0.55:
|
|
if best and best["score"] >= 0.55:
|
|
|
action = f"enable_{best['strategy_type']}"
|
|
action = f"enable_{best['strategy_type']}"
|