d6c802ef26
Batch of the pre-launch audit campaign (BUG-01…14 plus three new features): Pricing / TP-SL protection - Add app/services/hl_price_feed.py: supplemental HL allMids poller for HL-native assets (HYPE, PURR) not listed on Binance. Pumps price_store + tp_sl_monitor.on_price_tick so bot trades on these assets keep full stop-loss / take-profit / trailing protection instead of max-hold only. - Wire feed into main.py lifespan (startup task + graceful shutdown cancel). Telegram - Add format_trump_mention + PATH B in _dispatch: crypto-relevant Trump posts with no directional signal (relevant=True, signal=hold) now alert the public channel only (no per-subscriber noise). - Rate limiter (slowapi) on the API; assorted bot/digest fixes. KOL on-chain - seed_kol_wallets.py: KOL_FEEDS coverage cross-check; reversibly deactivate orphaned wallets (handle not in KOL_FEEDS → can never produce divergence) so the scanner stops burning cycles on them. Tests / misc - Fix brittle test_macro_ahr999_uses_same_formula_as_scanner: mock now uses realistic ms timestamps so the in-progress-day drop fires, matching the fetcher's bar count (was 0.3179 vs 0.3178 off-by-one). - Refresh stale notify_signal comment in truth_social.py. Frontend reduce-action type fix lives in the sibling repo. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
97 lines
3.5 KiB
Python
97 lines
3.5 KiB
Python
"""
|
||
Backfill AI analysis onto historical Truth posts.
|
||
|
||
Safety rules:
|
||
* Only re-analyze `source='truth'` rows. Technical/scanner posts already
|
||
carry their own signal payloads and must not be sent through the Trump
|
||
text analyzer.
|
||
* Persist the full analysis payload so history doesn't become a mixed schema
|
||
of old partial rows and new v5 rows.
|
||
"""
|
||
import asyncio
|
||
import argparse
|
||
import sys
|
||
import os
|
||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
||
from app.services.analysis import analyze_post
|
||
from app.database import AsyncSessionLocal
|
||
from app.models import Post
|
||
from sqlalchemy import select
|
||
|
||
CONCURRENCY = 5 # 并发数,避免触发API限速
|
||
|
||
|
||
async def process_one(post: Post, semaphore: asyncio.Semaphore, overwrite: bool) -> str:
|
||
"""分析单条帖子并写回DB,返回状态字符串"""
|
||
if not overwrite and post.signal is not None:
|
||
return "skip"
|
||
|
||
async with semaphore:
|
||
analysis = await analyze_post(post.text)
|
||
await asyncio.sleep(0.1) # 轻度限速
|
||
|
||
async with AsyncSessionLocal() as db:
|
||
result = await db.execute(select(Post).where(Post.id == post.id))
|
||
p = result.scalar_one_or_none()
|
||
if p:
|
||
p.sentiment = analysis["sentiment"]
|
||
p.signal = analysis["signal"]
|
||
p.ai_confidence = analysis["confidence"]
|
||
p.ai_reasoning = analysis["reasoning"]
|
||
p.relevant = analysis["relevant"]
|
||
p.prefilter_reason = analysis.get("prefilter_reason")
|
||
p.analysis_version = analysis.get("analysis_version")
|
||
p.price_impact_asset = analysis["asset"] if analysis["relevant"] else None
|
||
p.target_asset = analysis.get("target_asset")
|
||
p.category = analysis.get("category")
|
||
p.expected_move_pct = analysis.get("expected_move_pct")
|
||
p.invalidation_price = analysis.get("invalidation_price")
|
||
await db.commit()
|
||
|
||
sig = analysis["signal"]
|
||
return f"{sig}:{analysis['confidence']}%"
|
||
|
||
|
||
async def main(limit: int, overwrite: bool):
|
||
async with AsyncSessionLocal() as db:
|
||
result = await db.execute(
|
||
select(Post)
|
||
.where(Post.source == "truth")
|
||
.order_by(Post.published_at.desc())
|
||
.limit(limit)
|
||
)
|
||
posts = result.scalars().all()
|
||
|
||
total = len(posts)
|
||
print(f"共 {total} 条帖子,并发={CONCURRENCY},overwrite={overwrite}\n")
|
||
|
||
semaphore = asyncio.Semaphore(CONCURRENCY)
|
||
tasks = [process_one(p, semaphore, overwrite) for p in posts]
|
||
|
||
done = 0
|
||
buy = short = sell = hold = skipped = 0
|
||
|
||
for coro in asyncio.as_completed(tasks):
|
||
status = await coro
|
||
done += 1
|
||
if status == "skip":
|
||
skipped += 1
|
||
elif status.startswith("buy"): buy += 1
|
||
elif status.startswith("short"): short += 1
|
||
elif status.startswith("sell"): sell += 1
|
||
else: hold += 1
|
||
|
||
if done % 20 == 0 or done == total:
|
||
print(f" 进度 {done}/{total} | BUY={buy} SHORT={short} SELL={sell} HOLD={hold} SKIP={skipped}")
|
||
|
||
print(f"\n✅ 完成: BUY={buy} SHORT={short} SELL={sell} HOLD={hold} SKIP={skipped}")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument("--limit", type=int, default=400)
|
||
ap.add_argument("--overwrite", action="store_true", help="重新分析已有signal的帖子")
|
||
args = ap.parse_args()
|
||
asyncio.run(main(args.limit, args.overwrite))
|