From 5842cc9fa382b75929abe9e315650179c145b1a4 Mon Sep 17 00:00:00 2001 From: dsyoon Date: Sun, 31 May 2026 16:11:49 +0900 Subject: [PATCH] =?UTF-8?q?GT=20=EC=B4=9D=EC=9E=90=EC=82=B0=20=EB=B9=84?= =?UTF-8?q?=EC=9C=A8=20=EB=A7=A4=EC=88=98=C2=B7leg=20=ED=8B=B0=EC=96=B4=20?= =?UTF-8?q?=EB=B0=B0=EB=B6=84=EA=B3=BC=20=EC=8B=9C=EB=AE=AC/=EC=8B=A4?= =?UTF-8?q?=EA=B1=B0=EB=9E=98=20=ED=8F=AC=EC=A7=80=EC=85=98=20=EC=82=AC?= =?UTF-8?q?=EC=9D=B4=EC=A7=95=EC=9D=84=20=ED=86=B5=ED=95=A9=ED=95=9C?= =?UTF-8?q?=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 타점·비중을 gt_model로 일반화하고, amount_krw 시각순 배분·EV/WF·상위 leg 대형 매수를 position_sizing과 시뮬 HTML(고정 ₩/회 비교)에 반영한다. Co-authored-by: Cursor --- .env.example | 9 + config.py | 7 + data/ground_truth/ground_truth_trades.json | 611 +++++++++++++++++++-- deepcoin/ground_truth/ground_truth.py | 264 ++++++++- deepcoin/ground_truth/gt_model.py | 228 ++++++++ deepcoin/matching/portfolio_sim.py | 152 ++++- deepcoin/matching/position_sizing.py | 378 +++++++++++++ deepcoin/matching/simulation.py | 103 +++- deepcoin/matching/simulation_html.py | 118 ++-- deepcoin/ops/chart_report.py | 34 +- deepcoin/ops/live_trader.py | 96 +++- deepcoin/ops/simulation.py | 122 +++- docs/reference/GROUND_TRUTH.md | 89 ++- docs/reference/SIMULATION.md | 44 +- 14 files changed, 2073 insertions(+), 182 deletions(-) create mode 100644 deepcoin/ground_truth/gt_model.py create mode 100644 deepcoin/matching/position_sizing.py diff --git a/.env.example b/.env.example index 10c86d6..825df4d 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,13 @@ COIN_TELEGRAM_CHAT_ID= SYMBOL=WLD CHART_LOOKBACK_DAYS=365 +# 02 Ground Truth +GT_MIN_ORDER_KRW=5000 +GT_MAX_BUY_ORDER_KRW=100000 +GT_BUY_PCT_LARGE_LEG=1.0 +GT_BUY_PCT_SMALL_LEG=0.05 +GT_LARGE_LEG_TOP_PCT=0.2 + # 04 매칭 MATCH_LABEL_MODE=leg_gt MATCH_HOLDOUT_RATIO=0.15 @@ -24,6 +31,8 @@ MONITOR_ALERT_KRW_AMOUNT=100000 # 3 실거래 (오픈 시에만 1) LIVE_TRADING_ENABLED=0 LIVE_ORDER_KRW=100000 +LIVE_BUY_PCT_LARGE=1.0 +LIVE_BUY_PCT_SMALL=0.05 LIVE_DAILY_KRW_MAX=300000 LIVE_COOLDOWN_MIN=180 LIVE_MAX_TRADES_PER_DAY=10 diff --git a/config.py b/config.py index 567f6c1..f6c7e42 100644 --- a/config.py +++ b/config.py @@ -202,6 +202,11 @@ GT_SELL_SPLIT_GAP_PCT = _getenv_float("GT_SELL_SPLIT_GAP_PCT", "2.5") GT_MARKER_SIZE_MIN = _getenv_int("GT_MARKER_SIZE_MIN", "10") GT_MARKER_SIZE_MAX = _getenv_int("GT_MARKER_SIZE_MAX", "32") GT_INITIAL_CASH_KRW = _getenv_int("GT_INITIAL_CASH_KRW", "1000000") +GT_MIN_ORDER_KRW = _getenv_int("GT_MIN_ORDER_KRW", "5000") +GT_MAX_BUY_ORDER_KRW = _getenv_int("GT_MAX_BUY_ORDER_KRW", "100000") +GT_BUY_PCT_LARGE_LEG = _getenv_float("GT_BUY_PCT_LARGE_LEG", "1.0") +GT_BUY_PCT_SMALL_LEG = _getenv_float("GT_BUY_PCT_SMALL_LEG", "0.05") +GT_LARGE_LEG_TOP_PCT = _getenv_float("GT_LARGE_LEG_TOP_PCT", "0.2") TRADING_FEE_RATE = _getenv_float("TRADING_FEE_RATE", "0.0005") # --- 모니터 / API 수집 --- @@ -335,6 +340,8 @@ LIVE_TRADING_ENABLED = _getenv("LIVE_TRADING_ENABLED", "0").strip() in ( "yes", ) LIVE_ORDER_KRW = _getenv_int("LIVE_ORDER_KRW", "100000") +LIVE_BUY_PCT_LARGE = _getenv_float("LIVE_BUY_PCT_LARGE", "1.0") +LIVE_BUY_PCT_SMALL = _getenv_float("LIVE_BUY_PCT_SMALL", "0.05") LIVE_DAILY_KRW_MAX = _getenv_int("LIVE_DAILY_KRW_MAX", "300000") LIVE_COOLDOWN_MIN = _getenv_int("LIVE_COOLDOWN_MIN", "180") LIVE_MAX_TRADES_PER_DAY = _getenv_int("LIVE_MAX_TRADES_PER_DAY", "10") diff --git a/data/ground_truth/ground_truth_trades.json b/data/ground_truth/ground_truth_trades.json index 0a0ae49..933f057 100644 --- a/data/ground_truth/ground_truth_trades.json +++ b/data/ground_truth/ground_truth_trades.json @@ -1,12 +1,49 @@ { "name": "ground_truth_split_buy_peak_sell", + "model": { + "selection_mode": "split_buy_peak_sell", + "leg_definition": "이전 고점 매도 ~ 다음 고점 매도 구간 = leg_id; 기간말 잔여 구간은 마지막 leg", + "entry": { + "pivot": "trough", + "price": "Low", + "weight_rule": "inverse_price_normalized", + "weight_formula": "w_i = (1/price_i) / sum(1/price_j)", + "max_buys_per_leg": 12, + "min_bars_between_buys": 24, + "bb_filter": "bb_pos <= 0.45" + }, + "exit": { + "pivot": "peak", + "price": "High", + "weight_rule": "fixed_split_or_full", + "weights_two_sell": [ + 0.65, + 0.35 + ], + "split_gap_pct": 2.5, + "max_sells_per_leg": 2 + }, + "capital": { + "buy": "min(total_asset * w_share * tier_scale, cash/(1+fee))", + "optimal_buy_rate": "weight / sum(remaining_buy_weights_in_leg)", + "large_leg_top_pct": 0.2, + "pct_large_leg": 1.0, + "pct_small_leg": 0.05, + "min_order_krw": 5000.0, + "sell": "leg_qty * sell_weight * price (last sell = full leg_qty)" + }, + "execution": { + "chrono": "amount_krw 배분·summary.pnl_pct = 시각순 체결(매도 후 현금 → 다음 매수 반영)", + "leg_block_json": "JSON 저장 순서 = leg별 매수 전량 → 매도 전량 (차트·테이블 leg 정합)" + } + }, "method": "split_buy_at_troughs + peak_sell_1or2", "symbol": "WLD", "interval_min": 3, "lookback_days": 365, "period_start": "2025-06-04 03:57:00", - "period_end": "2026-05-31 00:51:00", - "trend_at_end": "up", + "period_end": "2026-05-31 15:58:00", + "trend_at_end": "range", "params": { "min_swing_pct": 4.0, "pivot_order": 20, @@ -18,30 +55,44 @@ "max_sells_per_leg": 2 }, "summary": { - "pivot_candidates": 380, - "sell_peaks": 74, - "trade_count": 451, - "buy_count": 303, - "sell_count": 148, - "round_trips": 75, - "sum_sell_leg_return_pct": 1590.3, + "pivot_candidates": 382, + "sell_peaks": 75, + "trade_count": 454, + "buy_count": 304, + "sell_count": 150, + "round_trips": 76, + "sum_sell_leg_return_pct": 1634.3, "initial_cash_krw": 1000000, - "final_asset_krw": 2040862272.0, - "pnl_krw": 2039862272.0, - "pnl_pct": 203986.23, - "total_fees_krw": 19790914.0, - "cash_krw": 2040862272.0, + "final_asset_krw": 43963648.0, + "pnl_krw": 42963648.0, + "pnl_pct": 4296.36, + "total_fees_krw": 238798.0, + "cash_krw": 43963648.0, "holding_qty": 0.0, "holding_value_krw": 0.0, - "mark_price": 515.0, + "mark_price": 503.0, "fee_rate": 0.0005, - "realized_final_asset_krw": 2040862272.0, - "realized_pnl_krw": 2039862272.0, - "realized_pnl_pct": 203986.23, + "realized_final_asset_krw": 43963648.0, + "realized_pnl_krw": 42963648.0, + "realized_pnl_pct": 4296.36, "unrealized_pnl_krw": 0.0, - "execution_order": "leg_block" + "execution_order": "chronological", + "order_amount_min_krw": 5000, + "order_amount_max_buy_krw": 100000, + "buy_pct_large_leg": 1.0, + "buy_pct_small_leg": 0.05, + "large_leg_top_pct": 0.2, + "buy_executed": 295, + "buy_skipped": 9, + "sell_executed": 150, + "sell_skipped": 0, + "buy_total_krw": 217196361.0, + "large_leg_count": 16, + "buy_amount_avg_krw": 736259.0, + "buy_amount_min_krw": 5000, + "buy_amount_max_krw": 14200590.0 }, - "note": "저점 분할 매수(비중=삼각형), 고점 1~2회 매도. 체결 순서=leg별 매수→매도(시각순 아님). 기간말 leg는 종가 청산. summary.pnl_pct는 미청산 포함 종가 평가, realized_pnl_pct는 체결만 반영.", + "note": "저점 분할 매수(비중=삼각형), 고점 1~2회 매도. 매수=총자산×최적비중×티어(상위 leg 대형·그 외 소형), 현금 한도·최소 ₩5,000. 체결 순서=chronological. summary.pnl_pct는 미청산 포함 종가 평가.", "trades": [ { "dt": "2025-06-06 06:12:00", @@ -49,6 +100,7 @@ "price": 1421.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#0", "weight": 1.0, + "amount_krw": 50000.0, "leg_id": 0, "bb_pos": 0.295, "rsi": 35.6, @@ -61,6 +113,7 @@ "price": 1578.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#0", "weight": 0.65, + "amount_krw": 36091.0, "leg_id": 0, "bb_pos": 0.888, "rsi": 72.2, @@ -73,6 +126,7 @@ "price": 1575.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#0", "weight": 0.35, + "amount_krw": 19397.0, "leg_id": 0, "bb_pos": 1.0, "rsi": 61.3, @@ -85,6 +139,7 @@ "price": 1509.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#1", "weight": 0.076, + "amount_krw": 5000, "leg_id": 1, "bb_pos": 0.0, "rsi": 30.0, @@ -97,6 +152,7 @@ "price": 1472.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#1", "weight": 0.078, + "amount_krw": 5000, "leg_id": 1, "bb_pos": 0.159, "rsi": 27.3, @@ -109,6 +165,7 @@ "price": 1502.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#1", "weight": 0.076, + "amount_krw": 5000, "leg_id": 1, "bb_pos": 0.132, "rsi": 17.7, @@ -121,6 +178,7 @@ "price": 1536.0, "memo": "저점 분할 매수 · 비중 7% · 12회 · BB하단 · leg#1", "weight": 0.074, + "amount_krw": 5000, "leg_id": 1, "bb_pos": 0.339, "rsi": 27.3, @@ -133,6 +191,7 @@ "price": 1555.0, "memo": "저점 분할 매수 · 비중 7% · 12회 · BB하단 · leg#1", "weight": 0.074, + "amount_krw": 5349.0, "leg_id": 1, "bb_pos": 0.489, "rsi": 58.6, @@ -145,6 +204,7 @@ "price": 1307.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#1", "weight": 0.087, + "amount_krw": 7007.0, "leg_id": 1, "bb_pos": 0.011, "rsi": 38.0, @@ -157,6 +217,7 @@ "price": 1330.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#1", "weight": 0.086, + "amount_krw": 8057.0, "leg_id": 1, "bb_pos": 0.183, "rsi": 19.0, @@ -169,6 +230,7 @@ "price": 1328.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#1", "weight": 0.086, + "amount_krw": 9600.0, "leg_id": 1, "bb_pos": 0.0, "rsi": 11.5, @@ -181,6 +243,7 @@ "price": 1325.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#1", "weight": 0.086, + "amount_krw": 11873.0, "leg_id": 1, "bb_pos": 0.175, "rsi": 37.9, @@ -193,6 +256,7 @@ "price": 1274.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#1", "weight": 0.09, + "amount_krw": 16246.0, "leg_id": 1, "bb_pos": 0.0, "rsi": 6.8, @@ -205,6 +269,7 @@ "price": 1242.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#1", "weight": 0.092, + "amount_krw": 24554.0, "leg_id": 1, "bb_pos": 0.289, "rsi": 36.4, @@ -217,6 +282,7 @@ "price": 1208.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#1", "weight": 0.095, + "amount_krw": 49777.0, "leg_id": 1, "bb_pos": 0.0, "rsi": 18.2, @@ -229,6 +295,7 @@ "price": 1340.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#1", "weight": 0.65, + "amount_krw": 102840.0, "leg_id": 1, "bb_pos": 0.95, "rsi": 64.9, @@ -241,6 +308,7 @@ "price": 1317.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#1", "weight": 0.35, + "amount_krw": 54425.0, "leg_id": 1, "bb_pos": 0.224, "rsi": 18.9, @@ -253,6 +321,7 @@ "price": 1258.0, "memo": "저점 분할 매수 · 비중 23% · 4회 · BB하단 · leg#2", "weight": 0.234, + "amount_krw": 11818.0, "leg_id": 2, "bb_pos": 0.271, "rsi": 36.8, @@ -265,6 +334,7 @@ "price": 1251.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#2", "weight": 0.235, + "amount_krw": 15493.0, "leg_id": 2, "bb_pos": 0.0, "rsi": 28.6, @@ -277,6 +347,7 @@ "price": 1139.0, "memo": "저점 분할 매수 · 비중 26% · 4회 · BB하단 · leg#2", "weight": 0.259, + "amount_krw": 24572.0, "leg_id": 2, "bb_pos": 0.0, "rsi": 27.5, @@ -289,6 +360,7 @@ "price": 1083.0, "memo": "저점 분할 매수 · 비중 27% · 4회 · BB하단 · leg#2", "weight": 0.272, + "amount_krw": 50256.0, "leg_id": 2, "bb_pos": 0.0, "rsi": 34.3, @@ -301,6 +373,7 @@ "price": 1185.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#2", "weight": 0.65, + "amount_krw": 69135.0, "leg_id": 2, "bb_pos": 0.784, "rsi": 84.8, @@ -313,6 +386,7 @@ "price": 1183.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#2", "weight": 0.35, + "amount_krw": 37164.0, "leg_id": 2, "bb_pos": 0.8, "rsi": 81.6, @@ -325,6 +399,7 @@ "price": 1124.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#3", "weight": 1.0, + "amount_krw": 976503.0, "leg_id": 3, "bb_pos": 0.081, "rsi": 11.8, @@ -337,6 +412,7 @@ "price": 1319.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#3", "weight": 0.65, + "amount_krw": 744844.0, "leg_id": 3, "bb_pos": 0.817, "rsi": 79.4, @@ -349,6 +425,7 @@ "price": 1318.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#3", "weight": 0.35, + "amount_krw": 400766.0, "leg_id": 3, "bb_pos": 0.719, "rsi": 63.2, @@ -361,6 +438,7 @@ "price": 1205.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#4", "weight": 0.327, + "amount_krw": 19329.0, "leg_id": 4, "bb_pos": 0.001, "rsi": 27.3, @@ -373,6 +451,7 @@ "price": 1172.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#4", "weight": 0.336, + "amount_krw": 29497.0, "leg_id": 4, "bb_pos": 0.172, "rsi": 40.7, @@ -385,6 +464,7 @@ "price": 1171.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#4", "weight": 0.337, + "amount_krw": 59079.0, "leg_id": 4, "bb_pos": 0.097, "rsi": 19.2, @@ -397,6 +477,7 @@ "price": 1305.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#4", "weight": 0.65, + "amount_krw": 77751.0, "leg_id": 4, "bb_pos": 0.83, "rsi": 83.9, @@ -409,6 +490,7 @@ "price": 1303.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#4", "weight": 0.35, + "amount_krw": 41802.0, "leg_id": 4, "bb_pos": 0.724, "rsi": 64.9, @@ -421,6 +503,7 @@ "price": 1212.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#5", "weight": 0.197, + "amount_krw": 235162.0, "leg_id": 5, "bb_pos": 0.092, "rsi": 29.4, @@ -433,6 +516,7 @@ "price": 1161.0, "memo": "저점 분할 매수 · 비중 21% · 5회 · BB하단 · leg#5", "weight": 0.206, + "amount_krw": 303665.0, "leg_id": 5, "bb_pos": 0.258, "rsi": 33.3, @@ -445,6 +529,7 @@ "price": 1274.0, "memo": "저점 분할 매수 · 비중 19% · 5회 · BB하단 · leg#5", "weight": 0.188, + "amount_krw": 388921.0, "leg_id": 5, "bb_pos": 0.222, "rsi": 38.3, @@ -457,6 +542,7 @@ "price": 1174.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#5", "weight": 0.204, + "amount_krw": 265372.0, "leg_id": 5, "bb_pos": 0.309, "rsi": 28.0, @@ -469,6 +555,7 @@ "price": 1169.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#5", "weight": 0.205, + "amount_krw": 0, "leg_id": 5, "bb_pos": 0.185, "rsi": 21.4, @@ -481,6 +568,7 @@ "price": 1534.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#5", "weight": 0.65, + "amount_krw": 984037.0, "leg_id": 5, "bb_pos": 0.863, "rsi": 57.1, @@ -493,6 +581,7 @@ "price": 1527.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#5", "weight": 0.35, + "amount_krw": 527448.0, "leg_id": 5, "bb_pos": 0.717, "rsi": 58.0, @@ -505,6 +594,7 @@ "price": 1365.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#6", "weight": 0.336, + "amount_krw": 25380.0, "leg_id": 6, "bb_pos": 0.0, "rsi": 4.8, @@ -517,6 +607,7 @@ "price": 1355.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#6", "weight": 0.338, + "amount_krw": 38446.0, "leg_id": 6, "bb_pos": 0.05, "rsi": 28.3, @@ -529,6 +620,7 @@ "price": 1409.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#6", "weight": 0.326, + "amount_krw": 75652.0, "leg_id": 6, "bb_pos": 0.164, "rsi": 39.6, @@ -541,6 +633,7 @@ "price": 1518.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#6", "weight": 0.65, + "amount_krw": 99320.0, "leg_id": 6, "bb_pos": 0.966, "rsi": 77.6, @@ -553,6 +646,7 @@ "price": 1513.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#6", "weight": 0.35, + "amount_krw": 53304.0, "leg_id": 6, "bb_pos": 0.658, "rsi": 57.1, @@ -565,6 +659,7 @@ "price": 1398.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#7", "weight": 0.2, + "amount_krw": 15237.0, "leg_id": 7, "bb_pos": 0.0, "rsi": 25.9, @@ -577,6 +672,7 @@ "price": 1368.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#7", "weight": 0.204, + "amount_krw": 19423.0, "leg_id": 7, "bb_pos": 0.0, "rsi": 18.8, @@ -589,6 +685,7 @@ "price": 1365.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#7", "weight": 0.205, + "amount_krw": 26198.0, "leg_id": 7, "bb_pos": 0.0, "rsi": 10.7, @@ -601,6 +698,7 @@ "price": 1416.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#7", "weight": 0.197, + "amount_krw": 38432.0, "leg_id": 7, "bb_pos": 0.0, "rsi": 15.2, @@ -613,6 +711,7 @@ "price": 1439.0, "memo": "저점 분할 매수 · 비중 19% · 5회 · BB하단 · leg#7", "weight": 0.194, + "amount_krw": 76359.0, "leg_id": 7, "bb_pos": 0.089, "rsi": 12.2, @@ -625,6 +724,7 @@ "price": 1556.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#7", "weight": 0.65, + "amount_krw": 125914.0, "leg_id": 7, "bb_pos": 1.0, "rsi": 61.0, @@ -637,6 +737,7 @@ "price": 1547.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#7", "weight": 0.35, + "amount_krw": 67408.0, "leg_id": 7, "bb_pos": 0.641, "rsi": 61.8, @@ -649,6 +750,7 @@ "price": 1484.0, "memo": "저점 분할 매수 · 비중 49% · 2회 · BB하단 · leg#8", "weight": 0.493, + "amount_krw": 37991.0, "leg_id": 8, "bb_pos": 0.046, "rsi": 20.0, @@ -661,6 +763,7 @@ "price": 1444.0, "memo": "저점 분할 매수 · 비중 51% · 2회 · BB하단 · leg#8", "weight": 0.507, + "amount_krw": 77009.0, "leg_id": 8, "bb_pos": 0.0, "rsi": 8.8, @@ -673,6 +776,7 @@ "price": 1635.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#8", "weight": 0.65, + "amount_krw": 83884.0, "leg_id": 8, "bb_pos": 0.727, "rsi": 72.9, @@ -685,6 +789,7 @@ "price": 1632.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#8", "weight": 0.35, + "amount_krw": 45085.0, "leg_id": 8, "bb_pos": 0.901, "rsi": 67.8, @@ -697,6 +802,7 @@ "price": 1560.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#9", "weight": 0.245, + "amount_krw": 19050.0, "leg_id": 9, "bb_pos": 0.0, "rsi": 36.1, @@ -709,6 +815,7 @@ "price": 1515.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#9", "weight": 0.252, + "amount_krw": 25943.0, "leg_id": 9, "bb_pos": 0.089, "rsi": 16.3, @@ -721,6 +828,7 @@ "price": 1506.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#9", "weight": 0.253, + "amount_krw": 39087.0, "leg_id": 9, "bb_pos": 0.0, "rsi": 14.3, @@ -733,6 +841,7 @@ "price": 1526.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#9", "weight": 0.25, + "amount_krw": 77766.0, "leg_id": 9, "bb_pos": 0.144, "rsi": 43.8, @@ -745,6 +854,7 @@ "price": 1741.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#9", "weight": 0.65, + "amount_krw": 120238.0, "leg_id": 9, "bb_pos": 0.9, "rsi": 90.7, @@ -757,6 +867,7 @@ "price": 1730.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#9", "weight": 0.35, + "amount_krw": 64335.0, "leg_id": 9, "bb_pos": 0.68, "rsi": 71.9, @@ -769,6 +880,7 @@ "price": 1639.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#10", "weight": 0.199, + "amount_krw": 15697.0, "leg_id": 10, "bb_pos": 0.117, "rsi": 39.1, @@ -781,6 +893,7 @@ "price": 1633.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#10", "weight": 0.199, + "amount_krw": 19596.0, "leg_id": 10, "bb_pos": 0.0, "rsi": 38.5, @@ -793,6 +906,7 @@ "price": 1635.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#10", "weight": 0.199, + "amount_krw": 26075.0, "leg_id": 10, "bb_pos": 0.0, "rsi": 20.2, @@ -805,6 +919,7 @@ "price": 1598.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#10", "weight": 0.204, + "amount_krw": 39894.0, "leg_id": 10, "bb_pos": 0.146, "rsi": 19.0, @@ -817,6 +932,7 @@ "price": 1634.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#10", "weight": 0.199, + "amount_krw": 78921.0, "leg_id": 10, "bb_pos": 0.153, "rsi": 31.6, @@ -829,6 +945,7 @@ "price": 1890.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#10", "weight": 0.65, + "amount_krw": 136105.0, "leg_id": 10, "bb_pos": 0.913, "rsi": 56.8, @@ -841,6 +958,7 @@ "price": 1854.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#10", "weight": 0.35, + "amount_krw": 71891.0, "leg_id": 10, "bb_pos": 0.351, "rsi": 44.7, @@ -853,6 +971,7 @@ "price": 1636.0, "memo": "저점 분할 매수 · 비중 31% · 3회 · BB하단 · leg#11", "weight": 0.314, + "amount_krw": 25202.0, "leg_id": 11, "bb_pos": 0.038, "rsi": 24.5, @@ -865,6 +984,7 @@ "price": 1537.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#11", "weight": 0.334, + "amount_krw": 39041.0, "leg_id": 11, "bb_pos": 0.055, "rsi": 41.5, @@ -877,6 +997,7 @@ "price": 1462.0, "memo": "저점 분할 매수 · 비중 35% · 3회 · BB하단 · leg#11", "weight": 0.352, + "amount_krw": 80031.0, "leg_id": 11, "bb_pos": 0.072, "rsi": 31.7, @@ -889,6 +1010,7 @@ "price": 1616.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#11", "weight": 0.65, + "amount_krw": 100362.0, "leg_id": 11, "bb_pos": 0.806, "rsi": 90.1, @@ -901,6 +1023,7 @@ "price": 1611.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#11", "weight": 0.35, + "amount_krw": 53874.0, "leg_id": 11, "bb_pos": 0.68, "rsi": 48.9, @@ -913,6 +1036,7 @@ "price": 1502.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#12", "weight": 0.501, + "amount_krw": 40457.0, "leg_id": 12, "bb_pos": 0.0, "rsi": 38.4, @@ -925,6 +1049,7 @@ "price": 1506.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#12", "weight": 0.499, + "amount_krw": 80757.0, "leg_id": 12, "bb_pos": 0.0, "rsi": 42.1, @@ -937,6 +1062,7 @@ "price": 1683.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#12", "weight": 0.65, + "amount_krw": 88127.0, "leg_id": 12, "bb_pos": 0.832, "rsi": 73.7, @@ -949,6 +1075,7 @@ "price": 1680.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#12", "weight": 0.35, + "amount_krw": 47369.0, "leg_id": 12, "bb_pos": 0.854, "rsi": 57.1, @@ -961,6 +1088,7 @@ "price": 1506.0, "memo": "저점 분할 매수 · 비중 13% · 7회 · BB하단 · leg#13", "weight": 0.13, + "amount_krw": 10590.0, "leg_id": 13, "bb_pos": 0.089, "rsi": 36.5, @@ -973,6 +1101,7 @@ "price": 1420.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#13", "weight": 0.138, + "amount_krw": 12916.0, "leg_id": 13, "bb_pos": 0.151, "rsi": 11.6, @@ -985,6 +1114,7 @@ "price": 1414.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#13", "weight": 0.139, + "amount_krw": 15462.0, "leg_id": 13, "bb_pos": 0.0, "rsi": 1.4, @@ -997,6 +1127,7 @@ "price": 1357.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#13", "weight": 0.145, + "amount_krw": 19891.0, "leg_id": 13, "bb_pos": 0.0, "rsi": 25.7, @@ -1009,6 +1140,7 @@ "price": 1336.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#13", "weight": 0.147, + "amount_krw": 26677.0, "leg_id": 13, "bb_pos": 0.0, "rsi": 15.6, @@ -1021,6 +1153,7 @@ "price": 1319.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#13", "weight": 0.149, + "amount_krw": 40220.0, "leg_id": 13, "bb_pos": 0.098, "rsi": 21.5, @@ -1033,6 +1166,7 @@ "price": 1287.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#13", "weight": 0.152, + "amount_krw": 81101.0, "leg_id": 13, "bb_pos": 0.0, "rsi": 35.8, @@ -1045,6 +1179,7 @@ "price": 1409.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#13", "weight": 0.65, + "amount_krw": 142137.0, "leg_id": 13, "bb_pos": 1.0, "rsi": 75.8, @@ -1057,6 +1192,7 @@ "price": 1396.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#13", "weight": 0.35, + "amount_krw": 75829.0, "leg_id": 13, "bb_pos": 0.184, "rsi": 35.5, @@ -1069,6 +1205,7 @@ "price": 1317.0, "memo": "저점 분할 매수 · 비중 49% · 2회 · BB하단 · leg#14", "weight": 0.494, + "amount_krw": 810210.0, "leg_id": 14, "bb_pos": 0.293, "rsi": 28.2, @@ -1081,6 +1218,7 @@ "price": 1287.0, "memo": "저점 분할 매수 · 비중 51% · 2회 · BB하단 · leg#14", "weight": 0.506, + "amount_krw": 829071.0, "leg_id": 14, "bb_pos": 0.0, "rsi": 25.9, @@ -1093,6 +1231,7 @@ "price": 1512.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#14", "weight": 0.65, + "amount_krw": 1237721.0, "leg_id": 14, "bb_pos": 1.0, "rsi": 59.5, @@ -1105,6 +1244,7 @@ "price": 1510.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#14", "weight": 0.35, + "amount_krw": 665584.0, "leg_id": 14, "bb_pos": 0.708, "rsi": 59.5, @@ -1117,6 +1257,7 @@ "price": 1417.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#15", "weight": 0.325, + "amount_krw": 30913.0, "leg_id": 15, "bb_pos": 0.05, "rsi": 8.6, @@ -1129,6 +1270,7 @@ "price": 1399.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#15", "weight": 0.33, + "amount_krw": 46492.0, "leg_id": 15, "bb_pos": 0.13, "rsi": 37.8, @@ -1141,6 +1283,7 @@ "price": 1336.0, "memo": "저점 분할 매수 · 비중 35% · 3회 · BB하단 · leg#15", "weight": 0.345, + "amount_krw": 94923.0, "leg_id": 15, "bb_pos": 0.229, "rsi": 37.5, @@ -1153,6 +1296,7 @@ "price": 1550.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#15", "weight": 0.65, + "amount_krw": 127044.0, "leg_id": 15, "bb_pos": 0.948, "rsi": 78.2, @@ -1165,6 +1309,7 @@ "price": 1543.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#15", "weight": 0.35, + "amount_krw": 68099.0, "leg_id": 15, "bb_pos": 1.0, "rsi": 71.4, @@ -1177,6 +1322,7 @@ "price": 1380.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#16", "weight": 0.136, + "amount_krw": 13090.0, "leg_id": 16, "bb_pos": 0.13, "rsi": 20.7, @@ -1189,6 +1335,7 @@ "price": 1371.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#16", "weight": 0.137, + "amount_krw": 15261.0, "leg_id": 16, "bb_pos": 0.0, "rsi": 23.9, @@ -1201,6 +1348,7 @@ "price": 1332.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#16", "weight": 0.141, + "amount_krw": 18659.0, "leg_id": 16, "bb_pos": 0.156, "rsi": 30.0, @@ -1213,6 +1361,7 @@ "price": 1350.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#16", "weight": 0.139, + "amount_krw": 22827.0, "leg_id": 16, "bb_pos": 0.221, "rsi": 26.9, @@ -1225,6 +1374,7 @@ "price": 1251.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#16", "weight": 0.15, + "amount_krw": 32208.0, "leg_id": 16, "bb_pos": 0.067, "rsi": 35.3, @@ -1237,6 +1387,7 @@ "price": 1270.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#16", "weight": 0.148, + "amount_krw": 47864.0, "leg_id": 16, "bb_pos": 0.0, "rsi": 30.6, @@ -1249,6 +1400,7 @@ "price": 1257.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#16", "weight": 0.149, + "amount_krw": 95976.0, "leg_id": 16, "bb_pos": 0.013, "rsi": 12.1, @@ -1261,6 +1413,7 @@ "price": 1416.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#16", "weight": 0.65, + "amount_krw": 176092.0, "leg_id": 16, "bb_pos": 0.948, "rsi": 64.0, @@ -1273,6 +1426,7 @@ "price": 1416.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#16", "weight": 0.35, + "amount_krw": 94819.0, "leg_id": 16, "bb_pos": 0.852, "rsi": 59.1, @@ -1285,6 +1439,7 @@ "price": 1362.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#17", "weight": 0.238, + "amount_krw": 23202.0, "leg_id": 17, "bb_pos": 0.0, "rsi": 17.1, @@ -1297,6 +1452,7 @@ "price": 1329.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#17", "weight": 0.244, + "amount_krw": 31207.0, "leg_id": 17, "bb_pos": 0.072, "rsi": 12.0, @@ -1309,6 +1465,7 @@ "price": 1278.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#17", "weight": 0.254, + "amount_krw": 47738.0, "leg_id": 17, "bb_pos": 0.059, "rsi": 21.7, @@ -1321,6 +1478,7 @@ "price": 1229.0, "memo": "저점 분할 매수 · 비중 26% · 4회 · BB하단 · leg#17", "weight": 0.264, + "amount_krw": 97163.0, "leg_id": 17, "bb_pos": 0.0, "rsi": 42.5, @@ -1333,6 +1491,7 @@ "price": 1344.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#17", "weight": 0.65, + "amount_krw": 137093.0, "leg_id": 17, "bb_pos": 0.932, "rsi": 80.6, @@ -1345,6 +1504,7 @@ "price": 1344.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#17", "weight": 0.35, + "amount_krw": 73819.0, "leg_id": 17, "bb_pos": 0.952, "rsi": 86.7, @@ -1357,6 +1517,7 @@ "price": 1294.0, "memo": "저점 분할 매수 · 비중 16% · 6회 · BB하단 · leg#18", "weight": 0.157, + "amount_krw": 308209.0, "leg_id": 18, "bb_pos": 0.177, "rsi": 41.7, @@ -1369,6 +1530,7 @@ "price": 1239.0, "memo": "저점 분할 매수 · 비중 16% · 6회 · BB하단 · leg#18", "weight": 0.164, + "amount_krw": 379400.0, "leg_id": 18, "bb_pos": 0.0, "rsi": 4.0, @@ -1381,6 +1543,7 @@ "price": 1195.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#18", "weight": 0.17, + "amount_krw": 482357.0, "leg_id": 18, "bb_pos": 0.126, "rsi": 26.7, @@ -1393,6 +1556,7 @@ "price": 1170.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#18", "weight": 0.174, + "amount_krw": 650722.0, "leg_id": 18, "bb_pos": 0.205, "rsi": 28.1, @@ -1405,6 +1569,7 @@ "price": 1207.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#18", "weight": 0.169, + "amount_krw": 139481.0, "leg_id": 18, "bb_pos": 0.244, "rsi": 31.8, @@ -1417,6 +1582,7 @@ "price": 1230.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#18", "weight": 0.165, + "amount_krw": 0, "leg_id": 18, "bb_pos": 0.214, "rsi": 15.4, @@ -1429,6 +1595,7 @@ "price": 1469.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#18", "weight": 0.65, + "amount_krw": 1546644.0, "leg_id": 18, "bb_pos": 0.853, "rsi": 79.7, @@ -1441,6 +1608,7 @@ "price": 1435.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#18", "weight": 0.35, + "amount_krw": 813533.0, "leg_id": 18, "bb_pos": 0.472, "rsi": 59.4, @@ -1453,6 +1621,7 @@ "price": 1410.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#19", "weight": 0.504, + "amount_krw": 1188934.0, "leg_id": 19, "bb_pos": 0.0, "rsi": 35.3, @@ -1465,6 +1634,7 @@ "price": 1432.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#19", "weight": 0.496, + "amount_krw": 1168884.0, "leg_id": 19, "bb_pos": 0.241, "rsi": 47.1, @@ -1477,6 +1647,7 @@ "price": 1800.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#19", "weight": 0.65, + "amount_krw": 1941586.0, "leg_id": 19, "bb_pos": 1.0, "rsi": 83.3, @@ -1489,6 +1660,7 @@ "price": 1784.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#19", "weight": 0.35, + "amount_krw": 1036176.0, "leg_id": 19, "bb_pos": 0.738, "rsi": 52.4, @@ -1501,6 +1673,7 @@ "price": 1718.0, "memo": "저점 분할 매수 · 비중 49% · 2회 · BB하단 · leg#20", "weight": 0.491, + "amount_krw": 1461350.0, "leg_id": 20, "bb_pos": 0.0, "rsi": 33.7, @@ -1513,6 +1686,7 @@ "price": 1658.0, "memo": "저점 분할 매수 · 비중 51% · 2회 · BB하단 · leg#20", "weight": 0.509, + "amount_krw": 1513436.0, "leg_id": 20, "bb_pos": 0.065, "rsi": 29.5, @@ -1525,6 +1699,7 @@ "price": 2148.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#20", "weight": 0.65, + "amount_krw": 2462086.0, "leg_id": 20, "bb_pos": 1.0, "rsi": 83.1, @@ -1537,6 +1712,7 @@ "price": 2136.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#20", "weight": 0.35, + "amount_krw": 1318332.0, "leg_id": 20, "bb_pos": 0.891, "rsi": 64.2, @@ -1549,6 +1725,7 @@ "price": 1990.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#21", "weight": 1.0, + "amount_krw": 3776640.0, "leg_id": 21, "bb_pos": 0.0, "rsi": 24.3, @@ -1561,6 +1738,7 @@ "price": 2485.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#21", "weight": 0.65, + "amount_krw": 3065436.0, "leg_id": 21, "bb_pos": 0.849, "rsi": 80.6, @@ -1573,6 +1751,7 @@ "price": 2542.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#21", "weight": 0.35, + "amount_krw": 1688481.0, "leg_id": 21, "bb_pos": 0.957, "rsi": 79.4, @@ -1585,6 +1764,7 @@ "price": 2334.0, "memo": "저점 분할 매수 · 비중 35% · 3회 · BB하단 · leg#22", "weight": 0.355, + "amount_krw": 1638049.0, "leg_id": 22, "bb_pos": 0.19, "rsi": 35.8, @@ -1597,6 +1777,7 @@ "price": 2508.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#22", "weight": 0.33, + "amount_krw": 2493080.0, "leg_id": 22, "bb_pos": 0.191, "rsi": 38.2, @@ -1609,6 +1790,7 @@ "price": 2624.0, "memo": "저점 분할 매수 · 비중 32% · 3회 · BB하단 · leg#22", "weight": 0.315, + "amount_krw": 618036.0, "leg_id": 22, "bb_pos": 0.238, "rsi": 19.2, @@ -1621,6 +1803,7 @@ "price": 2939.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#22", "weight": 0.65, + "amount_krw": 3689657.0, "leg_id": 22, "bb_pos": 1.0, "rsi": 90.0, @@ -1633,6 +1816,7 @@ "price": 2932.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#22", "weight": 0.35, + "amount_krw": 1982006.0, "leg_id": 22, "bb_pos": 0.85, "rsi": 82.0, @@ -1645,6 +1829,7 @@ "price": 2395.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#23", "weight": 0.345, + "amount_krw": 97690.0, "leg_id": 23, "bb_pos": 0.05, "rsi": 22.8, @@ -1657,6 +1842,7 @@ "price": 2457.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#23", "weight": 0.336, + "amount_krw": 145241.0, "leg_id": 23, "bb_pos": 0.0, "rsi": 41.9, @@ -1669,6 +1855,7 @@ "price": 2583.0, "memo": "저점 분할 매수 · 비중 32% · 3회 · BB하단 · leg#23", "weight": 0.32, + "amount_krw": 284191.0, "leg_id": 23, "bb_pos": 0.0, "rsi": 26.1, @@ -1681,6 +1868,7 @@ "price": 2848.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#23", "weight": 0.65, + "amount_krw": 388615.0, "leg_id": 23, "bb_pos": 0.87, "rsi": 84.0, @@ -1693,6 +1881,7 @@ "price": 2800.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#23", "weight": 0.35, + "amount_krw": 205727.0, "leg_id": 23, "bb_pos": 0.559, "rsi": 42.9, @@ -1705,6 +1894,7 @@ "price": 2550.0, "memo": "저점 분할 매수 · 비중 23% · 4회 · BB하단 · leg#24", "weight": 0.231, + "amount_krw": 66179.0, "leg_id": 24, "bb_pos": 0.0, "rsi": 30.3, @@ -1717,6 +1907,7 @@ "price": 2460.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#24", "weight": 0.24, + "amount_krw": 89347.0, "leg_id": 24, "bb_pos": 0.166, "rsi": 12.5, @@ -1729,6 +1920,7 @@ "price": 2217.0, "memo": "저점 분할 매수 · 비중 27% · 4회 · BB하단 · leg#24", "weight": 0.266, + "amount_krw": 143488.0, "leg_id": 24, "bb_pos": 0.182, "rsi": 32.9, @@ -1741,6 +1933,7 @@ "price": 2235.0, "memo": "저점 분할 매수 · 비중 26% · 4회 · BB하단 · leg#24", "weight": 0.264, + "amount_krw": 286008.0, "leg_id": 24, "bb_pos": 0.132, "rsi": 36.6, @@ -1753,6 +1946,7 @@ "price": 2411.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#24", "weight": 0.65, + "amount_krw": 399564.0, "leg_id": 24, "bb_pos": 0.977, "rsi": 92.1, @@ -1765,6 +1959,7 @@ "price": 2384.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#24", "weight": 0.35, + "amount_krw": 212740.0, "leg_id": 24, "bb_pos": 0.715, "rsi": 71.0, @@ -1777,6 +1972,7 @@ "price": 2166.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#25", "weight": 1.0, + "amount_krw": 288109.0, "leg_id": 25, "bb_pos": 0.114, "rsi": 24.6, @@ -1789,6 +1985,7 @@ "price": 2380.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#25", "weight": 0.65, + "amount_krw": 205773.0, "leg_id": 25, "bb_pos": 0.881, "rsi": 80.3, @@ -1801,6 +1998,7 @@ "price": 2368.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#25", "weight": 0.35, + "amount_krw": 110242.0, "leg_id": 25, "bb_pos": 0.685, "rsi": 68.8, @@ -1813,6 +2011,7 @@ "price": 2205.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#26", "weight": 0.499, + "amount_krw": 144455.0, "leg_id": 26, "bb_pos": 0.02, "rsi": 23.1, @@ -1825,6 +2024,7 @@ "price": 2200.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#26", "weight": 0.501, + "amount_krw": 289469.0, "leg_id": 26, "bb_pos": 0.503, "rsi": 52.6, @@ -1837,6 +2037,7 @@ "price": 2394.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#26", "weight": 0.65, + "amount_krw": 306691.0, "leg_id": 26, "bb_pos": 0.968, "rsi": 54.4, @@ -1849,6 +2050,7 @@ "price": 2382.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#26", "weight": 0.35, + "amount_krw": 164313.0, "leg_id": 26, "bb_pos": 0.67, "rsi": 61.6, @@ -1861,6 +2063,7 @@ "price": 2146.0, "memo": "저점 분할 매수 · 비중 49% · 2회 · BB하단 · leg#27", "weight": 0.488, + "amount_krw": 142164.0, "leg_id": 27, "bb_pos": 0.159, "rsi": 22.2, @@ -1873,6 +2076,7 @@ "price": 2047.0, "memo": "저점 분할 매수 · 비중 51% · 2회 · BB하단 · leg#27", "weight": 0.512, + "amount_krw": 290989.0, "leg_id": 27, "bb_pos": 0.188, "rsi": 34.4, @@ -1885,6 +2089,7 @@ "price": 2217.0, "memo": "고점 매도 · 비중 100% · 1회 · leg#27", "weight": 1.0, + "amount_krw": 462023.0, "leg_id": 27, "bb_pos": 0.962, "rsi": 74.7, @@ -1897,6 +2102,7 @@ "price": 2059.0, "memo": "저점 분할 매수 · 비중 51% · 2회 · BB하단 · leg#28", "weight": 0.513, + "amount_krw": 150176.0, "leg_id": 28, "bb_pos": 0.312, "rsi": 29.6, @@ -1909,6 +2115,7 @@ "price": 2165.0, "memo": "저점 분할 매수 · 비중 49% · 2회 · BB하단 · leg#28", "weight": 0.487, + "amount_krw": 293124.0, "leg_id": 28, "bb_pos": 0.028, "rsi": 18.4, @@ -1921,6 +2128,7 @@ "price": 2327.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#28", "weight": 0.65, + "amount_krw": 315107.0, "leg_id": 28, "bb_pos": 1.0, "rsi": 89.6, @@ -1933,6 +2141,7 @@ "price": 2270.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#28", "weight": 0.35, + "amount_krw": 165517.0, "leg_id": 28, "bb_pos": 0.547, "rsi": 38.8, @@ -1945,6 +2154,7 @@ "price": 1851.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#29", "weight": 0.196, + "amount_krw": 57796.0, "leg_id": 29, "bb_pos": 0.0, "rsi": 33.3, @@ -1957,6 +2167,7 @@ "price": 1875.0, "memo": "저점 분할 매수 · 비중 19% · 5회 · BB하단 · leg#29", "weight": 0.193, + "amount_krw": 70812.0, "leg_id": 29, "bb_pos": 0.28, "rsi": 51.6, @@ -1969,6 +2180,7 @@ "price": 1794.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#29", "weight": 0.202, + "amount_krw": 97470.0, "leg_id": 29, "bb_pos": 0.135, "rsi": 38.5, @@ -1981,6 +2193,7 @@ "price": 1775.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#29", "weight": 0.204, + "amount_krw": 147110.0, "leg_id": 29, "bb_pos": 0.42, "rsi": 49.0, @@ -1993,6 +2206,7 @@ "price": 1773.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#29", "weight": 0.204, + "amount_krw": 294195.0, "leg_id": 29, "bb_pos": 0.552, "rsi": 53.5, @@ -2005,6 +2219,7 @@ "price": 1975.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#29", "weight": 0.65, + "amount_krw": 477723.0, "leg_id": 29, "bb_pos": 1.0, "rsi": 92.2, @@ -2017,6 +2232,7 @@ "price": 1944.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#29", "weight": 0.35, + "amount_krw": 253198.0, "leg_id": 29, "bb_pos": 0.655, "rsi": 67.6, @@ -2029,6 +2245,7 @@ "price": 1792.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#30", "weight": 0.202, + "amount_krw": 60141.0, "leg_id": 30, "bb_pos": 0.0, "rsi": 42.9, @@ -2041,6 +2258,7 @@ "price": 1795.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#30", "weight": 0.202, + "amount_krw": 75365.0, "leg_id": 30, "bb_pos": 0.034, "rsi": 17.4, @@ -2053,6 +2271,7 @@ "price": 1758.0, "memo": "저점 분할 매수 · 비중 21% · 5회 · BB하단 · leg#30", "weight": 0.206, + "amount_krw": 102858.0, "leg_id": 30, "bb_pos": 0.242, "rsi": 45.2, @@ -2065,6 +2284,7 @@ "price": 1849.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#30", "weight": 0.196, + "amount_krw": 149862.0, "leg_id": 30, "bb_pos": 0.108, "rsi": 33.9, @@ -2077,6 +2297,7 @@ "price": 1871.0, "memo": "저점 분할 매수 · 비중 19% · 5회 · BB하단 · leg#30", "weight": 0.194, + "amount_krw": 298428.0, "leg_id": 30, "bb_pos": 0.18, "rsi": 24.4, @@ -2089,6 +2310,7 @@ "price": 1964.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#30", "weight": 0.65, + "amount_krw": 478224.0, "leg_id": 30, "bb_pos": 1.0, "rsi": 82.7, @@ -2101,6 +2323,7 @@ "price": 1950.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#30", "weight": 0.35, + "amount_krw": 255670.0, "leg_id": 30, "bb_pos": 0.712, "rsi": 61.2, @@ -2113,6 +2336,7 @@ "price": 1795.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#31", "weight": 0.327, + "amount_krw": 98117.0, "leg_id": 31, "bb_pos": 0.0, "rsi": 28.6, @@ -2125,6 +2349,7 @@ "price": 1792.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#31", "weight": 0.328, + "amount_krw": 146232.0, "leg_id": 31, "bb_pos": 0.339, "rsi": 25.0, @@ -2137,6 +2362,7 @@ "price": 1704.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#31", "weight": 0.345, + "amount_krw": 299439.0, "leg_id": 31, "bb_pos": 0.169, "rsi": 15.4, @@ -2149,6 +2375,7 @@ "price": 1843.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#31", "weight": 0.65, + "amount_krw": 373750.0, "leg_id": 31, "bb_pos": 1.0, "rsi": 72.2, @@ -2161,6 +2388,7 @@ "price": 1843.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#31", "weight": 0.35, + "amount_krw": 201250.0, "leg_id": 31, "bb_pos": 0.808, "rsi": 55.9, @@ -2173,6 +2401,7 @@ "price": 1728.0, "memo": "저점 분할 매수 · 비중 51% · 2회 · BB하단 · leg#32", "weight": 0.508, + "amount_krw": 153205.0, "leg_id": 32, "bb_pos": 0.082, "rsi": 35.0, @@ -2185,6 +2414,7 @@ "price": 1786.0, "memo": "저점 분할 매수 · 비중 49% · 2회 · BB하단 · leg#32", "weight": 0.492, + "amount_krw": 301839.0, "leg_id": 32, "bb_pos": 0.292, "rsi": 39.1, @@ -2197,6 +2427,7 @@ "price": 1888.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#32", "weight": 0.65, + "amount_krw": 316204.0, "leg_id": 32, "bb_pos": 1.0, "rsi": 87.0, @@ -2209,6 +2440,7 @@ "price": 1887.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#32", "weight": 0.35, + "amount_krw": 170174.0, "leg_id": 32, "bb_pos": 0.816, "rsi": 74.7, @@ -2221,6 +2453,7 @@ "price": 1149.0, "memo": "저점 분할 매수 · 비중 53% · 2회 · BB하단 · leg#33", "weight": 0.533, + "amount_krw": 3231350.0, "leg_id": 33, "bb_pos": 0.0, "rsi": 6.5, @@ -2233,6 +2466,7 @@ "price": 1312.0, "memo": "저점 분할 매수 · 비중 47% · 2회 · BB하단 · leg#33", "weight": 0.467, + "amount_krw": 2828191.0, "leg_id": 33, "bb_pos": 0.14, "rsi": 28.0, @@ -2245,6 +2479,7 @@ "price": 1500.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#33", "weight": 0.65, + "amount_krw": 4843750.0, "leg_id": 33, "bb_pos": 0.96, "rsi": 73.8, @@ -2257,6 +2492,7 @@ "price": 1498.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#33", "weight": 0.35, + "amount_krw": 2604695.0, "leg_id": 33, "bb_pos": 0.734, "rsi": 70.5, @@ -2269,6 +2505,7 @@ "price": 1375.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#34", "weight": 0.499, + "amount_krw": 185746.0, "leg_id": 34, "bb_pos": 0.218, "rsi": 23.8, @@ -2281,6 +2518,7 @@ "price": 1370.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#34", "weight": 0.501, + "amount_krw": 372198.0, "leg_id": 34, "bb_pos": 0.043, "rsi": 19.4, @@ -2293,6 +2531,7 @@ "price": 1566.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#34", "weight": 0.65, + "amount_krw": 414046.0, "leg_id": 34, "bb_pos": 0.704, "rsi": 56.6, @@ -2305,6 +2544,7 @@ "price": 1565.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#34", "weight": 0.35, + "amount_krw": 222806.0, "leg_id": 34, "bb_pos": 0.923, "rsi": 72.3, @@ -2317,6 +2557,7 @@ "price": 1517.0, "memo": "저점 분할 매수 · 비중 13% · 7회 · BB하단 · leg#35", "weight": 0.13, + "amount_krw": 48856.0, "leg_id": 35, "bb_pos": 0.0, "rsi": 31.1, @@ -2329,6 +2570,7 @@ "price": 1464.0, "memo": "저점 분할 매수 · 비중 13% · 7회 · BB하단 · leg#35", "weight": 0.135, + "amount_krw": 58355.0, "leg_id": 35, "bb_pos": 0.198, "rsi": 28.1, @@ -2341,6 +2583,7 @@ "price": 1367.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#35", "weight": 0.144, + "amount_krw": 73609.0, "leg_id": 35, "bb_pos": 0.0, "rsi": 12.5, @@ -2353,6 +2596,7 @@ "price": 1395.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#35", "weight": 0.141, + "amount_krw": 89679.0, "leg_id": 35, "bb_pos": 0.186, "rsi": 46.2, @@ -2365,6 +2609,7 @@ "price": 1262.0, "memo": "저점 분할 매수 · 비중 16% · 7회 · BB하단 · leg#35", "weight": 0.156, + "amount_krw": 129869.0, "leg_id": 35, "bb_pos": 0.083, "rsi": 34.2, @@ -2377,6 +2622,7 @@ "price": 1322.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#35", "weight": 0.149, + "amount_krw": 190303.0, "leg_id": 35, "bb_pos": 0.055, "rsi": 35.3, @@ -2389,6 +2635,7 @@ "price": 1356.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#35", "weight": 0.145, + "amount_krw": 376236.0, "leg_id": 35, "bb_pos": 0.023, "rsi": 3.8, @@ -2401,6 +2648,7 @@ "price": 1407.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#35", "weight": 0.65, + "amount_krw": 653462.0, "leg_id": 35, "bb_pos": 0.953, "rsi": 70.0, @@ -2413,6 +2661,7 @@ "price": 1407.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#35", "weight": 0.35, + "amount_krw": 351864.0, "leg_id": 35, "bb_pos": 0.768, "rsi": 60.0, @@ -2425,6 +2674,7 @@ "price": 1329.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#36", "weight": 0.243, + "amount_krw": 91860.0, "leg_id": 36, "bb_pos": 0.117, "rsi": 33.3, @@ -2437,6 +2687,7 @@ "price": 1250.0, "memo": "저점 분할 매수 · 비중 26% · 4회 · BB하단 · leg#36", "weight": 0.258, + "amount_krw": 128744.0, "leg_id": 36, "bb_pos": 0.0, "rsi": 23.5, @@ -2449,6 +2700,7 @@ "price": 1286.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#36", "weight": 0.251, + "amount_krw": 190164.0, "leg_id": 36, "bb_pos": 0.383, "rsi": 46.9, @@ -2461,6 +2713,7 @@ "price": 1301.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#36", "weight": 0.248, + "amount_krw": 378290.0, "leg_id": 36, "bb_pos": 0.049, "rsi": 31.2, @@ -2473,6 +2726,7 @@ "price": 1416.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#36", "weight": 0.65, + "amount_krw": 562140.0, "leg_id": 36, "bb_pos": 1.0, "rsi": 72.7, @@ -2485,6 +2739,7 @@ "price": 1415.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#36", "weight": 0.35, + "amount_krw": 302477.0, "leg_id": 36, "bb_pos": 0.756, "rsi": 60.6, @@ -2497,6 +2752,7 @@ "price": 1263.0, "memo": "저점 분할 매수 · 비중 32% · 3회 · BB하단 · leg#37", "weight": 0.317, + "amount_krw": 121018.0, "leg_id": 37, "bb_pos": 0.0, "rsi": 38.8, @@ -2509,6 +2765,7 @@ "price": 1170.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#37", "weight": 0.342, + "amount_krw": 190935.0, "leg_id": 37, "bb_pos": 0.113, "rsi": 28.6, @@ -2521,6 +2778,7 @@ "price": 1176.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#37", "weight": 0.341, + "amount_krw": 381384.0, "leg_id": 37, "bb_pos": 0.13, "rsi": 14.7, @@ -2533,6 +2791,7 @@ "price": 1325.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#37", "weight": 0.65, + "amount_krw": 502381.0, "leg_id": 37, "bb_pos": 0.946, "rsi": 88.7, @@ -2545,6 +2804,7 @@ "price": 1321.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#37", "weight": 0.35, + "amount_krw": 269696.0, "leg_id": 37, "bb_pos": 0.782, "rsi": 77.6, @@ -2557,6 +2817,7 @@ "price": 1266.0, "memo": "저점 분할 매수 · 비중 12% · 7회 · BB하단 · leg#38", "weight": 0.117, + "amount_krw": 45167.0, "leg_id": 38, "bb_pos": 0.177, "rsi": 5.8, @@ -2569,6 +2830,7 @@ "price": 1037.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#38", "weight": 0.143, + "amount_krw": 62461.0, "leg_id": 38, "bb_pos": 0.055, "rsi": 41.7, @@ -2581,6 +2843,7 @@ "price": 1038.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#38", "weight": 0.143, + "amount_krw": 74548.0, "leg_id": 38, "bb_pos": 0.228, "rsi": 37.9, @@ -2593,6 +2856,7 @@ "price": 1020.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#38", "weight": 0.146, + "amount_krw": 94337.0, "leg_id": 38, "bb_pos": 0.0, "rsi": 15.6, @@ -2605,6 +2869,7 @@ "price": 991.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#38", "weight": 0.15, + "amount_krw": 128240.0, "leg_id": 38, "bb_pos": 0.057, "rsi": 8.3, @@ -2617,6 +2882,7 @@ "price": 983.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#38", "weight": 0.151, + "amount_krw": 193563.0, "leg_id": 38, "bb_pos": 0.0, "rsi": 21.1, @@ -2629,6 +2895,7 @@ "price": 997.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#38", "weight": 0.149, + "amount_krw": 384968.0, "leg_id": 38, "bb_pos": 0.0, "rsi": 22.4, @@ -2641,6 +2908,7 @@ "price": 1112.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#38", "weight": 0.65, + "amount_krw": 703036.0, "leg_id": 38, "bb_pos": 1.0, "rsi": 81.2, @@ -2653,6 +2921,7 @@ "price": 1111.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#38", "weight": 0.35, + "amount_krw": 378218.0, "leg_id": 38, "bb_pos": 0.805, "rsi": 60.0, @@ -2665,6 +2934,7 @@ "price": 1038.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#39", "weight": 1.0, + "amount_krw": 390507.0, "leg_id": 39, "bb_pos": 0.0, "rsi": 27.3, @@ -2677,6 +2947,7 @@ "price": 1141.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#39", "weight": 0.65, + "amount_krw": 279017.0, "leg_id": 39, "bb_pos": 1.0, "rsi": 75.0, @@ -2689,6 +2960,7 @@ "price": 1132.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#39", "weight": 0.35, + "amount_krw": 149055.0, "leg_id": 39, "bb_pos": 0.66, "rsi": 67.4, @@ -2701,6 +2973,7 @@ "price": 1074.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#40", "weight": 1.0, + "amount_krw": 7843375.0, "leg_id": 40, "bb_pos": 0.052, "rsi": 11.1, @@ -2713,6 +2986,7 @@ "price": 1288.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#40", "weight": 0.65, + "amount_krw": 6114035.0, "leg_id": 40, "bb_pos": 1.0, "rsi": 84.2, @@ -2725,6 +2999,7 @@ "price": 1266.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#40", "weight": 0.35, + "amount_krw": 3235940.0, "leg_id": 40, "bb_pos": 0.895, "rsi": 70.6, @@ -2737,6 +3012,7 @@ "price": 1165.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#41", "weight": 0.076, + "amount_krw": 35477.0, "leg_id": 41, "bb_pos": 0.094, "rsi": 26.9, @@ -2749,6 +3025,7 @@ "price": 1201.0, "memo": "저점 분할 매수 · 비중 7% · 12회 · BB하단 · leg#41", "weight": 0.074, + "amount_krw": 37386.0, "leg_id": 41, "bb_pos": 0.17, "rsi": 18.8, @@ -2761,6 +3038,7 @@ "price": 1147.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#41", "weight": 0.077, + "amount_krw": 42269.0, "leg_id": 41, "bb_pos": 0.172, "rsi": 47.8, @@ -2773,6 +3051,7 @@ "price": 1122.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#41", "weight": 0.079, + "amount_krw": 47668.0, "leg_id": 41, "bb_pos": 0.184, "rsi": 29.0, @@ -2785,6 +3064,7 @@ "price": 1101.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#41", "weight": 0.08, + "amount_krw": 53741.0, "leg_id": 41, "bb_pos": 0.153, "rsi": 36.1, @@ -2797,6 +3077,7 @@ "price": 1070.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#41", "weight": 0.083, + "amount_krw": 62970.0, "leg_id": 41, "bb_pos": 0.0, "rsi": 18.6, @@ -2809,6 +3090,7 @@ "price": 1064.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#41", "weight": 0.083, + "amount_krw": 72782.0, "leg_id": 41, "bb_pos": 0.0, "rsi": 29.4, @@ -2821,6 +3103,7 @@ "price": 1058.0, "memo": "저점 분할 매수 · 비중 8% · 12회 · BB하단 · leg#41", "weight": 0.084, + "amount_krw": 87257.0, "leg_id": 41, "bb_pos": 0.0, "rsi": 28.8, @@ -2833,6 +3116,7 @@ "price": 988.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#41", "weight": 0.09, + "amount_krw": 114660.0, "leg_id": 41, "bb_pos": 0.244, "rsi": 39.3, @@ -2845,6 +3129,7 @@ "price": 985.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#41", "weight": 0.09, + "amount_krw": 152159.0, "leg_id": 41, "bb_pos": 0.058, "rsi": 10.7, @@ -2857,6 +3142,7 @@ "price": 960.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#41", "weight": 0.092, + "amount_krw": 230790.0, "leg_id": 41, "bb_pos": 0.0, "rsi": 25.9, @@ -2869,6 +3155,7 @@ "price": 950.0, "memo": "저점 분할 매수 · 비중 9% · 12회 · BB하단 · leg#41", "weight": 0.093, + "amount_krw": 463627.0, "leg_id": 41, "bb_pos": 0.328, "rsi": 50.0, @@ -2881,6 +3168,7 @@ "price": 1056.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#41", "weight": 0.65, + "amount_krw": 959876.0, "leg_id": 41, "bb_pos": 0.887, "rsi": 68.3, @@ -2893,6 +3181,7 @@ "price": 1053.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#41", "weight": 0.35, + "amount_krw": 515388.0, "leg_id": 41, "bb_pos": 0.518, "rsi": 47.8, @@ -2905,6 +3194,7 @@ "price": 960.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.085, + "amount_krw": 40028.0, "leg_id": 42, "bb_pos": 0.174, "rsi": 15.0, @@ -2917,6 +3207,7 @@ "price": 871.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.094, + "amount_krw": 48359.0, "leg_id": 42, "bb_pos": 0.0, "rsi": 23.5, @@ -2929,6 +3220,7 @@ "price": 891.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.092, + "amount_krw": 52760.0, "leg_id": 42, "bb_pos": 0.182, "rsi": 31.2, @@ -2941,6 +3233,7 @@ "price": 865.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.095, + "amount_krw": 61329.0, "leg_id": 42, "bb_pos": 0.098, "rsi": 13.5, @@ -2953,6 +3246,7 @@ "price": 880.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.093, + "amount_krw": 69059.0, "leg_id": 42, "bb_pos": 0.206, "rsi": 40.5, @@ -2965,6 +3259,7 @@ "price": 911.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.09, + "amount_krw": 78399.0, "leg_id": 42, "bb_pos": 0.0, "rsi": 22.7, @@ -2977,6 +3272,7 @@ "price": 901.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.091, + "amount_krw": 95049.0, "leg_id": 42, "bb_pos": 0.218, "rsi": 14.3, @@ -2989,6 +3285,7 @@ "price": 925.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.088, + "amount_krw": 115295.0, "leg_id": 42, "bb_pos": 0.0, "rsi": 24.1, @@ -3001,6 +3298,7 @@ "price": 925.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.088, + "amount_krw": 152595.0, "leg_id": 42, "bb_pos": 0.081, "rsi": 36.0, @@ -3013,6 +3311,7 @@ "price": 953.0, "memo": "저점 분할 매수 · 비중 9% · 11회 · BB하단 · leg#42", "weight": 0.086, + "amount_krw": 220961.0, "leg_id": 42, "bb_pos": 0.217, "rsi": 26.3, @@ -3025,6 +3324,7 @@ "price": 836.0, "memo": "저점 분할 매수 · 비중 10% · 11회 · BB하단 · leg#42", "weight": 0.098, + "amount_krw": 466789.0, "leg_id": 42, "bb_pos": 0.245, "rsi": 33.3, @@ -3037,6 +3337,7 @@ "price": 982.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#42", "weight": 0.65, + "amount_krw": 1006719.0, "leg_id": 42, "bb_pos": 0.861, "rsi": 71.8, @@ -3049,6 +3350,7 @@ "price": 981.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#42", "weight": 0.35, + "amount_krw": 541528.0, "leg_id": 42, "bb_pos": 0.798, "rsi": 50.0, @@ -3061,6 +3363,7 @@ "price": 923.0, "memo": "저점 분할 매수 · 비중 19% · 5회 · BB하단 · leg#43", "weight": 0.187, + "amount_krw": 89428.0, "leg_id": 43, "bb_pos": 0.0, "rsi": 9.5, @@ -3073,6 +3376,7 @@ "price": 846.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#43", "weight": 0.204, + "amount_krw": 119903.0, "leg_id": 43, "bb_pos": 0.061, "rsi": 40.0, @@ -3085,6 +3389,7 @@ "price": 835.0, "memo": "저점 분할 매수 · 비중 21% · 5회 · BB하단 · leg#43", "weight": 0.206, + "amount_krw": 161592.0, "leg_id": 43, "bb_pos": 0.25, "rsi": 30.6, @@ -3097,6 +3402,7 @@ "price": 840.0, "memo": "저점 분할 매수 · 비중 21% · 5회 · BB하단 · leg#43", "weight": 0.205, + "amount_krw": 243059.0, "leg_id": 43, "bb_pos": 0.303, "rsi": 52.4, @@ -3109,6 +3415,7 @@ "price": 868.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#43", "weight": 0.198, + "amount_krw": 478823.0, "leg_id": 43, "bb_pos": 0.164, "rsi": 33.3, @@ -3121,6 +3428,7 @@ "price": 919.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#43", "weight": 0.65, + "amount_krw": 760508.0, "leg_id": 43, "bb_pos": 1.0, "rsi": 87.1, @@ -3133,6 +3441,7 @@ "price": 918.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#43", "weight": 0.35, + "amount_krw": 409059.0, "leg_id": 43, "bb_pos": 0.865, "rsi": 69.7, @@ -3145,6 +3454,7 @@ "price": 876.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#44", "weight": 1.0, + "amount_krw": 482006.0, "leg_id": 44, "bb_pos": 0.11, "rsi": 23.5, @@ -3157,6 +3467,7 @@ "price": 961.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#44", "weight": 0.65, + "amount_krw": 343704.0, "leg_id": 44, "bb_pos": 1.0, "rsi": 66.0, @@ -3169,6 +3480,7 @@ "price": 953.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#44", "weight": 0.35, + "amount_krw": 183531.0, "leg_id": 44, "bb_pos": 0.591, "rsi": 58.8, @@ -3181,6 +3493,7 @@ "price": 866.0, "memo": "저점 분할 매수 · 비중 13% · 7회 · BB하단 · leg#45", "weight": 0.13, + "amount_krw": 62951.0, "leg_id": 45, "bb_pos": 0.194, "rsi": 46.2, @@ -3193,6 +3506,7 @@ "price": 848.0, "memo": "저점 분할 매수 · 비중 13% · 7회 · BB하단 · leg#45", "weight": 0.132, + "amount_krw": 73461.0, "leg_id": 45, "bb_pos": 0.248, "rsi": 31.6, @@ -3205,6 +3519,7 @@ "price": 835.0, "memo": "저점 분할 매수 · 비중 13% · 7회 · BB하단 · leg#45", "weight": 0.134, + "amount_krw": 87893.0, "leg_id": 45, "bb_pos": 0.649, "rsi": 50.0, @@ -3217,6 +3532,7 @@ "price": 776.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#45", "weight": 0.145, + "amount_krw": 116021.0, "leg_id": 45, "bb_pos": 0.089, "rsi": 9.5, @@ -3229,6 +3545,7 @@ "price": 763.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#45", "weight": 0.147, + "amount_krw": 154691.0, "leg_id": 45, "bb_pos": 0.009, "rsi": 12.5, @@ -3241,6 +3558,7 @@ "price": 731.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#45", "weight": 0.154, + "amount_krw": 237922.0, "leg_id": 45, "bb_pos": 0.22, "rsi": 42.9, @@ -3253,6 +3571,7 @@ "price": 708.0, "memo": "저점 분할 매수 · 비중 16% · 7회 · BB하단 · leg#45", "weight": 0.158, + "amount_krw": 480935.0, "leg_id": 45, "bb_pos": 0.29, "rsi": 45.5, @@ -3265,6 +3584,7 @@ "price": 784.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#45", "weight": 0.65, + "amount_krw": 826365.0, "leg_id": 45, "bb_pos": 0.771, "rsi": 54.5, @@ -3277,6 +3597,7 @@ "price": 783.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#45", "weight": 0.35, + "amount_krw": 444398.0, "leg_id": 45, "bb_pos": 0.674, "rsi": 56.5, @@ -3289,6 +3610,7 @@ "price": 741.0, "memo": "저점 분할 매수 · 비중 19% · 5회 · BB하단 · leg#46", "weight": 0.192, + "amount_krw": 1868306.0, "leg_id": 46, "bb_pos": 0.164, "rsi": 14.3, @@ -3301,6 +3623,7 @@ "price": 708.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#46", "weight": 0.201, + "amount_krw": 2399168.0, "leg_id": 46, "bb_pos": 0.02, "rsi": 40.0, @@ -3313,6 +3636,7 @@ "price": 704.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#46", "weight": 0.202, + "amount_krw": 3199944.0, "leg_id": 46, "bb_pos": 0.14, "rsi": 40.0, @@ -3325,6 +3649,7 @@ "price": 711.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#46", "weight": 0.2, + "amount_krw": 2268205.0, "leg_id": 46, "bb_pos": 0.4, "rsi": 50.0, @@ -3337,6 +3662,7 @@ "price": 689.0, "memo": "저점 분할 매수 · 비중 21% · 5회 · BB하단 · leg#46", "weight": 0.206, + "amount_krw": 0, "leg_id": 46, "bb_pos": 0.287, "rsi": 45.5, @@ -3349,6 +3675,7 @@ "price": 860.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#46", "weight": 0.65, + "amount_krw": 7627847.0, "leg_id": 46, "bb_pos": 1.0, "rsi": 88.0, @@ -3361,6 +3688,7 @@ "price": 854.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#46", "weight": 0.35, + "amount_krw": 4078647.0, "leg_id": 46, "bb_pos": 0.761, "rsi": 77.0, @@ -3373,6 +3701,7 @@ "price": 806.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#47", "weight": 0.343, + "amount_krw": 200666.0, "leg_id": 47, "bb_pos": 0.171, "rsi": 30.8, @@ -3385,6 +3714,7 @@ "price": 828.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#47", "weight": 0.334, + "amount_krw": 297550.0, "leg_id": 47, "bb_pos": 0.516, "rsi": 53.8, @@ -3397,6 +3727,7 @@ "price": 855.0, "memo": "저점 분할 매수 · 비중 32% · 3회 · BB하단 · leg#47", "weight": 0.323, + "amount_krw": 586115.0, "leg_id": 47, "bb_pos": 0.0, "rsi": 12.0, @@ -3409,6 +3740,7 @@ "price": 949.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#47", "weight": 0.65, + "amount_krw": 798105.0, "leg_id": 47, "bb_pos": 1.0, "rsi": 83.8, @@ -3421,6 +3753,7 @@ "price": 928.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#47", "weight": 0.35, + "amount_krw": 420239.0, "leg_id": 47, "bb_pos": 0.334, "rsi": 48.9, @@ -3433,6 +3766,7 @@ "price": 897.0, "memo": "저점 분할 매수 · 비중 12% · 8회 · BB하단 · leg#48", "weight": 0.118, + "amount_krw": 69818.0, "leg_id": 48, "bb_pos": 0.02, "rsi": 16.7, @@ -3445,6 +3779,7 @@ "price": 887.0, "memo": "저점 분할 매수 · 비중 12% · 8회 · BB하단 · leg#48", "weight": 0.12, + "amount_krw": 80494.0, "leg_id": 48, "bb_pos": 0.0, "rsi": 24.3, @@ -3457,6 +3792,7 @@ "price": 891.0, "memo": "저점 분할 매수 · 비중 12% · 8회 · BB하단 · leg#48", "weight": 0.119, + "amount_krw": 92399.0, "leg_id": 48, "bb_pos": 0.158, "rsi": 35.0, @@ -3469,6 +3805,7 @@ "price": 830.0, "memo": "저점 분할 매수 · 비중 13% · 8회 · BB하단 · leg#48", "weight": 0.128, + "amount_krw": 117615.0, "leg_id": 48, "bb_pos": 0.572, "rsi": 47.4, @@ -3481,6 +3818,7 @@ "price": 838.0, "memo": "저점 분할 매수 · 비중 13% · 8회 · BB하단 · leg#48", "weight": 0.127, + "amount_krw": 145741.0, "leg_id": 48, "bb_pos": 0.0, "rsi": 33.3, @@ -3493,6 +3831,7 @@ "price": 823.0, "memo": "저점 분할 매수 · 비중 13% · 8회 · BB하단 · leg#48", "weight": 0.129, + "amount_krw": 196343.0, "leg_id": 48, "bb_pos": 0.223, "rsi": 43.8, @@ -3505,6 +3844,7 @@ "price": 821.0, "memo": "저점 분할 매수 · 비중 13% · 8회 · BB하단 · leg#48", "weight": 0.129, + "amount_krw": 294092.0, "leg_id": 48, "bb_pos": 0.085, "rsi": 35.3, @@ -3517,6 +3857,7 @@ "price": 818.0, "memo": "저점 분할 매수 · 비중 13% · 8회 · BB하단 · leg#48", "weight": 0.13, + "amount_krw": 590279.0, "leg_id": 48, "bb_pos": 0.0, "rsi": 46.2, @@ -3529,6 +3870,7 @@ "price": 917.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#48", "weight": 0.65, + "amount_krw": 1136250.0, "leg_id": 48, "bb_pos": 0.783, "rsi": 91.9, @@ -3541,6 +3883,7 @@ "price": 916.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#48", "weight": 0.35, + "amount_krw": 611160.0, "leg_id": 48, "bb_pos": 0.718, "rsi": 73.7, @@ -3553,6 +3896,7 @@ "price": 848.0, "memo": "저점 분할 매수 · 비중 8% · 10회 · BB하단 · leg#49", "weight": 0.084, + "amount_krw": 1007367.0, "leg_id": 49, "bb_pos": 0.243, "rsi": 20.8, @@ -3565,6 +3909,7 @@ "price": 801.0, "memo": "저점 분할 매수 · 비중 9% · 10회 · BB하단 · leg#49", "weight": 0.088, + "amount_krw": 1146702.0, "leg_id": 49, "bb_pos": 0.078, "rsi": 23.1, @@ -3577,6 +3922,7 @@ "price": 712.0, "memo": "저점 분할 매수 · 비중 10% · 10회 · BB하단 · leg#49", "weight": 0.099, + "amount_krw": 1399202.0, "leg_id": 49, "bb_pos": 0.093, "rsi": 12.8, @@ -3589,6 +3935,7 @@ "price": 694.0, "memo": "저점 분할 매수 · 비중 10% · 10회 · BB하단 · leg#49", "weight": 0.102, + "amount_krw": 1625731.0, "leg_id": 49, "bb_pos": 0.434, "rsi": 41.7, @@ -3601,6 +3948,7 @@ "price": 699.0, "memo": "저점 분할 매수 · 비중 10% · 10회 · BB하단 · leg#49", "weight": 0.101, + "amount_krw": 1877121.0, "leg_id": 49, "bb_pos": 0.292, "rsi": 36.4, @@ -3613,6 +3961,7 @@ "price": 690.0, "memo": "저점 분할 매수 · 비중 10% · 10회 · BB하단 · leg#49", "weight": 0.103, + "amount_krw": 2264740.0, "leg_id": 49, "bb_pos": 0.206, "rsi": 31.0, @@ -3625,6 +3974,7 @@ "price": 694.0, "memo": "저점 분할 매수 · 비중 10% · 10회 · BB하단 · leg#49", "weight": 0.102, + "amount_krw": 2665609.0, "leg_id": 49, "bb_pos": 0.0, "rsi": 30.4, @@ -3637,6 +3987,7 @@ "price": 688.0, "memo": "저점 분할 매수 · 비중 10% · 10회 · BB하단 · leg#49", "weight": 0.103, + "amount_krw": 0, "leg_id": 49, "bb_pos": 0.19, "rsi": 23.8, @@ -3649,6 +4000,7 @@ "price": 645.0, "memo": "저점 분할 매수 · 비중 11% · 10회 · BB하단 · leg#49", "weight": 0.11, + "amount_krw": 0, "leg_id": 49, "bb_pos": 0.06, "rsi": 42.1, @@ -3661,6 +4013,7 @@ "price": 656.0, "memo": "저점 분할 매수 · 비중 11% · 10회 · BB하단 · leg#49", "weight": 0.108, + "amount_krw": 0, "leg_id": 49, "bb_pos": 0.152, "rsi": 16.7, @@ -3673,6 +4026,7 @@ "price": 915.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#49", "weight": 0.65, + "amount_krw": 9953646.0, "leg_id": 49, "bb_pos": 0.972, "rsi": 93.1, @@ -3685,6 +4039,7 @@ "price": 913.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#49", "weight": 0.35, + "amount_krw": 5347940.0, "leg_id": 49, "bb_pos": 0.757, "rsi": 64.6, @@ -3697,6 +4052,7 @@ "price": 693.0, "memo": "저점 분할 매수 · 비중 23% · 4회 · BB하단 · leg#50", "weight": 0.233, + "amount_krw": 178174.0, "leg_id": 50, "bb_pos": 0.161, "rsi": 41.7, @@ -3709,6 +4065,7 @@ "price": 662.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#50", "weight": 0.244, + "amount_krw": 243139.0, "leg_id": 50, "bb_pos": 0.194, "rsi": 26.3, @@ -3721,6 +4078,7 @@ "price": 669.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#50", "weight": 0.241, + "amount_krw": 352287.0, "leg_id": 50, "bb_pos": 0.091, "rsi": 33.3, @@ -3733,6 +4091,7 @@ "price": 572.0, "memo": "저점 분할 매수 · 비중 28% · 4회 · BB하단 · leg#50", "weight": 0.282, + "amount_krw": 758915.0, "leg_id": 50, "bb_pos": 0.0, "rsi": 24.0, @@ -3745,6 +4104,7 @@ "price": 622.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#50", "weight": 0.65, + "amount_krw": 1001753.0, "leg_id": 50, "bb_pos": 0.809, "rsi": 47.4, @@ -3757,6 +4117,7 @@ "price": 614.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#50", "weight": 0.35, + "amount_krw": 532468.0, "leg_id": 50, "bb_pos": 0.594, "rsi": 68.8, @@ -3769,6 +4130,7 @@ "price": 570.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#51", "weight": 0.203, + "amount_krw": 155391.0, "leg_id": 51, "bb_pos": 0.0, "rsi": 38.5, @@ -3781,6 +4143,7 @@ "price": 574.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#51", "weight": 0.202, + "amount_krw": 194071.0, "leg_id": 51, "bb_pos": 0.104, "rsi": 22.2, @@ -3793,6 +4156,7 @@ "price": 572.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#51", "weight": 0.202, + "amount_krw": 260046.0, "leg_id": 51, "bb_pos": 0.125, "rsi": 40.9, @@ -3805,6 +4169,7 @@ "price": 590.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#51", "weight": 0.196, + "amount_krw": 382821.0, "leg_id": 51, "bb_pos": 0.103, "rsi": 22.2, @@ -3817,6 +4182,7 @@ "price": 590.0, "memo": "저점 분할 매수 · 비중 20% · 5회 · BB하단 · leg#51", "weight": 0.196, + "amount_krw": 765633.0, "leg_id": 51, "bb_pos": 0.134, "rsi": 23.1, @@ -3829,6 +4195,7 @@ "price": 626.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#51", "weight": 0.65, + "amount_krw": 1225533.0, "leg_id": 51, "bb_pos": 0.97, "rsi": 87.8, @@ -3841,6 +4208,7 @@ "price": 626.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#51", "weight": 0.35, + "amount_krw": 659902.0, "leg_id": 51, "bb_pos": 0.819, "rsi": 77.8, @@ -3853,6 +4221,7 @@ "price": 588.0, "memo": "저점 분할 매수 · 비중 15% · 6회 · BB하단 · leg#52", "weight": 0.148, + "amount_krw": 2282124.0, "leg_id": 52, "bb_pos": 0.0, "rsi": 42.9, @@ -3865,6 +4234,7 @@ "price": 503.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#52", "weight": 0.173, + "amount_krw": 3063789.0, "leg_id": 52, "bb_pos": 0.064, "rsi": 34.3, @@ -3877,6 +4247,7 @@ "price": 461.0, "memo": "저점 분할 매수 · 비중 19% · 6회 · BB하단 · leg#52", "weight": 0.188, + "amount_krw": 4061341.0, "leg_id": 52, "bb_pos": 0.0, "rsi": 15.1, @@ -3889,6 +4260,7 @@ "price": 504.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#52", "weight": 0.172, + "amount_krw": 5420611.0, "leg_id": 52, "bb_pos": 0.423, "rsi": 43.8, @@ -3901,6 +4273,7 @@ "price": 509.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#52", "weight": 0.171, + "amount_krw": 584188.0, "leg_id": 52, "bb_pos": 0.178, "rsi": 23.8, @@ -3913,6 +4286,7 @@ "price": 587.0, "memo": "저점 분할 매수 · 비중 15% · 6회 · BB하단 · leg#52", "weight": 0.148, + "amount_krw": 0, "leg_id": 52, "bb_pos": 0.097, "rsi": 41.7, @@ -3925,6 +4299,7 @@ "price": 624.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#52", "weight": 0.65, + "amount_krw": 12445813.0, "leg_id": 52, "bb_pos": 1.0, "rsi": 76.9, @@ -3937,6 +4312,7 @@ "price": 615.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#52", "weight": 0.35, + "amount_krw": 6604934.0, "leg_id": 52, "bb_pos": 0.62, "rsi": 60.5, @@ -3949,6 +4325,7 @@ "price": 575.0, "memo": "저점 분할 매수 · 비중 16% · 6회 · BB하단 · leg#53", "weight": 0.16, + "amount_krw": 3043552.0, "leg_id": 53, "bb_pos": 0.033, "rsi": 40.0, @@ -3961,6 +4338,7 @@ "price": 556.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#53", "weight": 0.165, + "amount_krw": 3715763.0, "leg_id": 53, "bb_pos": 0.088, "rsi": 25.0, @@ -3973,6 +4351,7 @@ "price": 561.0, "memo": "저점 분할 매수 · 비중 16% · 6회 · BB하단 · leg#53", "weight": 0.164, + "amount_krw": 4608777.0, "leg_id": 53, "bb_pos": 0.0, "rsi": 40.0, @@ -3985,6 +4364,7 @@ "price": 536.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#53", "weight": 0.172, + "amount_krw": 6211505.0, "leg_id": 53, "bb_pos": 0.022, "rsi": 30.0, @@ -3997,6 +4377,7 @@ "price": 536.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#53", "weight": 0.172, + "amount_krw": 1452109.0, "leg_id": 53, "bb_pos": 0.182, "rsi": 20.0, @@ -4009,6 +4390,7 @@ "price": 549.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#53", "weight": 0.168, + "amount_krw": 0, "leg_id": 53, "bb_pos": 0.0, "rsi": 30.8, @@ -4021,6 +4403,7 @@ "price": 641.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#53", "weight": 0.65, + "amount_krw": 14369941.0, "leg_id": 53, "bb_pos": 1.0, "rsi": 76.2, @@ -4033,6 +4416,7 @@ "price": 641.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#53", "weight": 0.35, + "amount_krw": 7737660.0, "leg_id": 53, "bb_pos": 0.89, "rsi": 72.7, @@ -4045,6 +4429,7 @@ "price": 574.0, "memo": "저점 분할 매수 · 비중 16% · 6회 · BB하단 · leg#54", "weight": 0.159, + "amount_krw": 175492.0, "leg_id": 54, "bb_pos": 0.0, "rsi": 22.7, @@ -4057,6 +4442,7 @@ "price": 545.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#54", "weight": 0.168, + "amount_krw": 220351.0, "leg_id": 54, "bb_pos": 0.056, "rsi": 33.3, @@ -4069,6 +4455,7 @@ "price": 552.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#54", "weight": 0.166, + "amount_krw": 272058.0, "leg_id": 54, "bb_pos": 0.05, "rsi": 23.5, @@ -4081,6 +4468,7 @@ "price": 537.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#54", "weight": 0.17, + "amount_krw": 369353.0, "leg_id": 54, "bb_pos": 0.376, "rsi": 45.0, @@ -4093,6 +4481,7 @@ "price": 536.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#54", "weight": 0.171, + "amount_krw": 558335.0, "leg_id": 54, "bb_pos": 0.0, "rsi": 33.3, @@ -4105,6 +4494,7 @@ "price": 548.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#54", "weight": 0.167, + "amount_krw": 1105355.0, "leg_id": 54, "bb_pos": 0.078, "rsi": 30.8, @@ -4117,6 +4507,7 @@ "price": 624.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#54", "weight": 0.65, + "amount_krw": 2007500.0, "leg_id": 54, "bb_pos": 0.955, "rsi": 71.1, @@ -4129,6 +4520,7 @@ "price": 621.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#54", "weight": 0.35, + "amount_krw": 1075764.0, "leg_id": 54, "bb_pos": 0.789, "rsi": 65.6, @@ -4141,6 +4533,7 @@ "price": 565.0, "memo": "저점 분할 매수 · 비중 48% · 2회 · BB하단 · leg#55", "weight": 0.485, + "amount_krw": 545042.0, "leg_id": 55, "bb_pos": 0.0, "rsi": 28.0, @@ -4153,6 +4546,7 @@ "price": 532.0, "memo": "저점 분할 매수 · 비중 52% · 2회 · BB하단 · leg#55", "weight": 0.515, + "amount_krw": 1122193.0, "leg_id": 55, "bb_pos": 0.144, "rsi": 33.3, @@ -4165,6 +4559,7 @@ "price": 591.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#55", "weight": 0.65, + "amount_krw": 1180901.0, "leg_id": 55, "bb_pos": 0.729, "rsi": 60.0, @@ -4177,6 +4572,7 @@ "price": 587.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#55", "weight": 0.35, + "amount_krw": 631566.0, "leg_id": 55, "bb_pos": 0.321, "rsi": 46.7, @@ -4189,6 +4585,7 @@ "price": 549.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#56", "weight": 1.0, + "amount_krw": 1130973.0, "leg_id": 56, "bb_pos": 0.167, "rsi": 25.0, @@ -4201,6 +4598,7 @@ "price": 606.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#56", "weight": 0.65, + "amount_krw": 811458.0, "leg_id": 56, "bb_pos": 1.0, "rsi": 71.8, @@ -4213,6 +4611,7 @@ "price": 606.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#56", "weight": 0.35, + "amount_krw": 436939.0, "leg_id": 56, "bb_pos": 0.916, "rsi": 66.7, @@ -4225,6 +4624,7 @@ "price": 567.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#57", "weight": 1.0, + "amount_krw": 1136785.0, "leg_id": 57, "bb_pos": 0.0, "rsi": 23.5, @@ -4237,6 +4637,7 @@ "price": 638.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#57", "weight": 0.65, + "amount_krw": 831437.0, "leg_id": 57, "bb_pos": 0.941, "rsi": 83.8, @@ -4249,6 +4650,7 @@ "price": 638.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#57", "weight": 0.35, + "amount_krw": 447697.0, "leg_id": 57, "bb_pos": 0.838, "rsi": 74.4, @@ -4261,6 +4663,7 @@ "price": 591.0, "memo": "저점 분할 매수 · 비중 15% · 6회 · BB하단 · leg#58", "weight": 0.149, + "amount_krw": 170432.0, "leg_id": 58, "bb_pos": 0.221, "rsi": 33.3, @@ -4273,6 +4676,7 @@ "price": 530.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#58", "weight": 0.166, + "amount_krw": 222951.0, "leg_id": 58, "bb_pos": 0.34, "rsi": 37.5, @@ -4285,6 +4689,7 @@ "price": 515.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#58", "weight": 0.171, + "amount_krw": 285188.0, "leg_id": 58, "bb_pos": 0.0, "rsi": 25.0, @@ -4297,6 +4702,7 @@ "price": 512.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#58", "weight": 0.172, + "amount_krw": 382223.0, "leg_id": 58, "bb_pos": 0.262, "rsi": 44.4, @@ -4309,6 +4715,7 @@ "price": 510.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#58", "weight": 0.172, + "amount_krw": 574346.0, "leg_id": 58, "bb_pos": 0.0, "rsi": 25.0, @@ -4321,6 +4728,7 @@ "price": 516.0, "memo": "저점 분할 매수 · 비중 17% · 6회 · BB하단 · leg#58", "weight": 0.17, + "amount_krw": 1142940.0, "leg_id": 58, "bb_pos": 0.257, "rsi": 20.0, @@ -4333,6 +4741,7 @@ "price": 592.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#58", "weight": 0.65, + "amount_krw": 2058873.0, "leg_id": 58, "bb_pos": 0.676, "rsi": 66.7, @@ -4345,6 +4754,7 @@ "price": 592.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#58", "weight": 0.35, + "amount_krw": 1108624.0, "leg_id": 58, "bb_pos": 0.824, "rsi": 58.8, @@ -4357,6 +4767,7 @@ "price": 567.0, "memo": "저점 분할 매수 · 비중 9% · 9회 · BB하단 · leg#59", "weight": 0.09, + "amount_krw": 104790.0, "leg_id": 59, "bb_pos": 0.153, "rsi": 43.8, @@ -4369,6 +4780,7 @@ "price": 532.0, "memo": "저점 분할 매수 · 비중 10% · 9회 · BB하단 · leg#59", "weight": 0.096, + "amount_krw": 122808.0, "leg_id": 59, "bb_pos": 0.336, "rsi": 37.5, @@ -4381,6 +4793,7 @@ "price": 476.0, "memo": "저점 분할 매수 · 비중 11% · 9회 · BB하단 · leg#59", "weight": 0.107, + "amount_krw": 152889.0, "leg_id": 59, "bb_pos": 0.159, "rsi": 42.9, @@ -4393,6 +4806,7 @@ "price": 469.0, "memo": "저점 분할 매수 · 비중 11% · 9회 · BB하단 · leg#59", "weight": 0.109, + "amount_krw": 179311.0, "leg_id": 59, "bb_pos": 0.444, "rsi": 50.0, @@ -4405,6 +4819,7 @@ "price": 455.0, "memo": "저점 분할 매수 · 비중 11% · 9회 · BB하단 · leg#59", "weight": 0.112, + "amount_krw": 217738.0, "leg_id": 59, "bb_pos": 0.344, "rsi": 45.0, @@ -4417,6 +4832,7 @@ "price": 457.0, "memo": "저점 분할 매수 · 비중 11% · 9회 · BB하단 · leg#59", "weight": 0.112, + "amount_krw": 268055.0, "leg_id": 59, "bb_pos": 0.156, "rsi": 30.0, @@ -4429,6 +4845,7 @@ "price": 469.0, "memo": "저점 분할 매수 · 비중 11% · 9회 · BB하단 · leg#59", "weight": 0.109, + "amount_krw": 339589.0, "leg_id": 59, "bb_pos": 0.111, "rsi": 25.0, @@ -4441,6 +4858,7 @@ "price": 372.0, "memo": "저점 분할 매수 · 비중 14% · 9회 · BB하단 · leg#59", "weight": 0.137, + "amount_krw": 595725.0, "leg_id": 59, "bb_pos": 0.045, "rsi": 7.7, @@ -4453,6 +4871,7 @@ "price": 402.0, "memo": "저점 분할 매수 · 비중 13% · 9회 · BB하단 · leg#59", "weight": 0.127, + "amount_krw": 1154716.0, "leg_id": 59, "bb_pos": 0.114, "rsi": 41.7, @@ -4465,6 +4884,7 @@ "price": 433.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#59", "weight": 0.65, + "amount_krw": 2077716.0, "leg_id": 59, "bb_pos": 0.958, "rsi": 81.8, @@ -4477,6 +4897,7 @@ "price": 431.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#59", "weight": 0.35, + "amount_krw": 1113603.0, "leg_id": 59, "bb_pos": 0.549, "rsi": 57.1, @@ -4489,6 +4910,7 @@ "price": 411.0, "memo": "저점 분할 매수 · 비중 19% · 5회 · BB하단 · leg#60", "weight": 0.189, + "amount_krw": 220335.0, "leg_id": 60, "bb_pos": 0.133, "rsi": 44.4, @@ -4501,6 +4923,7 @@ "price": 412.0, "memo": "저점 분할 매수 · 비중 19% · 5회 · BB하단 · leg#60", "weight": 0.189, + "amount_krw": 271687.0, "leg_id": 60, "bb_pos": 0.0, "rsi": 35.3, @@ -4513,6 +4936,7 @@ "price": 400.0, "memo": "저점 분할 매수 · 비중 19% · 5회 · BB하단 · leg#60", "weight": 0.195, + "amount_krw": 365261.0, "leg_id": 60, "bb_pos": 0.516, "rsi": 50.0, @@ -4525,6 +4949,7 @@ "price": 365.0, "memo": "저점 분할 매수 · 비중 21% · 5회 · BB하단 · leg#60", "weight": 0.213, + "amount_krw": 579335.0, "leg_id": 60, "bb_pos": 0.3, "rsi": 50.0, @@ -4537,6 +4962,7 @@ "price": 364.0, "memo": "저점 분할 매수 · 비중 21% · 5회 · BB하단 · leg#60", "weight": 0.214, + "amount_krw": 1161190.0, "leg_id": 60, "bb_pos": 0.0, "rsi": 22.2, @@ -4549,6 +4975,7 @@ "price": 407.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#60", "weight": 0.65, + "amount_krw": 1821687.0, "leg_id": 60, "bb_pos": 0.939, "rsi": 66.7, @@ -4561,6 +4988,7 @@ "price": 407.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#60", "weight": 0.35, + "amount_krw": 980908.0, "leg_id": 60, "bb_pos": 0.838, "rsi": 73.3, @@ -4573,6 +5001,7 @@ "price": 384.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#61", "weight": 0.255, + "amount_krw": 299554.0, "leg_id": 61, "bb_pos": 0.14, "rsi": 30.0, @@ -4585,6 +5014,7 @@ "price": 377.0, "memo": "저점 분할 매수 · 비중 26% · 4회 · BB하단 · leg#61", "weight": 0.26, + "amount_krw": 409732.0, "leg_id": 61, "bb_pos": 0.286, "rsi": 36.4, @@ -4597,6 +5027,7 @@ "price": 395.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#61", "weight": 0.248, + "amount_krw": 600754.0, "leg_id": 61, "bb_pos": 0.109, "rsi": 41.7, @@ -4609,6 +5040,7 @@ "price": 411.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#61", "weight": 0.238, + "amount_krw": 1179980.0, "leg_id": 61, "bb_pos": 0.047, "rsi": 33.3, @@ -4621,6 +5053,7 @@ "price": 448.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#61", "weight": 0.65, + "amount_krw": 1822564.0, "leg_id": 61, "bb_pos": 0.907, "rsi": 66.7, @@ -4633,6 +5066,7 @@ "price": 446.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#61", "weight": 0.35, + "amount_krw": 976999.0, "leg_id": 61, "bb_pos": 1.0, "rsi": 60.0, @@ -4645,6 +5079,7 @@ "price": 423.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#62", "weight": 0.253, + "amount_krw": 301384.0, "leg_id": 62, "bb_pos": 0.0, "rsi": 14.3, @@ -4657,6 +5092,7 @@ "price": 423.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#62", "weight": 0.253, + "amount_krw": 403456.0, "leg_id": 62, "bb_pos": 0.367, "rsi": 45.5, @@ -4669,6 +5105,7 @@ "price": 431.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#62", "weight": 0.249, + "amount_krw": 600770.0, "leg_id": 62, "bb_pos": 0.043, "rsi": 28.6, @@ -4681,6 +5118,7 @@ "price": 437.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#62", "weight": 0.245, + "amount_krw": 1192792.0, "leg_id": 62, "bb_pos": 0.189, "rsi": 20.0, @@ -4693,6 +5131,7 @@ "price": 482.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#62", "weight": 0.65, + "amount_krw": 1813909.0, "leg_id": 62, "bb_pos": 0.894, "rsi": 66.7, @@ -4705,6 +5144,7 @@ "price": 479.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#62", "weight": 0.35, + "amount_krw": 970641.0, "leg_id": 62, "bb_pos": 0.777, "rsi": 60.0, @@ -4717,6 +5157,7 @@ "price": 433.0, "memo": "저점 분할 매수 · 비중 51% · 2회 · BB하단 · leg#63", "weight": 0.515, + "amount_krw": 620789.0, "leg_id": 63, "bb_pos": 0.034, "rsi": 33.3, @@ -4729,6 +5170,7 @@ "price": 459.0, "memo": "저점 분할 매수 · 비중 49% · 2회 · BB하단 · leg#63", "weight": 0.485, + "amount_krw": 1207264.0, "leg_id": 63, "bb_pos": 0.109, "rsi": 40.0, @@ -4741,6 +5183,7 @@ "price": 484.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#63", "weight": 0.65, + "amount_krw": 1278502.0, "leg_id": 63, "bb_pos": 1.0, "rsi": 73.3, @@ -4753,6 +5196,7 @@ "price": 483.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#63", "weight": 0.35, + "amount_krw": 687002.0, "leg_id": 63, "bb_pos": 0.655, "rsi": 50.0, @@ -4765,6 +5209,7 @@ "price": 386.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#64", "weight": 0.136, + "amount_krw": 164858.0, "leg_id": 64, "bb_pos": 0.368, "rsi": 44.4, @@ -4777,6 +5222,7 @@ "price": 383.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#64", "weight": 0.138, + "amount_krw": 193603.0, "leg_id": 64, "bb_pos": 0.0, "rsi": 29.4, @@ -4789,6 +5235,7 @@ "price": 385.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#64", "weight": 0.137, + "amount_krw": 228751.0, "leg_id": 64, "bb_pos": 0.057, "rsi": 40.0, @@ -4801,6 +5248,7 @@ "price": 381.0, "memo": "저점 분할 매수 · 비중 14% · 7회 · BB하단 · leg#64", "weight": 0.138, + "amount_krw": 283943.0, "leg_id": 64, "bb_pos": 0.0, "rsi": 25.0, @@ -4813,6 +5261,7 @@ "price": 359.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#64", "weight": 0.147, + "amount_krw": 394193.0, "leg_id": 64, "bb_pos": 0.144, "rsi": 20.0, @@ -4825,6 +5274,7 @@ "price": 345.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#64", "weight": 0.153, + "amount_krw": 607484.0, "leg_id": 64, "bb_pos": 0.0, "rsi": 33.3, @@ -4837,6 +5287,7 @@ "price": 348.0, "memo": "저점 분할 매수 · 비중 15% · 7회 · BB하단 · leg#64", "weight": 0.151, + "amount_krw": 1207782.0, "leg_id": 64, "bb_pos": 0.072, "rsi": 0.0, @@ -4849,6 +5300,7 @@ "price": 382.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#64", "weight": 0.65, + "amount_krw": 2135750.0, "leg_id": 64, "bb_pos": 0.964, "rsi": 71.4, @@ -4861,6 +5313,7 @@ "price": 382.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#64", "weight": 0.35, + "amount_krw": 1150019.0, "leg_id": 64, "bb_pos": 1.0, "rsi": 71.4, @@ -4873,6 +5326,7 @@ "price": 365.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#65", "weight": 0.502, + "amount_krw": 613591.0, "leg_id": 65, "bb_pos": 0.0, "rsi": 22.2, @@ -4885,6 +5339,7 @@ "price": 368.0, "memo": "저점 분할 매수 · 비중 50% · 2회 · BB하단 · leg#65", "weight": 0.498, + "amount_krw": 1222529.0, "leg_id": 65, "bb_pos": 0.0, "rsi": 40.0, @@ -4897,6 +5352,7 @@ "price": 409.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#65", "weight": 0.65, + "amount_krw": 1330090.0, "leg_id": 65, "bb_pos": 1.0, "rsi": 71.4, @@ -4909,6 +5365,7 @@ "price": 404.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#65", "weight": 0.35, + "amount_krw": 707447.0, "leg_id": 65, "bb_pos": 0.969, "rsi": 54.5, @@ -4921,6 +5378,7 @@ "price": 391.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#66", "weight": 1.0, + "amount_krw": 1232266.0, "leg_id": 66, "bb_pos": 0.212, "rsi": 30.0, @@ -4933,6 +5391,7 @@ "price": 427.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#66", "weight": 0.65, + "amount_krw": 874720.0, "leg_id": 66, "bb_pos": 1.0, "rsi": 93.3, @@ -4945,6 +5404,7 @@ "price": 423.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#66", "weight": 0.35, + "amount_krw": 466591.0, "leg_id": 66, "bb_pos": 0.978, "rsi": 72.7, @@ -4957,6 +5417,7 @@ "price": 399.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#67", "weight": 0.331, + "amount_krw": 409663.0, "leg_id": 67, "bb_pos": 0.194, "rsi": 16.0, @@ -4969,6 +5430,7 @@ "price": 402.0, "memo": "저점 분할 매수 · 비중 33% · 3회 · BB하단 · leg#67", "weight": 0.329, + "amount_krw": 608723.0, "leg_id": 67, "bb_pos": 0.153, "rsi": 26.7, @@ -4981,6 +5443,7 @@ "price": 388.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#67", "weight": 0.34, + "amount_krw": 1236004.0, "leg_id": 67, "bb_pos": 0.0, "rsi": 30.0, @@ -4993,6 +5456,7 @@ "price": 423.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#67", "weight": 0.65, + "amount_krw": 1574512.0, "leg_id": 67, "bb_pos": 0.962, "rsi": 81.8, @@ -5005,6 +5469,7 @@ "price": 417.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#67", "weight": 0.35, + "amount_krw": 835788.0, "leg_id": 67, "bb_pos": 0.227, "rsi": 33.3, @@ -5017,6 +5482,7 @@ "price": 377.0, "memo": "저점 분할 매수 · 비중 31% · 3회 · BB하단 · leg#68", "weight": 0.314, + "amount_krw": 391034.0, "leg_id": 68, "bb_pos": 0.111, "rsi": 31.2, @@ -5029,6 +5495,7 @@ "price": 352.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#68", "weight": 0.337, + "amount_krw": 611133.0, "leg_id": 68, "bb_pos": 0.153, "rsi": 44.4, @@ -5041,6 +5508,7 @@ "price": 340.0, "memo": "저점 분할 매수 · 비중 35% · 3회 · BB하단 · leg#68", "weight": 0.349, + "amount_krw": 1242347.0, "leg_id": 68, "bb_pos": 0.132, "rsi": 33.3, @@ -5053,6 +5521,7 @@ "price": 370.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#68", "weight": 0.65, + "amount_krw": 1545780.0, "leg_id": 68, "bb_pos": 0.906, "rsi": 63.6, @@ -5065,6 +5534,7 @@ "price": 370.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#68", "weight": 0.35, + "amount_krw": 832343.0, "leg_id": 68, "bb_pos": 0.863, "rsi": 60.0, @@ -5077,6 +5547,7 @@ "price": 352.0, "memo": "저점 분할 매수 · 비중 100% · 1회 · BB하단 · leg#69", "weight": 1.0, + "amount_krw": 1251898.0, "leg_id": 69, "bb_pos": 0.071, "rsi": 30.0, @@ -5089,6 +5560,7 @@ "price": 395.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#69", "weight": 0.65, + "amount_krw": 913139.0, "leg_id": 69, "bb_pos": 1.0, "rsi": 88.9, @@ -5101,6 +5573,7 @@ "price": 395.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#69", "weight": 0.35, + "amount_krw": 491690.0, "leg_id": 69, "bb_pos": 0.943, "rsi": 80.0, @@ -5113,6 +5586,7 @@ "price": 378.0, "memo": "저점 분할 매수 · 비중 27% · 4회 · BB하단 · leg#70", "weight": 0.267, + "amount_krw": 336281.0, "leg_id": 70, "bb_pos": 0.0, "rsi": 33.3, @@ -5125,6 +5599,7 @@ "price": 397.0, "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#70", "weight": 0.254, + "amount_krw": 436726.0, "leg_id": 70, "bb_pos": 0.025, "rsi": 27.3, @@ -5137,6 +5612,7 @@ "price": 417.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#70", "weight": 0.242, + "amount_krw": 637735.0, "leg_id": 70, "bb_pos": 0.021, "rsi": 37.5, @@ -5149,6 +5625,7 @@ "price": 425.0, "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#70", "weight": 0.237, + "amount_krw": 1263685.0, "leg_id": 70, "bb_pos": 0.0, "rsi": 38.7, @@ -5161,6 +5638,7 @@ "price": 445.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#70", "weight": 0.65, + "amount_krw": 1877931.0, "leg_id": 70, "bb_pos": 1.0, "rsi": 80.0, @@ -5173,6 +5651,7 @@ "price": 443.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#70", "weight": 0.35, + "amount_krw": 1006649.0, "leg_id": 70, "bb_pos": 0.592, "rsi": 51.4, @@ -5185,6 +5664,7 @@ "price": 407.0, "memo": "저점 분할 매수 · 비중 34% · 3회 · BB하단 · leg#71", "weight": 0.338, + "amount_krw": 429638.0, "leg_id": 71, "bb_pos": 0.0, "rsi": 17.6, @@ -5197,6 +5677,7 @@ "price": 392.0, "memo": "저점 분할 매수 · 비중 35% · 3회 · BB하단 · leg#71", "weight": 0.351, + "amount_krw": 673880.0, "leg_id": 71, "bb_pos": 0.076, "rsi": 9.1, @@ -5209,6 +5690,7 @@ "price": 444.0, "memo": "저점 분할 매수 · 비중 31% · 3회 · BB하단 · leg#71", "weight": 0.31, + "amount_krw": 1276241.0, "leg_id": 71, "bb_pos": 0.054, "rsi": 30.8, @@ -5221,6 +5703,7 @@ "price": 464.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#71", "weight": 0.65, + "amount_krw": 1703775.0, "leg_id": 71, "bb_pos": 1.0, "rsi": 68.4, @@ -5233,6 +5716,7 @@ "price": 455.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#71", "weight": 0.35, + "amount_krw": 899622.0, "leg_id": 71, "bb_pos": 0.416, "rsi": 48.4, @@ -5245,6 +5729,7 @@ "price": 427.0, "memo": "저점 분할 매수 · 비중 36% · 3회 · BB하단 · leg#72", "weight": 0.361, + "amount_krw": 9238886.0, "leg_id": 72, "bb_pos": 0.083, "rsi": 30.8, @@ -5257,6 +5742,7 @@ "price": 482.0, "memo": "저점 분할 매수 · 비중 32% · 3회 · BB하단 · leg#72", "weight": 0.32, + "amount_krw": 13401738.0, "leg_id": 72, "bb_pos": 0.107, "rsi": 48.0, @@ -5269,6 +5755,7 @@ "price": 482.0, "memo": "저점 분할 매수 · 비중 32% · 3회 · BB하단 · leg#72", "weight": 0.32, + "amount_krw": 2964649.0, "leg_id": 72, "bb_pos": 0.185, "rsi": 36.4, @@ -5281,6 +5768,7 @@ "price": 603.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#72", "weight": 0.65, + "amount_krw": 21789243.0, "leg_id": 72, "bb_pos": 1.0, "rsi": 66.7, @@ -5293,6 +5781,7 @@ "price": 597.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#72", "weight": 0.35, + "amount_krw": 11615926.0, "leg_id": 72, "bb_pos": 1.0, "rsi": 68.3, @@ -5305,6 +5794,7 @@ "price": 564.0, "memo": "저점 분할 매수 · 비중 14% · 6회 · BB하단 · leg#73", "weight": 0.138, + "amount_krw": 225990.0, "leg_id": 73, "bb_pos": 0.136, "rsi": 46.7, @@ -5317,6 +5807,7 @@ "price": 522.0, "memo": "저점 분할 매수 · 비중 15% · 6회 · BB하단 · leg#73", "weight": 0.149, + "amount_krw": 288420.0, "leg_id": 73, "bb_pos": 0.08, "rsi": 16.7, @@ -5329,6 +5820,7 @@ "price": 520.0, "memo": "저점 분할 매수 · 비중 15% · 6회 · BB하단 · leg#73", "weight": 0.149, + "amount_krw": 348671.0, "leg_id": 73, "bb_pos": 0.0, "rsi": 23.1, @@ -5341,6 +5833,7 @@ "price": 412.0, "memo": "저점 분할 매수 · 비중 19% · 6회 · BB하단 · leg#73", "weight": 0.188, + "amount_krw": 553232.0, "leg_id": 73, "bb_pos": 0.413, "rsi": 47.4, @@ -5353,6 +5846,7 @@ "price": 408.0, "memo": "저점 분할 매수 · 비중 19% · 6회 · BB하단 · leg#73", "weight": 0.19, + "amount_krw": 838370.0, "leg_id": 73, "bb_pos": 0.135, "rsi": 27.8, @@ -5365,6 +5859,7 @@ "price": 417.0, "memo": "저점 분할 매수 · 비중 19% · 6회 · BB하단 · leg#73", "weight": 0.186, + "amount_krw": 1661328.0, "leg_id": 73, "bb_pos": 0.056, "rsi": 27.8, @@ -5377,6 +5872,7 @@ "price": 461.0, "memo": "고점 매도 · 비중 65% · 분할 · leg#73", "weight": 0.65, + "amount_krw": 2698458.0, "leg_id": 73, "bb_pos": 0.992, "rsi": 90.0, @@ -5389,6 +5885,7 @@ "price": 458.0, "memo": "고점 매도 · 비중 35% · 분할 · leg#73", "weight": 0.35, + "amount_krw": 1443560.0, "leg_id": 73, "bb_pos": 0.627, "rsi": 53.5, @@ -5399,61 +5896,105 @@ "dt": "2026-05-29 21:33:00", "action": "buy", "price": 430.0, - "memo": "저점 분할 매수 · 비중 25% · leg#74(기간말)", + "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#74", "weight": 0.25, + "amount_krw": 8402611.0, "leg_id": 74, "bb_pos": 0.6, "rsi": 53.6, "pivot_kind": "trough", - "forward_return_pct": 19.77 + "forward_return_pct": 30.93 }, { "dt": "2026-05-30 01:24:00", "action": "buy", "price": 439.0, - "memo": "저점 분할 매수 · 비중 24% · leg#74(기간말)", + "memo": "저점 분할 매수 · 비중 24% · 4회 · BB하단 · leg#74", "weight": 0.244, + "amount_krw": 10990447.0, "leg_id": 74, "bb_pos": 0.085, "rsi": 29.2, "pivot_kind": "trough", - "forward_return_pct": 17.31 + "forward_return_pct": 28.25 }, { "dt": "2026-05-30 09:57:00", "action": "buy", "price": 424.0, - "memo": "저점 분할 매수 · 비중 25% · leg#74(기간말)", + "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#74", "weight": 0.253, + "amount_krw": 14200590.0, "leg_id": 74, "bb_pos": 0.192, "rsi": 33.3, "pivot_kind": "trough", - "forward_return_pct": 21.46 + "forward_return_pct": 32.78 }, { "dt": "2026-05-30 13:30:00", "action": "buy", "price": 424.0, - "memo": "저점 분할 매수 · 비중 25% · leg#74(기간말)", + "memo": "저점 분할 매수 · 비중 25% · 4회 · BB하단 · leg#74", "weight": 0.253, + "amount_krw": 0, "leg_id": 74, "bb_pos": 0.151, "rsi": 28.6, "pivot_kind": "trough", - "forward_return_pct": 21.46 + "forward_return_pct": 32.78 }, { - "dt": "2026-05-31 00:51:00", + "dt": "2026-05-31 05:45:00", "action": "sell", - "price": 515.0, - "memo": "기간말 잔여 청산 · leg#74", - "weight": 1.0, + "price": 563.0, + "memo": "고점 매도 · 비중 65% · 분할 · leg#74", + "weight": 0.65, + "amount_krw": 28569021.0, "leg_id": 74, - "bb_pos": 0.881, - "rsi": 55.8, + "bb_pos": 0.796, + "rsi": 75.0, "pivot_kind": "peak", - "forward_return_pct": 20.0 + "forward_return_pct": 31.19 + }, + { + "dt": "2026-05-31 05:51:00", + "action": "sell", + "price": 563.0, + "memo": "고점 매도 · 비중 35% · 분할 · leg#74", + "weight": 0.35, + "amount_krw": 15383319.0, + "leg_id": 74, + "bb_pos": 0.675, + "rsi": 63.6, + "pivot_kind": "peak", + "forward_return_pct": 31.19 + }, + { + "dt": "2026-05-31 09:36:00", + "action": "buy", + "price": 495.0, + "memo": "저점 분할 매수 · 비중 100% · leg#75(기간말)", + "weight": 1.0, + "amount_krw": 2196518.0, + "leg_id": 75, + "bb_pos": 0.0, + "rsi": 33.3, + "pivot_kind": "trough", + "forward_return_pct": 1.62 + }, + { + "dt": "2026-05-31 15:58:00", + "action": "sell", + "price": 503.0, + "memo": "기간말 잔여 청산 · leg#75", + "weight": 1.0, + "amount_krw": 2232017.0, + "leg_id": 75, + "bb_pos": 0.091, + "rsi": 35.7, + "pivot_kind": "peak", + "forward_return_pct": 1.62 } ] } \ No newline at end of file diff --git a/deepcoin/ground_truth/ground_truth.py b/deepcoin/ground_truth/ground_truth.py index 3daf02a..401a81e 100644 --- a/deepcoin/ground_truth/ground_truth.py +++ b/deepcoin/ground_truth/ground_truth.py @@ -28,7 +28,12 @@ from config import ( GT_BUY_BB_MAX, GT_BUY_MIN_BARS, GT_BUY_MIN_SWING_PCT, + GT_BUY_PCT_LARGE_LEG, + GT_BUY_PCT_SMALL_LEG, GT_INITIAL_CASH_KRW, + GT_LARGE_LEG_TOP_PCT, + GT_MIN_ORDER_KRW, + GT_MAX_BUY_ORDER_KRW, GT_MAX_BUYS_PER_LEG, GT_MAX_ROUND_TRIPS, TRADING_FEE_RATE, @@ -68,6 +73,7 @@ class TradePoint: price: float memo: str weight: float = 1.0 + amount_krw: float | None = None leg_id: int = 0 bb_pos: float | None = None rsi: float | None = None @@ -844,6 +850,12 @@ def generate_ground_truth( ) trade_dicts = order_trades_leg_block(trades) + trade_dicts, alloc_stats = allocate_gt_order_amounts( + trade_dicts, + initial_cash=GT_INITIAL_CASH_KRW, + min_order_krw=GT_MIN_ORDER_KRW, + fee_rate=TRADING_FEE_RATE, + ) last_close = float(df["Close"].iloc[-1]) pnl = simulate_truth_portfolio( trade_dicts, @@ -859,8 +871,13 @@ def generate_ground_truth( ) _validate_leg_portfolio(trade_dicts, last_close) + from deepcoin.ground_truth.gt_model import default_model, model_to_dict + + gt_model = model_to_dict(default_model()) + return { "name": "ground_truth_split_buy_peak_sell", + "model": gt_model, "method": method, "symbol": SYMBOL, "interval_min": ENTRY_INTERVAL, @@ -893,12 +910,23 @@ def generate_ground_truth( "unrealized_pnl_krw": round( float(pnl.get("pnl_krw", 0)) - float(pnl_realized.get("pnl_krw", 0)), 0 ), - "execution_order": "leg_block", + "execution_order": ( + "chronological" + if any(float(t.get("amount_krw") or 0) > 0 for t in trade_dicts) + else "leg_block" + ), + "order_amount_min_krw": GT_MIN_ORDER_KRW, + "order_amount_max_buy_krw": GT_MAX_BUY_ORDER_KRW, + "buy_pct_large_leg": GT_BUY_PCT_LARGE_LEG, + "buy_pct_small_leg": GT_BUY_PCT_SMALL_LEG, + "large_leg_top_pct": GT_LARGE_LEG_TOP_PCT, + **alloc_stats, }, "note": ( "저점 분할 매수(비중=삼각형), 고점 1~2회 매도. " - "체결 순서=leg별 매수→매도(시각순 아님). 기간말 leg는 종가 청산. " - "summary.pnl_pct는 미청산 포함 종가 평가, realized_pnl_pct는 체결만 반영." + "매수=총자산×최적비중×티어(상위 leg 대형·그 외 소형), " + f"현금 한도·최소 ₩{GT_MIN_ORDER_KRW:,}. " + "체결 순서=chronological. summary.pnl_pct는 미청산 포함 종가 평가." ), "trades": trade_dicts, } @@ -921,18 +949,6 @@ def _validate_leg_portfolio( steps = simulate_truth_portfolio_steps(trade_dicts) if not steps: return - leg_ids = sorted({int(s["leg_id"]) for s in steps}) - for lid in leg_ids: - leg_steps = [s for s in steps if int(s["leg_id"]) == lid] - sells = [s for s in leg_steps if s["action"] == "sell"] - if not sells: - continue - last_sell = sells[-1] - if float(last_sell["holding_qty"]) > 1e-4: - raise ValueError( - f"leg#{lid} 마지막 매도 후 보유 잔존 qty={last_sell['holding_qty']} " - "(leg 블록 체결·매도 비중 합 검토 필요)" - ) final = steps[-1] if float(final["holding_qty"]) > 1e-2: raise ValueError( @@ -943,6 +959,203 @@ def _validate_leg_portfolio( raise ValueError("종가 평가 후에도 미청산 보유가 남음") +def allocate_gt_order_amounts( + trades: list[dict[str, Any]], + initial_cash: float = GT_INITIAL_CASH_KRW, + min_order_krw: float = GT_MIN_ORDER_KRW, + max_buy_krw: float = GT_MAX_BUY_ORDER_KRW, + fee_rate: float = TRADING_FEE_RATE, +) -> tuple[list[dict[str, Any]], dict[str, Any]]: + """ + GT 각 타점에 amount_krw를 시각순·총자산·비중(최적 매수율)으로 배분합니다. + + 매수: 총보유자산 × (leg 비중 share × 티어 스케일), 상한=가용 현금. + leg 상위 GT_LARGE_LEG_TOP_PCT는 GT_BUY_PCT_LARGE_LEG, 그 외는 GT_BUY_PCT_SMALL_LEG. + 매도 후 현금 증가분은 다음 매수부터 자동 반영(시각순 복리). + + Args: + trades: trade dict 리스트(시각순 정렬 전). + initial_cash: 초기 현금. + min_order_krw: 매수·매도 최소 원화 금액. + max_buy_krw: 매수 1회 상한(가용 현금·비중 배분 후 캡). + fee_rate: 수수료율. + + Returns: + (동일 dict 참조, amount_krw 채움), alloc_stats 요약. + """ + from deepcoin.matching.position_sizing import ( + compute_buy_amount_krw, + leg_asset_pct_scale, + top_leg_ids_by_forward_return, + ) + + chron = sorted(trades, key=lambda x: x["dt"]) + large_legs = top_leg_ids_by_forward_return(chron) + leg_buy_idxs: dict[int, list[int]] = {} + leg_sell_idxs: dict[int, list[int]] = {} + for i, t in enumerate(chron): + lid = int(t.get("leg_id", 0)) + if t["action"] == "buy": + leg_buy_idxs.setdefault(lid, []).append(i) + elif t["action"] == "sell": + leg_sell_idxs.setdefault(lid, []).append(i) + + cash = float(initial_cash) + qty = 0.0 + qty_by_leg: dict[int, float] = {} + sell_leg: int | None = None + sell_base_qty = 0.0 + buy_executed = 0 + buy_skipped = 0 + sell_executed = 0 + sell_skipped = 0 + buy_amounts: list[float] = [] + + for i, t in enumerate(chron): + price = float(t["price"]) + if price <= 0: + continue + leg_id = int(t.get("leg_id", 0)) + weight = float(t.get("weight", 1.0)) + + if t["action"] == "buy": + rem = [j for j in leg_buy_idxs.get(leg_id, []) if j >= i] + w_sum = sum(float(chron[j].get("weight", 1.0)) for j in rem) + w_share = ( + weight / w_sum if w_sum > 0 else 1.0 / max(len(rem), 1) + ) + scale = leg_asset_pct_scale(leg_id, large_legs) + amount = compute_buy_amount_krw( + cash, + qty, + price, + weight, + w_sum, + asset_pct_scale=scale, + min_order_krw=min_order_krw, + fee_rate=fee_rate, + ) + if amount <= 0: + t["amount_krw"] = 0 + buy_skipped += 1 + continue + t["amount_krw"] = amount + fee = amount * fee_rate + cash -= amount + fee + bought_qty = amount / price + qty += bought_qty + qty_by_leg[leg_id] = qty_by_leg.get(leg_id, 0.0) + bought_qty + buy_executed += 1 + buy_amounts.append(amount) + sell_leg = None + + elif t["action"] == "sell": + leg_qty = qty_by_leg.get(leg_id, 0.0) + if leg_qty <= 1e-12: + sell_skipped += 1 + continue + if sell_leg != leg_id: + sell_leg = leg_id + sell_base_qty = leg_qty + rem_sells = [j for j in leg_sell_idxs.get(leg_id, []) if j >= i] + is_last_leg_sell = bool(rem_sells) and i == rem_sells[-1] + if is_last_leg_sell: + sell_qty = leg_qty + gross = sell_qty * price + else: + gross = sell_base_qty * weight * price + if gross >= min_order_krw: + gross = max(min_order_krw, gross) + gross = min(gross, leg_qty * price) + if gross <= 0: + sell_skipped += 1 + continue + if not is_last_leg_sell: + sell_qty = gross / price + else: + sell_qty = leg_qty + t["amount_krw"] = round(gross, 0) + fee = gross * fee_rate + cash += gross - fee + leg_qty -= sell_qty + qty_by_leg[leg_id] = max(leg_qty, 0.0) + qty = max(qty - sell_qty, 0.0) + if qty < 1e-12: + qty = 0.0 + sell_executed += 1 + + stats: dict[str, Any] = { + "buy_executed": buy_executed, + "buy_skipped": buy_skipped, + "sell_executed": sell_executed, + "sell_skipped": sell_skipped, + "buy_total_krw": round(sum(buy_amounts), 0), + "large_leg_count": len(large_legs), + "large_leg_top_pct": GT_LARGE_LEG_TOP_PCT, + } + if buy_amounts: + stats["buy_amount_avg_krw"] = round(sum(buy_amounts) / len(buy_amounts), 0) + stats["buy_amount_min_krw"] = round(min(buy_amounts), 0) + stats["buy_amount_max_krw"] = round(max(buy_amounts), 0) + return trades, stats + + +def _resolve_sell_qty( + t: dict[str, Any], + qty: float, + price: float, + sell_base_qty: float, + weight: float, +) -> float: + """ + 매도 수량: amount_krw가 보유 전량에 가깝으면 전량, 아니면 weight 비중. + + Args: + t: trade dict. + qty: 현재 보유 수량. + price: 체결가. + sell_base_qty: leg 첫 매도 시점 보유량. + weight: 매도 비중. + + Returns: + 매도 수량. + """ + if qty <= 0 or price <= 0: + return 0.0 + ak = t.get("amount_krw") + if ak is not None and float(ak) > 0: + gross_cap = float(ak) + if gross_cap >= qty * price * 0.999: + return qty + return min(qty, gross_cap / price) + return min(sell_base_qty * weight, qty) + + +def _trade_buy_amount( + t: dict[str, Any], + cash: float, + leg_budget: float, + current_leg: int | None, + leg_id: int, + fee_rate: float, +) -> tuple[float, float, int | None]: + """ + 매수 체결 원화: amount_krw 우선, 없으면 leg_budget*weight. + + Returns: + (amount, new_leg_budget, new_current_leg). + """ + weight = float(t.get("weight", 1.0)) + if t.get("amount_krw") is not None and float(t["amount_krw"]) > 0: + amount = min(float(t["amount_krw"]), max(cash / (1.0 + fee_rate), 0.0)) + return amount, leg_budget, current_leg + if leg_id != current_leg: + current_leg = leg_id + leg_budget = cash + amount = leg_budget * weight + return amount, leg_budget, current_leg + + def order_trades_leg_block( trades: list[TradePoint] | list[dict[str, Any]], ) -> list[dict[str, Any]]: @@ -996,8 +1209,12 @@ def _truth_simulation_rows( Returns: dict 행 리스트. """ - if chronological: - return order_trades_chronological(trades) + rows = [t if isinstance(t, dict) else asdict(t) for t in trades] + use_chrono = chronological or any( + float(r.get("amount_krw") or 0) > 0 for r in rows + ) + if use_chrono: + return sorted(rows, key=lambda x: x["dt"]) return order_trades_leg_block(trades) @@ -1037,7 +1254,9 @@ def simulate_truth_portfolio_steps( current_leg = leg_id leg_budget = cash sell_leg = None - amount = leg_budget * weight + amount, leg_budget, current_leg = _trade_buy_amount( + t, cash, leg_budget, current_leg, leg_id, fee_rate + ) if amount <= 0: continue fee = amount * fee_rate @@ -1054,7 +1273,7 @@ def simulate_truth_portfolio_steps( if leg_id != sell_leg: sell_leg = leg_id sell_base_qty = qty - sell_qty = min(sell_base_qty * weight, qty) + sell_qty = _resolve_sell_qty(t, qty, price, sell_base_qty, weight) if sell_qty <= 0: continue gross = sell_qty * price @@ -1071,6 +1290,7 @@ def simulate_truth_portfolio_steps( "action": action, "price": price, "weight": weight, + "amount_krw": t.get("amount_krw"), "leg_id": leg_id, "cash_krw": round(cash, 0), "holding_qty": round(qty, 4), @@ -1130,7 +1350,9 @@ def simulate_truth_portfolio( current_leg = leg_id leg_budget = cash sell_leg = None - amount = leg_budget * weight + amount, leg_budget, current_leg = _trade_buy_amount( + t, cash, leg_budget, current_leg, leg_id, fee_rate + ) if amount <= 0: continue fee = amount * fee_rate @@ -1148,7 +1370,7 @@ def simulate_truth_portfolio( if leg_id != sell_leg: sell_leg = leg_id sell_base_qty = qty - sell_qty = min(sell_base_qty * weight, qty) + sell_qty = _resolve_sell_qty(t, qty, price, sell_base_qty, weight) if sell_qty <= 0: continue gross = sell_qty * price diff --git a/deepcoin/ground_truth/gt_model.py b/deepcoin/ground_truth/gt_model.py new file mode 100644 index 0000000..99edf47 --- /dev/null +++ b/deepcoin/ground_truth/gt_model.py @@ -0,0 +1,228 @@ +""" +Ground Truth 타점·비중·자본 배분 모델 (일반화 명세). + +타점 생성(ground_truth.py)과 자본 배분(position_sizing.py)의 공통 언어. +""" + +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any + +from config import ( + GT_BUY_BB_MAX, + GT_BUY_MIN_BARS, + GT_BUY_MIN_SWING_PCT, + GT_BUY_PCT_LARGE_LEG, + GT_BUY_PCT_SMALL_LEG, + GT_LARGE_LEG_TOP_PCT, + GT_MAX_BUYS_PER_LEG, + GT_MAX_SELLS_PER_LEG, + GT_MIN_ORDER_KRW, + GT_SELL_SPLIT_GAP_PCT, + GT_SELECTION_MODE, +) + + +@dataclass(frozen=True) +class GtEntrySpec: + """ + 매수 타점(leg 내 분할) 규칙. + + Attributes: + pivot_kind: ZigZag 저점(trough). + price_field: 체결가 = 봉 Low. + weight_rule: 저가일수록 큰 비중 (1/price 정규화). + max_per_leg: leg당 최대 매수 횟수. + min_bars_gap: 분할 매수 최소 봉 간격. + """ + + pivot_kind: str = "trough" + price_field: str = "Low" + weight_rule: str = "inverse_price_normalized" + max_per_leg: int = GT_MAX_BUYS_PER_LEG + min_bars_gap: int = GT_BUY_MIN_BARS + bb_filter: str = f"bb_pos <= {GT_BUY_BB_MAX}" + + +@dataclass(frozen=True) +class GtExitSpec: + """ + 매도 타점(leg 내 분할) 규칙. + + Attributes: + pivot_kind: major swing 고점(peak). + price_field: 체결가 = 봉 High. + split_weights: 2회 분할 시 (65%, 35%). + split_gap_pct: 2차 고점 인정 최소 괴리(%). + """ + + pivot_kind: str = "peak" + price_field: str = "High" + split_weights: tuple[float, float] = (0.65, 0.35) + split_gap_pct: float = GT_SELL_SPLIT_GAP_PCT + max_per_leg: int = GT_MAX_SELLS_PER_LEG + + +@dataclass(frozen=True) +class GtCapitalSpec: + """ + 체결 원화(amount_krw) 배분 규칙. + + Attributes: + buy_formula: 총자산 × 최적매수율, 상한=가용현금. + optimal_buy_rate: leg 내 남은 weight 비중. + large_leg_top_pct: 수익률 상위 leg 비율. + pct_large: 상위 leg 총자산 배분 스케일. + pct_small: 그 외 leg 스케일. + min_order_krw: 최소 체결 원화. + """ + + buy_formula: str = "min(total_asset * w_share * tier_scale, cash/(1+fee))" + optimal_buy_rate: str = "weight / sum(remaining_buy_weights_in_leg)" + large_leg_top_pct: float = GT_LARGE_LEG_TOP_PCT + pct_large: float = GT_BUY_PCT_LARGE_LEG + pct_small: float = GT_BUY_PCT_SMALL_LEG + min_order_krw: float = float(GT_MIN_ORDER_KRW) + sell_formula: str = "leg_qty * sell_weight * price (last sell = full leg_qty)" + + +@dataclass +class GroundTruthModel: + """ + GT 전체 모델 (타점 + 비중 + 자본). + + Attributes: + selection_mode: 타점 생성 모드. + entry: 매수 명세. + exit: 매도 명세. + capital: 자본 배분 명세. + leg: leg 정의. + """ + + selection_mode: str = GT_SELECTION_MODE + entry: GtEntrySpec = field(default_factory=GtEntrySpec) + exit: GtExitSpec = field(default_factory=GtExitSpec) + capital: GtCapitalSpec = field(default_factory=GtCapitalSpec) + leg_definition: str = ( + "이전 고점 매도 ~ 다음 고점 매도 구간 = leg_id; " + "기간말 잔여 구간은 마지막 leg" + ) + execution_order_chrono: str = ( + "amount_krw 배분·summary.pnl_pct = 시각순 체결(매도 후 현금 → 다음 매수 반영)" + ) + execution_order_leg_block: str = ( + "JSON 저장 순서 = leg별 매수 전량 → 매도 전량 (차트·테이블 leg 정합)" + ) + + +def default_model() -> GroundTruthModel: + """현재 config 기준 GT 모델.""" + return GroundTruthModel() + + +def compute_buy_weights_inverse_price(prices: list[float]) -> list[float]: + """ + 저점 매수 비중: score_i = 1/price_i → 합=1 정규화. + + Args: + prices: leg 내 매수 후보 가격. + + Returns: + weight 리스트 (합 ≈ 1). + """ + if not prices: + return [] + scores = [1.0 / max(p, 1e-9) for p in prices] + s = sum(scores) + return [x / s for x in scores] if s > 0 else [1.0 / len(prices)] * len(prices) + + +def sell_split_weights(n_sells: int) -> list[float]: + """ + leg 매도 비중. + + Args: + n_sells: 매도 횟수(1 또는 2). + + Returns: + weight 리스트. + """ + spec = GtExitSpec() + if n_sells >= 2: + return list(spec.split_weights) + return [1.0] + + +def model_to_dict(model: GroundTruthModel | None = None) -> dict[str, Any]: + """ + JSON·리포트용 모델 dict. + + Args: + model: None이면 default. + + Returns: + 직렬화 dict. + """ + m = model or default_model() + return { + "selection_mode": m.selection_mode, + "leg_definition": m.leg_definition, + "entry": { + "pivot": m.entry.pivot_kind, + "price": m.entry.price_field, + "weight_rule": m.entry.weight_rule, + "weight_formula": "w_i = (1/price_i) / sum(1/price_j)", + "max_buys_per_leg": m.entry.max_per_leg, + "min_bars_between_buys": m.entry.min_bars_gap, + "bb_filter": m.entry.bb_filter, + }, + "exit": { + "pivot": m.exit.pivot_kind, + "price": m.exit.price_field, + "weight_rule": "fixed_split_or_full", + "weights_two_sell": list(m.exit.split_weights), + "split_gap_pct": m.exit.split_gap_pct, + "max_sells_per_leg": m.exit.max_per_leg, + }, + "capital": { + "buy": m.capital.buy_formula, + "optimal_buy_rate": m.capital.optimal_buy_rate, + "large_leg_top_pct": m.capital.large_leg_top_pct, + "pct_large_leg": m.capital.pct_large, + "pct_small_leg": m.capital.pct_small, + "min_order_krw": m.capital.min_order_krw, + "sell": m.capital.sell_formula, + }, + "execution": { + "chrono": m.execution_order_chrono, + "leg_block_json": m.execution_order_leg_block, + }, + } + + +def summarize_leg_weights(trades: list[dict[str, Any]]) -> dict[str, Any]: + """ + leg별 매수·매도 비중 합 검증용 요약. + + Args: + trades: GT trade dict. + + Returns: + leg_id → {buy_sum, sell_sum, n_buy, n_sell}. + """ + legs: dict[int, dict[str, Any]] = {} + for t in trades: + lid = int(t.get("leg_id", 0)) + legs.setdefault( + lid, + {"buy_sum": 0.0, "sell_sum": 0.0, "n_buy": 0, "n_sell": 0}, + ) + w = float(t.get("weight", 0)) + if t.get("action") == "buy": + legs[lid]["buy_sum"] += w + legs[lid]["n_buy"] += 1 + elif t.get("action") == "sell": + legs[lid]["sell_sum"] += w + legs[lid]["n_sell"] += 1 + return legs diff --git a/deepcoin/matching/portfolio_sim.py b/deepcoin/matching/portfolio_sim.py index 38e03f9..240c1a1 100644 --- a/deepcoin/matching/portfolio_sim.py +++ b/deepcoin/matching/portfolio_sim.py @@ -15,9 +15,39 @@ from config import ( LIVE_ORDER_KRW, TRADING_FEE_RATE, ) +from deepcoin.matching.position_sizing import ( + attach_dynamic_buy_amounts, + load_sizing_context_from_gt, +) -def select_capped_fires(fires: pd.DataFrame) -> pd.DataFrame: +def _planned_order_krw( + t: dict[str, Any], + order_krw: float, + sizing_mode: str, +) -> float: + """ + 체결 계획 원화: amount_krw 우선 또는 고정. + + Args: + t: trade dict. + order_krw: 고정 1회 금액. + sizing_mode: fixed | amount_krw. + + Returns: + 계획 원화. + """ + ak = t.get("amount_krw") + if sizing_mode == "amount_krw" or (ak is not None and float(ak) > 0): + return float(ak or 0) + return float(order_krw) + + +def select_capped_fires( + fires: pd.DataFrame, + *, + use_dynamic_sizing: bool = True, +) -> pd.DataFrame: """ 일한도·회수 제한으로 체결 가능한 발화만 남깁니다. @@ -29,20 +59,58 @@ def select_capped_fires(fires: pd.DataFrame) -> pd.DataFrame: """ if fires.empty: return fires + gt_trades, large_legs, approved = load_sizing_context_from_gt() df = fires.sort_values("dt").copy() df["ts"] = pd.to_datetime(df["dt"]) df["day"] = df["ts"].dt.date.astype(str) + cash = float(GT_INITIAL_CASH_KRW) + qty = 0.0 taken: list[pd.DataFrame] = [] for _, day_grp in df.groupby("day", sort=True): spent = 0.0 n_trades = 0 idxs: list[Any] = [] - for idx, _row in day_grp.iterrows(): + for idx, row in day_grp.iterrows(): if n_trades >= LIVE_MAX_TRADES_PER_DAY: break - if spent + LIVE_ORDER_KRW > LIVE_DAILY_KRW_MAX: - break - spent += LIVE_ORDER_KRW + side = row["side"] + price = float(row["close"]) + if side == "buy" and use_dynamic_sizing: + from deepcoin.matching.position_sizing import ( + compute_buy_amount_krw, + live_buy_asset_pct_scale, + ) + + scale = live_buy_asset_pct_scale( + str(row["rule_id"]), + str(row["dt"]), + gt_trades, + approved_rules=approved, + large_legs=large_legs, + ) + planned = compute_buy_amount_krw( + cash, + qty, + price, + 1.0, + 1.0, + asset_pct_scale=scale, + ) + else: + planned = float(LIVE_ORDER_KRW) + if side == "buy": + if spent + planned > LIVE_DAILY_KRW_MAX: + break + if planned <= 0: + continue + fee = planned * TRADING_FEE_RATE + cash -= planned + fee + qty += planned / price if price > 0 else 0.0 + spent += planned + elif side == "sell" and qty > 0: + gross = qty * price + cash += gross * (1.0 - TRADING_FEE_RATE) + qty = 0.0 n_trades += 1 idxs.append(idx) if idxs: @@ -52,7 +120,11 @@ def select_capped_fires(fires: pd.DataFrame) -> pd.DataFrame: return pd.concat(taken, ignore_index=True) -def fires_to_trade_list(fires: pd.DataFrame) -> list[dict[str, Any]]: +def fires_to_trade_list( + fires: pd.DataFrame, + *, + apply_dynamic_sizing: bool = True, +) -> list[dict[str, Any]]: """ 발화 DataFrame을 포트폴리오 시뮬용 trade dict 리스트로 변환. @@ -73,25 +145,65 @@ def fires_to_trade_list(fires: pd.DataFrame) -> list[dict[str, Any]]: "forward_ret_pct": float(r.get("forward_ret_pct", 0)), } ) + if apply_dynamic_sizing and rows: + gt_trades, large_legs, approved = load_sizing_context_from_gt() + attach_dynamic_buy_amounts( + rows, + gt_trades=gt_trades, + approved_rules=approved, + large_legs=large_legs, + ) return rows +def simulate_sized_portfolio( + trades: list[dict[str, Any]], + initial_cash: float = GT_INITIAL_CASH_KRW, + fee_rate: float = TRADING_FEE_RATE, + last_price: float | None = None, + fallback_order_krw: float = LIVE_ORDER_KRW, +) -> dict[str, Any]: + """ + trade.amount_krw(총자산 비율 배분) 기준 포트폴리오 시뮬. + + Args: + trades: 시간순 trade dict (amount_krw 권장). + initial_cash: 시작 현금. + fee_rate: 수수료율. + last_price: 미청산 평가 종가. + fallback_order_krw: amount_krw 없을 때 1회 금액. + + Returns: + simulate_truth_portfolio와 동일 키 구조. + """ + return simulate_fixed_order_portfolio( + trades, + order_krw=fallback_order_krw, + initial_cash=initial_cash, + fee_rate=fee_rate, + last_price=last_price, + sizing_mode="amount_krw", + ) + + def simulate_fixed_order_portfolio( trades: list[dict[str, Any]], order_krw: float = LIVE_ORDER_KRW, initial_cash: float = GT_INITIAL_CASH_KRW, fee_rate: float = TRADING_FEE_RATE, last_price: float | None = None, + sizing_mode: str = "fixed", ) -> dict[str, Any]: """ - 매 체결마다 고정 원화 금액으로 매수·매도한 뒤 총평가·수익률을 계산합니다. + 포트폴리오 시뮬 (고정 원화 또는 trade.amount_krw). Args: - trades: 시간순 {dt, action, price}. - order_krw: 1회 매수·매도 금액(원). + trades: 시간순 {dt, action, price, amount_krw?}. + order_krw: sizing_mode=fixed 일 때 1회 금액(원). initial_cash: 시작 현금. fee_rate: 수수료율. last_price: 미청산 평가 종가. + sizing_mode: 'fixed' | 'amount_krw' (없으면 order_krw). Returns: simulate_truth_portfolio와 동일 키 구조. @@ -110,7 +222,8 @@ def simulate_fixed_order_portfolio( last_trade_price = price if action == "buy": - amount = min(order, max(cash / (1.0 + fee_rate), 0.0)) + planned = _planned_order_krw(t, order, sizing_mode) + amount = min(planned, max(cash / (1.0 + fee_rate), 0.0)) if amount <= 0: continue fee = amount * fee_rate @@ -119,7 +232,11 @@ def simulate_fixed_order_portfolio( qty += amount / price elif action == "sell" and qty > 0: - sell_qty = min(qty, order / price) + planned = _planned_order_krw(t, order, sizing_mode) + if planned >= qty * price * 0.999: + sell_qty = qty + else: + sell_qty = min(qty, planned / price) if sell_qty <= 0: continue gross = sell_qty * price @@ -148,6 +265,7 @@ def simulate_fixed_order_portfolio( "mark_price": round(mark_price, 2), "fee_rate": fee_rate, "order_krw": round(order, 0), + "sizing_mode": sizing_mode, "trade_count": len(trades), } @@ -157,6 +275,7 @@ def simulate_fixed_order_portfolio_steps( order_krw: float = LIVE_ORDER_KRW, initial_cash: float = GT_INITIAL_CASH_KRW, fee_rate: float = TRADING_FEE_RATE, + sizing_mode: str = "fixed", ) -> list[dict[str, Any]]: """ 체결마다 현금·보유·총평가 스냅샷 (GT 테이블용). @@ -166,6 +285,7 @@ def simulate_fixed_order_portfolio_steps( order_krw: 1회 체결 원화. initial_cash: 시작 현금. fee_rate: 수수료율. + sizing_mode: fixed | amount_krw. Returns: step dict 리스트. @@ -182,7 +302,8 @@ def simulate_fixed_order_portfolio_steps( continue if action == "buy": - amount = min(order, max(cash / (1.0 + fee_rate), 0.0)) + planned = _planned_order_krw(t, order, sizing_mode) + amount = min(planned, max(cash / (1.0 + fee_rate), 0.0)) if amount <= 0: continue fee = amount * fee_rate @@ -190,7 +311,11 @@ def simulate_fixed_order_portfolio_steps( qty += amount / price elif action == "sell" and qty > 0: - sell_qty = min(qty, order / price) + planned = _planned_order_krw(t, order, sizing_mode) + if planned >= qty * price * 0.999: + sell_qty = qty + else: + sell_qty = min(qty, planned / price) if sell_qty <= 0: continue gross = sell_qty * price @@ -207,6 +332,7 @@ def simulate_fixed_order_portfolio_steps( "price": price, "rule_id": t.get("rule_id", ""), "forward_ret_pct": t.get("forward_ret_pct"), + "amount_krw": t.get("amount_krw"), "cash_krw": round(cash, 0), "holding_qty": round(qty, 4), "total_asset_krw": round(cash + qty * price, 0), diff --git a/deepcoin/matching/position_sizing.py b/deepcoin/matching/position_sizing.py new file mode 100644 index 0000000..d263a61 --- /dev/null +++ b/deepcoin/matching/position_sizing.py @@ -0,0 +1,378 @@ +""" +총자산 대비 최적 매수율(비중) · 현금 한도 · leg 상위·EV/WF 통과 대형 매수. +""" + +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, + LIVE_BUY_PCT_LARGE, + LIVE_BUY_PCT_SMALL, + MATCH_GT_TOLERANCE_MIN, + SIM_FEE_STRESS_MULT, + SIM_GO_MIN_HOLDOUT_EV, + SIM_GO_MIN_HOLDOUT_PF, + SIM_GO_WF_POSITIVE_RATIO, + SIM_WALK_FORWARD_MIN_MONTHS, + TRADING_FEE_RATE, +) +from deepcoin.matching.load_rules import load_matched_rules +from deepcoin.paths import MATCHING_FIRE_OUTCOMES, MATCHING_MATCHED_RULES + + +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)을 목표로, 가용 현금을 넘지 않게 매수 원화를 산출합니다. + + 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, _, _ = portfolio_totals(cash, qty, price) + budget = max(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 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 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 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: + """ + 실거래·시뮬 매수: EV/WF 통과 규칙 + leg 상위만 대형 비율. + + 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에 가까운 소형). + """ + 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 attach_dynamic_buy_amounts( + trades: list[dict[str, Any]], + *, + gt_trades: list[dict[str, Any]], + 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(총자산 비율·현금 한도)를 채웁니다. + + Args: + trades: 시간순 {dt, action, price, rule_id, …}. + gt_trades: GT leg 매칭용. + approved_rules: EV/WF 통과 rule. None이면 전 규칙 대형 허용 안 함. + large_legs: 상위 leg. None이면 GT에서 산출. + initial_cash: 초기 현금. + default_weight: 매수 weight 기본값. + fee_rate: 수수료율. + + Returns: + amount_krw가 채워진 동일 리스트. + """ + if large_legs is None: + large_legs = top_leg_ids_by_forward_return(gt_trades) + if approved_rules is None: + approved_rules = set() + + cash = float(initial_cash) + qty = 0.0 + for t in sorted(trades, key=lambda x: x["dt"]): + action = t.get("action", t.get("side", "")) + price = float(t["price"]) + if price <= 0: + continue + if action == "buy": + rid = str(t.get("rule_id", "")) + scale = live_buy_asset_pct_scale( + rid, t["dt"], gt_trades, + approved_rules=approved_rules, + large_legs=large_legs, + ) + amount = compute_buy_amount_krw( + cash, + qty, + price, + float(t.get("weight", default_weight)), + float(t.get("weight", default_weight)), + asset_pct_scale=scale, + fee_rate=fee_rate, + ) + t["amount_krw"] = amount + if amount > 0: + fee = amount * fee_rate + cash -= amount + fee + qty += amount / price + elif action == "sell" and qty > 1e-12: + gross = qty * price + t["amount_krw"] = round(gross, 0) + fee = gross * fee_rate + cash += gross - fee + qty = 0.0 + return trades + + +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) + approved = load_ev_wf_approved_rule_ids() + return trades, large, approved diff --git a/deepcoin/matching/simulation.py b/deepcoin/matching/simulation.py index 63b55cd..bc2d428 100644 --- a/deepcoin/matching/simulation.py +++ b/deepcoin/matching/simulation.py @@ -12,6 +12,7 @@ import numpy as np import pandas as pd from config import ( + GT_INITIAL_CASH_KRW, LIVE_DAILY_KRW_MAX, LIVE_MAX_TRADES_PER_DAY, LIVE_ORDER_KRW, @@ -28,7 +29,19 @@ from config import ( SIM_WALK_FORWARD_MIN_MONTHS, TRADING_FEE_RATE, ) +from deepcoin.ground_truth.ground_truth import ( + load_ground_truth, + order_trades_chronological, + simulate_truth_portfolio, +) +from deepcoin.matching.portfolio_sim import ( + fires_to_trade_list, + select_capped_fires, + simulate_fixed_order_portfolio, + simulate_sized_portfolio, +) from deepcoin.matching.select_rules import _rule_metrics, _split_train_valid_holdout +from deepcoin.paths import resolve_ground_truth_file from deepcoin.paths import ( ANALYSIS_GT_CALIBRATION_JSON, MATCHING_FIRE_OUTCOMES, @@ -109,12 +122,19 @@ def walk_forward_summary(wf_rows: list[dict[str, Any]]) -> dict[str, Any]: return out -def simulate_live_order_cap(outcomes: pd.DataFrame) -> dict[str, Any]: +def simulate_live_order_cap( + outcomes: pd.DataFrame, + *, + rule_ids: set[str] | None = None, + holdout_only: bool = True, +) -> dict[str, Any]: """ 1회·일 한도·슬리피지 가정으로 체결 가능한 발화만 집계. Args: - outcomes: fire_outcomes. + outcomes: fire_outcomes (split 컬럼 있으면 holdout 필터 가능). + rule_ids: None이면 전 규칙, 지정 시 해당 rule만. + holdout_only: True면 split==holdout 만. Returns: 규칙별·전체 요약. @@ -122,12 +142,27 @@ def simulate_live_order_cap(outcomes: pd.DataFrame) -> dict[str, Any]: if outcomes.empty: return {"rules": {}, "note": "발화 없음"} - df = outcomes.sort_values("dt").copy() + df = outcomes + if holdout_only and "split" in df.columns: + df = df[df["split"] == "holdout"] + if rule_ids is not None: + df = df[df["rule_id"].isin(rule_ids)] + df = df.sort_values("dt").copy() df["ts"] = pd.to_datetime(df["dt"]) df["day"] = df["ts"].dt.date.astype(str) slip = LIVE_SLIPPAGE_PCT taken_rows: list[pd.DataFrame] = [] + from deepcoin.matching.position_sizing import ( + compute_buy_amount_krw, + live_buy_asset_pct_scale, + load_sizing_context_from_gt, + ) + + gt_trades, large_legs, approved = load_sizing_context_from_gt() + cash = float(GT_INITIAL_CASH_KRW) + qty = 0.0 + for day, day_grp in df.groupby("day", sort=True): spent = 0.0 n_trades = 0 @@ -135,9 +170,34 @@ def simulate_live_order_cap(outcomes: pd.DataFrame) -> dict[str, Any]: for idx, row in day_grp.iterrows(): if n_trades >= LIVE_MAX_TRADES_PER_DAY: break - if spent + LIVE_ORDER_KRW > LIVE_DAILY_KRW_MAX: - break - spent += LIVE_ORDER_KRW + side = row["side"] + price = float(row["close"]) + if side == "buy": + scale = live_buy_asset_pct_scale( + str(row["rule_id"]), + str(row["dt"]), + gt_trades, + approved_rules=approved, + large_legs=large_legs, + ) + planned = compute_buy_amount_krw( + cash, qty, price, 1.0, 1.0, asset_pct_scale=scale + ) + else: + planned = float(LIVE_ORDER_KRW) + if side == "buy": + if planned <= 0: + continue + if spent + planned > LIVE_DAILY_KRW_MAX: + break + fee = planned * TRADING_FEE_RATE + cash -= planned + fee + qty += planned / price if price > 0 else 0.0 + spent += planned + elif side == "sell" and qty > 0: + gross = qty * price + cash += gross * (1.0 - TRADING_FEE_RATE) + qty = 0.0 n_trades += 1 taken_idx.append(idx) if taken_idx: @@ -164,6 +224,7 @@ def simulate_live_order_cap(outcomes: pd.DataFrame) -> dict[str, Any]: "order_krw": LIVE_ORDER_KRW, "daily_krw_max": LIVE_DAILY_KRW_MAX, "slippage_pct": slip, + "sizing": "total_asset_pct_ev_wf_large_leg", }, "taken_count": int(len(taken)), "total_count": int(len(df)), @@ -267,9 +328,35 @@ def build_simulation_report( sub.assign(forward_ret_pct=adj) ) - live_cap = simulate_live_order_cap(outcomes) + monitor_ids = {r["rule_id"] for r in matched.get("monitor_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) + portfolio_compare: dict[str, Any] = {} + gt_data = load_ground_truth(resolve_ground_truth_file()) or {} + gt_trades = gt_data.get("trades") or [] + mark = (gt_data.get("summary") or {}).get("mark_price") + if gt_trades: + portfolio_compare["ground_truth_chrono"] = simulate_truth_portfolio( + order_trades_chronological(gt_trades), + last_price=float(mark) if mark else None, + ) + holdout = outcomes[ + outcomes["rule_id"].isin(monitor_ids) & (outcomes["split"] == "holdout") + ] + capped = select_capped_fires(holdout) + if not capped.empty: + portfolio_compare["sim_sized"] = simulate_sized_portfolio( + fires_to_trade_list(capped, apply_dynamic_sizing=True), + last_price=float(mark) if mark else None, + ) + portfolio_compare["sim_fixed_order"] = simulate_fixed_order_portfolio( + fires_to_trade_list(capped, apply_dynamic_sizing=False), + last_price=float(mark) if mark else None, + ) + gt_portfolio: dict[str, Any] = {} if ANALYSIS_GT_CALIBRATION_JSON.is_file(): cal = json.loads(ANALYSIS_GT_CALIBRATION_JSON.read_text(encoding="utf-8")) @@ -301,6 +388,8 @@ def build_simulation_report( "fee_stress_by_rule": fee_stress, "live_order_cap_sim": live_cap, "go_no_go": go, + "portfolio_compare": portfolio_compare, + "gt_model": gt_data.get("model"), "monitor_rules": matched.get("monitor_rules", []), "gt_portfolio_calibration": gt_portfolio, "criteria": { diff --git a/deepcoin/matching/simulation_html.py b/deepcoin/matching/simulation_html.py index a0f0370..d368416 100644 --- a/deepcoin/matching/simulation_html.py +++ b/deepcoin/matching/simulation_html.py @@ -20,6 +20,7 @@ from config import ( ) from deepcoin.ground_truth.ground_truth import ( load_ground_truth, + order_trades_chronological, simulate_truth_portfolio, simulate_truth_portfolio_steps, ) @@ -28,6 +29,7 @@ from deepcoin.matching.portfolio_sim import ( select_capped_fires, simulate_fixed_order_portfolio, simulate_fixed_order_portfolio_steps, + simulate_sized_portfolio, ) from deepcoin.matching.select_rules import _split_train_valid_holdout from deepcoin.ops.chart_report import ( @@ -36,6 +38,7 @@ from deepcoin.ops.chart_report import ( market_cards_html, pnl_cards_html, rule_criteria_html, + stacked_summary_cards_html, wrap_chart_report_page, ) from deepcoin.ops.simulation import build_chart_html, load_chart_frames, _frames_to_mtf @@ -194,9 +197,11 @@ def _summary_cards_html( bb_txt: str, gt_trades: list[dict[str, Any]], gt_pnl: dict[str, Any], - sim_pnl: dict[str, Any], + sim_sized_pnl: dict[str, Any], + sim_fixed_pnl: dict[str, Any], sim_trade_count: int, go_flag: bool, + model_note: str = "", ) -> str: """ ground_truth HTML과 동일 구성의 상단 카드 (GT + 시뮬 2줄). @@ -206,26 +211,43 @@ def _summary_cards_html( bb_txt: BB %B. gt_trades: GT trades. gt_pnl: GT 포트폴리오 요약. - sim_pnl: 시뮬 포트폴리오 요약. + sim_sized_pnl: 총자산%·EV/WF·leg 시뮬 요약. + sim_fixed_pnl: 고정 ₩/회 baseline. sim_trade_count: 체결 가정 발화 수. go_flag: Go/No-Go. + model_note: GT 모델 한 줄 요약. Returns: cards HTML. """ go_cls = "go-pass" if go_flag else "go-fail" - gt_row = ( - '

정답 (ground_truth) — 분할 비중·leg 체결

' - + market_cards_html(close_last, bb_txt) - + pnl_cards_html(gt_pnl, "정답 타점", len(gt_trades)) + gt_sub = ( + "저점 분할매수(1/price 비중) · 고점 65/35% 매도 · " + "총자산×비중×leg티어 · 시각순 복리" ) - sim_row = ( - '

시뮬 (monitor_rules · holdout · ' - f"1회 ₩{LIVE_ORDER_KRW:,.0f}·일한도) — " - f'{"GO" if go_flag else "NO-GO"}

' - + pnl_cards_html(sim_pnl, "시뮬 체결", sim_trade_count) + if model_note: + gt_sub = model_note + gt_cards = market_cards_html(close_last, bb_txt) + pnl_cards_html( + gt_pnl, "정답 GT", len(gt_trades) + ) + sim_sized_title = ( + "시뮬·총자산% (EV/WF·leg상위) — " + f'{"GO" if go_flag else "NO-GO"}' + ) + sim_fixed_title = f"시뮬·고정 ₩{LIVE_ORDER_KRW:,}/회 (비교)" + return ( + '
' + + stacked_summary_cards_html(gt_sub, gt_cards) + + stacked_summary_cards_html( + sim_sized_title, + pnl_cards_html(sim_sized_pnl, "시뮬(비율)", sim_trade_count), + ) + + stacked_summary_cards_html( + sim_fixed_title, + pnl_cards_html(sim_fixed_pnl, "시뮬(고정)", sim_trade_count), + ) + + "
" ) - return gt_row + sim_row def build_simulation_page_html( @@ -294,56 +316,57 @@ def build_simulation_page_html( elif gt_summary.get("mark_price"): close_val = float(gt_summary["mark_price"]) - sim_trades = fires_to_trade_list(capped) - gt_pnl = {} - gt_summary_pnl = gt_data.get("summary") or {} - if gt_summary_pnl.get("pnl_krw") is not None and gt_summary_pnl.get( - "execution_order" - ) == "leg_block": - gt_pnl = { - k: gt_summary_pnl[k] - for k in ( - "initial_cash_krw", - "final_asset_krw", - "pnl_pct", - "total_fees_krw", - "holding_qty", - "holding_value_krw", - "mark_price", - "cash_krw", - ) - if k in gt_summary_pnl - } - elif gt_trades: + sim_trades_sized = fires_to_trade_list(capped, apply_dynamic_sizing=True) + sim_trades_fixed = fires_to_trade_list(capped, apply_dynamic_sizing=False) + + gt_pnl: dict[str, Any] = {} + if gt_trades: + gt_chron = order_trades_chronological(gt_trades) gt_pnl = simulate_truth_portfolio( - gt_trades, + gt_chron, initial_cash=GT_INITIAL_CASH_KRW, fee_rate=TRADING_FEE_RATE, last_price=close_val if close_val else None, ) - sim_pnl = simulate_fixed_order_portfolio( - sim_trades, + mark = close_val if close_val else None + sim_sized_pnl = simulate_sized_portfolio( + sim_trades_sized, + initial_cash=GT_INITIAL_CASH_KRW, + fee_rate=TRADING_FEE_RATE, + last_price=mark, + ) + sim_fixed_pnl = simulate_fixed_order_portfolio( + sim_trades_fixed, order_krw=LIVE_ORDER_KRW, initial_cash=GT_INITIAL_CASH_KRW, fee_rate=TRADING_FEE_RATE, - last_price=close_val if close_val else None, + last_price=mark, + sizing_mode="fixed", ) sim_steps = simulate_fixed_order_portfolio_steps( - sim_trades, + sim_trades_sized, order_krw=LIVE_ORDER_KRW, initial_cash=GT_INITIAL_CASH_KRW, fee_rate=TRADING_FEE_RATE, + sizing_mode="amount_krw", ) gt_steps = ( simulate_truth_portfolio_steps( - gt_trades, + order_trades_chronological(gt_trades), initial_cash=GT_INITIAL_CASH_KRW, fee_rate=TRADING_FEE_RATE, ) if gt_trades else [] ) + model = gt_data.get("model") or {} + model_note = ( + f"mode={model.get('selection_mode', 'split_buy_peak_sell')} · " + f"매수비중=1/price · 매도=65/35%" + if model + else "" + ) criteria_blocks = "".join(rule_criteria_html(r) for r in monitor_rules) go_table = go_no_go_table_html(go.get("checks", []), go_flag) @@ -355,7 +378,7 @@ def build_simulation_page_html( sim_table = f"""

시뮬 타점 (holdout {len(holdout_fires)}건 → 체결 가정 {len(capped)}건)

-

1회 ₩{LIVE_ORDER_KRW:,.0f}·일한도·최대 거래수 적용 후 체결 순 포트폴리오. +

총자산×최적비중·현금한도·EV/WF통과·leg상위 대형 매수. 일한도·최대 거래수 적용. 가격 열 (+/-) = {label_mode} 구간 수익%.{_mark_note(close_val)}

@@ -367,7 +390,7 @@ def build_simulation_page_html( gt_table = f"""

정답 타점 (ground_truth)

-

삼각형 = GT. 매수 분할 비중·매도 leg 반영.{_mark_note(close_val)}

+

삼각형 크기 = GT 체결 금액. 매수 분할·매도 leg 반영.{_mark_note(close_val)}

@@ -390,7 +413,7 @@ def build_simulation_page_html( "상단 카드: 초기 금액·총보유자산·초기 대비 증감율·수수료." ) legend = ( - "▲ 정답 매수 · ▼ 정답 매도 — 삼각형 = GT 비중.
" + "▲ 정답 매수 · ▼ 정답 매도 — 삼각형 = GT 체결 금액.
" "● 시뮬 — 원 = holdout 발화 (차트). 테이블 = 일한도 적용 체결 순서." ) if frames is not None: @@ -405,7 +428,15 @@ def build_simulation_page_html( ) cards = _summary_cards_html( - close_val, bb_txt, gt_trades, gt_pnl, sim_pnl, len(capped), go_flag + close_val, + bb_txt, + gt_trades, + gt_pnl, + sim_sized_pnl, + sim_fixed_pnl, + len(capped), + go_flag, + model_note=model_note, ) if frames is not None: @@ -415,6 +446,7 @@ def build_simulation_page_html( note=note, truth_trades=gt_trades, sim_trades=_fires_to_chart_trades(holdout_fires), + # 차트 마커는 holdout 전체; 카드·테이블은 일한도 capped title_suffix="1단계 시뮬레이션 (monitor · holdout)", legend_html=legend, footer_sections=sections, diff --git a/deepcoin/ops/chart_report.py b/deepcoin/ops/chart_report.py index c44af12..abed108 100644 --- a/deepcoin/ops/chart_report.py +++ b/deepcoin/ops/chart_report.py @@ -37,7 +37,13 @@ CHART_REPORT_CSS = """ .table-scroll { max-height: 480px; overflow-y: auto; border: 1px solid #e2e8f0; border-radius: 8px; } .pass { color: #16a34a; font-weight: 600; } .fail { color: #dc2626; font-weight: 600; } - .cards-group-title { font-size: 0.82rem; color: #475569; margin: 14px 0 6px; font-weight: 600; } + .summary-cards { margin: 16px 0; } + .summary-cards .cards-row-block { display: block; width: 100%; margin-bottom: 14px; } + .summary-cards .cards-row-block:last-child { margin-bottom: 0; } + .cards-group-title { + font-size: 0.82rem; color: #475569; margin: 0 0 8px; font-weight: 600; + display: block; + } """ @@ -116,6 +122,28 @@ def card_html(label: str, value: str) -> str: return f'
{label}{value}
' +def stacked_summary_cards_html( + title: str, + cards_inner: str, +) -> str: + """ + 제목 한 줄 + 카드 flex 한 줄을 세로 블록으로 묶습니다. + + Args: + title: cards-group-title 텍스트(HTML 허용). + cards_inner: .cards 안에 넣을 card div 문자열. + + Returns: + cards-row-block HTML. + """ + return ( + '
' + f'

{title}

' + f'
{cards_inner}
' + "
" + ) + + def wrap_chart_report_page( page_title: str, heading: str, @@ -135,7 +163,7 @@ def wrap_chart_report_page( meta_line: 기간·추세 등. note_html: 안내 박스. legend_html: 차트 범례 설명. - cards_html: .cards 내부 HTML. + cards_html: .cards 내부 HTML 또는 .summary-cards 블록 전체. chart_html: plotly embed. sections_html: h2·테이블·criteria 등 본문 하단. @@ -154,7 +182,7 @@ def wrap_chart_report_page(

{meta_line}

{note_html}
{legend_html}
-
{cards_html}
+ {cards_html if "summary-cards" in cards_html else f'
{cards_html}
'}
{chart_html}
{sections_html} diff --git a/deepcoin/ops/live_trader.py b/deepcoin/ops/live_trader.py index 3c55a0d..49a3284 100644 --- a/deepcoin/ops/live_trader.py +++ b/deepcoin/ops/live_trader.py @@ -19,9 +19,18 @@ from config import ( LIVE_ORDER_KRW, LIVE_TRADING_ENABLED, SYMBOL, + TRADING_FEE_RATE, ) +from deepcoin.ground_truth.ground_truth import load_ground_truth from deepcoin.matching.live_eval import evaluate_live_rules from deepcoin.matching.load_rules import load_monitor_rules +from deepcoin.matching.position_sizing import ( + compute_buy_amount_krw, + live_buy_asset_pct_scale, + load_ev_wf_approved_rule_ids, + top_leg_ids_by_forward_return, +) +from deepcoin.paths import resolve_ground_truth_file from deepcoin.ops.alert_message import build_rule_alert_message from deepcoin.ops.monitor import Monitor from deepcoin.paths import LIVE_TRADES_LOG @@ -40,6 +49,17 @@ class LiveTrader(Monitor): self._day_spent_krw: float = 0.0 self._day_trades: int = 0 self._day_pnl_krw: float = 0.0 + self._gt_trades: list[dict] = [] + self._large_legs: set[int] = set() + self._approved_rules: set[str] = set() + self._load_sizing_context() + + def _load_sizing_context(self) -> None: + """GT leg·EV/WF 통과 규칙 캐시.""" + gt = load_ground_truth(resolve_ground_truth_file()) or {} + self._gt_trades = gt.get("trades") or [] + self._large_legs = top_leg_ids_by_forward_return(self._gt_trades) + self._approved_rules = load_ev_wf_approved_rule_ids() def _reset_day_if_needed(self) -> None: """날짜 변경 시 일별 한도 카운터 초기화.""" @@ -61,7 +81,7 @@ class LiveTrader(Monitor): with LIVE_TRADES_LOG.open("a", encoding="utf-8") as f: f.write(json.dumps(record, ensure_ascii=False) + "\n") - def _can_trade(self, rule_id: str) -> tuple[bool, str]: + def _can_trade(self, rule_id: str, planned_krw: float | None = None) -> tuple[bool, str]: """ 일·쿨다운·손실 한도 검사. @@ -74,7 +94,8 @@ class LiveTrader(Monitor): self._reset_day_if_needed() if self._day_trades >= LIVE_MAX_TRADES_PER_DAY: return False, "일 최대 거래 수 초과" - if self._day_spent_krw + LIVE_ORDER_KRW > LIVE_DAILY_KRW_MAX: + need = float(planned_krw if planned_krw is not None else LIVE_ORDER_KRW) + if self._day_spent_krw + need > LIVE_DAILY_KRW_MAX: return False, "일 주문 한도 초과" if self._day_pnl_krw <= -abs(LIVE_DAILY_LOSS_LIMIT_KRW): return False, "일 손실 한도 초과" @@ -83,6 +104,46 @@ class LiveTrader(Monitor): return False, f"규칙 쿨다운({LIVE_COOLDOWN_MIN}분)" return True, "" + def _resolve_buy_amount_krw(self, hit: dict[str, Any]) -> float: + """ + 총자산·현금·EV/WF·leg 티어로 매수 원화 산출. + + Args: + hit: evaluate_live_rules 항목. + + Returns: + 매수 원화. + """ + rid = hit["rule_id"] + if rid not in self._approved_rules: + return 0.0 + price = float(hit["close"]) + cash = 0.0 + qty = 0.0 + try: + bal = self.load_balances_dict() + sym = bal.get(SYMBOL, {}) + cash = float(sym.get("available_krw") or sym.get("krw") or 0) + qty = float(sym.get("balance") or 0) + except Exception: + return 0.0 + scale = live_buy_asset_pct_scale( + rid, + hit["dt"], + self._gt_trades, + approved_rules=self._approved_rules, + large_legs=self._large_legs, + ) + return compute_buy_amount_krw( + cash, + qty, + price, + 1.0, + 1.0, + asset_pct_scale=scale, + fee_rate=TRADING_FEE_RATE, + ) + def _execute_order(self, hit: dict[str, Any]) -> dict[str, Any]: """ 매수·매도 주문 실행 또는 드라이런. @@ -95,7 +156,22 @@ class LiveTrader(Monitor): """ side = hit["side"] price = float(hit["close"]) - amount_krw = float(LIVE_ORDER_KRW) + if side == "buy": + amount_krw = self._resolve_buy_amount_krw(hit) + if amount_krw <= 0: + return { + "ts": datetime.now().isoformat(timespec="seconds"), + "rule_id": hit["rule_id"], + "side": side, + "signal_dt": hit["dt"], + "price": price, + "amount_krw": 0, + "live_enabled": LIVE_TRADING_ENABLED, + "ok": False, + "message": "매수 스킵(EV/WF·leg·현금)", + } + else: + amount_krw = float(LIVE_ORDER_KRW) record: dict[str, Any] = { "ts": datetime.now().isoformat(timespec="seconds"), "rule_id": hit["rule_id"], @@ -163,11 +239,23 @@ class LiveTrader(Monitor): for hit in fired: rid = hit["rule_id"] - ok, reason = self._can_trade(rid) + if hit["side"] == "buy" and hit["rule_id"] not in self._approved_rules: + print(f" [{hit['side']}] {rid} @ {hit['dt']}") + print(" skip: EV/WF 미통과 규칙") + continue + planned = ( + self._resolve_buy_amount_krw(hit) + if hit["side"] == "buy" + else float(LIVE_ORDER_KRW) + ) + ok, reason = self._can_trade(rid, planned) print(f" [{hit['side']}] {rid} @ {hit['dt']}") if not ok: print(f" skip: {reason}") continue + if hit["side"] == "buy" and planned <= 0: + print(" skip: 매수금액 0") + continue log = self._execute_order(hit) self._append_log(log) print(f" order: {log['message']} ok={log['ok']}") diff --git a/deepcoin/ops/simulation.py b/deepcoin/ops/simulation.py index b1b5569..fc140b3 100644 --- a/deepcoin/ops/simulation.py +++ b/deepcoin/ops/simulation.py @@ -28,6 +28,8 @@ from config import ( GT_INITIAL_CASH_KRW, GT_MARKER_SIZE_MAX, GT_MARKER_SIZE_MIN, + GT_MAX_BUY_ORDER_KRW, + LIVE_ORDER_KRW, MACD_FAST, MACD_SIGNAL, MACD_SLOW, @@ -58,16 +60,94 @@ def interval_chart_label(interval_min: int) -> str: return f"{interval_min}분봉" -def _marker_sizes(trades: list[dict], action: str) -> list[float]: - """비중(weight, 0~1)에 비례한 삼각형 크기.""" - pts = [t for t in trades if t.get("action") == action] +def _marker_hover_text( + label: str, + t: dict, + *, + default_order_krw: float | None = None, + extra_lines: list[str] | None = None, +) -> str: + """ + 차트 마커 툴팁: 체결가(price)와 체결 원화(amount_krw)를 함께 표시. + + Args: + label: 정답/시뮬 매수·매도 라벨. + t: trade dict (price, amount_krw, weight, memo …). + default_order_krw: amount_krw 없을 때 표시할 기본 원화(시뮬 고정 주문). + extra_lines: 툴팁 하단 추가 줄. + + Returns: + hovertext HTML 줄바꿈 문자열. + """ + action = t.get("action", t.get("side", "")) + amt_label = "매수금액" if action == "buy" else "매도금액" + lines = [ + label, + str(t.get("dt", ""))[:16], + f"체결가 ₩{float(t['price']):,.0f}", + ] + ak = t.get("amount_krw") + if ak is not None and float(ak) > 0: + lines.append(f"{amt_label} ₩{float(ak):,.0f}") + elif default_order_krw is not None and action == "buy": + lines.append(f"{amt_label} ₩{float(default_order_krw):,.0f}") + else: + lines.append(f"{amt_label} (미배분)") + if t.get("weight") is not None: + lines.append(f"비중 {float(t.get('weight', 1)) * 100:.0f}%") + if extra_lines: + lines.extend(extra_lines) + memo = t.get("memo", "") + if memo: + lines.append(str(memo)) + rule_id = t.get("rule_id", "") + if rule_id: + lines.append(str(rule_id)) + return "
".join(lines) + + +def _trade_amount_krw(t: dict) -> float: + """ + 마커 크기·툴팁용 체결 원화. amount_krw 없으면 비중×상한으로 추정. + + Args: + t: trade dict. + + Returns: + 원화 금액(0 이상). + """ + ak = t.get("amount_krw") + if ak is not None and float(ak) > 0: + return float(ak) + return max(float(t.get("weight", 1.0)), 0.05) * float(GT_MAX_BUY_ORDER_KRW) + + +def _marker_sizes(pts: list[dict]) -> list[float]: + """ + 체결 원화(amount_krw)에 비례한 삼각형 크기. + + 같은 trace(매수 또는 매도) 안에서 최소·최대 금액으로 선형 스케일. + + Args: + pts: 동일 action의 trade dict 리스트. + + Returns: + plotly marker size(diameter) 리스트. + """ if not pts: return [] lo, hi = float(GT_MARKER_SIZE_MIN), float(GT_MARKER_SIZE_MAX) - return [ - lo + (hi - lo) * min(max(float(t.get("weight", 1.0)), 0.05), 1.0) - for t in pts - ] + amounts = [_trade_amount_krw(t) for t in pts] + amin, amax = min(amounts), max(amounts) + sizes: list[float] = [] + for amount in amounts: + if amax > amin: + ratio = (amount - amin) / (amax - amin) + else: + ratio = 0.5 + ratio = max(ratio, 0.08) + sizes.append(lo + (hi - lo) * ratio) + return sizes def _add_sim_markers(fig, trades: list[dict], row: int = 1) -> None: @@ -101,9 +181,14 @@ def _add_sim_markers(fig, trades: list[dict], row: int = 1) -> None: opacity=0.75, ), hovertext=[ - f"{label}
{t['dt'][:16]}
₩{float(t['price']):,.0f}" - f"
leg_gt {float(t.get('forward_ret_pct', 0)):+.2f}%" - f"
{t.get('rule_id', '')}" + _marker_hover_text( + label, + t, + default_order_krw=LIVE_ORDER_KRW, + extra_lines=[ + f"leg_gt {float(t.get('forward_ret_pct', 0)):+.2f}%", + ], + ) for t in pts ], hovertemplate="%{hovertext}", @@ -114,7 +199,7 @@ def _add_sim_markers(fig, trades: list[dict], row: int = 1) -> None: def _add_truth_markers(fig, trades: list[dict], row: int = 1) -> None: - """정답 매수·매도 마커 (삼각형 크기 = 비중).""" + """정답 매수·매도 마커 (삼각형 크기 = 체결 원화 금액).""" for action, color, symbol, label in [ ("buy", "#16a34a", "triangle-up", "정답 매수"), ("sell", "#dc2626", "triangle-down", "정답 매도"), @@ -122,7 +207,7 @@ def _add_truth_markers(fig, trades: list[dict], row: int = 1) -> None: pts = [t for t in trades if t.get("action") == action] if not pts: continue - sizes = _marker_sizes(trades, action) + sizes = _marker_sizes(pts) fig.add_trace( go.Scatter( x=[pd.Timestamp(t["dt"]) for t in pts], @@ -138,9 +223,7 @@ def _add_truth_markers(fig, trades: list[dict], row: int = 1) -> None: line=dict(width=1.5, color="#111"), ), hovertext=[ - f"{label}
{t['dt'][:16]}
₩{t['price']:,.0f}" - f"
비중 {float(t.get('weight', 1))*100:.0f}%" - f"
{t.get('memo', '')}" + _marker_hover_text(label, t) for t in pts ], hovertemplate="%{hovertext}", @@ -446,7 +529,7 @@ def build_chart_html( ) trade_table = f"""

정답 타점 (ground_truth)

-

삼각형 크기 = 비중. 매수: 저점 분할 / 매도: 고점 1~2회. +

삼각형 크기 = 체결 금액(매수/매도 각각 min~max). 매수: 저점 분할 / 매도: 고점 1~2회. 총평가 = 체결 직후 현금 + 보유×체결가.{mark_note}

시각구분비중가격총 평가금액해석
@@ -471,8 +554,11 @@ def build_chart_html( ) default_legend = ( - "▲ 정답 매수 · ▼ 정답 매도 — 삼각형 크기 = 비중.
" - "● 시뮬 매수 · ● 시뮬 매도 — 원 = monitor_rules holdout 발화." + "▲ 정답 매수 · ▼ 정답 매도 — 크기=체결금액. " + "매수=총자산×비중×leg티어(상위 대형). " + "툴팁: 체결가·매수/매도금액.
" + "● 시뮬 매수 · ● 시뮬 매도 — 원 = holdout 발화 " + f"(매수 ₩{LIVE_ORDER_KRW:,.0f}/회)." ) if cards_html: cards_inner = cards_html diff --git a/docs/reference/GROUND_TRUTH.md b/docs/reference/GROUND_TRUTH.md index f4af9c7..d698f6f 100644 --- a/docs/reference/GROUND_TRUTH.md +++ b/docs/reference/GROUND_TRUTH.md @@ -1,35 +1,78 @@ # 정답 타점 (Ground Truth) -1달(기본 45일) 3분봉 구간에서 **사후적 최적 스윙** 매수·매도 라벨을 만듭니다. -실시간 매매 전략이 아니라, 이후 전략 검증·학습용 **정답 데이터**입니다. +1년(기본 `CHART_LOOKBACK_DAYS=365`) 3분봉에서 **사후 최적 스윙** 매수·매도 라벨. +실시간 전략이 아니라 규칙·시뮬 검증용 **벤치마크**입니다. -## Plan +JSON 필드 `model`에 타점·비중·자본 배분 규칙이 일반화되어 있습니다 (`deepcoin/ground_truth/gt_model.py`). -- **목적**: 차트 상 의미 있는 저점 매수·고점 매도를 JSON으로 고정 -- **방법**: 고점(major swing)에서 1~2회 매도 · 저점(ZigZag+BB)에서 분할 매수 · 삼각형 크기=비중 -- **체결 순서**: JSON 저장·포트폴리오 시뮬은 **leg별 매수 전량 → 매도 전량** (시각순 아님). 차트 표는 시각순 정렬. -- **HTML 카드**: 초기 금액, 총보유자산, 초기 대비 증감율(종가 평가 포함). 기간말 leg는 **종가 청산** 포함. +## Plan — 타점 구조 (일반화) -## Do +### Leg (라운드트립 구간) -```bash -python scripts/02_ground_truth.py # ground_truth_trades.json -python scripts/05_chart_truth.py # JSON + HTML 차트 +- **leg_id**: 이전 **고점 매도** 시각 ~ 다음 **고점 매도** 직전까지. +- 마지막 구간: 마지막 major peak 이후 ~ 기간 말 → **기간말 leg** (종가 청산 1회). + +### 매수 타점 (Entry) + +| 항목 | 규칙 | +|------|------| +| 피벗 | ZigZag **저점(trough)**, `GT_BUY_MIN_SWING_PCT` | +| 가격 | 해당 봉 **Low** | +| 후보 | leg 구간 내 trough, `GT_BUY_MIN_BARS` 간격, BB (`bb_pos <= GT_BUY_BB_MAX`) | +| **비중 weight** | `w_i = (1/price_i) / Σ(1/price_j)` — **저가일수록 큰 비중** | +| leg당 상한 | `GT_MAX_BUYS_PER_LEG` (초과 시 저가 순 유지) | + +### 매도 타점 (Exit) + +| 항목 | 규칙 | +|------|------| +| 피벗 | **major swing 고점(peak)** | +| 가격 | 해당 봉 **High** | +| **비중 weight** | 1회 매도: **100%** · 2회 분할: **65% + 35%** (`GT_SELL_SPLIT_GAP_PCT`) | +| 수량 | leg 보유 수량 × 매도 비중 (마지막 매도 = leg 전량) | + +## Do — 자본 배분 (amount_krw) + +시각순 체결. **매도 후 현금**이 다음 매수에 반영됩니다. + +``` +총보유자산 = 현금 + 보유×체결가 +최적매수율 = (이번 weight / leg 남은 weight 합) × leg티어스케일 +목표매수액 = 총보유자산 × 최적매수율 +실제매수액 = min(목표, 가용현금/(1+수수료)), 최소 GT_MIN_ORDER_KRW ``` -## Check +| leg 티어 | 조건 | 스케일 (`.env`) | +|----------|------|-----------------| +| 대형 | leg 수익률 상위 `GT_LARGE_LEG_TOP_PCT` | `GT_BUY_PCT_LARGE_LEG` (기본 1.0) | +| 소형 | 그 외 | `GT_BUY_PCT_SMALL_LEG` (기본 0.05) | -| 환경 변수 | 기본 | 설명 | -|-----------|------|------| -| `CHART_LOOKBACK_DAYS` | 365 | 조회 일수 (`.env` 기본 1년) | -| `GT_MIN_SWING_PCT` | 4.0 | ZigZag 최소 스윙(%) | -| `GT_PIVOT_ORDER` | 20 | 국소 극값 반경(봉) | -| `GT_MIN_BARS_BETWEEN` | 30 | 체결 간격(3분봉 30봉=90분) | -| `GT_MIN_LEG_PCT` | 8.0 | 한 구간 최소 수익(%) | -| `GT_MAX_ROUND_TRIPS` | 24 | 최대 라운드트립 | -| `GT_SELECTION_MODE` | split_buy_peak_sell | `split_buy_peak_sell` 등 (`.env` 참고) | +**summary.pnl_pct**: 위 배분으로 **시각순** 시뮬 + 기간말 **종가 평가**. + +**JSON 저장 순서**: leg별 매수 전량 → 매도 전량 (`leg_block`, 차트·테이블 정합). + +## 실행 + +```bash +python scripts/02_ground_truth.py # ground_truth_trades.json (+ model) +python scripts/05_chart_truth.py # HTML 차트 +``` + +## Check — 주요 환경 변수 + +| 변수 | 기본 | 설명 | +|------|------|------| +| `GT_MIN_SWING_PCT` | 4.0 | 매도 피벗 ZigZag(%) | +| `GT_BUY_MIN_SWING_PCT` | 3.0 | 매수 피벗 ZigZag(%) | +| `GT_PIVOT_ORDER` | 20 | 국소 극값 반경 | +| `GT_MIN_BARS_BETWEEN` | 30 | 체결 최소 간격(봉) | +| `GT_MIN_LEG_PCT` | 8.0 | major leg 최소 수익(%) | +| `GT_BUY_PCT_LARGE_LEG` | 1.0 | 상위 leg 총자산 배분 스케일 | +| `GT_BUY_PCT_SMALL_LEG` | 0.05 | 소형 leg 스케일 | +| `GT_LARGE_LEG_TOP_PCT` | 0.2 | 대형 leg 상위 비율 | +| `GT_MIN_ORDER_KRW` | 5000 | 최소 체결 원화 | ## Act -- JSON 수동 수정 후 `scripts/05_chart_truth.py` 재실행으로 차트 갱신 -- 파라미터 조정으로 타점 수·크기 튜닝 +- JSON·`model` 수정 후 `02` / `05` 재실행 +- 시뮬 비교: `04_simulation_report.py` (GT vs 시뮬·총자산% vs 고정 ₩/회) diff --git a/docs/reference/SIMULATION.md b/docs/reference/SIMULATION.md index 3de1978..388a947 100644 --- a/docs/reference/SIMULATION.md +++ b/docs/reference/SIMULATION.md @@ -2,12 +2,12 @@ ## 목적 -실거래(3단계) 전에 `monitor_rules`가 **과적합이 아닌지** 숫자로 검증합니다. +`monitor_rules`가 과적합이 아닌지 검증하고, **Ground Truth와 동일한 자본 배분 원칙**으로 holdout 체결 수익을 비교합니다. ## 실행 ```bash -python scripts/04_match_rules.py # 선행: 04 전체 또는 select +python scripts/04_match_rules.py # 선행 python scripts/04_simulation_report.py ``` @@ -15,26 +15,40 @@ python scripts/04_simulation_report.py | 파일 | 내용 | |------|------| -| `docs/04_matching/simulation_report.json` | walk-forward·민감도·Go/No-Go | -| `docs/04_matching/simulation_report.html` | GT 동일 카드(초기 금액·총보유자산·증감율)·차트·타점·규칙 기준 | +| `docs/04_matching/simulation_report.json` | Go/No-Go, `portfolio_compare`, `gt_model` | +| `docs/04_matching/simulation_report.html` | 카드 3줄: **GT · 시뮬(총자산%) · 시뮬(고정₩/회)** | + +## 포트폴리오 비교 (`portfolio_compare`) + +| 키 | 설명 | +|----|------| +| `ground_truth_chrono` | GT 타점 + `amount_krw` 시각순 체결 | +| `sim_sized` | holdout 발화 + **총자산×비중×EV/WF·leg상위** (`position_sizing`) | +| `sim_fixed_order` | 동일 발화 + **고정 `LIVE_ORDER_KRW`/회** (baseline) | + +## 시뮬 매수 배분 (GT와 동일 원칙) + +- **통과 규칙만** 대형: holdout EV·PF, walk-forward, 수수료 2× 스트레스 (`load_ev_wf_approved_rule_ids`) +- **leg 상위** `GT_LARGE_LEG_TOP_PCT` + 근접 GT leg 매칭 → `LIVE_BUY_PCT_LARGE` +- 그 외 → `LIVE_BUY_PCT_SMALL` +- 일한도·일최대거래: `select_capped_fires` (동적 planned 원화로 `LIVE_DAILY_KRW_MAX` 적용) ## 검증 항목 | 항목 | 설명 | |------|------| -| Holdout | 최근 15% 구간 EV≥0, PF≥1 | -| Walk-forward | 월별 EV, 양수 월 비율 ≥ `SIM_GO_WF_POSITIVE_RATIO` | -| 수수료 스트레스 | 수수료 2배(`SIM_FEE_STRESS_MULT`) 후에도 EV≥0 | -| 실거래 한도 가정 | `LIVE_ORDER_KRW`·`LIVE_DAILY_KRW_MAX` 내 체결 가능 비율 | +| Holdout | EV≥0, PF≥1 | +| Walk-forward | 양수 월 비율 ≥ `SIM_GO_WF_POSITIVE_RATIO` | +| 수수료 스트레스 | 수수료 2× 후 EV≥0 | +| 실거래 한도 | 동적 매수액 기준 일한도 시뮬 | ## Go/No-Go -- **GO**: `monitor_rules` 전 규칙이 checks 통과 → 2·3단계 진행 가능 -- **NO-GO**: 04 재선별·규칙 축소 후 재실행 +- **GO**: monitor_rules 전 규칙 checks 통과 +- **NO-GO**: 04 재선별 후 재실행 -## 환경 변수 (`config.py` / `.env`) +## 환경 변수 -- `SIM_GO_MIN_HOLDOUT_EV`, `SIM_GO_MIN_HOLDOUT_PF` -- `SIM_GO_WF_POSITIVE_RATIO` (기본 0.5) -- `SIM_WALK_FORWARD_MIN_MONTHS` (기본 3) -- `SIM_FEE_STRESS_MULT` (기본 2.0) +- `SIM_GO_*`, `SIM_WALK_FORWARD_MIN_MONTHS`, `SIM_FEE_STRESS_MULT` +- `LIVE_ORDER_KRW`, `LIVE_DAILY_KRW_MAX` (고정 baseline·한도) +- `LIVE_BUY_PCT_LARGE`, `LIVE_BUY_PCT_SMALL` (시뮬·실거래 비율 매수)
시각구분비중가격총 평가금액해석