From 872163a3d1b005b3692fcb6c07dfc071fc5911b7 Mon Sep 17 00:00:00 2001 From: joungmin Date: Thu, 5 Mar 2026 22:54:56 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=A0=80=EA=B0=80=20=EC=BD=94=EC=9D=B8?= =?UTF-8?q?=20=EC=86=8C=EC=88=98=EC=A0=90=20=ED=91=9C=EC=8B=9C=20+=20VOL?= =?UTF-8?q?=5FMIN=206.0=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fp() 헬퍼: 100원 미만 코인 소수점 표시 (HOLO 등) - VOL_MIN 8→6: 신호 빈도 적정화 - LLM 로그 가격도 :,.2f 통일 Co-Authored-By: Claude Opus 4.6 --- core/llm_advisor.py | 4 ++-- daemons/tick_trader.py | 48 +++++++++++++++++++++++++----------------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/core/llm_advisor.py b/core/llm_advisor.py index ed00a21..39581dc 100644 --- a/core/llm_advisor.py +++ b/core/llm_advisor.py @@ -590,7 +590,7 @@ F&G지수: {fng} ({'공포' if fng <= 40 else '중립' if fng <= 50 else '탐욕 if data.get('action') == 'buy': data['price'] = float(data['price']) - log.info(f'[LLM매수] {ticker} → buy {data["price"]:,.0f}원 | {confidence} | {status} | {reason}') + log.info(f'[LLM매수] {ticker} → buy {data["price"]:,.2f}원 | {confidence} | {status} | {reason}') return data # action=buy, price=float log.warning(f'[LLM매수] {ticker} 알 수 없는 action: {data}') @@ -646,7 +646,7 @@ def get_exit_price( data['price'] = float(data['price']) pnl_from_entry = (data['price'] - entry_price) / entry_price * 100 log.info( - f'[LLM매도] {ticker} 지정가 교체: {current_target:,.0f} → {data["price"]:,.0f}원 ' + f'[LLM매도] {ticker} 지정가 교체: {current_target:,.2f} → {data["price"]:,.2f}원 ' f'(진입 대비 {pnl_from_entry:+.2f}%) | {confidence} | {status} | watch={watch} | {reason}' ) return data # action=sell, price=float diff --git a/daemons/tick_trader.py b/daemons/tick_trader.py index e776ece..b8f3173 100644 --- a/daemons/tick_trader.py +++ b/daemons/tick_trader.py @@ -43,7 +43,7 @@ TICKERS = [ BAR_SEC = 20 # 봉 주기 (초) VOL_LOOKBACK = 61 # 거래량 평균 기준 봉 수 ATR_LOOKBACK = 28 # ATR 계산 봉 수 -VOL_MIN = 8.0 # 거래량 배수 임계값 +VOL_MIN = 6.0 # 거래량 배수 임계값 BUY_TIMEOUT = 60 # 지정가 매수 미체결 타임아웃 (초) MAX_POS = int(os.environ.get('MAX_POSITIONS', 3)) @@ -80,6 +80,16 @@ logging.basicConfig( log = logging.getLogger(__name__) +def fp(price: float) -> str: + """가격을 단위에 맞게 포맷. 100원 미만은 소수점 표시.""" + if price >= 100: + return f"{price:,.0f}" + elif price >= 10: + return f"{price:,.1f}" + else: + return f"{price:,.2f}" + + def tg(msg: str) -> None: if not TG_TOKEN or not TG_CHAT_ID: return @@ -301,7 +311,7 @@ def process_signal(sig: dict) -> None: log.info(f"[시그널] {ticker} 포지션 한도 도달 → 스킵") return - log.info(f"[시그널] {ticker} {cur_price:,.0f}원 vol {vol_ratio:.1f}x → LLM 판단 요청") + log.info(f"[시그널] {ticker} {fp(cur_price)}원 vol {vol_ratio:.1f}x → LLM 판단 요청") llm_result = get_entry_price( ticker=ticker, @@ -318,7 +328,7 @@ def process_signal(sig: dict) -> None: log.info(f"[매수/LLM] {ticker} → 스킵 | {reason}") tg( f"⏭️ 매수 스킵 {ticker}\n" - f"현재가: {cur_price:,.0f}원 볼륨: {vol_ratio:.1f}x\n" + f"현재가: {fp(cur_price)}원 볼륨: {vol_ratio:.1f}x\n" f"시장: {status}\n" f"사유: {reason}" ) @@ -335,7 +345,7 @@ def process_signal(sig: dict) -> None: status = llm_result.get('market_status', '') qty = PER_POS * (1 - FEE) / buy_price diff_pct = (buy_price - cur_price) / cur_price * 100 - log.info(f"[매수/LLM] {ticker} → 승인 {buy_price:,.0f}원 (현재가 {cur_price:,.0f}원, 차이 {diff_pct:+.2f}%)") + log.info(f"[매수/LLM] {ticker} → 승인 {fp(buy_price)}원 (현재가 {fp(cur_price)}원, 차이 {diff_pct:+.2f}%)") if SIM_MODE: uuid = f"sim-buy-{ticker}" @@ -357,10 +367,10 @@ def process_signal(sig: dict) -> None: 'ts': datetime.now(), 'vol_ratio': vol_ratio, } - log.info(f"[지정가매수] {ticker} {buy_price:,.0f}원 수량: {qty:.6f}") + log.info(f"[지정가매수] {ticker} {fp(buy_price)}원 수량: {qty:.6f}") tg( f"📥 지정가 매수 {ticker}\n" - f"지정가: {buy_price:,.0f}원 (현재가 대비 {diff_pct:+.2f}%)\n" + f"지정가: {fp(buy_price)}원 (현재가 대비 {diff_pct:+.2f}%)\n" f"수량: {qty:.6f} 볼륨: {vol_ratio:.1f}x\n" f"확신: {confidence} 시장: {status}\n" f"LLM: {reason}\n" @@ -384,7 +394,7 @@ def check_pending_buys() -> None: if SIM_MODE: bar_list = list(bars.get(ticker, [])) if bar_list and bar_list[-1]['low'] <= pb['price']: - log.info(f"[SIM 매수체결] {ticker} {pb['price']:,.0f}원") + log.info(f"[SIM 매수체결] {ticker} {fp(pb['price'])}원") _activate_position(ticker, pb['price'], pb['qty'], pb['vol_ratio']) del pending_buys[ticker] continue @@ -401,7 +411,7 @@ def check_pending_buys() -> None: if elapsed >= BUY_TIMEOUT: cancel_order_safe(pb['uuid']) log.info(f"[매수취소] {ticker} {elapsed:.0f}초 미체결 → 취소") - tg(f"❌ 매수 취소 {ticker}\n{pb['price']:,.0f}원 {elapsed:.0f}초 미체결") + tg(f"❌ 매수 취소 {ticker}\n{fp(pb['price'])}원 {elapsed:.0f}초 미체결") del pending_buys[ticker] @@ -422,11 +432,11 @@ def _activate_position(ticker: str, entry_price: float, qty: float, vol_ratio: f 'llm_last_ts': None, 'llm_active': False, } - log.info(f"[진입] {ticker} {entry_price:,.0f}원 vol {vol_ratio:.1f}x 지정가 {tag} {target:,.0f}원") + log.info(f"[진입] {ticker} {fp(entry_price)}원 vol {vol_ratio:.1f}x 지정가 {tag} {fp(target)}원") tg( f"🟢 매수 체결 {ticker}\n" - f"체결가: {entry_price:,.0f}원 수량: {qty:.6f}\n" - f"지정가 매도: {tag} {target:,.0f}원 (+{lr*100:.1f}%)\n" + f"체결가: {fp(entry_price)}원 수량: {qty:.6f}\n" + f"지정가 매도: {tag} {fp(target)}원 (+{lr*100:.1f}%)\n" f"{'[시뮬]' if SIM_MODE else '[실거래]'}" ) @@ -448,7 +458,7 @@ def _advance_stage(ticker: str) -> None: uuid = submit_limit_sell(ticker, pos['qty'], target) pos['sell_uuid'] = uuid pos['sell_price'] = target - log.info(f"[단계전환] {ticker} → {tag} 목표가 {target:,.0f}원") + log.info(f"[단계전환] {ticker} → {tag} 목표가 {fp(target)}원") else: pos['sell_uuid'] = None pos['sell_price'] = None @@ -470,11 +480,11 @@ def _record_exit(ticker: str, exit_price: float, tag: str) -> None: llm_flag = 'LLM' if pos.get('llm_active') else 'cascade' icon = "✅" if pnl > 0 else "🔴" - log.info(f"[청산/{tag}/{llm_flag}] {ticker} {exit_price:,.0f}원 PNL {pnl:+.2f}% {krw:+,.0f}원 {held}초 보유") + log.info(f"[청산/{tag}/{llm_flag}] {ticker} {fp(exit_price)}원 PNL {pnl:+.2f}% {krw:+,.0f}원 {held}초 보유") tg( f"{icon} 청산 {ticker} [{reason_tag}] ({llm_flag})\n" - f"진입: {pos['entry_price']:,.0f}원\n" - f"청산: {exit_price:,.0f}원\n" + f"진입: {fp(pos['entry_price'])}원\n" + f"청산: {fp(exit_price)}원\n" f"PNL: {pnl:+.2f}% ({krw:+,.0f}원) {held}초 보유\n" f"{'[시뮬]' if SIM_MODE else '[실거래]'}" ) @@ -548,10 +558,10 @@ def check_filled_positions() -> None: pos['sell_uuid'] = new_uuid pos['sell_price'] = new_price pos['llm_active'] = True - log.info(f"[매도/LLM] {ticker} 지정가 {new_price:,.0f}원 설정") + log.info(f"[매도/LLM] {ticker} 지정가 {fp(new_price)}원 설정") tg( f"🤖 LLM 매도 설정 {ticker}\n" - f"지정가: {new_price:,.0f}원 (진입 대비 {pnl_pct:+.2f}%)\n" + f"지정가: {fp(new_price)}원 (진입 대비 {pnl_pct:+.2f}%)\n" f"확신: {confidence} 시장: {status} 관망: {'Y' if watch else 'N'}\n" f"LLM: {reason}" ) @@ -675,8 +685,8 @@ def restore_positions() -> None: 'llm_last_ts': None, 'llm_active': False, } - log.info(f"[복구] {ticker} 수량:{actual_bal:.6f} 매수평균:{avg:,.0f}원") - tg(f"♻️ 포지션 복구 {ticker}\n매수평균: {avg:,.0f}원 수량: {actual_bal:.6f}") + log.info(f"[복구] {ticker} 수량:{actual_bal:.6f} 매수평균:{fp(avg)}원") + tg(f"♻️ 포지션 복구 {ticker}\n매수평균: {fp(avg)}원 수량: {actual_bal:.6f}") if positions: log.info(f"[복구] 총 {len(positions)}개 포지션 복구됨") except Exception as e: