diff --git a/db/trade_repo.go b/db/trade_repo.go index d59c027..6b809ec 100644 --- a/db/trade_repo.go +++ b/db/trade_repo.go @@ -118,6 +118,12 @@ func (d *DB) UpdateTradeEntry(id int64, t *TradeRecord) error { return err } +// UpdateTradeScale updates scale-in fields on an existing trade (amount_usd, scale_count). +func (d *DB) UpdateTradeScale(id int64, amountUSD float64, scaleCount int) error { + _, err := d.Exec("UPDATE trades SET amount_usd=?, scale_count=? WHERE id=?", amountUSD, scaleCount, id) + return err +} + // GetOpenTrades returns all non-closed trades (status='open' or status='entering'). func (d *DB) GetOpenTrades() ([]TradeRecord, error) { rows, err := d.Query(`SELECT id, coin, direction, status, entry_spread, exit_spread, diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a573f1a..a0384e2 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -651,6 +651,11 @@ function PnlChart() { // Compute cumulative PnL const points = [] let cum = 0 + // Prepend a zero point so single-trade chart still draws + if (data.length > 0) { + const t0 = new Date(data[0].ClosedAt).getTime() - 1000 + points.push({ x: t0, y: 0 }) + } for (const t of data) { cum += 2 * (t.AmountUSD || 0) * (t.NetPnl || 0) / 100 points.push({ x: new Date(t.ClosedAt).getTime(), y: cum }) @@ -756,7 +761,7 @@ function PnlChart() {

📈 总PnL成长曲线 {data.length > 0 ? `$${totalPnl.toFixed(2)}` : ''}

- {data.length < 2 ? ( + {data.length < 1 ? (
暂无数据...
) : ( diff --git a/trader.go b/trader.go index 8c6800c..2ccb314 100644 --- a/trader.go +++ b/trader.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log" + "strconv" "strings" "sync" "time" @@ -939,6 +940,8 @@ func (t *Trader) checkScaleIn(pos *ArbPosition, bgP, hlP, diffPct float64, store LongOrderID: &longOID, ShortOrderID: &shortOID, CreatedAt: now, }) + // Persist updated amount_usd and scale_count immediately + t.db.UpdateTradeScale(pos.DBTradeID, pos.AmountUSD, pos.ScaleLevels) } log.Printf("[Trader] %s: Scale-in #%d executed | spread=%.4f%% (entry=%.4f%%) | total=$%.0f", @@ -1274,11 +1277,12 @@ func (t *Trader) placeOrder(leg *PositionLeg, side string, store *PriceStore) (s leg.OrderID = resp log.Printf("[ExRes] HL %s %s: size=%s", side, leg.Coin, szStr) - // Parse actual fill price and estimate fee from HL response - fillPrice, _, parseErr := t.hyperliquid.ParseFillFromResponse(resp) + // Parse actual fill price, OID, and estimate fee from HL response + fillPrice, _, oid, parseErr := t.hyperliquid.ParseFillFromResponse(resp) if parseErr == nil && fillPrice > 0 { leg.EntryPrice = fillPrice - log.Printf("[Fill] HL %s %s: actual fillPrice=%.6f", side, leg.Coin, fillPrice) + leg.OrderID = strconv.FormatInt(oid, 10) + log.Printf("[Fill] HL %s %s: actual fillPrice=%.6f oid=%d", side, leg.Coin, fillPrice, oid) } fee, fetchErr := t.hyperliquid.EstimateFeeFromResponse(resp, takerFees[ExHyperLiquid]) if fetchErr != nil { @@ -1531,11 +1535,12 @@ func (t *Trader) closeLeg(leg *PositionLeg) string { log.Printf("[ExRes] HL close %s %s: size=%s → response=%s", side, leg.Coin, leg.Size, resp) leg.OrderID = resp - // Parse actual fill price from HL close response - fillPrice, _, parseErr := t.hyperliquid.ParseFillFromResponse(resp) + // Parse actual fill price and OID from HL close response + fillPrice, _, oid, parseErr := t.hyperliquid.ParseFillFromResponse(resp) if parseErr == nil && fillPrice > 0 { leg.ExitPrice = fillPrice - log.Printf("[Fill] HL close %s: actual exitPrice=%.6f", leg.Coin, fillPrice) + leg.OrderID = strconv.FormatInt(oid, 10) + log.Printf("[Fill] HL close %s: actual exitPrice=%.6f oid=%d", leg.Coin, fillPrice, oid) } } leg.Closed = true @@ -1754,11 +1759,12 @@ func (t *Trader) placeOrderAt(leg *PositionLeg, side string, store *PriceStore, } leg.OrderID = oid - // Parse actual fill price from HL response - fillPrice, _, parseErr := t.hyperliquid.ParseFillFromResponse(oid) + // Parse actual fill price and OID from HL response + fillPrice, _, oidNum, parseErr := t.hyperliquid.ParseFillFromResponse(oid) if parseErr == nil && fillPrice > 0 { leg.EntryPrice = fillPrice - log.Printf("[Fill] HL scale %s %s: actual fillPrice=%.6f", side, leg.Coin, fillPrice) + leg.OrderID = strconv.FormatInt(oidNum, 10) + log.Printf("[Fill] HL scale %s %s: actual fillPrice=%.6f oid=%d", side, leg.Coin, fillPrice, oidNum) } // Estimate fee from HL response @@ -1957,6 +1963,11 @@ func (t *Trader) restoreOpenPositions() { if err == nil { pos.LongEntryPrices = append(pos.LongEntryPrices, scaleLong...) pos.ShortEntryPrices = append(pos.ShortEntryPrices, scaleShort...) + // Restore ScaleLevels from actual scale order count + if len(scaleLong) > 0 { + pos.ScaleLevels = len(scaleLong) + pos.AmountUSD = tr.AmountUSD * (1 + float64(pos.ScaleLevels)) + } // Refresh leg EntryPrice to reflect all scale levels if len(pos.LongEntryPrices) > 1 { pos.LongLeg.EntryPrice = weightedAvgPrice(pos.LongEntryPrices, t.cfg.TradeAmountUSD)