@@ -437,21 +437,25 @@ def evaluate_crypto_trend_rotation(ctx: StrategyContext) -> StrategyDecision:
437437def evaluate_crypto_equity_combo (ctx : StrategyContext ) -> StrategyDecision :
438438 from crypto_strategies .strategies .crypto_equity_combo import compute_signals
439439
440+ legacy_core , legacy_rotation = _load_legacy_modules ()
440441 config = _merge_runtime_config (ctx , crypto_equity_combo_manifest .default_config )
441442 prices = _require_market_data (ctx , "market_prices" )
442443 indicators_map = _require_market_data (ctx , "derived_indicators" )
443444 benchmark_snapshot = _require_market_data (ctx , "benchmark_snapshot" )
444445 portfolio = _resolve_portfolio_snapshot (ctx )
446+ account_metrics = _resolve_account_metrics (ctx )
445447 universe_snapshot = list (_require_market_data (ctx , "universe_snapshot" ))
448+ state = dict (ctx .state )
446449 translator = _resolve_translator (config )
450+ get_symbol_trade_state_fn , set_symbol_trade_state_fn = _resolve_state_helpers (config )
447451
448452 weights , signal_desc , has_cash_residual , status_desc , metadata = compute_signals (
449453 prices = prices ,
450454 indicators_map = indicators_map ,
451455 universe_snapshot = universe_snapshot ,
452456 benchmark_snapshot = benchmark_snapshot ,
453457 portfolio = portfolio ,
454- state = dict ( ctx . state ) ,
458+ state = state ,
455459 translator = translator ,
456460 btc_weight = float (config .get ("btc_weight" , 0.30 )),
457461 trend_weight = float (config .get ("trend_weight" , 0.70 )),
@@ -489,11 +493,69 @@ def evaluate_crypto_equity_combo(ctx: StrategyContext) -> StrategyDecision:
489493 )
490494 )
491495
496+ btc_target_ratio = float (weights .get ("BTCUSDT" , 0.0 ))
497+ trend_target_ratio = float (sum (weight for symbol , weight in weights .items () if symbol != "BTCUSDT" ))
498+ total_equity = float (account_metrics ["total_equity" ])
499+ cash_usdt = max (0.0 , float (account_metrics ["cash_usdt" ]))
500+ trend_value = max (0.0 , float (account_metrics .get ("trend_value" , 0.0 )))
501+ dca_value = max (0.0 , float (account_metrics .get ("dca_value" , 0.0 )))
502+ trend_usdt_pool = max (0.0 , min (cash_usdt , (total_equity * trend_target_ratio ) - trend_value ))
503+ remaining_cash = max (0.0 , cash_usdt - trend_usdt_pool )
504+ dca_usdt_pool = max (0.0 , min (remaining_cash , (total_equity * btc_target_ratio ) - dca_value ))
505+ btc_base_order_usdt = float (legacy_core .get_dynamic_btc_base_order (total_equity ))
506+ trend_metadata = metadata .get ("trend_leg" , {}) if isinstance (metadata .get ("trend_leg" ), Mapping ) else {}
507+ selected_candidates = {
508+ str (symbol ): {
509+ "weight" : float (payload .get ("weight" , 0.0 )),
510+ "relative_score" : float (payload .get ("relative_score" , 0.0 )),
511+ "abs_momentum" : float (payload .get ("abs_momentum" , 0.0 )),
512+ }
513+ for symbol , payload in dict (trend_metadata .get ("rotation_candidates" , {})).items ()
514+ }
515+ runtime_trend_universe = {symbol : {"base_asset" : symbol [:- 4 ]} for symbol in universe_snapshot }
516+ sell_reasons : dict [str , str ] = {}
517+ atr_multiplier = float (config .get ("atr_multiplier" , 2.5 ))
518+ for symbol in universe_snapshot :
519+ curr_price = prices .get (symbol )
520+ if curr_price is None :
521+ continue
522+ reason = legacy_rotation .get_trend_sell_reason (
523+ state ,
524+ symbol ,
525+ curr_price ,
526+ indicators_map .get (symbol ),
527+ selected_candidates ,
528+ atr_multiplier ,
529+ get_symbol_trade_state_fn = get_symbol_trade_state_fn ,
530+ set_symbol_trade_state_fn = set_symbol_trade_state_fn ,
531+ translate_fn = translator ,
532+ )
533+ if reason :
534+ sell_reasons [symbol ] = str (reason )
535+
536+ eligible_buy_symbols , planned_trend_buys = legacy_rotation .plan_trend_buys (
537+ state ,
538+ runtime_trend_universe = runtime_trend_universe ,
539+ selected_candidates = selected_candidates ,
540+ trend_indicators = indicators_map ,
541+ prices = prices ,
542+ available_trend_buy_budget = trend_usdt_pool ,
543+ allow_new_trend_entries = bool (config .get ("allow_new_trend_entries" , True )),
544+ get_symbol_trade_state_fn = get_symbol_trade_state_fn ,
545+ allocate_trend_buy_budget_fn = legacy_core .allocate_trend_buy_budget ,
546+ )
547+
492548 budget_intents = (
493549 BudgetIntent (
494- name = "combo_pool" ,
495- amount = 0.0 ,
496- purpose = "combined_allocation" ,
550+ name = "btc_core_dca_pool" ,
551+ symbol = "BTCUSDT" ,
552+ amount = dca_usdt_pool ,
553+ purpose = "btc_core_accumulation" ,
554+ ),
555+ BudgetIntent (
556+ name = "trend_rotation_pool" ,
557+ amount = trend_usdt_pool ,
558+ purpose = "trend_rotation" ,
497559 ),
498560 )
499561
@@ -502,6 +564,8 @@ def evaluate_crypto_equity_combo(ctx: StrategyContext) -> StrategyDecision:
502564 risk_flags += ("regime_off" ,)
503565 if has_cash_residual :
504566 risk_flags += ("cash_residual" ,)
567+ if not selected_candidates :
568+ risk_flags += ("no_trend_candidates" ,)
505569 if not weights :
506570 risk_flags += ("no_positions" ,)
507571
@@ -511,6 +575,24 @@ def evaluate_crypto_equity_combo(ctx: StrategyContext) -> StrategyDecision:
511575 "metadata" : metadata ,
512576 "managed_symbols" : metadata .get ("managed_symbols" , ()),
513577 "profile" : metadata .get ("profile" ),
578+ "trend_pool" : tuple (trend_metadata .get ("trend_pool" , ())),
579+ "rotation_candidates" : selected_candidates ,
580+ "ranking_preview" : tuple (trend_metadata .get ("ranking_preview" , ())),
581+ "rotation_pool_source_version" : trend_metadata .get ("rotation_pool_source_version" ),
582+ "rotation_pool_source_as_of_date" : trend_metadata .get ("rotation_pool_source_as_of_date" ),
583+ "rotation_pool_last_month" : trend_metadata .get ("rotation_pool_last_month" ),
584+ "sell_reasons" : sell_reasons ,
585+ "eligible_buy_symbols" : tuple (eligible_buy_symbols ),
586+ "planned_trend_buys" : {symbol : float (amount ) for symbol , amount in planned_trend_buys .items ()},
587+ "btc_base_order_usdt" : btc_base_order_usdt ,
588+ "btc_target_ratio" : btc_target_ratio ,
589+ "trend_target_ratio" : trend_target_ratio ,
590+ "artifact_contract" : {
591+ "version" : config .get ("artifact_contract_version" ),
592+ "max_age_days" : config .get ("artifact_max_age_days" ),
593+ "acceptable_modes" : tuple (config .get ("artifact_acceptable_modes" , ())),
594+ ** dict (ctx .artifacts .get ("trend_pool_contract" , {})),
595+ },
514596 }
515597
516598 return StrategyDecision (
0 commit comments