Switch demo trading to 5m short-term swing setups (plan 3.1)

- The model now submits a pending setup (direction, zone, confirm price,
  validity); the executor enters at market once a 5m close confirms a
  dip into support or a rejection from resistance, and cancels the setup
  when the zone breaks, the trigger is missed or the stop is too far
- Stop beyond the confirmation extreme by 0.5 x ATR14(5m), skipped above
  1%; fixed take-profit ladders from the fill price (long 60/30/10 at
  +0.5/0.75/1%, short 80/20 at -0.5/0.75%); 4h time stop
- While holding, the model may only keep or exit
- Drop the long-term angle from the program, API, tables and page
This commit is contained in:
eezd committed 2026-10-15 13:09:19 +08:00
1 parent 1fcf41952c
commit 7efa2eb68b
12 files changed
+696 -746

No files matched your search

+6 -25
View File
@@ -3,7 +3,7 @@
import json
from datetime import datetime, timedelta
from decimal import Decimal
from typing import Annotated, Literal
from typing import Annotated
from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, Field
@@ -24,11 +24,9 @@ from alphapulse.trading.snapshot import read_snapshot
router = APIRouter(prefix="/api/trading", tags=["trading"], dependencies=[Depends(require_admin)])
Angle = Literal["short", "long"]
ANGLE_LABELS = {"short": "短线", "long": "长线"}
SYMBOLS_CACHE_KEY = f"{KEY_PREFIX}trading:symbols"
SYMBOLS_CACHE_SECONDS = 3600
# 删除或切换角度前要求快照足够新,否则无法确认没有仓位和挂单
# 删除前要求快照足够新,否则无法确认没有仓位和挂单
SNAPSHOT_FRESH_SECONDS = 30
# 执行器正在管理的计划状态
LIVE_PLAN_STATUSES = ("pending", "open", "closing")
@@ -68,7 +66,6 @@ class SymbolOption(BaseModel):
class SymbolOut(BaseModel):
symbol: str
angle: str
added_at: datetime
analysis_requested_at: datetime | None
analyzing: bool
@@ -80,17 +77,11 @@ class SymbolOut(BaseModel):
class SymbolIn(BaseModel):
symbol: str
angle: Angle
class SymbolPatch(BaseModel):
angle: Angle
class AnalysisOut(BaseModel):
id: int
symbol: str
angle: str
reasons: list[str]
dry_run: bool
status: str
@@ -267,7 +258,7 @@ async def list_symbols(session: Annotated[AsyncSession, Depends(get_session)]) -
)}
return [
SymbolOut(
symbol=s.symbol, angle=s.angle, added_at=s.added_at, analysis_requested_at=s.analysis_requested_at,
symbol=s.symbol, added_at=s.added_at, analysis_requested_at=s.analysis_requested_at,
analyzing=s.symbol in analyzing, plan_id=plans[s.symbol].id if s.symbol in plans else None,
plan_status=plans[s.symbol].plan_status if s.symbol in plans else None,
close_deadline=plans[s.symbol].close_deadline if s.symbol in plans else None,
@@ -281,8 +272,8 @@ async def add_symbol(body: SymbolIn, session: Annotated[AsyncSession, Depends(ge
symbol = body.symbol.strip().upper()
if not any(o["symbol"] == symbol for o in await symbol_options()):
raise HTTPException(status.HTTP_400_BAD_REQUEST, f"{symbol} 不是模拟盘和实盘都在交易的加密货币 USDT 永续合约")
session.add(TradingSymbol(symbol=symbol, angle=body.angle))
session.add(TradingEvent(symbol=symbol, kind="symbol_added", message=f"加入交易列表,角度:{ANGLE_LABELS[body.angle]}"))
session.add(TradingSymbol(symbol=symbol))
session.add(TradingEvent(symbol=symbol, kind="symbol_added", message="加入交易列表"))
try:
await session.commit()
except IntegrityError:
@@ -290,7 +281,7 @@ async def add_symbol(body: SymbolIn, session: Annotated[AsyncSession, Depends(ge
async def ensure_idle(session: AsyncSession, symbol: str, action: str) -> TradingSymbol:
"""只有空仓、没有本程序的挂单、没有进行中的计划和分析时,才能删除或切换角度"""
"""只有空仓、没有本程序的挂单、没有进行中的计划和分析时,才能删除"""
s = await session.get(TradingSymbol, symbol)
if s is None:
raise HTTPException(status.HTTP_404_NOT_FOUND, f"{symbol} 不在交易列表中")
@@ -311,16 +302,6 @@ async def ensure_idle(session: AsyncSession, symbol: str, action: str) -> Tradin
return s
@router.patch("/symbols/{symbol}")
async def patch_symbol(symbol: str, body: SymbolPatch, session: Annotated[AsyncSession, Depends(get_session)]) -> None:
s = await ensure_idle(session, symbol, "切换角度")
if s.angle != body.angle:
session.add(TradingEvent(symbol=symbol, kind="angle_changed",
message=f"角度从{ANGLE_LABELS[s.angle]}改为{ANGLE_LABELS[body.angle]}"))
s.angle = body.angle
await session.commit()
@router.delete("/symbols/{symbol}")
async def delete_symbol(symbol: str, session: Annotated[AsyncSession, Depends(get_session)]) -> None:
"""只移出列表;分析、订单流水和时间线保留"""