fix: 저가 코인 소수점 표시 + VOL_MIN 6.0 조정

- fp() 헬퍼: 100원 미만 코인 소수점 표시 (HOLO 등)
- VOL_MIN 8→6: 신호 빈도 적정화
- LLM 로그 가격도 :,.2f 통일

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
joungmin
2026-03-05 22:54:56 +09:00
parent 9944b55f94
commit 872163a3d1
2 changed files with 31 additions and 21 deletions

View File

@@ -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"⏭️ <b>매수 스킵</b> {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"📥 <b>지정가 매수</b> {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"❌ <b>매수 취소</b> {ticker}\n{pb['price']:,.0f}{elapsed:.0f}초 미체결")
tg(f"❌ <b>매수 취소</b> {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"🟢 <b>매수 체결</b> {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} <b>청산</b> {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: <b>{pnl:+.2f}%</b> ({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"🤖 <b>LLM 매도 설정</b> {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"♻️ <b>포지션 복구</b> {ticker}\n매수평균: {avg:,.0f}원 수량: {actual_bal:.6f}")
log.info(f"[복구] {ticker} 수량:{actual_bal:.6f} 매수평균:{fp(avg)}")
tg(f"♻️ <b>포지션 복구</b> {ticker}\n매수평균: {fp(avg)}원 수량: {actual_bal:.6f}")
if positions:
log.info(f"[복구] 총 {len(positions)}개 포지션 복구됨")
except Exception as e: