feat: 手续费改为逐笔USD累算 + Vite React前端 + system_orders表 + README

This commit is contained in:
jackyu66git
2026-05-04 04:34:27 +08:00
parent 15a4684208
commit 0288dc8284
18 changed files with 2919 additions and 521 deletions
+166 -9
View File
@@ -520,7 +520,7 @@ func (t *Trader) checkExit(pos *ArbPosition, bgP, hlP, diffPct float64, notifier
longPnl := (longCurrent - longAvg) / longAvg * 100
shortPnl := (shortAvg - shortCurrent) / shortAvg * 100
totalFees := 2 * (takerFees[ExBitget] + takerFees[ExHyperLiquid]) // 开仓 + 平仓手续费
totalFees := float64(2+pos.ScaleLevels) * (takerFees[ExBitget] + takerFees[ExHyperLiquid]) // 开仓(含加仓) + 平仓手续费
netPnl := longPnl + shortPnl - totalFees
elapsed := time.Since(pos.StartedAt)
@@ -905,15 +905,35 @@ func (t *Trader) GetClosedTrades() []TradeRecord {
return r
}
// persistTrade saves a completed trade to SQLite.
// persistTrade saves a completed trade to SQLite, with per-leg orders and system_orders.
func (t *Trader) persistTrade(pos *ArbPosition, exitSpread float64, convergence, exitReason string, netPnl, longPnl, shortPnl, totalFees float64) {
var entrySpread, fe float64
var entrySpread float64
if pos.LongLeg != nil {
entrySpread = pos.EntrySpread
}
fe = totalFees / 2 // split into entry/exit halves
now := time.Now()
tradeUnit := t.cfg.TradeAmountUSD
// Pre-calculate all fees BEFORE saving the trade
totalFeeEntryUSD := 0.0
for range pos.LongEntryPrices {
totalFeeEntryUSD += tradeUnit * takerFees[pos.LongLeg.Exchange] / 100
}
for range pos.ShortEntryPrices {
totalFeeEntryUSD += tradeUnit * takerFees[pos.ShortLeg.Exchange] / 100
}
totalLongShares := 0.0
for _, p := range pos.LongEntryPrices {
totalLongShares += tradeUnit / p
}
totalShortShares := 0.0
for _, p := range pos.ShortEntryPrices {
totalShortShares += tradeUnit / p
}
totalFeeExitUSD := totalLongShares*pos.LongLeg.ExitPrice*takerFees[pos.LongLeg.Exchange]/100 +
totalShortShares*pos.ShortLeg.ExitPrice*takerFees[pos.ShortLeg.Exchange]/100
dbTrade := &db.TradeRecord{
Coin: pos.Coin,
Direction: pos.Direction,
@@ -928,8 +948,8 @@ func (t *Trader) persistTrade(pos *ArbPosition, exitSpread float64, convergence,
ShortExit: &pos.ShortLeg.ExitPrice,
LongPnl: &longPnl,
ShortPnl: &shortPnl,
FeeEntry: &fe,
FeeExit: &fe,
FeeEntry: &totalFeeEntryUSD,
FeeExit: &totalFeeExitUSD,
NetPnl: &netPnl,
AmountUSD: pos.AmountUSD,
ScaleCount: pos.ScaleLevels,
@@ -938,8 +958,145 @@ func (t *Trader) persistTrade(pos *ArbPosition, exitSpread float64, convergence,
OpenedAt: pos.StartedAt,
ClosedAt: &now,
}
if _, err := t.db.SaveTrade(dbTrade); err != nil {
tradeID, err := t.db.SaveTrade(dbTrade)
if err != nil {
log.Printf("[Trader] Failed to save trade to DB: %v", err)
return
}
// Save per-leg order records
// Long leg: entry (buy), scales (buy), exit (sell)
status := "filled"
var longEntryOrderIDs []int64
for i, p := range pos.LongEntryPrices {
shares := tradeUnit / p
orderType := "entry"
if i > 0 {
orderType = "scale"
}
fee := tradeUnit * takerFees[pos.LongLeg.Exchange] / 100
oid, oErr := t.db.SaveOrder(&db.OrderRecord{
TradeID: tradeID,
Leg: "long",
Type: orderType,
Exchange: pos.LongLeg.Exchange,
Side: "buy",
Price: &p,
Size: &shares,
Fee: &fee,
Status: &status,
CreatedAt: pos.StartedAt,
})
if oErr != nil {
log.Printf("[Trader] Failed to save long entry order: %v", oErr)
} else {
longEntryOrderIDs = append(longEntryOrderIDs, oid)
}
}
// Long exit (sell)
longExitShares := totalLongShares
longExitFee := totalLongShares * pos.LongLeg.ExitPrice * takerFees[pos.LongLeg.Exchange] / 100
status = "filled"
var longExitOrderID int64
if oid, oErr := t.db.SaveOrder(&db.OrderRecord{
TradeID: tradeID,
Leg: "long",
Type: "exit",
Exchange: pos.LongLeg.Exchange,
Side: "sell",
Price: &pos.LongLeg.ExitPrice,
Size: &longExitShares,
Fee: &longExitFee,
Status: &status,
CreatedAt: now,
}); oErr != nil {
log.Printf("[Trader] Failed to save long exit order: %v", oErr)
} else {
longExitOrderID = oid
}
// Short leg: entry (sell), scales (sell), exit (buy)
var shortEntryOrderIDs []int64
for i, p := range pos.ShortEntryPrices {
shares := tradeUnit / p
orderType := "entry"
if i > 0 {
orderType = "scale"
}
fee := tradeUnit * takerFees[pos.ShortLeg.Exchange] / 100
oid, oErr := t.db.SaveOrder(&db.OrderRecord{
TradeID: tradeID,
Leg: "short",
Type: orderType,
Exchange: pos.ShortLeg.Exchange,
Side: "sell",
Price: &p,
Size: &shares,
Fee: &fee,
Status: &status,
CreatedAt: pos.StartedAt,
})
if oErr != nil {
log.Printf("[Trader] Failed to save short entry order: %v", oErr)
} else {
shortEntryOrderIDs = append(shortEntryOrderIDs, oid)
}
}
// Short exit (buy)
shortExitShares := totalShortShares
shortExitFee := totalShortShares * pos.ShortLeg.ExitPrice * takerFees[pos.ShortLeg.Exchange] / 100
var shortExitOrderID int64
if oid, oErr := t.db.SaveOrder(&db.OrderRecord{
TradeID: tradeID,
Leg: "short",
Type: "exit",
Exchange: pos.ShortLeg.Exchange,
Side: "buy",
Price: &pos.ShortLeg.ExitPrice,
Size: &shortExitShares,
Fee: &shortExitFee,
Status: &status,
CreatedAt: now,
}); oErr != nil {
log.Printf("[Trader] Failed to save short exit order: %v", oErr)
} else {
shortExitOrderID = oid
}
// Save system orders linking long+short legs
es := pos.EntrySpread
for i := 0; i < len(longEntryOrderIDs) && i < len(shortEntryOrderIDs); i++ {
sysType := "entry"
if i > 0 {
sysType = "scale"
}
if _, sErr := t.db.SaveSystemOrder(&db.SystemOrderRecord{
TradeID: tradeID,
Type: sysType,
Status: "filled",
Spread: &es,
LongPrice: &pos.LongEntryPrices[i],
ShortPrice: &pos.ShortEntryPrices[i],
LongOrderID: &longEntryOrderIDs[i],
ShortOrderID: &shortEntryOrderIDs[i],
CreatedAt: pos.StartedAt,
}); sErr != nil {
log.Printf("[Trader] Failed to save entry system order: %v", sErr)
}
}
// Exit system order
if _, sErr := t.db.SaveSystemOrder(&db.SystemOrderRecord{
TradeID: tradeID,
Type: "exit",
Status: "filled",
Spread: &exitSpread,
LongPrice: &pos.LongLeg.ExitPrice,
ShortPrice: &pos.ShortLeg.ExitPrice,
LongOrderID: &longExitOrderID,
ShortOrderID: &shortExitOrderID,
CreatedAt: now,
}); sErr != nil {
log.Printf("[Trader] Failed to save exit system order: %v", sErr)
}
}
@@ -1004,7 +1161,7 @@ func (t *Trader) blacklistCoin(pos *ArbPosition, bgP, hlP, diffPct float64, noti
shortAvg := weightedAvgPrice(pos.ShortEntryPrices, t.cfg.TradeAmountUSD)
longPnl := (longCurrent - longAvg) / longAvg * 100
shortPnl := (shortAvg - shortCurrent) / shortAvg * 100
totalFees := 2 * (takerFees[ExBitget] + takerFees[ExHyperLiquid])
totalFees := float64(2+pos.ScaleLevels) * (takerFees[ExBitget] + takerFees[ExHyperLiquid])
netPnl := longPnl + shortPnl - totalFees
pos.ExitDiffPct = diffPct
@@ -1074,4 +1231,4 @@ func (t *Trader) RemoveBlacklist(coin string) {
defer t.mu.Unlock()
delete(t.blacklist, coin)
log.Printf("[Trader] ✅ %s: Removed from blacklist", coin)
}
}