feat(routing): wire AI target_asset end-to-end — bot trades the right perp
Closes the loop on the asset-routing prompt change. Previously the v5 prompt emitted target_asset (e.g. SOL, TRUMP) but bot_engine still read price_impact_asset and only ever traded BTC/ETH. Now the trade actually fires on whatever perp the AI picked. Schema (alembic 006): posts.target_asset (str) — HL perp ticker, any of the universe posts.category (str) — 6-class enum (direct_named, etc.) posts.expected_move_pct (float) — AI's 1h move estimate Wiring: truth_social.py persists the three fields when creating Post rows. bot_engine.py routing: asset = target_asset || price_impact_asset || BTC Old rows (target_asset=NULL) fall back to legacy BTC/ETH path — no retroactive scoring needed; new rows route correctly from now on. Hyperliquid trader doesn't need changes — `coin` is already a parameter, and analysis.py validated against HL_PERPS before storing target_asset so by the time bot_engine reads the field, it's guaranteed tradeable. Deployment: alembic upgrade head # adds the 3 columns Restart api container Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
"""Add target_asset / category / expected_move_pct to posts (v5 routing)
|
||||||
|
|
||||||
|
Revision ID: 006
|
||||||
|
Revises: 005
|
||||||
|
Create Date: 2026-05-08 00:00:00.000000
|
||||||
|
|
||||||
|
The v5 AI prompt outputs not just signal direction but also which specific
|
||||||
|
perp to trade (could be BTC/ETH/SOL/TRUMP/anything on Hyperliquid). The
|
||||||
|
bot reads `target_asset` to route the trade. `price_impact_asset` stays
|
||||||
|
bound to BTC/ETH for the existing price_impact_monitor.
|
||||||
|
"""
|
||||||
|
from typing import Sequence, Union
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
revision: str = "006"
|
||||||
|
down_revision: Union[str, None] = "005"
|
||||||
|
branch_labels: Union[str, Sequence[str], None] = None
|
||||||
|
depends_on: Union[str, Sequence[str], None] = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
with op.batch_alter_table("posts") as batch_op:
|
||||||
|
batch_op.add_column(sa.Column("target_asset", sa.String(16), nullable=True))
|
||||||
|
batch_op.add_column(sa.Column("category", sa.String(24), nullable=True))
|
||||||
|
batch_op.add_column(sa.Column("expected_move_pct", sa.Float(), nullable=True))
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
with op.batch_alter_table("posts") as batch_op:
|
||||||
|
batch_op.drop_column("expected_move_pct")
|
||||||
|
batch_op.drop_column("category")
|
||||||
|
batch_op.drop_column("target_asset")
|
||||||
@@ -53,6 +53,13 @@ class Post(Base):
|
|||||||
ai_reasoning: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
ai_reasoning: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
|
||||||
prefilter_reason: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
prefilter_reason: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
|
||||||
analysis_version: Mapped[Optional[str]] = mapped_column(String(16), nullable=True)
|
analysis_version: Mapped[Optional[str]] = mapped_column(String(16), nullable=True)
|
||||||
|
# v5: AI-decided trade target. Can be any HL perp (BTC/ETH/SOL/TRUMP/...),
|
||||||
|
# not just BTC/ETH. The bot routes the actual trade here.
|
||||||
|
# `price_impact_asset` stays bound to BTC/ETH for the existing
|
||||||
|
# price_impact_monitor; `target_asset` is what we actually trade.
|
||||||
|
target_asset: Mapped[Optional[str]] = mapped_column(String(16), nullable=True)
|
||||||
|
category: Mapped[Optional[str]] = mapped_column(String(24), nullable=True)
|
||||||
|
expected_move_pct: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
|
||||||
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=utcnow)
|
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False, default=utcnow)
|
||||||
|
|
||||||
trades: Mapped[List["BotTrade"]] = relationship("BotTrade", back_populates="trigger_post")
|
trades: Mapped[List["BotTrade"]] = relationship("BotTrade", back_populates="trigger_post")
|
||||||
|
|||||||
@@ -102,11 +102,16 @@ async def _process_entry(entry: dict, db: AsyncSession) -> Optional[Post]:
|
|||||||
prefilter_reason=analysis.get("prefilter_reason"),
|
prefilter_reason=analysis.get("prefilter_reason"),
|
||||||
analysis_version=analysis.get("analysis_version"),
|
analysis_version=analysis.get("analysis_version"),
|
||||||
relevant=analysis["relevant"],
|
relevant=analysis["relevant"],
|
||||||
|
# `asset` (BTC/ETH only) feeds the existing price_impact tracker.
|
||||||
price_impact_asset=asset if analysis["relevant"] else None,
|
price_impact_asset=asset if analysis["relevant"] else None,
|
||||||
price_impact_m5=None, # filled by price_impact_monitor after 5 m
|
price_impact_m5=None, # filled by price_impact_monitor after 5 m
|
||||||
price_impact_m15=None, # filled by price_impact_monitor after 15 m
|
price_impact_m15=None, # filled by price_impact_monitor after 15 m
|
||||||
price_impact_m1h=None, # filled by price_impact_monitor after 1 h
|
price_impact_m1h=None, # filled by price_impact_monitor after 1 h
|
||||||
price_at_post=price_at_post,
|
price_at_post=price_at_post,
|
||||||
|
# v5 routing: AI decides the actual perp to trade. May be SOL/TRUMP/etc.
|
||||||
|
target_asset=analysis.get("target_asset"),
|
||||||
|
category=analysis.get("category"),
|
||||||
|
expected_move_pct=analysis.get("expected_move_pct"),
|
||||||
)
|
)
|
||||||
db.add(post)
|
db.add(post)
|
||||||
await db.flush()
|
await db.flush()
|
||||||
|
|||||||
@@ -77,8 +77,17 @@ async def process_post(post: Post, db: AsyncSession) -> None:
|
|||||||
logger.info("Post %d skipped: signal=%s is not actionable", post.id, post.signal)
|
logger.info("Post %d skipped: signal=%s is not actionable", post.id, post.signal)
|
||||||
return
|
return
|
||||||
|
|
||||||
asset = post.price_impact_asset or 'BTC'
|
# v5: route the trade to the AI-decided perp. target_asset can be any
|
||||||
|
# Hyperliquid perp ticker (BTC/ETH/SOL/TRUMP/...) — analysis.py already
|
||||||
|
# validated against HL_PERPS and resolved chain fallbacks before this
|
||||||
|
# field was set, so by the time we get here it's safe to trade directly.
|
||||||
|
# Fallbacks for older / null rows: legacy price_impact_asset, then BTC.
|
||||||
|
asset = post.target_asset or post.price_impact_asset or 'BTC'
|
||||||
side = 'long' if post.signal == 'buy' else 'short'
|
side = 'long' if post.signal == 'buy' else 'short'
|
||||||
|
logger.info(
|
||||||
|
"Routing post %d → trade %s/%s (category=%s, expected_move=%.2f%%)",
|
||||||
|
post.id, asset, side, post.category, post.expected_move_pct or 0,
|
||||||
|
)
|
||||||
|
|
||||||
logger.info("Signal: post=%d asset=%s side=%s confidence=%d", post.id, asset, side, post.ai_confidence)
|
logger.info("Signal: post=%d asset=%s side=%s confidence=%d", post.id, asset, side, post.ai_confidence)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user