Add 5-tier LLM SELL profit thresholds, allow loss sells, fix PnL sign display
- Replace 3-tier LLM SELL thresholds with 5-tier descending confidence (1-2%→0.7, 2-3%→0.6, 3-5%→0.5, ≥5%→0.4), min profit 1% - Allow LLM SELL on losing positions (trust LLM signal for cut-loss) - Fix +-1.90% sign bug: use pnl_pct instead of pnl amount for sign Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e2ec3cd920
commit
7f03d479c7
16
config.py
16
config.py
@ -44,8 +44,22 @@ MIN_ORDER_AMOUNT = {
|
||||
"tLINK:UST": 0.2, "tXRPUST": 4.0, "tSHIB:UST": 141010.0,
|
||||
"tDOGE:UST": 22.0, "tATOUST": 0.02, "tXLMUST": 4.0,
|
||||
}
|
||||
STOP_LOSS_PCT = 0.03
|
||||
# ATR-based dynamic stop-loss
|
||||
ATR_SL_MULTIPLIER = 3.0 # stop = entry - ATR × multiplier
|
||||
ATR_SL_MIN_PCT = 0.03 # 下限:不低於 3%(低波動期等同現行)
|
||||
ATR_SL_MAX_PCT = 0.08 # 上限:不超過 8%
|
||||
STOP_LOSS_PCT = 0.08 # 安全網用上限值(防止交易所 stop 失敗時過早觸發)
|
||||
TAKE_PROFIT_PCT = 0.15
|
||||
|
||||
# LLM SELL profit-tiered thresholds (5 層遞減)
|
||||
LLM_SELL_MIN_PROFIT_PCT = 0.01 # < 1% 不賣
|
||||
LLM_SELL_TIERS = [
|
||||
# (獲利上限, 最低信心)
|
||||
(0.02, 0.7), # 1-2% 需信心 ≥ 0.7
|
||||
(0.03, 0.6), # 2-3% 需信心 ≥ 0.6
|
||||
(0.05, 0.5), # 3-5% 需信心 ≥ 0.5
|
||||
(None, 0.4), # ≥ 5% 需信心 ≥ 0.4
|
||||
]
|
||||
MAX_POSITION_PCT = 0.30 # Max 30% of portfolio per coin
|
||||
MAX_TOTAL_EXPOSURE_PCT = 1.00 # No cap — use full balance
|
||||
|
||||
|
||||
@ -101,7 +101,7 @@ def adjust_size_by_volatility(amount_usdt: float, atr: float, price: float) -> f
|
||||
return amount_usdt
|
||||
|
||||
|
||||
def validate_trade(signal: dict, portfolio: dict, indicators_df=None) -> tuple[dict | None, str]:
|
||||
def validate_trade(signal: dict, portfolio: dict, indicators_df=None, current_prices: dict | None = None) -> tuple[dict | None, str]:
|
||||
"""Run all risk checks. Returns (signal, "") on success or (None, reason) on rejection."""
|
||||
action = signal.get("action", "HOLD")
|
||||
if action == "HOLD":
|
||||
@ -170,6 +170,28 @@ def validate_trade(signal: dict, portfolio: dict, indicators_df=None) -> tuple[d
|
||||
if pos.get("amount", 0) <= 0:
|
||||
logger.info("No position to sell for %s", symbol)
|
||||
return None, "無持倉,跳過"
|
||||
|
||||
# Profit-tiered LLM SELL filter (止盈/止損由 risk_manager 和交易所處理,這裡只管 LLM 信號)
|
||||
entry = pos.get("entry_price", 0)
|
||||
current = (current_prices or {}).get(symbol, 0)
|
||||
if entry > 0 and current > 0:
|
||||
pnl_pct = (current - entry) / entry
|
||||
confidence = signal.get("confidence", 0)
|
||||
if 0 <= pnl_pct < config.LLM_SELL_MIN_PROFIT_PCT:
|
||||
return None, f"獲利不足 {pnl_pct:+.2%},不執行 LLM SELL"
|
||||
if pnl_pct < 0:
|
||||
logger.info("LLM SELL %s: 虧損中 (P&L %+.2f%%),信任 LLM 信號", symbol, pnl_pct * 100)
|
||||
else:
|
||||
required_conf = config.LLM_SELL_TIERS[-1][1] # default to loosest
|
||||
for upper, min_conf in config.LLM_SELL_TIERS:
|
||||
if upper is None or pnl_pct < upper:
|
||||
required_conf = min_conf
|
||||
break
|
||||
if confidence < required_conf:
|
||||
return None, f"獲利 {pnl_pct:+.2%} 需信心 ≥ {required_conf},目前 {confidence}"
|
||||
logger.info("LLM SELL %s: 通過 (P&L %+.2f%%, conf %.2f ≥ %.2f)",
|
||||
symbol, pnl_pct * 100, confidence, required_conf)
|
||||
|
||||
# SELL: 一律全部賣出
|
||||
pos_value = pos.get("value_usdt", 0)
|
||||
if pos_value <= 0:
|
||||
|
||||
@ -71,8 +71,10 @@ def send_cycle_report(
|
||||
amount_usdt = tp.get("amount_usdt", 0)
|
||||
pnl_str = ""
|
||||
if tp.get("realized_pnl") is not None:
|
||||
sign = "+" if tp["realized_pnl"] >= 0 else ""
|
||||
pnl_str = f",收益 {sign}{tp['realized_pnl']:.2f} USDT ({sign}{tp['realized_pnl_pct']:.2f}%)"
|
||||
pnl_val = tp["realized_pnl"]
|
||||
pnl_pct_val = tp["realized_pnl_pct"]
|
||||
sign = "+" if pnl_pct_val > 0 else ""
|
||||
pnl_str = f",收益 {sign}{pnl_val:.2f} USDT ({sign}{pnl_pct_val:.2f}%)"
|
||||
lines.append(f" 🔴 SELL {name} — {pnl},平倉 {amount_usdt:.1f} USDT{pnl_str}")
|
||||
lines.append("")
|
||||
|
||||
@ -116,8 +118,10 @@ def send_cycle_report(
|
||||
if action == "BUY" and t.get("stop_price"):
|
||||
extra = f" — 止損掛 {t['stop_price']:.6g}"
|
||||
elif action == "SELL" and t.get("realized_pnl") is not None:
|
||||
sign = "+" if t["realized_pnl"] >= 0 else ""
|
||||
extra = f" — 收益 {sign}{t['realized_pnl']:.2f} USDT ({sign}{t['realized_pnl_pct']:.2f}%)"
|
||||
pnl = t["realized_pnl"]
|
||||
pnl_pct = t["realized_pnl_pct"]
|
||||
sign = "+" if pnl_pct > 0 else ""
|
||||
extra = f" — 收益 {sign}{pnl:.2f} USDT ({sign}{pnl_pct:.2f}%)"
|
||||
tag = " [止損觸發]" if t.get("is_stop_loss") else ""
|
||||
lines.append(f" {emoji} {name} — {detail}{extra}{tag}")
|
||||
lines.append("")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user