feat(kol-v2): fix soft-signal recall — bullish/bearish over mention, category tickers

Two failure modes addressed:

1. Soft/indirect bullish signals classified as 'mention' instead of 'bullish'
   The prompt now explicitly lists casual English and Chinese expressions that
   count as bullish/bearish (not mention):
   - EN: 'looks interesting here', 'has room to run', 'wouldn't be short'
   - ZH: '感觉还有空间', '可以关注', '这里不错', '有上涨空间'
   mention is now reserved for ZERO directional lean (historical refs, neutral
   examples). Previously the vague boundary caused the model to use mention
   conservatively, losing real signals.

2. Category-level calls lost when no specific ticker named
   'memecoin season', 'memes look good', '梗币最近不错' had no ticker to attach
   to. Added synthetic tickers: MEME, ALTCOIN, DEFI, AI — the model uses these
   when the view is about the category, not a specific coin.
   Normalizer allows synthetic tickers through the len>12 guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
k
2026-05-29 20:04:25 +08:00
parent cc1dda832b
commit eb8ee9ea91
+28 -3
View File
@@ -107,10 +107,28 @@ TICKERS:
- action values:
"buy"/"sell" → author explicitly states a position action ("I bought", "we are long", "我们减仓了", "added to my bag", "taking profits", "已建仓", "我加仓了", "建了仓")
"reduce" → partially exiting or taking profits on an existing long ("trimming", "taking some off", "减了一部分", "止盈了一些")
"bullish"/"bearish" → directional view without explicit position statement
"mention" → ticker appears but no clear stance — use sparingly, only when ticker is central to the post
"bullish" → ANY expression of upside view, including SOFT/INDIRECT/CASUAL language. Err on the side of bullish over mention.
English soft bullish: "X looks interesting here", "I like X at these levels", "X has room to run",
"wouldn't be short X", "X is setting up well", "compelling setup in X", "X is undervalued",
"I'd be a buyer of X", "X deserves attention here", "keeping an eye on X"
Chinese soft bullish: "感觉还有空间", "这里可以关注", "值得关注", "看好", "还有机会",
"这个位置不错", "可以考虑", "有上涨空间", "这波不错", "可以关注一下", "挺有意思的"
"bearish" → ANY expression of downside view or caution, including soft signals.
English soft bearish: "X looks extended", "I'd be careful here", "not touching X",
"X has further to fall", "selling into strength on X", "X is a trap"
Chinese soft bearish: "感觉要跌", "要小心", "不敢碰", "还会跌", "高位了", "风险很大"
"mention" → ticker appears but author has ZERO directional lean — pure neutral reference,
historical context only, or just used as an example. If there is ANY lean at all,
use bullish or bearish instead. Reserve mention for truly neutral uses like:
"BTC started in 2009" or "unlike ETH, which uses proof-of-stake"
- Dedupe per ticker — at most one entry per symbol; pick the strongest action.
- Do NOT invent tickers. Skip "$XYZ" if unsure it is a real crypto token.
- For CATEGORY-LEVEL calls (no specific ticker named), use these synthetic tickers:
"MEME" → author is bullish/bearish on memecoins as a category ("memes look good", "memecoin season", "梗币最近不错")
"ALTCOIN" → author is bullish/bearish on altcoins broadly ("alts are waking up", "山寨季", "altseason")
"DEFI" → author is bullish/bearish on DeFi protocols broadly
"AI" → author is bullish/bearish on AI-related tokens broadly
Only use synthetic tickers when the view is genuinely about the category, not just a passing mention.
- conviction: 0.8+ = explicit + repeated + sized or timed; 0.50.7 = clear view, no commitment; <0.5 = passing reference.
- timeframe: "immediate" = acting now or within 24h; "days" = 17d; "weeks" = 14w; "months" = 1+ months; "unspecified" = not stated.
- Do not include fiat (USD/CNY/JPY) or stablecoins (USDT/USDC/DAI/FRAX/USDE) unless the post's main thesis is about them.
@@ -246,12 +264,19 @@ async def extract_kol_signal(
valid_actions = {"buy", "sell", "reduce", "bullish", "bearish", "mention"}
valid_timeframes = {"immediate", "days", "weeks", "months", "unspecified"}
valid_post_types = {"trade_update", "macro_thesis", "research", "news_recap", "opinion", "other"}
# Synthetic category tickers — allowed even though they're not real on-chain tokens.
# Used when the KOL expresses a view on a category without naming specific coins.
SYNTHETIC_TICKERS = {"MEME", "ALTCOIN", "DEFI", "AI"}
for t in tickers:
if not isinstance(t, dict):
continue
sym = (t.get("ticker") or "").strip().upper()
if not sym or len(sym) > 12:
if not sym:
continue
# Allow synthetic category tickers; reject anything else > 12 chars
# (12 chars covers most real tickers; synthetic ones are all ≤ 7 chars)
if sym not in SYNTHETIC_TICKERS and len(sym) > 12:
continue
action = (t.get("action") or "mention").lower()
if action not in valid_actions: