feat: add exchange account states and refine beginner trader creation flow (#1450)

* feat: implement exchange account state management and UI updates

- Added functionality to invalidate exchange account state cache on exchange config updates, creation, and deletion.
- Introduced new API endpoint to fetch exchange account states.
- Updated frontend components to display exchange account states, including status and balance information.
- Enhanced user experience by refreshing exchange account states after relevant actions.

* feat: enhance trader creation readiness in AITradersPage and BeginnerGuideCards

---------

Co-authored-by: Dean <afei.wuhao@gmail.com>
This commit is contained in:
deanokk
2026-04-01 16:26:04 +08:00
committed by shinchan-zhai
parent 649ef50e44
commit 085ae3c875
10 changed files with 551 additions and 186 deletions

View File

@@ -58,73 +58,7 @@ func (s *Server) handleSyncBalance(c *gin.Context) {
return
}
// Create temporary trader to query balance
var tempTrader trader.Trader
var createErr error
// Use ExchangeType (e.g., "binance") instead of ExchangeID (which is now UUID)
// Convert EncryptedString fields to string
switch exchangeCfg.ExchangeType {
case "binance":
tempTrader = binance.NewFuturesTrader(string(exchangeCfg.APIKey), string(exchangeCfg.SecretKey), userID)
case "hyperliquid":
tempTrader, createErr = hyperliquidtrader.NewHyperliquidTrader(
string(exchangeCfg.APIKey),
exchangeCfg.HyperliquidWalletAddr,
exchangeCfg.Testnet,
exchangeCfg.HyperliquidUnifiedAcct,
)
case "aster":
tempTrader, createErr = aster.NewAsterTrader(
exchangeCfg.AsterUser,
exchangeCfg.AsterSigner,
string(exchangeCfg.AsterPrivateKey),
)
case "bybit":
tempTrader = bybit.NewBybitTrader(
string(exchangeCfg.APIKey),
string(exchangeCfg.SecretKey),
)
case "okx":
tempTrader = okx.NewOKXTrader(
string(exchangeCfg.APIKey),
string(exchangeCfg.SecretKey),
string(exchangeCfg.Passphrase),
)
case "bitget":
tempTrader = bitget.NewBitgetTrader(
string(exchangeCfg.APIKey),
string(exchangeCfg.SecretKey),
string(exchangeCfg.Passphrase),
)
case "gate":
tempTrader = gate.NewGateTrader(
string(exchangeCfg.APIKey),
string(exchangeCfg.SecretKey),
)
case "kucoin":
tempTrader = kucoin.NewKuCoinTrader(
string(exchangeCfg.APIKey),
string(exchangeCfg.SecretKey),
string(exchangeCfg.Passphrase),
)
case "lighter":
if exchangeCfg.LighterWalletAddr != "" && string(exchangeCfg.LighterAPIKeyPrivateKey) != "" {
// Lighter only supports mainnet
tempTrader, createErr = lighter.NewLighterTraderV2(
exchangeCfg.LighterWalletAddr,
string(exchangeCfg.LighterAPIKeyPrivateKey),
exchangeCfg.LighterAPIKeyIndex,
false, // Always use mainnet for Lighter
)
} else {
createErr = fmt.Errorf("Lighter requires wallet address and API Key private key")
}
default:
c.JSON(http.StatusBadRequest, gin.H{"error": "Unsupported exchange type"})
return
}
tempTrader, createErr := buildExchangeProbeTrader(exchangeCfg, userID)
if createErr != nil {
logger.Infof("⚠️ Failed to create temporary trader: %v", createErr)
SafeInternalError(c, "Failed to connect to exchange", createErr)
@@ -140,20 +74,14 @@ func (s *Server) handleSyncBalance(c *gin.Context) {
}
// Extract total equity (for P&L calculation, we need total account value, not available balance)
var actualBalance float64
// Priority: total_equity > totalWalletBalance > wallet_balance > totalEq > balance
balanceKeys := []string{"total_equity", "totalWalletBalance", "wallet_balance", "totalEq", "balance"}
for _, key := range balanceKeys {
if balance, ok := balanceInfo[key].(float64); ok && balance > 0 {
actualBalance = balance
break
}
}
if actualBalance <= 0 {
actualBalance, found := extractExchangeTotalEquity(balanceInfo)
if !found {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Unable to get total equity"})
return
}
s.exchangeAccountStateCache.Invalidate(userID)
oldBalance := traderConfig.InitialBalance
// Smart balance change detection