미래 데이터를 쓰지 않는 causal 신호/tier와 전기간 복리 포트폴리오 비교로 GT 대비 sim_sized 검증 경로를 정리하고, 일한도·매수 상한·live_buy 스케일을 제거한다. Co-authored-by: Cursor <cursoragent@cursor.com>
607 lines
18 KiB
Python
607 lines
18 KiB
Python
"""
|
||
총자산 대비 GT 모델 매수율(비중) · 보유 현금 한도 · leg tier 배분.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from datetime import datetime
|
||
from pathlib import Path
|
||
from typing import Any
|
||
|
||
from config import (
|
||
GT_BUY_PCT_LARGE_LEG,
|
||
GT_BUY_PCT_SMALL_LEG,
|
||
GT_INITIAL_CASH_KRW,
|
||
GT_LARGE_LEG_TOP_PCT,
|
||
GT_MIN_ORDER_KRW,
|
||
MATCH_GT_TOLERANCE_MIN,
|
||
TRADING_FEE_RATE,
|
||
)
|
||
from deepcoin.matching.load_rules import load_matched_rules
|
||
from deepcoin.paths import MATCHING_FIRE_OUTCOMES, MATCHING_MATCHED_RULES
|
||
|
||
_GT_ALLOC_ANALYSIS_CACHE: dict[str, Any] | None = None
|
||
|
||
|
||
def portfolio_totals(
|
||
cash: float,
|
||
qty: float,
|
||
price: float,
|
||
) -> tuple[float, float, float]:
|
||
"""
|
||
총보유자산·코인평가·가용현금(=총자산-평가액)을 계산합니다.
|
||
|
||
Args:
|
||
cash: 현금.
|
||
qty: 보유 수량.
|
||
price: 평가·체결가.
|
||
|
||
Returns:
|
||
(total_asset_krw, holding_value_krw, cash_krw).
|
||
"""
|
||
holding = qty * price
|
||
total = cash + holding
|
||
return total, holding, cash
|
||
|
||
|
||
def optimal_weight_share(weight: float, weight_sum_remaining: float) -> float:
|
||
"""
|
||
leg 내 남은 매수 비중 대비 이번 체결 최적 매수율(0~1).
|
||
|
||
Args:
|
||
weight: 이번 타점 weight.
|
||
weight_sum_remaining: 동일 leg 남은 매수 weight 합.
|
||
|
||
Returns:
|
||
비중 비율.
|
||
"""
|
||
if weight_sum_remaining > 0:
|
||
return weight / weight_sum_remaining
|
||
return 1.0
|
||
|
||
|
||
def compute_buy_amount_krw(
|
||
cash: float,
|
||
qty: float,
|
||
price: float,
|
||
weight: float,
|
||
weight_sum_remaining: float,
|
||
*,
|
||
asset_pct_scale: float,
|
||
min_order_krw: float = GT_MIN_ORDER_KRW,
|
||
fee_rate: float = TRADING_FEE_RATE,
|
||
) -> float:
|
||
"""
|
||
목표=총보유자산×(최적 매수율×scale), 체결=min(목표, 보유현금/(1+fee)) 로 매수 원화를 산출합니다.
|
||
|
||
보유 현금 = 총보유자산 − 코인평가액(cash 인자).
|
||
|
||
Args:
|
||
cash: 보유 현금(가용 원화).
|
||
qty: 보유 수량.
|
||
price: 체결가.
|
||
weight: 타점 비중.
|
||
weight_sum_remaining: leg 내 남은 매수 weight 합.
|
||
asset_pct_scale: leg·규칙 티어(대형/소형) 스케일.
|
||
min_order_krw: 최소 주문 원화.
|
||
fee_rate: 수수료율.
|
||
|
||
Returns:
|
||
매수 원화(0이면 미체결).
|
||
"""
|
||
if price <= 0:
|
||
return 0.0
|
||
total_asset, _, available_cash = portfolio_totals(cash, qty, price)
|
||
budget = max(available_cash / (1.0 + fee_rate), 0.0)
|
||
opt_rate = optimal_weight_share(weight, weight_sum_remaining) * asset_pct_scale
|
||
target = total_asset * opt_rate
|
||
amount = min(target, budget)
|
||
if budget >= min_order_krw and 0 < amount < min_order_krw:
|
||
amount = min(min_order_krw, budget)
|
||
return round(max(amount, 0.0), 0)
|
||
|
||
|
||
def large_leg_ids_from_past_returns(
|
||
leg_returns: dict[int, float],
|
||
top_pct: float = GT_LARGE_LEG_TOP_PCT,
|
||
) -> set[int]:
|
||
"""
|
||
이미 청산된 leg의 realized return 상위 n% (인과적 tier).
|
||
|
||
Args:
|
||
leg_returns: leg_id → realized return %.
|
||
top_pct: 상위 비율.
|
||
|
||
Returns:
|
||
large leg id set.
|
||
"""
|
||
if not leg_returns:
|
||
return set()
|
||
ranked = sorted(leg_returns.items(), key=lambda x: x[1], reverse=True)
|
||
n = max(1, int(len(ranked) * top_pct + 0.999999))
|
||
return {lid for lid, _ in ranked[:n]}
|
||
|
||
|
||
def top_leg_ids_by_forward_return(
|
||
trades: list[dict[str, Any]],
|
||
top_pct: float = GT_LARGE_LEG_TOP_PCT,
|
||
) -> set[int]:
|
||
"""
|
||
leg별 최대 forward_return 기준 상위 n% leg_id 집합.
|
||
|
||
Args:
|
||
trades: GT trade dict.
|
||
top_pct: 상위 비율(0~1).
|
||
|
||
Returns:
|
||
대형 매수 leg_id set.
|
||
"""
|
||
leg_ret: dict[int, float] = {}
|
||
for t in trades:
|
||
if t.get("action") != "sell":
|
||
continue
|
||
lid = int(t.get("leg_id", 0))
|
||
ret = float(t.get("forward_return_pct") or 0.0)
|
||
leg_ret[lid] = max(leg_ret.get(lid, 0.0), ret)
|
||
if not leg_ret:
|
||
return set()
|
||
ranked = sorted(leg_ret.items(), key=lambda x: x[1], reverse=True)
|
||
n = max(1, int(len(ranked) * top_pct + 0.999999))
|
||
return {lid for lid, _ in ranked[:n]}
|
||
|
||
|
||
def leg_asset_pct_scale(leg_id: int, large_legs: set[int]) -> float:
|
||
"""
|
||
leg 티어에 따른 총자산 대비 매수 스케일.
|
||
|
||
Args:
|
||
leg_id: leg 번호.
|
||
large_legs: 상위 leg 집합.
|
||
|
||
Returns:
|
||
GT_BUY_PCT_LARGE_LEG 또는 GT_BUY_PCT_SMALL_LEG.
|
||
"""
|
||
if leg_id in large_legs:
|
||
return float(GT_BUY_PCT_LARGE_LEG)
|
||
return float(GT_BUY_PCT_SMALL_LEG)
|
||
|
||
|
||
def _parse_dt(dt: str) -> datetime:
|
||
return datetime.fromisoformat(str(dt).replace("Z", "+00:00")[:19])
|
||
|
||
|
||
def nearest_gt_leg_id(
|
||
dt: str,
|
||
gt_trades: list[dict[str, Any]],
|
||
tolerance_min: int = MATCH_GT_TOLERANCE_MIN,
|
||
) -> int | None:
|
||
"""
|
||
시각에 가장 가까운 GT trade의 leg_id (매수 우선).
|
||
|
||
Args:
|
||
dt: 발화 시각.
|
||
gt_trades: GT trades.
|
||
tolerance_min: 허용 분.
|
||
|
||
Returns:
|
||
leg_id 또는 None.
|
||
"""
|
||
if not gt_trades:
|
||
return None
|
||
t0 = _parse_dt(dt)
|
||
best_buy: int | None = None
|
||
best_buy_min = float(tolerance_min) + 1.0
|
||
best_any: int | None = None
|
||
best_any_min = float(tolerance_min) + 1.0
|
||
for t in gt_trades:
|
||
try:
|
||
t1 = _parse_dt(t["dt"])
|
||
except ValueError:
|
||
continue
|
||
delta = abs((t0 - t1).total_seconds()) / 60.0
|
||
if delta > tolerance_min:
|
||
continue
|
||
lid = int(t.get("leg_id", 0))
|
||
if t.get("action") == "buy" and delta < best_buy_min:
|
||
best_buy_min = delta
|
||
best_buy = lid
|
||
if delta < best_any_min:
|
||
best_any_min = delta
|
||
best_any = lid
|
||
return best_buy if best_buy is not None else best_any
|
||
|
||
|
||
_APPROVED_RULES_CACHE: set[str] | None = None
|
||
|
||
|
||
def load_ev_wf_approved_rule_ids(
|
||
matched_path: Path | None = None,
|
||
outcomes_path: Path | None = None,
|
||
) -> set[str]:
|
||
"""
|
||
holdout EV·PF, walk-forward, 수수료 스트레스를 모두 통과한 rule_id.
|
||
|
||
Args:
|
||
matched_path: matched_rules.json.
|
||
outcomes_path: fire_outcomes.csv.
|
||
|
||
Returns:
|
||
통과 rule_id set. 산출 불가 시 monitor_rules 전체 fallback.
|
||
"""
|
||
global _APPROVED_RULES_CACHE
|
||
if _APPROVED_RULES_CACHE is not None:
|
||
return set(_APPROVED_RULES_CACHE)
|
||
|
||
from config import SIM_FEE_STRESS_MULT
|
||
|
||
from deepcoin.matching.select_rules import _rule_metrics, _split_train_valid_holdout
|
||
from deepcoin.matching.simulation import (
|
||
evaluate_go_no_go,
|
||
simulate_live_order_cap,
|
||
walk_forward_by_month,
|
||
walk_forward_summary,
|
||
)
|
||
|
||
mp = matched_path or MATCHING_MATCHED_RULES
|
||
op = outcomes_path or MATCHING_FIRE_OUTCOMES
|
||
matched = load_matched_rules(mp)
|
||
rules = matched.get("monitor_rules") or []
|
||
if not rules or not op.is_file():
|
||
return {r["rule_id"] for r in rules}
|
||
|
||
import pandas as pd
|
||
|
||
from config import MATCH_FEE_RATE
|
||
|
||
outcomes = pd.read_csv(op)
|
||
outcomes["split"] = _split_train_valid_holdout(outcomes)
|
||
wf_sum = walk_forward_summary(walk_forward_by_month(outcomes))
|
||
fee_stress: dict[str, Any] = {}
|
||
for rid in outcomes["rule_id"].unique():
|
||
sub = outcomes[outcomes["rule_id"] == rid]
|
||
from deepcoin.matching.simulation import _fee_adjust_ret
|
||
|
||
adj = _fee_adjust_ret(sub["forward_ret_pct"], SIM_FEE_STRESS_MULT)
|
||
fee_stress[rid] = _rule_metrics(sub.assign(forward_ret_pct=adj))
|
||
monitor_ids = {r["rule_id"] for r in rules}
|
||
live_cap = simulate_live_order_cap(
|
||
outcomes, rule_ids=monitor_ids, holdout_only=True
|
||
)
|
||
go = evaluate_go_no_go(matched, wf_sum, fee_stress, live_cap)
|
||
passed = {c["rule_id"] for c in go.get("checks", []) if c.get("pass")}
|
||
if passed:
|
||
_APPROVED_RULES_CACHE = passed
|
||
return passed
|
||
fallback = monitor_ids
|
||
_APPROVED_RULES_CACHE = fallback
|
||
return fallback
|
||
|
||
|
||
def load_gt_allocation_analysis(
|
||
gt_trades: list[dict[str, Any]] | None = None,
|
||
) -> dict[str, Any]:
|
||
"""
|
||
GT amount_krw 분석 캐시 (tier 권장 pct).
|
||
|
||
Args:
|
||
gt_trades: GT trades. None이면 파일 로드.
|
||
|
||
Returns:
|
||
analyze_gt_buy_allocation 결과.
|
||
"""
|
||
global _GT_ALLOC_ANALYSIS_CACHE
|
||
if _GT_ALLOC_ANALYSIS_CACHE is not None:
|
||
return _GT_ALLOC_ANALYSIS_CACHE
|
||
from deepcoin.ground_truth.gt_allocation_analysis import analyze_gt_buy_allocation
|
||
from deepcoin.paths import resolve_ground_truth_file
|
||
|
||
trades = gt_trades
|
||
if trades is None:
|
||
p = resolve_ground_truth_file()
|
||
if p.is_file():
|
||
trades = json.loads(p.read_text(encoding="utf-8")).get("trades") or []
|
||
if not trades:
|
||
_GT_ALLOC_ANALYSIS_CACHE = {}
|
||
return _GT_ALLOC_ANALYSIS_CACHE
|
||
chron = sorted(trades, key=lambda x: x["dt"])
|
||
if not any(float(t.get("amount_krw") or 0) > 0 for t in chron):
|
||
from deepcoin.ground_truth.ground_truth import allocate_gt_order_amounts
|
||
|
||
allocate_gt_order_amounts(chron)
|
||
_GT_ALLOC_ANALYSIS_CACHE = analyze_gt_buy_allocation(chron)
|
||
return _GT_ALLOC_ANALYSIS_CACHE
|
||
|
||
|
||
def gt_tier_scale_for_trade(
|
||
trade: dict[str, Any],
|
||
gt_trades: list[dict[str, Any]],
|
||
large_legs: set[int],
|
||
*,
|
||
analysis: dict[str, Any] | None = None,
|
||
) -> float:
|
||
"""
|
||
GT leg tier 배분 스케일 (분석 권장값 또는 config).
|
||
|
||
시뮬은 live_buy_asset_pct_scale 대신 GT와 동일 tier 정책을 사용합니다.
|
||
|
||
Args:
|
||
trade: {dt, leg_id?, action, ...}.
|
||
gt_trades: GT trades (leg 매칭).
|
||
large_legs: 상위 leg.
|
||
analysis: analyze_gt_buy_allocation 결과.
|
||
|
||
Returns:
|
||
pct_large 또는 pct_small.
|
||
"""
|
||
from deepcoin.ground_truth.gt_allocation_analysis import gt_tier_scale_from_analysis
|
||
|
||
lid = trade.get("leg_id")
|
||
if lid is None:
|
||
lid = nearest_gt_leg_id(str(trade["dt"]), gt_trades)
|
||
if lid is None:
|
||
return float(GT_BUY_PCT_SMALL_LEG)
|
||
return gt_tier_scale_from_analysis(int(lid), large_legs, analysis)
|
||
|
||
|
||
def live_buy_asset_pct_scale(
|
||
rule_id: str,
|
||
dt: str,
|
||
gt_trades: list[dict[str, Any]],
|
||
*,
|
||
approved_rules: set[str],
|
||
large_legs: set[int],
|
||
) -> float:
|
||
"""
|
||
실거래 전용 매수 tier (EV/WF·leg 상위). 시뮬은 gt_tier_scale_for_trade 사용.
|
||
|
||
Args:
|
||
rule_id: 규칙 ID.
|
||
dt: 체결 시각.
|
||
gt_trades: GT trades.
|
||
approved_rules: 통과 rule_id.
|
||
large_legs: 상위 leg.
|
||
|
||
Returns:
|
||
LIVE_BUY_PCT_LARGE 또는 LIVE_BUY_PCT_SMALL(또는 0에 가까운 소형).
|
||
"""
|
||
from config import LIVE_BUY_PCT_LARGE, LIVE_BUY_PCT_SMALL
|
||
|
||
if rule_id not in approved_rules:
|
||
return float(LIVE_BUY_PCT_SMALL)
|
||
lid = nearest_gt_leg_id(dt, gt_trades)
|
||
if lid is not None and lid in large_legs:
|
||
return float(LIVE_BUY_PCT_LARGE)
|
||
return float(LIVE_BUY_PCT_SMALL)
|
||
|
||
|
||
def enrich_sim_trades_with_gt_weights(
|
||
trades: list[dict[str, Any]],
|
||
gt_trades: list[dict[str, Any]],
|
||
*,
|
||
causal_legs: bool = False,
|
||
) -> list[dict[str, Any]]:
|
||
"""
|
||
규칙 발화에 GT leg_id·매수/매도 weight를 부여합니다.
|
||
|
||
causal_legs=True: GT leg 매칭 없이 매수~매도 구간 순번 leg_id (인과적).
|
||
|
||
Args:
|
||
trades: {dt, action/side, price, rule_id}.
|
||
gt_trades: GT trades (leg 매칭, causal_legs=False 일 때).
|
||
causal_legs: 순차 leg_id.
|
||
|
||
Returns:
|
||
leg_id·weight가 채워진 trade dict.
|
||
"""
|
||
from deepcoin.ground_truth.gt_model import leg_entry_weights, leg_exit_weights
|
||
|
||
rows = sorted(trades, key=lambda x: x["dt"])
|
||
pos = 0
|
||
seq_leg = 0
|
||
while pos < len(rows):
|
||
action = rows[pos].get("action", rows[pos].get("side", ""))
|
||
if action != "buy":
|
||
if causal_legs:
|
||
rows[pos]["leg_id"] = seq_leg
|
||
elif "leg_id" not in rows[pos]:
|
||
rows[pos]["leg_id"] = nearest_gt_leg_id(rows[pos]["dt"], gt_trades) or 0
|
||
rows[pos]["weight"] = float(rows[pos].get("weight", 1.0))
|
||
pos += 1
|
||
continue
|
||
buy_end = pos
|
||
while buy_end < len(rows):
|
||
a = rows[buy_end].get("action", rows[buy_end].get("side", ""))
|
||
if a != "buy":
|
||
break
|
||
buy_end += 1
|
||
buy_slice = rows[pos:buy_end]
|
||
sell_slice: list[dict[str, Any]] = []
|
||
sell_end = buy_end
|
||
while sell_end < len(rows):
|
||
a = rows[sell_end].get("action", rows[sell_end].get("side", ""))
|
||
if a == "buy":
|
||
break
|
||
if a == "sell":
|
||
sell_slice.append(rows[sell_end])
|
||
sell_end += 1
|
||
|
||
if causal_legs:
|
||
leg_id = seq_leg
|
||
else:
|
||
leg_id = nearest_gt_leg_id(buy_slice[0]["dt"], gt_trades) or 0
|
||
prices = [float(t["price"]) for t in buy_slice]
|
||
buy_weights = leg_entry_weights(prices)
|
||
for t, w in zip(buy_slice, buy_weights):
|
||
t["leg_id"] = leg_id
|
||
t["weight"] = round(w, 4)
|
||
if "action" not in t and "side" in t:
|
||
t["action"] = t["side"]
|
||
|
||
if sell_slice:
|
||
sw = leg_exit_weights(len(sell_slice))
|
||
for t, w in zip(sell_slice, sw):
|
||
t["leg_id"] = leg_id
|
||
t["weight"] = round(w, 4)
|
||
if "action" not in t and "side" in t:
|
||
t["action"] = t["side"]
|
||
if causal_legs and sell_slice:
|
||
seq_leg += 1
|
||
pos = sell_end if sell_slice else buy_end
|
||
return rows
|
||
|
||
|
||
def attach_gt_model_amounts(
|
||
trades: list[dict[str, Any]],
|
||
*,
|
||
gt_trades: list[dict[str, Any]] | None = None,
|
||
approved_rules: set[str] | None = None,
|
||
large_legs: set[int] | None = None,
|
||
initial_cash: float = GT_INITIAL_CASH_KRW,
|
||
fee_rate: float = TRADING_FEE_RATE,
|
||
) -> list[dict[str, Any]]:
|
||
"""
|
||
GT 모델 비중 + 공통 배분 엔진으로 amount_krw를 채웁니다.
|
||
|
||
시뮬·매칭 전용: leg·tier 모두 인과적(과거 청산 leg 수익만). GT 정답 배분은
|
||
ground_truth.allocate_gt_order_amounts 를 사용하세요.
|
||
|
||
Args:
|
||
trades: enrich_sim_trades_with_gt_weights 출력 또는 raw fires.
|
||
gt_trades: GT trades. None이면 파일 로드.
|
||
approved_rules: EV/WF 통과 rule (live scale용).
|
||
large_legs: 상위 leg.
|
||
initial_cash: 초기 현금.
|
||
fee_rate: 수수료율.
|
||
|
||
Returns:
|
||
amount_krw·weight·leg_id가 채워진 trade dict.
|
||
"""
|
||
from deepcoin.ground_truth.gt_allocation import allocate_order_amounts_chronological
|
||
|
||
if gt_trades is None:
|
||
gt_trades, _, _ = load_sizing_context_from_gt()
|
||
|
||
enriched = enrich_sim_trades_with_gt_weights(
|
||
list(trades),
|
||
gt_trades,
|
||
causal_legs=True,
|
||
)
|
||
|
||
allocate_order_amounts_chronological(
|
||
enriched,
|
||
initial_cash=initial_cash,
|
||
fee_rate=fee_rate,
|
||
large_legs=None,
|
||
asset_pct_scale_fn=None,
|
||
causal_tier=True,
|
||
)
|
||
return enriched
|
||
|
||
|
||
def plan_open_position_buy(
|
||
open_buys: list[dict[str, Any]],
|
||
candidate: dict[str, Any],
|
||
cash: float,
|
||
qty: float,
|
||
gt_trades: list[dict[str, Any]] | None = None,
|
||
*,
|
||
large_legs: set[int],
|
||
analysis: dict[str, Any] | None = None,
|
||
fee_rate: float = TRADING_FEE_RATE,
|
||
) -> float:
|
||
"""
|
||
미청산 포지션 내 다음 매수 원화 (GT tier·보유 현금 한도, 1회 상한 없음).
|
||
|
||
Args:
|
||
open_buys: 현재 포지션에서 이미 체결된 매수 dict.
|
||
candidate: 이번 매수 후보 {dt, price, rule_id, leg_id?, ...}.
|
||
cash: 보유 현금.
|
||
qty: 보유 수량.
|
||
gt_trades: GT leg 매칭용.
|
||
large_legs: 상위 leg.
|
||
analysis: GT 배분 분석.
|
||
fee_rate: 수수료율.
|
||
|
||
Returns:
|
||
매수 계획 원화.
|
||
"""
|
||
from deepcoin.ground_truth.gt_model import leg_entry_weights
|
||
|
||
if gt_trades is None:
|
||
gt_trades, _, _ = load_sizing_context_from_gt()
|
||
if analysis is None:
|
||
analysis = load_gt_allocation_analysis(gt_trades)
|
||
|
||
prices = [float(t["price"]) for t in open_buys] + [float(candidate["price"])]
|
||
weights = leg_entry_weights(prices)
|
||
idx = len(open_buys)
|
||
w = weights[idx]
|
||
w_sum = sum(weights[idx:])
|
||
cand = dict(candidate)
|
||
if "leg_id" not in cand:
|
||
cand["leg_id"] = nearest_gt_leg_id(str(candidate["dt"]), gt_trades)
|
||
scale = gt_tier_scale_for_trade(
|
||
cand,
|
||
gt_trades,
|
||
large_legs,
|
||
analysis=analysis,
|
||
)
|
||
return compute_buy_amount_krw(
|
||
cash,
|
||
qty,
|
||
float(candidate["price"]),
|
||
w,
|
||
w_sum,
|
||
asset_pct_scale=scale,
|
||
fee_rate=fee_rate,
|
||
)
|
||
|
||
|
||
def attach_dynamic_buy_amounts(
|
||
trades: list[dict[str, Any]],
|
||
*,
|
||
gt_trades: list[dict[str, Any]] | None = None,
|
||
approved_rules: set[str] | None = None,
|
||
large_legs: set[int] | None = None,
|
||
initial_cash: float = GT_INITIAL_CASH_KRW,
|
||
default_weight: float = 1.0,
|
||
fee_rate: float = TRADING_FEE_RATE,
|
||
) -> list[dict[str, Any]]:
|
||
"""
|
||
시뮬 발화 trade dict에 amount_krw(GT 모델·보유 현금 한도)를 채웁니다.
|
||
|
||
attach_gt_model_amounts 별칭.
|
||
"""
|
||
return attach_gt_model_amounts(
|
||
trades,
|
||
gt_trades=gt_trades,
|
||
approved_rules=approved_rules,
|
||
large_legs=large_legs,
|
||
initial_cash=initial_cash,
|
||
fee_rate=fee_rate,
|
||
)
|
||
|
||
|
||
def load_sizing_context_from_gt(
|
||
gt_path: Path | None = None,
|
||
) -> tuple[list[dict[str, Any]], set[int], set[str]]:
|
||
"""
|
||
GT JSON에서 trades, 상위 leg, EV/WF 통과 rule을 로드합니다.
|
||
|
||
Args:
|
||
gt_path: ground_truth_trades.json.
|
||
|
||
Returns:
|
||
(gt_trades, large_legs, approved_rules).
|
||
"""
|
||
from deepcoin.paths import resolve_ground_truth_file
|
||
|
||
p = gt_path or resolve_ground_truth_file()
|
||
trades: list[dict[str, Any]] = []
|
||
if p.is_file():
|
||
data = json.loads(p.read_text(encoding="utf-8"))
|
||
trades = data.get("trades") or []
|
||
large = top_leg_ids_by_forward_return(trades)
|
||
return trades, large, set()
|