Refactor frontend workspaces and centralize API contracts

Organize domain APIs and types under common/apis, shared components under named folders, and private workspace logic alongside each page.

Use Bearer sessions, explicit response envelopes, session-aware routing and streams, and configurable login captcha presentation for the deployed backend contract.

Preserve news, economic calendar, crawler, market, chat, Skills, settings, and demo trading workflows with responsive layouts and explicit configuration saves.

Validate with frontend lint, type checking, 22 test groups, production builds, and deployed desktop/mobile page checks.
This commit is contained in:
eezd committed 2026-10-26 11:39:52 +08:00
1 parent 77b33e5e77
commit bc480fb806
102 files changed
+4716 -1707

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
import type * as Auth from "./type"
import { request } from "@/http/axios"
/** 获取当前身份 */
export function getIdentityApi() {
return request<Auth.GetIdentityResponseData>({
url: "/auth/me",
method: "get"
})
}
/** 撤销当前会话 */
export function revokeSessionApi() {
return request<Auth.RevokeSessionResponseData>({
url: "/auth/logout",
method: "post"
})
}
/** 获取登录验证码模式及挑战 */
export function getCaptchaApi() {
return request<Auth.GetCaptchaResponseData>({
url: "/auth/captcha",
method: "get",
anonymous: true,
notify: false
})
}
/** 提交登录凭据 */
export function loginApi(body: Auth.LoginForm) {
return request<Auth.LoginResponseData>({
url: "/auth/login",
method: "post",
data: body,
anonymous: true
})
}
/** 一次性短票据,只用于行情 WebSocket 握手。 */
export function createWebSocketTicketApi() {
return request<Auth.WebSocketTicket>({
url: "/auth/ws-ticket",
method: "post"
})
}
+37
View File
@@ -0,0 +1,37 @@
export interface Identity {
username: string
is_admin: boolean
}
export type GetIdentityResponseData = Identity
export type RevokeSessionResponseData = null
export type Captcha = { enabled: false } | {
enabled: true
captcha_id: string
image: string
expires_in: number
}
export interface LoginForm {
username: string
password: string
captcha_id?: string
code?: string
}
export interface LoginResult {
token: string
token_type: "bearer"
expires_at: string
}
export type GetCaptchaResponseData = Captcha
export type LoginResponseData = LoginResult
export interface WebSocketTicket {
ticket: string
expires_in: number
}
+37
View File
@@ -0,0 +1,37 @@
import type * as Calendar from "./type"
import { request } from "@/http/axios"
/** 按日期范围查询经济日历 */
export function listCalendarApi(start: string, days: number) {
return request<Calendar.ListCalendarResponseData>({
url: "/calendar",
method: "get",
params: { start, days }
})
}
/** 读取本期事件修订记录 */
export function calendarRevisionsApi(id: number) {
return request<Calendar.CalendarRevisionsResponseData>({
url: `/calendar/${id}/revisions`,
method: "get"
})
}
/** 读取同指标历次公布值 */
export function calendarHistoryApi(id: number, indicator: string) {
return request<Calendar.CalendarHistoryResponseData>({
url: `/calendar/${id}/history`,
method: "get",
params: { indicator }
})
}
/** 读取当前官方历史序列 */
export function calendarSeriesApi(id: number, indicator: string) {
return request<Calendar.CalendarSeriesResponseData>({
url: `/calendar/${id}/series`,
method: "get",
params: { indicator }
})
}
+73
View File
@@ -0,0 +1,73 @@
export interface CalendarItem {
release_id: number
indicator: string
name: string
country_code: string
country_name: string
event_name: string
title: string
period: string | null
stage: string | null
source: string
unit: string
importance: number
scheduled_at: string
status: "scheduled" | "waiting" | "delayed" | "released" | "withdrawn" | "error" | "historical" | "unavailable"
actual: string | null
previous: string | null
forecast: string | null
previous_basis: string | null
impact: "pending" | "bullish" | "bearish" | "neutral" | "uncertain"
impact_basis: "forecast" | "previous" | null
explanation: string
release_url: string | null
schedule_url: string
first_received_at: string | null
last_checked_at: string | null
error: string | null
revision_no: number
}
export interface CalendarData {
items: CalendarItem[]
countries: { code: string, name: string }[]
worker: { at: string, sources: Record<string, { source: string, checked_at: string, error: string | null }> } | null
server_time: string
}
export interface CalendarRevision {
revision_no: number
captured_at: string
payload: { title: string, scheduled_at: string, schedule_status: string, values: Record<string, { actual: string | null, previous: string | null }> }
}
export interface CalendarHistory {
release_id: number
scheduled_at: string
title: string
period: string | null
stage: string | null
actual: string
previous: string | null
forecast: string | null
first_recorded_actual: string | null
first_received_at: string
release_url: string | null
revised: boolean
}
export type ListCalendarResponseData = CalendarData
export type CalendarRevisionsResponseData = CalendarRevision[]
export type CalendarHistoryResponseData = CalendarHistory[]
export interface CalendarSeriesValue {
period: string
stage: string
value: string
captured_at: string
source_url: string
}
export type CalendarSeriesResponseData = CalendarSeriesValue[]
+59
View File
@@ -0,0 +1,59 @@
import type * as Chat from "./type"
import { request } from "@/http/axios"
import { consumeStream } from "@/http/stream"
/** 读取当前对话及生成状态 */
export function getChatSessionApi() {
return request<Chat.GetChatSessionResponseData>({
url: "/chat/session",
method: "get"
})
}
/** 重置当前对话,可按选择清空上下文 */
export function resetChatApi(clearContext: boolean) {
return request<Chat.ResetChatResponseData>({
url: "/chat/reset",
method: "post",
data: { clear_context: clearContext }
})
}
/** 读取 Chat 上下文 */
export function getChatContextApi() {
return request<Chat.GetChatContextResponseData>({
url: "/chat/context",
method: "get"
})
}
/** 保存 Chat 上下文版本 */
export function saveChatContextApi(content: string) {
return request<Chat.SaveChatContextResponseData>({
url: "/chat/context",
method: "put",
data: { content }
})
}
/** 读取 Chat 上下文历史 */
export function listChatContextVersionsApi() {
return request<Chat.ListChatContextVersionsResponseData>({
url: "/chat/context/versions",
method: "get"
})
}
/** POST 只发送一次;这里声明 SSE 事件协议,生命周期由 Chat 管理。 */
export function sendChatApi(body: Chat.SendChatRequestData, signal: AbortSignal, version: number, onEvent: Chat.ChatStreamHandler) {
return consumeStream<Chat.ChatStreamEvent["data"]>("/api/chat/messages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
signal
}, version, () => {}, (name, data) => {
// SSE 将事件名与 JSON 分开发送;在协议边界恢复同一事件的关联类型。
const event = { name, data } as Chat.ChatStreamEvent
onEvent(event)
})
}
+65
View File
@@ -0,0 +1,65 @@
import type { Effort } from "../settings/type"
export interface ToolCall {
id: string
name: string
arguments: Record<string, unknown>
}
interface MessageMetadata {
id: number
provider_id: number | null
usage: { input_tokens?: number, output_tokens?: number } | null
created_at: string
}
export type ChatMessage = MessageMetadata & (
| { role: "user", content: { text: string } }
| { role: "assistant", content: { text: string, tool_calls: ToolCall[], stopped?: boolean, error?: string, thinking_blocks?: unknown[] } }
| { role: "tool", content: { tool_call_id: string, name: string, result: string } }
)
export interface ChatSession {
id: number
created_at: string
messages: ChatMessage[]
estimated_tokens: number
generating: boolean
}
export interface ChatContext {
id: number | null
content: string
source: "user" | "chat" | "reset" | null
created_at: string | null
}
export type GetChatSessionResponseData = ChatSession
export type ResetChatResponseData = null
export type GetChatContextResponseData = ChatContext
export type SaveChatContextResponseData = ChatContext
export type ListChatContextVersionsResponseData = ChatContext[]
export interface SendChatRequestData {
text: string
provider_id: number
reasoning_effort: Effort | null
}
export type ChatStreamEvent
= | { name: "prompt", data: { estimated_tokens: number, context_window: number, provider: string } }
| { name: "thinking", data: Record<string, never> }
| { name: "done", data: Record<string, never> }
| { name: "context_updated", data: { id: number } }
| { name: "delta", data: { text: string } }
| { name: "tool_call", data: ToolCall }
| { name: "tool_result", data: { id: string, name: string, preview: string } }
| { name: "skill_draft", data: { skill_id: number, path: string, version: number } }
| { name: "usage", data: { input_tokens: number, output_tokens: number } }
| { name: "error", data: { message: string } }
export type ChatStreamHandler = (event: ChatStreamEvent) => void
+45
View File
@@ -0,0 +1,45 @@
import type * as Crawler from "./type"
import { request } from "@/http/axios"
import { consumeStream } from "@/http/stream"
/** 读取新闻与日历来源状态 */
export function listSourcesApi() {
return request<Crawler.ListSourcesResponseData>({
url: "/crawler/sources",
method: "get"
})
}
/** 修改新闻来源设置 */
export function patchSourceApi(code: string, body: Crawler.SourcePatch) {
return request<Crawler.PatchSourceResponseData>({
url: `/crawler/sources/${code}`,
method: "patch",
data: body
})
}
/** 读取来源历史日志 */
export function listLogsApi(source: string, limit = 200) {
return request<Crawler.ListLogsResponseData>({
url: "/crawler/logs",
method: "get",
params: { source, limit }
})
}
/** 请求重启新闻采集 */
export function restartCrawlerApi(source: string) {
return request<Crawler.RestartCrawlerResponseData>({
url: "/crawler/restart",
method: "post",
data: { source }
})
}
/** 只读订阅,重连由订阅生命周期负责。 */
export function subscribeCrawlerApi(signal: AbortSignal, version: number, onOpen: () => void, onMessage: (data: Crawler.CrawlLog) => void) {
return consumeStream<Crawler.CrawlLog>("/api/crawler/logs/stream", { signal }, version, onOpen, (name, data) => {
if (name === "log") onMessage(data)
})
}
+75
View File
@@ -0,0 +1,75 @@
export interface ProcessStatus {
state: "running" | "stopped" | "restarting"
pid: number | null
started_at: string | null
next_run_at: string
}
export interface SourceInfo {
code: string
name: string
region_code: string | null
list_url: string
enabled: boolean
interval_seconds: number
backfill_hours: number
consecutive_failures: number
last_success_at: string | null
last_error: string | null
paused_until: string | null
supports_history: boolean
history_target: string | null
history_position: string | null
history_done: boolean
process: ProcessStatus | null
today_new: number
today_revised: number
total_items: number
}
export interface SourcesInfo {
manager_alive: boolean
db_size_bytes: number
sources: SourceInfo[]
calendar_sources: CalendarSourceInfo[]
}
export interface CalendarSourceInfo {
code: string
name: string
region_code: string
worker_alive: boolean
heartbeat_at: string | null
schedule_checked_at: string | null
schedule_success_at: string | null
latest_result_at: string | null
next_event_at: string | null
overdue_count: number
history_start: string | null
history_end: string | null
errors: string[]
}
export type ListSourcesResponseData = SourcesInfo
export interface CrawlLog {
id: number
source_code: string
run_id: string | null
ts: string
level: string
message: string
}
export interface SourcePatch {
enabled?: boolean
interval_seconds?: number
/** ISO 时间表示回补到该时间;null 表示取消回补 */
history_target?: string | null
}
export type PatchSourceResponseData = null
export type ListLogsResponseData = CrawlLog[]
export type RestartCrawlerResponseData = null
+59
View File
@@ -0,0 +1,59 @@
import type * as Market from "./type"
import { request } from "@/http/axios"
/** 读取研究行情交易对 */
export function listSymbolsApi() {
return request<Market.ListSymbolsResponseData>({
url: "/market/symbols",
method: "get"
})
}
/** 新增研究行情交易对 */
export function addSymbolApi(market: Market.Market, symbol: string) {
return request<Market.AddSymbolResponseData>({
url: "/market/symbols",
method: "post",
data: { market, symbol }
})
}
/** 移出研究行情交易对 */
export function deleteSymbolApi(id: number) {
return request<Market.DeleteSymbolResponseData>({
url: `/market/symbols/${id}`,
method: "delete"
})
}
/** 查询交易所可用交易对 */
export function listTradableApi(market: Market.Market) {
return request<Market.ListTradableResponseData>({
url: "/market/exchange-info",
method: "get",
params: { market }
})
}
/** 读取币安原生 K 线 */
export function listKlinesApi(params: Market.ListKlinesParamsRequestData) {
return request<Market.ListKlinesResponseData>({
url: "/market/klines",
method: "get",
params
})
}
/** 读取币安 24 小时行情 */
export function getTickerApi(market: Market.Market, symbol: string) {
return request<Market.GetTickerResponseData>({
url: "/market/ticker",
method: "get",
params: { market, symbol }
})
}
/** 使用一次性票据连接行情 WebSocket */
export function createMarketSocketApi(ticket: string) {
return new WebSocket(`${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/api/market/ws`, ["alphapulse.market.v1", `ticket.${ticket}`])
}
+86
View File
@@ -0,0 +1,86 @@
export type Market = "spot" | "um"
export interface IntervalProgress {
done: boolean
percent: number
}
export interface MarketSymbol {
id: number
market: Market
symbol: string
base: string
quote: string
price_precision: number
volume_precision: number
status: "pending" | "backfilling" | "ready" | "error" | "delisted"
is_default: boolean
added_at: string
last_error: string | null
progress: Record<string, IntervalProgress>
}
export interface TradableSymbol {
symbol: string
base: string
quote: string
}
/** 币安 24hr ticker 原始字段(只列用到的) */
export interface Ticker24h {
lastPrice: string
priceChangePercent: string
highPrice: string
lowPrice: string
volume: string
quoteVolume: string
}
/** [open_time, open, high, low, close, volume, quote_volume] */
export type KlineRow = [number, number, number, number, number, number, number]
export type ListSymbolsResponseData = MarketSymbol[]
export type AddSymbolResponseData = null
export type DeleteSymbolResponseData = null
export type ListTradableResponseData = TradableSymbol[]
export type ListKlinesResponseData = KlineRow[]
export interface ListKlinesParamsRequestData { market: Market, symbol: string, interval: string, end?: number, limit?: number }
export type GetTickerResponseData = Ticker24h
export interface KlineMessage {
t: number
o: string
h: string
l: string
c: string
v: string
q: string
x: boolean
}
export interface TickerMessage {
c: string
o: string
h: string
l: string
v: string
q: string
E: number
}
export interface Subscription {
market: Market
symbol: string
interval: string
}
export type MarketMessage
= | { type: "kline", market: Market, symbol: string, interval: string, data: KlineMessage }
| { type: "ticker", market: Market, symbol: string, interval: null, data: TickerMessage }
| { type: "error", message: string }
+36
View File
@@ -0,0 +1,36 @@
import type * as News from "./type"
import { request } from "@/http/axios"
import { consumeStream } from "@/http/stream"
/** 分页查询新闻 */
export function listNewsApi(params: News.NewsQuery) {
return request<News.ListNewsResponseData>({
url: "/news",
method: "get",
params,
paramsSerializer: { indexes: null }
})
}
/** 读取新闻详情 */
export function getNewsApi(id: number) {
return request<News.GetNewsResponseData>({
url: `/news/${id}`,
method: "get"
})
}
/** 读取新闻修订记录 */
export function listRevisionsApi(id: number) {
return request<News.ListRevisionsResponseData>({
url: `/news/${id}/revisions`,
method: "get"
})
}
/** 只读订阅,重连由订阅生命周期负责。 */
export function subscribeNewsApi(signal: AbortSignal, version: number, onOpen: () => void, onMessage: (data: News.NewsEvent) => void) {
return consumeStream<News.NewsEvent>("/api/news/stream", { signal }, version, onOpen, (name, data) => {
if (name === "news") onMessage(data)
})
}
+51
View File
@@ -0,0 +1,51 @@
export interface NewsBrief {
id: number
source_code: string
url: string
title: string
summary: string
excerpt: string
author: string | null
tags: string[]
is_paywalled: boolean
published_at: string
first_seen_at: string
revision_no: number
}
export interface NewsQuery {
source?: string[]
q?: string
start?: string
end?: string
/** 从 1 开始 */
page?: number
limit?: number
}
export interface ListNewsResponseData { items: NewsBrief[], total: number }
export interface NewsDetail extends NewsBrief {
content_html: string
source_updated_at: string | null
last_seen_at: string
extra: Record<string, unknown>
}
export interface Revision {
revision_no: number
title: string
summary: string
content_html: string
captured_at: string
}
export interface NewsEvent {
id: number
source_code: string
kind: "new" | "revised"
}
export type GetNewsResponseData = NewsDetail
export type ListRevisionsResponseData = Revision[]
+81
View File
@@ -0,0 +1,81 @@
import type * as Settings from "./type"
import { request } from "@/http/axios"
/** 读取模型配置(不返回密钥) */
export function listProvidersApi() {
return request<Settings.ListProvidersResponseData>({
url: "/settings/providers",
method: "get"
})
}
/** 新增模型配置 */
export function createProviderApi(body: Settings.ProviderForm) {
return request<Settings.CreateProviderResponseData>({
url: "/settings/providers",
method: "post",
data: body
})
}
/** 修改模型配置;空密钥保持原值 */
export function patchProviderApi(id: number, body: Partial<Settings.ProviderForm>) {
return request<Settings.PatchProviderResponseData>({
url: `/settings/providers/${id}`,
method: "patch",
data: body
})
}
/** 删除模型配置 */
export function deleteProviderApi(id: number) {
return request<Settings.DeleteProviderResponseData>({
url: `/settings/providers/${id}`,
method: "delete"
})
}
/** 测试模型连接;按后端 SDK 重试上限等待 */
export function testProviderApi(id: number) {
return request<Settings.TestProviderResponseData>({
url: `/settings/providers/${id}/test`,
method: "post",
data: null,
timeout: 1900000
})
}
/** 读取当前模拟盘交易所设置 */
export function getExchangeApi() {
return request<Settings.GetExchangeResponseData>({
url: "/settings/exchange",
method: "get"
})
}
/** 切换交易所或保存其凭证 */
export function putExchangeApi(body: Settings.PutExchangeBodyRequestData) {
return request<Settings.PutExchangeResponseData>({
url: "/settings/exchange",
method: "put",
data: body
})
}
/** 删除当前交易所凭证 */
export function deleteExchangeApi() {
return request<Settings.DeleteExchangeResponseData>({
url: "/settings/exchange",
method: "delete"
})
}
/** 测试当前模拟盘连接 */
export function testExchangeApi() {
return request<Settings.TestExchangeResponseData>({
url: "/settings/exchange/test",
method: "post",
data: null,
timeout: 30000
})
}
+81
View File
@@ -0,0 +1,81 @@
export type Protocol = "openai" | "anthropic"
export type Effort = "low" | "medium" | "high" | "xhigh" | "max"
export interface Provider {
id: number
name: string
protocol: Protocol
base_url: string
api_key_hint: string
model: string
max_tokens: number
context_window: number
/** 为空表示不传,由模型决定 */
reasoning_effort: Effort | null
is_default: boolean
updated_at: string
}
export type ListProvidersResponseData = Provider[]
export interface ProviderForm {
name: string
protocol: Protocol
base_url: string
/** 编辑时为空表示不修改 */
api_key: string
model: string
max_tokens: number
context_window: number
/** 为空表示不传,由模型决定 */
reasoning_effort: Effort | null
is_default: boolean
}
export interface TestResult {
ok: boolean
elapsed_ms: number
reply: string | null
error: string | null
}
export type CreateProviderResponseData = Provider
export type PatchProviderResponseData = Provider
export type DeleteProviderResponseData = null
export type TestProviderResponseData = TestResult
export type ExchangeName = "binance" | "bybit"
export interface Exchange {
exchange: ExchangeName
configured_exchanges: ExchangeName[]
configured: boolean
api_key_hint: string | null
/** 是否有币种在交易:交易中不能修改 key */
running: boolean
updated_at: string | null
}
export interface ExchangeTestResult {
ok: boolean
elapsed_ms: number
available_usdt: string | null
wallet_usdt: string | null
/** 双向持仓模式;引擎使用单向持仓,启动时会切换 */
dual_side_position: boolean | null
error: string | null
}
export type GetExchangeResponseData = Exchange
export type PutExchangeResponseData = Exchange
export interface PutExchangeBodyRequestData { exchange?: ExchangeName, api_key?: string, api_secret?: string }
export type DeleteExchangeResponseData = Exchange
export type TestExchangeResponseData = ExchangeTestResult
+71
View File
@@ -0,0 +1,71 @@
import type * as Skills from "./type"
import { request } from "@/http/axios"
/** 读取 Skills 树 */
export function listSkillTreeApi() {
return request<Skills.ListSkillTreeResponseData>({
url: "/skills/tree",
method: "get"
})
}
/** 新增文件夹或 Skill */
export function createSkillNodeApi(body: Skills.CreateSkillNodeBodyRequestData) {
return request<Skills.CreateSkillNodeResponseData>({
url: "/skills/nodes",
method: "post",
data: body
})
}
/** 修改 Skill 元信息 */
export function patchSkillNodeApi(id: number, body: Skills.PatchSkillNodeBodyRequestData) {
return request<Skills.PatchSkillNodeResponseData>({
url: `/skills/nodes/${id}`,
method: "patch",
data: body
})
}
/** 移动 Skill 节点 */
export function moveSkillNodeApi(id: number, parentId: number | null, index: number) {
return request<Skills.MoveSkillNodeResponseData>({
url: `/skills/nodes/${id}/move`,
method: "post",
data: { parent_id: parentId, index }
})
}
/** 删除 Skill 节点 */
export function deleteSkillNodeApi(id: number) {
return request<Skills.DeleteSkillNodeResponseData>({
url: `/skills/nodes/${id}`,
method: "delete"
})
}
/** 读取 Skill 历史版本 */
export function listSkillVersionsApi(id: number) {
return request<Skills.ListSkillVersionsResponseData>({
url: `/skills/${id}/versions`,
method: "get"
})
}
/** 发布指定 Skill 版本 */
export function publishSkillVersionApi(id: number, versionId: number) {
return request<Skills.PublishSkillVersionResponseData>({
url: `/skills/${id}/publish`,
method: "post",
data: { version_id: versionId }
})
}
/** 保存 Skill 草稿,不自动发布 */
export function saveSkillDraftApi(id: number, content: string, note = "") {
return request<Skills.SaveSkillDraftResponseData>({
url: `/skills/${id}/versions`,
method: "post",
data: { content, note }
})
}
+45
View File
@@ -0,0 +1,45 @@
export type NodeKind = "folder" | "skill"
export interface SkillNode {
id: number
parent_id: number | null
kind: NodeKind
name: string
path: string
sort_order: number
priority: number
enabled: boolean
published_version_no: number | null
latest_version_no: number | null
has_draft: boolean
updated_at: string
}
export interface SkillVersion {
id: number
version_no: number
content: string
source: "user" | "chat"
note: string
created_at: string
}
export type ListSkillTreeResponseData = SkillNode[]
export type CreateSkillNodeResponseData = SkillNode
export interface CreateSkillNodeBodyRequestData { parent_id: number | null, kind: NodeKind, name: string, priority?: number, content?: string }
export type PatchSkillNodeResponseData = SkillNode
export interface PatchSkillNodeBodyRequestData { name?: string, priority?: number, enabled?: boolean }
export type MoveSkillNodeResponseData = null
export type DeleteSkillNodeResponseData = null
export type ListSkillVersionsResponseData = SkillVersion[]
export type PublishSkillVersionResponseData = SkillNode
export type SaveSkillDraftResponseData = SkillVersion
+152
View File
@@ -0,0 +1,152 @@
import type * as Trading from "./type"
import { request } from "@/http/axios"
/** 读取模拟盘账户与全局分析状态 */
export function getStatusApi() {
return request<Trading.GetStatusResponseData>({
url: "/trading/status",
method: "get"
})
}
/** 开始指定币种的自动交易 */
export function startTradingApi(symbol: string) {
return request<Trading.StartTradingResponseData>({
url: `/trading/symbols/${symbol}/start`,
method: "post"
})
}
/** 停止指定币种的新分析并撤销入场单 */
export function stopTradingApi(symbol: string) {
return request<Trading.StopTradingResponseData>({
url: `/trading/symbols/${symbol}/stop`,
method: "post"
})
}
/** 设置该币的交易资金:各币合计不超过账户权益 */
export function patchSymbolApi(symbol: string, body: Trading.PatchSymbolBodyRequestData) {
return request<Trading.PatchSymbolResponseData>({
url: `/trading/symbols/${symbol}`,
method: "patch",
data: body
})
}
/** 更新指定的全局交易配置字段 */
export function patchConfigApi(body: Trading.PatchConfigBodyRequestData) {
return request<Trading.PatchConfigResponseData>({
url: "/trading/config",
method: "patch",
data: body
})
}
/** 查询模拟盘可交易币种 */
export function listSymbolOptionsApi() {
return request<Trading.ListSymbolOptionsResponseData>({
url: "/trading/symbol-options",
method: "get"
})
}
/** 读取交易币种列表 */
export function listSymbolsApi() {
return request<Trading.ListSymbolsResponseData>({
url: "/trading/symbols",
method: "get"
})
}
/** 新增交易币种 */
export function addSymbolApi(symbol: string) {
return request<Trading.AddSymbolResponseData>({
url: "/trading/symbols",
method: "post",
data: { symbol }
})
}
/** 移出交易币种,保留历史 */
export function deleteSymbolApi(symbol: string) {
return request<Trading.DeleteSymbolResponseData>({
url: `/trading/symbols/${symbol}`,
method: "delete"
})
}
/** 分页读取交易时间线 */
export function listEventsApi(params: Trading.ListEventsParamsRequestData) {
return request<Trading.ListEventsResponseData>({
url: "/trading/events",
method: "get",
params
})
}
/** 筛选已平仓历史及汇总 */
export function listPositionHistoryApi(params: Trading.PositionHistoryQuery) {
return request<Trading.ListPositionHistoryResponseData>({
url: "/trading/positions/history",
method: "get",
params
})
}
/** 读取历史仓位币种 */
export function listPositionHistorySymbolsApi() {
return request<Trading.ListPositionHistorySymbolsResponseData>({
url: "/trading/positions/history/symbols",
method: "get"
})
}
/** 读取新闻信号前向统计 */
export function getNewsSignalsApi() {
return request<Trading.GetNewsSignalsResponseData>({
url: "/trading/news-signals",
method: "get"
})
}
/** 读取当前执行计划与订单流水 */
export function getSymbolPlanApi(symbol: string) {
return request<Trading.GetSymbolPlanResponseData>({
url: `/trading/symbols/${symbol}/plan`,
method: "get"
})
}
/** 排队手动分析;202 仅表示已受理 */
export function requestAnalysisApi(symbol: string) {
return request<Trading.RequestAnalysisResponseData>({
url: `/trading/symbols/${symbol}/analyze`,
method: "post"
})
}
/** 一键平仓(同时停止该币的行情交易)、清除挂单:由执行器在几秒内执行,结果写入时间线 */
export function manualActionApi(symbol: string, action: "close" | "cancel") {
return request<Trading.ManualActionResponseData>({
url: `/trading/symbols/${symbol}/manual/${action}`,
method: "post"
})
}
/** 读取分析记录列表 */
export function listAnalysesApi(params: Trading.ListAnalysesParamsRequestData) {
return request<Trading.ListAnalysesResponseData>({
url: "/trading/analyses",
method: "get",
params
})
}
/** 读取分析详情与工具调用过程 */
export function getAnalysisApi(id: number) {
return request<Trading.GetAnalysisResponseData>({
url: `/trading/analyses/${id}`,
method: "get"
})
}
+310
View File
@@ -0,0 +1,310 @@
import type { Effort } from "../settings/type"
/** 以下数值字段都是交易所返回的十进制字符串,避免浮点误差 */
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 {
exchange: "binance" | "bybit"
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 {
exchange: "binance" | "bybit"
configured: boolean
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
/** 该币是否在交易;停止时分析为试运行,不挂新的入场单 */
running: boolean
running_changed_at: string | null
/** 该币的交易资金,十进制字符串;未设置时不能开始交易 */
capital_usdt: string | null
/** 实际用于仓位计算的资金:min(交易资金, 账户权益) */
effective_capital: string | null
/** 执行器正在管理的计划 */
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 type GetStatusResponseData = TradingStatus
export type StartTradingResponseData = null
export type StopTradingResponseData = null
export type PatchSymbolResponseData = null
export interface PatchSymbolBodyRequestData { capital_usdt: string | null }
export type PatchConfigResponseData = TradingStatus
export interface PatchConfigBodyRequestData { provider_id?: number | null, reasoning_effort?: Effort | null, daily_analysis_limit?: number }
export type ListSymbolOptionsResponseData = SymbolOption[]
export type ListSymbolsResponseData = TradingSymbol[]
export type AddSymbolResponseData = null
export type DeleteSymbolResponseData = null
export type ListEventsResponseData = TradingEvent[]
export interface ListEventsParamsRequestData { symbol?: string, before?: number, limit?: number }
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 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 interface PositionHistory {
symbol: string
direction: "long" | "short"
opened_at: string
closed_at: string
quantity: string
entry_price: string
exit_price: string
realized_pnl: string
commission: string
net: string
source: "program" | "manual"
/** 手动仓位没有关联计划,为空 */
analysis_id: number | null
close_reason: string | null
}
export interface PositionHistoryQuery {
symbol?: string
/** 按平仓时间筛选,ISO 时间 */
start?: string
end?: string
outcome?: "profit" | "loss"
source?: "program" | "manual"
page: number
limit: number
}
export interface PositionHistoryPage {
/** 按筛选条件统计全部仓位 */
summary: { count: number, wins: number, net: string, commission: string }
total: number
items: PositionHistory[]
}
export type ListPositionHistoryResponseData = PositionHistoryPage
export type ListPositionHistorySymbolsResponseData = string[]
export type SignalHorizon = "1h" | "4h"
/** 新闻信号前向记录:按模型标注方向(利多做多、利空做空)交易的统计;收益单位 bp */
export interface NewsSignalStat {
category: "price_report" | "event" | "irrelevant"
symbol: string
horizon: SignalHorizon
n: number
mean_bp: number | null
t: number | null
/** 扣除来回手续费后的平均收益 */
net_bp: number | null
win_rate: number | null
}
export interface NewsSignalItem {
news_id: number
published_at: string
source_code: string
title: string
category: NewsSignalStat["category"]
direction: "bullish" | "bearish" | "neutral"
/** 进场后的原始涨跌(bp,未按方向取正负);发布未满 4 小时为空 */
returns: Record<string, Record<SignalHorizon, number>> | null
}
export interface NewsSignals {
/** 前向记录的起点:之后发布的新闻才计入 */
start: string
labeled: number
filled: number
stats: NewsSignalStat[]
recent: NewsSignalItem[]
}
export type GetNewsSignalsResponseData = NewsSignals
export interface GetSymbolPlanResponseData { plan: Analysis | null, orders: OrderRow[] }
export type RequestAnalysisResponseData = null
export type ManualActionResponseData = null
export type ListAnalysesResponseData = Analysis[]
export interface ListAnalysesParamsRequestData { symbol?: string, limit?: number }
export type GetAnalysisResponseData = AnalysisDetail