Files
AlphaPulse/web/src/apis/trading.ts
T
eezd fb63ebb697 Add a position history view to demo trading
New GET /api/trading/positions/history lists closed plans (entry filled,
position back to zero) newest first with before/limit paging. Each row
carries the filled quantity, volume-weighted entry and exit prices from
the plan's own fills, the close reason and the realized PnL, commission
and net from the plan result.

The positions panel gets a "历史仓位" button that opens a drawer with the
table, a "load earlier" button and links to each plan's analysis.
2026-10-15 23:10:09 +08:00

307 lines
8.5 KiB
TypeScript

import type { Effort } from "@/apis/settings"
import { http } from "@/http"
/** 以下数值字段都是币安返回的十进制字符串,避免浮点误差 */
export interface SnapshotPosition {
symbol: string
side: "LONG" | "SHORT"
amount: string
entry_price: string
break_even_price: string
mark_price: string
unrealized_pnl: string
liquidation_price: string
notional: string
margin: string
leverage: number
margin_type: string
}
export interface SnapshotOrder {
symbol: string
client_id: string
side: "BUY" | "SELL"
type: string
price: string
quantity: string
filled: string
reduce_only: boolean
time: number
}
export interface SnapshotAlgoOrder {
symbol: string
client_id: string
side: "BUY" | "SELL"
type: string
trigger_price: string
quantity: string
close_position: boolean
reduce_only: boolean
time: number
}
export interface Snapshot {
updated_at: string
/** 最近一次成功同步币安的时间 */
synced_at: string | null
error: string | null
account: { wallet_balance: string, margin_balance: string, available_balance: string, unrealized_pnl: string } | null
positions: SnapshotPosition[]
orders: SnapshotOrder[]
algo_orders: SnapshotAlgoOrder[]
}
export interface TradingStatus {
configured: boolean
running: boolean
running_changed_at: string | null
/** 设定的交易资金,十进制字符串;未设置时不能开始交易 */
capital_usdt: string | null
/** 实际用于仓位计算的资金:min(交易资金, 账户权益) */
effective_capital: string | null
provider_id: number | null
reasoning_effort: Effort | null
/** trader 进程离线时为空 */
snapshot: Snapshot | null
today: { analyses: number, limit: number, input_tokens: number, output_tokens: number }
}
export interface TradingSymbol {
symbol: string
added_at: string
analysis_requested_at: string | null
analyzing: boolean
/** 执行器正在管理的计划 */
plan_id: number | null
plan_status: "pending" | "open" | "closing" | null
close_deadline: string | null
}
export interface SymbolOption {
symbol: string
base: string
}
export interface TradingEvent {
id: number
created_at: string
symbol: string | null
analysis_id: number | null
kind: string
level: "info" | "warn" | "error"
message: string
data: Record<string, unknown> | null
}
/** 本程序下的单;其他是用户手动下的 */
export function isOwnOrder(clientId: string) {
return /^ap\d+$/.test(clientId)
}
export function getStatus(): Promise<TradingStatus> {
return http.get("/trading/status")
}
export function startTrading(): Promise<TradingStatus> {
return http.post("/trading/start")
}
export function stopTrading(): Promise<TradingStatus> {
return http.post("/trading/stop")
}
export function patchConfig(body: { capital_usdt?: string | null, provider_id?: number | null, reasoning_effort?: Effort | null }): Promise<TradingStatus> {
return http.patch("/trading/config", body)
}
export function listSymbolOptions(): Promise<SymbolOption[]> {
return http.get("/trading/symbol-options")
}
export function listSymbols(): Promise<TradingSymbol[]> {
return http.get("/trading/symbols")
}
export function addSymbol(symbol: string): Promise<void> {
return http.post("/trading/symbols", { symbol })
}
export function deleteSymbol(symbol: string): Promise<void> {
return http.delete(`/trading/symbols/${symbol}`)
}
export function listEvents(params: { symbol?: string, before?: number, limit?: number }): Promise<TradingEvent[]> {
return http.get("/trading/events", { params })
}
export interface PlanCondition {
direction: "above" | "below"
price: string
timeframe: "mark" | "1m" | "5m" | "15m" | "1h" | "4h"
basis: string
}
/** submit_trade_plan 的参数原样保存;数值为字符串 */
export interface TradeDecision {
action: "plan" | "keep" | "exit" | "wait"
summary: string
reasons: string
risks: string
reevaluate: PlanCondition[]
direction: "long" | "short" | null
confidence: "high" | "medium" | "low" | null
/** 区间与挂单价;更早格式的分析没有这个字段 */
setup?: { zone_low: string, zone_high: string, entry_price?: string, basis: string } | null
valid_hours?: string | null
}
/** 程序按止损和交易资金算出的数量与风险 */
export interface EntrySizing {
price: string
stop: string
stop_pct: string
quantity: string
notional: string
leverage: string
risk_amount: string
risk_at_stop: string
risk_pct: string
capped_by_leverage: boolean
}
export interface Sizing {
direction: "long" | "short"
setup: { zone_low: string, zone_high: string }
/** 限价、止损和数量 */
entry: EntrySizing
atr: string
position_qty: string
}
export interface Analysis {
id: number
symbol: string
reasons: string[]
dry_run: boolean
status: "running" | "done" | "failed"
action: TradeDecision["action"] | null
decision: TradeDecision | null
sizing: Sizing | null
error: string | null
model: string | null
reasoning_effort: string | null
usage: { input_tokens: number, output_tokens: number } | null
started_at: string
finished_at: string | null
plan_status: PlanStatus | null
valid_until: string | null
first_fill_at: string | null
close_deadline: string | null
closed_at: string | null
close_reason: string | null
result: { realized_pnl: string, commission: string, net: string } | null
}
/** 与协议无关的消息格式,同 Chat */
export type TranscriptMessage
= | { role: "user", text: string }
| { role: "assistant", text: string, tool_calls: { id: string, name: string, arguments: Record<string, unknown> }[] }
| { role: "tool", tool_call_id: string, name: string, result: string }
export interface AnalysisDetail extends Analysis {
text: string | null
transcript: TranscriptMessage[] | null
}
export type PlanStatus = "proposed" | "pending" | "open" | "closing" | "closed" | "canceled" | "replaced" | "stale" | "applied"
export const PLAN_STATUS_LABELS: Record<PlanStatus, string> = {
proposed: "待采纳",
pending: "挂单中",
open: "持仓中",
closing: "平仓中",
closed: "已结束",
canceled: "已撤销",
replaced: "已被替换",
stale: "已作废",
applied: "已执行"
}
/** 执行器的订单流水 */
export interface OrderRow {
id: number
purpose: "entry" | "stop" | "tp" | "close"
leg: number | null
side: "BUY" | "SELL"
type: string
price: string | null
trigger_price: string | null
quantity: string
status: string
filled_qty: string
avg_price: string | null
error: string | null
created_at: string
}
export const CLOSE_REASON_LABELS: Record<string, string> = {
stop: "止损",
take_profit: "止盈",
time: "到达持仓时限",
exit: "模型决定结束",
external: "外部平仓",
stop_failed: "止损挂单失败",
close: "市价平仓",
// 计划 3.1 之前的旧原因,历史仓位里还会出现
invalidation: "短线失效条件触发",
replaced: "被新计划替换",
expired: "限价单到期未成交",
stopped: "已停止交易",
entry_failed: "入场单被拒"
}
/** 止盈阶梯(与后端 plan.TAKE_PROFIT_STEPS 一致):[距成交均价的涨跌幅, 平仓比例] */
export const TAKE_PROFIT_STEPS: Record<"long" | "short", [number, number][]> = {
long: [[0.005, 0.6], [0.0075, 0.3], [0.01, 0.1]],
short: [[0.005, 0.8], [0.0075, 0.2]]
}
/** 已平仓的仓位:一个计划从首次入场到持仓归零;数值为十进制字符串 */
export interface PositionHistory {
analysis_id: number
symbol: string
direction: "long" | "short"
opened_at: string
closed_at: string
close_reason: string
/** 计划被替换后接管持仓的新计划没有自己的入场成交,为空 */
quantity: string | null
entry_price: string | null
/** 外部平仓没有成交记录,为空 */
exit_price: string | null
realized_pnl: string
commission: string
net: string
}
export function listPositionHistory(params: { before?: number, limit?: number }): Promise<PositionHistory[]> {
return http.get("/trading/positions/history", { params })
}
export function getSymbolPlan(symbol: string): Promise<{ plan: Analysis | null, orders: OrderRow[] }> {
return http.get(`/trading/symbols/${symbol}/plan`)
}
export function requestAnalysis(symbol: string): Promise<void> {
return http.post(`/trading/symbols/${symbol}/analyze`)
}
export function listAnalyses(params: { symbol?: string, limit?: number }): Promise<Analysis[]> {
return http.get("/trading/analyses", { params })
}
export function getAnalysis(id: number): Promise<AnalysisDetail> {
return http.get(`/trading/analyses/${id}`)
}