Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
128 commits
Select commit Hold shift + click to select a range
302e591
docs: add project takeover baseline
MAX-LIUS Mar 21, 2026
01187dc
docs: add architecture and risk worker notes
MAX-LIUS Mar 21, 2026
664c405
docs: refine api ui mapping and keep worker heartbeat
MAX-LIUS Mar 21, 2026
a0c4067
fix: align public trader config route
MAX-LIUS Mar 21, 2026
626ec56
fix: remove stale frontend api references
MAX-LIUS Mar 23, 2026
ccb2d99
docs: remove stale admin mode wording
MAX-LIUS Mar 23, 2026
d5e3f4a
perf: lazy-load top-level app pages
MAX-LIUS Mar 23, 2026
231e584
perf: split dashboard heavy sections into lazy chunks
MAX-LIUS Mar 23, 2026
baadeb6
perf: split shared vendor chunks in vite build
MAX-LIUS Mar 23, 2026
41bc356
perf: lazy-load katex in metric tooltip
MAX-LIUS Mar 23, 2026
9c3e865
perf: lazy-load recharts entry components
MAX-LIUS Mar 23, 2026
77b8f5b
docs: record delivery progress and perf status
MAX-LIUS Mar 23, 2026
ad1be09
refactor: route core frontend calls through shared api layer
MAX-LIUS Mar 23, 2026
ac18d1b
refactor: route crypto service requests through http client
MAX-LIUS Mar 23, 2026
838347e
refactor: route password reset through http client
MAX-LIUS Mar 23, 2026
5f55b85
docs: codify fuxi delivery workflow
MAX-LIUS Mar 23, 2026
25a3763
docs: add closure and memory archive for handover
MAX-LIUS Mar 23, 2026
8bd7a5f
docs: sync handover logs, todo and decisions
MAX-LIUS Mar 23, 2026
cf9b471
docs: add detailed project manual for review
MAX-LIUS Mar 23, 2026
937fd15
fix: align execution market data with strategy timeframes
MAX-LIUS Mar 23, 2026
ca575a0
fix: refresh chart when clicking decision symbols
MAX-LIUS Mar 23, 2026
4b5df3b
fix: fallback chart klines to binance on empty exchange data
MAX-LIUS Mar 23, 2026
494b5f5
docs: add unified trading protection design plan
MAX-LIUS Mar 23, 2026
0d3c6dc
feat: add phase-1 trading protection execution loop
MAX-LIUS Mar 24, 2026
5fb070e
feat: add strategy protection editor
MAX-LIUS Mar 24, 2026
5f0a246
docs: add system trust boundary and chain closure
MAX-LIUS Mar 24, 2026
d7a429f
docs: expand kernel annotations and architecture notes
MAX-LIUS Mar 24, 2026
49493cf
docs: add module api and data model guides
MAX-LIUS Mar 24, 2026
3aabf24
refactor: unify remaining frontend api calls
MAX-LIUS Mar 25, 2026
4f59fac
feat: drive drawdown protection from strategy config
MAX-LIUS Mar 25, 2026
421d796
docs: sync protection and test-support audit
MAX-LIUS Mar 25, 2026
ee56cfe
feat: add break-even stop runtime execution
MAX-LIUS Mar 25, 2026
91604aa
test: cover break-even protection flow
MAX-LIUS Mar 25, 2026
d08cc62
feat: add ladder protection execution flow
MAX-LIUS Mar 25, 2026
e0dd030
docs: close out phase-2 protection delivery
MAX-LIUS Mar 25, 2026
e8b01a5
docs: upgrade delivery acceptance and test plan
MAX-LIUS Mar 25, 2026
0173568
feat: close handover gaps and wire protection phase 3
MAX-LIUS Mar 26, 2026
bb426b9
test: start replay and protection lifecycle harness
MAX-LIUS Mar 26, 2026
3822bf3
feat: add paper trader scaffold and replay fixtures
MAX-LIUS Mar 26, 2026
b7df2f0
feat: add replay runner smoke executor
MAX-LIUS Mar 26, 2026
9a5f063
feat: deepen replay runner with protection filters
MAX-LIUS Mar 26, 2026
fec1a18
feat(web): improve protection editor guidance and ladder controls
MAX-LIUS Mar 27, 2026
108e887
feat: harden protection execution reliability
MAX-LIUS Mar 29, 2026
72d42c6
test(replay): add open-close lifecycle scenario and closed pnl assertion
MAX-LIUS Apr 7, 2026
230918b
fix(paper): clear protection orders after close in replay flow
MAX-LIUS Apr 7, 2026
66b3235
test(replay): assert realized pnl and close-path regime behavior
MAX-LIUS Apr 7, 2026
9e05eac
feat: deepen replay/paper-trading verification and protection lifecyc…
MAX-LIUS Apr 8, 2026
5f58628
docs: finalize handover closure and project memory archive to complet…
MAX-LIUS Apr 8, 2026
e40569c
docs: complete handover closure doc - all sections updated to final s…
MAX-LIUS Apr 8, 2026
d74b7b2
feat: close protection visibility loop
MAX-LIUS Apr 10, 2026
24b0c49
feat: detail protection snapshot and split protection status
MAX-LIUS Apr 10, 2026
33a1e12
feat: reconcile missing exchange protection orders
MAX-LIUS Apr 10, 2026
b0cabd5
feat: show protection status per open position
MAX-LIUS Apr 10, 2026
41ef881
feat: expose per-position protection and break-even states
MAX-LIUS Apr 10, 2026
9322abf
feat: add native trailing drawdown path for binance
MAX-LIUS Apr 10, 2026
bd1d930
feat: clarify native trailing protection states
MAX-LIUS Apr 10, 2026
b044046
fix: avoid re-arming active native protection states
MAX-LIUS Apr 10, 2026
e057797
feat: add native trailing drawdown path for bitget
MAX-LIUS Apr 10, 2026
4195f91
feat: add native trailing drawdown path for okx
MAX-LIUS Apr 10, 2026
87bc253
feat: surface exchange-specific native protection labels
MAX-LIUS Apr 10, 2026
ed0dc66
feat: expose protection execution modes per position
MAX-LIUS Apr 10, 2026
8a239d4
feat: harden mcp response parsing and refresh protection audit
MAX-LIUS Apr 10, 2026
c04c19f
feat: break-even lifecycle fingerprint reset and UI boundary clarity
MAX-LIUS Apr 10, 2026
830bb12
feat: refine partial drawdown mode visibility
MAX-LIUS Apr 11, 2026
713904f
feat: scaffold partial drawdown nativeization plan
MAX-LIUS Apr 11, 2026
c7d1f23
feat: finish protection lifecycle and partial mode signaling
MAX-LIUS Apr 11, 2026
1368ca9
feat: replace protection snapshot focus with runtime action view
MAX-LIUS Apr 11, 2026
e65c3f6
chore: remove protection snapshot from decision cards
MAX-LIUS Apr 11, 2026
83915a0
fix: avoid filtering candidate coins on missing oi data
MAX-LIUS Apr 11, 2026
831af00
fix: count only displayed candidate coins in prompt
MAX-LIUS Apr 11, 2026
54c513e
feat: aggressively apply native protection targets after open
MAX-LIUS Apr 11, 2026
255c1be
feat: let reconciler arm advanced native protections
MAX-LIUS Apr 11, 2026
c6e2e4f
feat: route ai-mode strategy protection into execution path
MAX-LIUS Apr 11, 2026
058eb61
fix: stop misdetecting existing okx protection orders
MAX-LIUS Apr 11, 2026
de0329d
feat: merge full and ladder protection plans
MAX-LIUS Apr 11, 2026
4284c95
fix: dedupe native ladder protection orders
MAX-LIUS Apr 11, 2026
ee0191a
fix: add verification retry with delay to prevent duplicate protectio…
MAX-LIUS Apr 11, 2026
604b94f
fix: resolve Full TP/SL and Ladder TP/SL coexistence conflict
MAX-LIUS Apr 11, 2026
0e7ffb8
feat: route partial drawdown through native exchange orders instead o…
MAX-LIUS Apr 11, 2026
b18a856
feat: complete break-even stop lifecycle with re-arm on position resize
MAX-LIUS Apr 11, 2026
3a38d91
docs: update DEVLOG with 2026-04-11 protection system delivery notes
MAX-LIUS Apr 11, 2026
65ee80d
fix: add order accumulation safety cap and debug logging for verifica…
MAX-LIUS Apr 11, 2026
43fdb6c
fix: auto-cleanup duplicate protection orders and cancel orphaned dra…
MAX-LIUS Apr 11, 2026
d80b19e
docs: update TODO with protection system real-combat status and P0 items
MAX-LIUS Apr 11, 2026
424bebd
chore: add unified start.sh for backend + frontend startup
MAX-LIUS Apr 11, 2026
5e8a84e
fix: round OKX protection order prices to instrument tick size
MAX-LIUS Apr 11, 2026
d7cdd60
fix: avoid wiping ladder TP during drawdown cleanup and relax OKX pro…
MAX-LIUS Apr 12, 2026
606fa4b
fix: auto-clean orphaned protection orders after position close
MAX-LIUS Apr 12, 2026
a0ee8ec
fix: preserve native trailing protection during orphan cleanup
MAX-LIUS Apr 12, 2026
42cdff7
refactor: split managed partial drawdown from native trailing semantics
MAX-LIUS Apr 12, 2026
c9e5ea5
feat: add native partial trailing path for drawdown protection
MAX-LIUS Apr 12, 2026
ad99214
feat: upgrade position protection panel for native vs managed drawdow…
MAX-LIUS Apr 12, 2026
b396ce9
feat: expose runtime protection tiers for position protection panel
MAX-LIUS Apr 12, 2026
b46aaf2
fix: lock native-armed drawdown flow and expose tiered runtime execution
MAX-LIUS Apr 12, 2026
8ad2a0d
feat: surface native trailing orders in unified protection runtime view
MAX-LIUS Apr 12, 2026
c32e8ff
fix: prefer native partial trailing at post-open drawdown setup
MAX-LIUS Apr 12, 2026
04e3147
fix: allow managed partial drawdown to upgrade into native partial tr…
MAX-LIUS Apr 12, 2026
bc1d548
feat: add expandable execution attribution in position history
MAX-LIUS Apr 12, 2026
f2ca120
fix: distinguish pending native drawdown from fallback and add trade …
MAX-LIUS Apr 12, 2026
a83e4c4
refactor: simplify protection panel to focus on active orders only
MAX-LIUS Apr 12, 2026
cab8560
refactor: simplify protection panel and show activation gate clearly
MAX-LIUS Apr 12, 2026
de9395a
fix: arm native drawdown trailing at min-profit threshold instead of …
MAX-LIUS Apr 12, 2026
dc2711c
fix: stop generic protection reapply after native trailing is armed
MAX-LIUS Apr 12, 2026
9d82102
fix: re-arm native trailing when exchange order disappears
MAX-LIUS Apr 12, 2026
9e7153e
chore: add OKX trailing order diagnostics for live debugging
MAX-LIUS Apr 12, 2026
8712a5a
fix: suspend break-even arming while native trailing protection is ac…
MAX-LIUS Apr 12, 2026
ddba36d
fix: treat OKX trailing stop rejection as failure and use ratio callb…
MAX-LIUS Apr 12, 2026
56b5e03
fix: compute trailing callback from captured profit instead of raw pr…
MAX-LIUS Apr 12, 2026
13c85db
fix: unify drawdown trailing math around giveback of captured profit
MAX-LIUS Apr 12, 2026
dd945fa
fix: make drawdown profit-control mutually exclusive with generic tak…
MAX-LIUS Apr 12, 2026
ea62272
fix: disable managed partial drawdown fallback on native-partial-capa…
MAX-LIUS Apr 12, 2026
2b87ca9
fix: preserve OKX trailing callback ratio precision
MAX-LIUS Apr 12, 2026
c75a91b
fix: proactively remove legacy generic take-profit orders when drawdo…
MAX-LIUS Apr 12, 2026
631143c
fix: keep break-even stop independent from ladder stop-loss protection
MAX-LIUS Apr 12, 2026
18d6f09
fix: preserve generic stop-loss reconciliation while native trailing …
MAX-LIUS Apr 12, 2026
4678de2
fix: avoid cancel-before-place when arming native trailing protection
MAX-LIUS Apr 12, 2026
2992502
fix: prune stale OKX trailing orders only after new trailing order is…
MAX-LIUS Apr 12, 2026
bd691fc
fix: let break-even coexist with native trailing as independent prote…
MAX-LIUS Apr 12, 2026
682f3c1
fix: count native trailing and break-even as valid protections in dup…
MAX-LIUS Apr 12, 2026
b8b78b1
fix: keep break-even state separate from drawdown/trailing protection…
MAX-LIUS Apr 12, 2026
2b4cb88
fix: make runtime protection thresholds leverage-invariant
MAX-LIUS Apr 12, 2026
a754970
feat: tighten protection runtime and editor UI
MAX-LIUS Apr 12, 2026
f94390e
fix: use live market activation for native drawdown
MAX-LIUS Apr 12, 2026
3b54382
feat: surface actual trailing parameters in runtime
MAX-LIUS Apr 12, 2026
b76bcc2
feat: label trailing runtime parameter sources
MAX-LIUS Apr 12, 2026
856abe2
fix: refresh stale native trailing activation prices
MAX-LIUS Apr 12, 2026
118dc17
docs: finalize trailing execution semantics
MAX-LIUS Apr 12, 2026
65ef920
docs: archive trailing runtime decisions
MAX-LIUS Apr 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/handler_klines.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,16 @@ func (s *Server) getKlinesFromCoinank(symbol, interval, exchange string, limit i
}
}

// Some exchange/symbol combinations return HTTP 200 with an empty list.
// For chart rendering, treat empty non-Binance data as unsupported and fall back to Binance reference data.
if len(coinankKlines) == 0 && coinankExchange != coinank_enum.Binance {
logger.Warnf("⚠️ CoinAnk returned empty klines for %s on %s, falling back to Binance data", symbol, coinankExchange)
coinankKlines, err = coinank_api.Kline(ctx, symbol, coinank_enum.Binance, ts, coinank_enum.To, limit, coinankInterval)
if err != nil {
return nil, fmt.Errorf("coinank API empty-data fallback error: %w", err)
}
}

// Convert coinank kline format to market.Kline format
// Coinank: Volume = BTC quantity, Quantity = USDT turnover
klines := make([]market.Kline, len(coinankKlines))
Expand Down
67 changes: 66 additions & 1 deletion api/handler_order.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package api
import (
"net/http"
"strconv"
"strings"
"time"

"nofx/logger"
"nofx/market"
Expand Down Expand Up @@ -213,6 +215,69 @@ func (s *Server) handlePositionHistory(c *gin.Context) {
return
}

// Enrich with execution metadata for frontend expandable rows.
enrichedPositions := make([]map[string]interface{}, 0, len(positions))
for _, pos := range positions {
entryQty := pos.EntryQuantity
if entryQty <= 0 {
entryQty = pos.Quantity
}
closedQty := entryQty
if closedQty <= 0 {
closedQty = pos.Quantity
}
closeRatioPct := 0.0
if entryQty > 0 && closedQty > 0 {
closeRatioPct = closedQty / entryQty * 100
}
executionSource := pos.CloseReason
if executionSource == "" {
executionSource = "unknown"
}
executionOrderType := "unknown"
sourceLower := strings.ToLower(executionSource)
switch {
case strings.Contains(sourceLower, "trailing"):
executionOrderType = "TRAILING_STOP_MARKET"
case strings.Contains(sourceLower, "take_profit") || strings.Contains(sourceLower, "tp"):
executionOrderType = "TAKE_PROFIT_MARKET"
case strings.Contains(sourceLower, "stop") || strings.Contains(sourceLower, "sl"):
executionOrderType = "STOP_MARKET"
case strings.Contains(sourceLower, "break_even"):
executionOrderType = "BREAK_EVEN_STOP"
case strings.Contains(sourceLower, "manual"):
executionOrderType = "MANUAL"
}

enrichedPositions = append(enrichedPositions, map[string]interface{}{
"id": pos.ID,
"trader_id": pos.TraderID,
"exchange_id": pos.ExchangeID,
"exchange_type": pos.ExchangeType,
"symbol": pos.Symbol,
"side": pos.Side,
"quantity": pos.Quantity,
"entry_quantity": pos.EntryQuantity,
"entry_price": pos.EntryPrice,
"entry_order_id": pos.EntryOrderID,
"entry_time": time.UnixMilli(pos.EntryTime).UTC().Format(time.RFC3339),
"exit_price": pos.ExitPrice,
"exit_order_id": pos.ExitOrderID,
"exit_time": time.UnixMilli(pos.ExitTime).UTC().Format(time.RFC3339),
"realized_pnl": pos.RealizedPnL,
"fee": pos.Fee,
"leverage": pos.Leverage,
"status": pos.Status,
"close_reason": pos.CloseReason,
"execution_source": executionSource,
"execution_order_type": executionOrderType,
"close_ratio_pct": closeRatioPct,
"close_value_usdt": pos.ExitPrice * closedQty,
"created_at": time.UnixMilli(pos.CreatedAt).UTC().Format(time.RFC3339),
"updated_at": time.UnixMilli(pos.UpdatedAt).UTC().Format(time.RFC3339),
})
}

// Get statistics
stats, _ := store.Position().GetFullStats(trader.GetID())

Expand All @@ -223,7 +288,7 @@ func (s *Server) handlePositionHistory(c *gin.Context) {
directionStats, _ := store.Position().GetDirectionStats(trader.GetID())

c.JSON(http.StatusOK, gin.H{
"positions": positions,
"positions": enrichedPositions,
"stats": stats,
"symbol_stats": symbolStats,
"direction_stats": directionStats,
Expand Down
8 changes: 4 additions & 4 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ import (
"github.com/gin-gonic/gin"
)

// Server HTTP API server
// Server 是系统对前端、集成调用、公开查询暴露的统一 HTTP API 入口。
// 它负责路由装配、鉴权边界与 handler 分发;
// 真正的交易运行与执行仍下沉到 manager / trader 层。
type Server struct {
router *gin.Engine
traderManager *manager.TraderManager
Expand Down Expand Up @@ -78,13 +80,11 @@ func (s *Server) setupRoutes() {
// Health check
api.Any("/health", s.handleHealth)

// Admin login (used in admin mode, public)

// System supported models and exchanges (no authentication required)
s.route(api, "GET", "/supported-models", "List supported AI model providers", s.handleGetSupportedModels)
s.route(api, "GET", "/supported-exchanges", "List supported exchange types", s.handleGetSupportedExchanges)

// System config (no authentication required, for frontend to determine admin mode/registration status)
// System config (no authentication required, for frontend to determine registration status)
s.route(api, "GET", "/config", "Get system configuration", s.handleGetSystemConfig)

// Wallet validation (no authentication required — used by frontend config form)
Expand Down
79 changes: 73 additions & 6 deletions api/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,74 @@ func validateStrategyConfig(config *store.StrategyConfig) []string {
warnings = append(warnings, "NofxOS API key is not configured. NofxOS data sources may not work properly.")
}

full := config.Protection.FullTPSL
if full.Enabled {
if full.Mode != store.ProtectionModeManual && full.Mode != store.ProtectionModeAI {
warnings = append(warnings, "protection.full_tp_sl.mode should be 'manual' or 'ai'.")
}
if full.TakeProfit.Enabled && full.TakeProfit.PriceMovePct <= 0 {
warnings = append(warnings, "protection.full_tp_sl.take_profit.price_move_pct must be > 0 when enabled.")
}
if full.StopLoss.Enabled && full.StopLoss.PriceMovePct <= 0 {
warnings = append(warnings, "protection.full_tp_sl.stop_loss.price_move_pct must be > 0 when enabled.")
}
}

ladder := config.Protection.LadderTPSL
if ladder.Enabled {
if ladder.Mode != store.ProtectionModeManual && ladder.Mode != store.ProtectionModeAI {
warnings = append(warnings, "protection.ladder_tp_sl.mode should be 'manual' or 'ai'.")
}
for i, rule := range ladder.Rules {
if ladder.TakeProfitEnabled {
if rule.TakeProfitPct <= 0 || rule.TakeProfitCloseRatioPct <= 0 || rule.TakeProfitCloseRatioPct > 100 {
warnings = append(warnings, fmt.Sprintf("protection.ladder_tp_sl.rules[%d] take-profit fields are invalid.", i))
}
}
if ladder.StopLossEnabled {
if rule.StopLossPct <= 0 || rule.StopLossCloseRatioPct <= 0 || rule.StopLossCloseRatioPct > 100 {
warnings = append(warnings, fmt.Sprintf("protection.ladder_tp_sl.rules[%d] stop-loss fields are invalid.", i))
}
}
}
}

drawdown := config.Protection.DrawdownTakeProfit
if drawdown.Enabled {
for i, rule := range drawdown.Rules {
if rule.MinProfitPct <= 0 || rule.MaxDrawdownPct <= 0 || rule.MaxDrawdownPct > 100 ||
rule.CloseRatioPct <= 0 || rule.CloseRatioPct > 100 {
warnings = append(warnings, fmt.Sprintf("protection.drawdown_take_profit.rules[%d] contains invalid thresholds.", i))
}
if rule.PollIntervalSeconds > 0 && rule.PollIntervalSeconds < 5 {
warnings = append(warnings, fmt.Sprintf("protection.drawdown_take_profit.rules[%d].poll_interval_seconds is too small; recommended >= 5.", i))
}
}
}

be := config.Protection.BreakEvenStop
if be.Enabled {
if be.TriggerMode != store.BreakEvenTriggerProfitPct && be.TriggerMode != store.BreakEvenTriggerRMultiple {
warnings = append(warnings, "protection.break_even_stop.trigger_mode should be 'profit_pct' or 'r_multiple'.")
}
if be.TriggerValue <= 0 {
warnings = append(warnings, "protection.break_even_stop.trigger_value must be > 0 when enabled.")
}
if be.OffsetPct < 0 {
warnings = append(warnings, "protection.break_even_stop.offset_pct must be >= 0.")
}
}

regime := config.Protection.RegimeFilter
if regime.Enabled {
if regime.BlockHighFunding && regime.MaxFundingRateAbs <= 0 {
warnings = append(warnings, "protection.regime_filter.max_funding_rate_abs must be > 0 when block_high_funding is enabled.")
}
if regime.BlockHighVolatility && regime.MaxATR14Pct <= 0 {
warnings = append(warnings, "protection.regime_filter.max_atr14_pct must be > 0 when block_high_volatility is enabled.")
}
}

return warnings
}

Expand Down Expand Up @@ -150,8 +218,8 @@ func (s *Server) handleCreateStrategy(c *gin.Context) {
var req struct {
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Lang string `json:"lang"` // "zh" or "en", used when config is omitted
Config *store.StrategyConfig `json:"config"` // optional — uses default if omitted
Lang string `json:"lang"` // "zh" or "en", used when config is omitted
Config *store.StrategyConfig `json:"config"` // optional — uses default if omitted
}

if err := c.ShouldBindJSON(&req); err != nil {
Expand Down Expand Up @@ -419,9 +487,9 @@ func (s *Server) handlePreviewPrompt(c *gin.Context) {
}

var req struct {
Config store.StrategyConfig `json:"config" binding:"required"`
AccountEquity float64 `json:"account_equity"`
PromptVariant string `json:"prompt_variant"`
Config store.StrategyConfig `json:"config" binding:"required"`
AccountEquity float64 `json:"account_equity"`
PromptVariant string `json:"prompt_variant"`
}

if err := c.ShouldBindJSON(&req); err != nil {
Expand Down Expand Up @@ -664,4 +732,3 @@ func (s *Server) runRealAITest(userID, modelID, systemPrompt, userPrompt string)

return response, nil
}

102 changes: 102 additions & 0 deletions docs/ACCEPTANCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# NOFX 验收标准(交付版)

## 接管阶段验收

### 文档
- [x] 有中文项目总览
- [x] 有中文架构说明
- [x] 有模块索引
- [x] 有开发日志
- [x] 有待办与决策记录
- [x] 有接管执行工作流文档
- [x] 有阶段性接管结项总文档
- [x] 有项目记忆归档总表(阶段性版本)

### 基线
- [x] 后端测试可执行并通过
- [x] 前端测试可执行并通过
- [x] 前端构建可执行并通过
- [x] 关键运行方式被记录

### 认知
- [x] 已明确启动链(基础版)
- [x] 已明确决策链(完整收口版)
- [x] 已明确交易链(完整收口版)
- [x] 已明确风控链(完整收口版)
- [x] 已输出首轮风险清单

### 外部问题与交付
- [x] 首轮前后端接口失配已清理
- [x] 首轮明显死代码/误导注释已清理
- [x] 当前版本达到阶段性可交付状态

## 本轮主线功能交付验收(Protection Phase 2 + API 收束)

### A. 前端 API 收束
- [x] `ChartTabs` 已接入统一 HTTP client
- [x] `GridRiskPanel` 已接入统一 HTTP client
- [x] `StrategyStudioPage` 残留直接 API 调用已收束
- [x] `ModelConfigModal` 钱包相关 API 已收束
- [x] 收束后前端测试通过
- [x] 收束后前端构建通过

### B. Drawdown Take Profit
- [x] 不再依赖硬编码单规则
- [x] 支持从 `strategy.protection.drawdown_take_profit.rules` 读取规则
- [x] 支持 `poll_interval_seconds` 调整监控频率
- [x] 支持多规则匹配
- [x] 支持按 `close_ratio_pct` 部分平仓 / 全平
- [x] 后端测试通过

### C. Break-even Stop
- [x] 支持从 `strategy.protection.break_even_stop` 读取运行态配置
- [x] 达到利润阈值后可执行保本止损移动
- [x] 支持在可改单交易所上先取消旧止损再重挂
- [x] 已覆盖低于阈值 / 取消失败 / 正常设置路径测试
- [x] 后端测试通过

### D. Ladder TP/SL
- [x] 支持手动 ladder protection plan 生成
- [x] 支持多阶 TP / SL 价格换算
- [x] 支持 close ratio 累计裁剪到 100%
- [x] 支持按阶梯比例拆单执行
- [x] 支持开仓后逐阶 open-order 校验
- [x] 对不支持 partial close 的交易所会安全阻断
- [x] 后端测试通过

### E. 全量基线
- [x] `go test ./...` 通过
- [x] `cd web && npm test` 通过
- [x] `cd web && npm run build` 通过

## 本轮补充收口验收(可靠性 / 前端闭环 / 网络健壮性)

### F. Protection 执行可靠性
- [x] protection setup 增加重试机制
- [x] 手动 protection plan 与 AI fallback 两条路径均接入重试
- [x] protection setup 失败后统一触发立即平仓,避免裸仓残留
- [x] 已补重试恢复路径测试

### G. 前端 Protection 多规则闭环
- [x] Drawdown Take Profit 前端已支持多规则新增 / 编辑 / 删除
- [x] 前端配置形态与后端 `rules` 多规则读取模型对齐
- [x] 前端测试通过
- [x] 前端构建通过

### H. 网络层健壮性补强
- [x] OKX trader 已补独立 transport 与瞬时网络错误重试
- [x] NOFXOS client 已补 trusted host 校验与安全请求路径
- [x] 后端测试通过

## 仍不在本轮验收闭包内的项
- [x] replay / paper-trading / 仿真执行闭环(已深化到多场景、多侧、错误路径、protection 生命周期集成测试)

## 后续功能开发验收模板

每个功能至少回答:
1. 目标是什么
2. 改了哪些模块
3. 对哪些链路有影响
4. 如何测试
5. 是否更新文档/决策
6. 是否有回归风险
37 changes: 37 additions & 0 deletions docs/ACCEPTANCE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# NOFX 长期验收模板(长期使用版)

## 1. 验收对象
- 任务名称:
- 分支:
- 提交:

## 2. 目标是否达成
- 原始目标:
- 实际达成:
- 未纳入本轮闭包:

## 3. 影响面确认
- 后端:
- 前端:
- 策略配置 / API / 存储:
- 风控与交易安全:

## 4. 验证结果
- [ ] `go test ./...`
- [ ] `cd web && npm test`
- [ ] `cd web && npm run build`
- [ ] 手工关键路径检查已完成

## 5. 风险复核
- 是否引入新的高爆炸半径改动:是 / 否
- 是否存在已知未解决风险:
- 是否需要后续专项测试:

## 6. 文档闭环
- [ ] DEVLOG 已更新
- [ ] TODO 已更新
- [ ] DECISIONS / MEMORY / 专项文档已同步

## 7. 最终结论
- 结果:通过 / 有条件通过 / 不通过
- 备注:
Loading