fix: ensure all timestamps use UTC timezone

- Add NowFunc to GORM config for UTC auto-generated timestamps
- Add .UTC() to all time.UnixMilli() calls in trader files
- Add .UTC() to all time.Now() calls in store and api files
- Fix TypeScript unused imports in frontend
This commit is contained in:
tinkle-community
2026-01-04 20:03:56 +08:00
parent 50923f6a2e
commit 5c9e134e99
25 changed files with 96 additions and 85 deletions

View File

@@ -1452,9 +1452,9 @@ func (s *Server) recordClosePositionOrder(traderID, exchangeID, exchangeType, sy
FilledQuantity: quantity, FilledQuantity: quantity,
AvgFillPrice: exitPrice, AvgFillPrice: exitPrice,
Commission: fee, Commission: fee,
FilledAt: time.Now(), FilledAt: time.Now().UTC(),
CreatedAt: time.Now(), CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now(), UpdatedAt: time.Now().UTC(),
} }
if err := s.store.Order().CreateOrder(orderRecord); err != nil { if err := s.store.Order().CreateOrder(orderRecord); err != nil {
@@ -1482,7 +1482,7 @@ func (s *Server) recordClosePositionOrder(traderID, exchangeID, exchangeType, sy
CommissionAsset: "USDT", CommissionAsset: "USDT",
RealizedPnL: 0, RealizedPnL: 0,
IsMaker: false, IsMaker: false,
CreatedAt: time.Now(), CreatedAt: time.Now().UTC(),
} }
if err := s.store.Order().CreateFill(fillRecord); err != nil { if err := s.store.Order().CreateFill(fillRecord); err != nil {
@@ -1557,7 +1557,7 @@ func (s *Server) pollAndUpdateOrderStatus(orderRecordID int64, traderID, exchang
CommissionAsset: "USDT", CommissionAsset: "USDT",
RealizedPnL: 0, RealizedPnL: 0,
IsMaker: false, IsMaker: false,
CreatedAt: time.Now(), CreatedAt: time.Now().UTC(),
} }
if err := s.store.Order().CreateFill(fillRecord); err != nil { if err := s.store.Order().CreateFill(fillRecord); err != nil {

View File

@@ -149,7 +149,7 @@ func (s *AIModelStore) Update(userID, id string, enabled bool, apiKey, customAPI
"enabled": enabled, "enabled": enabled,
"custom_api_url": customAPIURL, "custom_api_url": customAPIURL,
"custom_model_name": customModelName, "custom_model_name": customModelName,
"updated_at": time.Now(), "updated_at": time.Now().UTC(),
} }
// If apiKey is not empty, update it (encryption handled by crypto.EncryptedString) // If apiKey is not empty, update it (encryption handled by crypto.EncryptedString)
if apiKey != "" { if apiKey != "" {
@@ -167,7 +167,7 @@ func (s *AIModelStore) Update(userID, id string, enabled bool, apiKey, customAPI
"enabled": enabled, "enabled": enabled,
"custom_api_url": customAPIURL, "custom_api_url": customAPIURL,
"custom_model_name": customModelName, "custom_model_name": customModelName,
"updated_at": time.Now(), "updated_at": time.Now().UTC(),
} }
if apiKey != "" { if apiKey != "" {
updates["api_key"] = crypto.EncryptedString(apiKey) updates["api_key"] = crypto.EncryptedString(apiKey)

View File

@@ -236,7 +236,7 @@ func (s *ExchangeStore) Update(userID, id string, enabled bool, apiKey, secretKe
"aster_signer": asterSigner, "aster_signer": asterSigner,
"lighter_wallet_addr": lighterWalletAddr, "lighter_wallet_addr": lighterWalletAddr,
"lighter_api_key_index": lighterApiKeyIndex, "lighter_api_key_index": lighterApiKeyIndex,
"updated_at": time.Now(), "updated_at": time.Now().UTC(),
} }
// Only update encrypted fields if not empty // Only update encrypted fields if not empty
@@ -275,7 +275,7 @@ func (s *ExchangeStore) UpdateAccountName(userID, id, accountName string) error
Where("id = ? AND user_id = ?", id, userID). Where("id = ? AND user_id = ?", id, userID).
Updates(map[string]interface{}{ Updates(map[string]interface{}{
"account_name": accountName, "account_name": accountName,
"updated_at": time.Now(), "updated_at": time.Now().UTC(),
}) })
if result.Error != nil { if result.Error != nil {
return result.Error return result.Error

View File

@@ -2,6 +2,7 @@ package store
import ( import (
"fmt" "fmt"
"time"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
"gorm.io/driver/sqlite" "gorm.io/driver/sqlite"
@@ -21,6 +22,10 @@ func DB() *gorm.DB {
func InitGorm(dbPath string) (*gorm.DB, error) { func InitGorm(dbPath string) (*gorm.DB, error) {
db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent), Logger: logger.Default.LogMode(logger.Silent),
// Use UTC for all auto-generated timestamps (autoCreateTime, autoUpdateTime)
NowFunc: func() time.Time {
return time.Now().UTC()
},
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open SQLite database: %w", err) return nil, fmt.Errorf("failed to open SQLite database: %w", err)
@@ -53,6 +58,10 @@ func InitGormPostgres(host string, port int, user, password, dbname, sslmode str
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent), Logger: logger.Default.LogMode(logger.Silent),
// Use UTC for all auto-generated timestamps (autoCreateTime, autoUpdateTime)
NowFunc: func() time.Time {
return time.Now().UTC()
},
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to open PostgreSQL database: %w", err) return nil, fmt.Errorf("failed to open PostgreSQL database: %w", err)

View File

@@ -69,8 +69,8 @@ func (pb *PositionBuilder) handleOpen(
Status: "OPEN", Status: "OPEN",
Source: "sync", Source: "sync",
Fee: fee, Fee: fee,
CreatedAt: time.Now(), CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now(), UpdatedAt: time.Now().UTC(),
} }
return pb.positionStore.CreateOpenPosition(position) return pb.positionStore.CreateOpenPosition(position)
} }

View File

@@ -328,7 +328,7 @@ func (s *StrategyStore) Update(strategy *Strategy) error {
"config": strategy.Config, "config": strategy.Config,
"is_public": strategy.IsPublic, "is_public": strategy.IsPublic,
"config_visible": strategy.ConfigVisible, "config_visible": strategy.ConfigVisible,
"updated_at": time.Now(), "updated_at": time.Now().UTC(),
}).Error }).Error
} }

View File

@@ -123,7 +123,7 @@ func (s *UserStore) UpdateOTPVerified(userID string, verified bool) error {
func (s *UserStore) UpdatePassword(userID, passwordHash string) error { func (s *UserStore) UpdatePassword(userID, passwordHash string) error {
return s.db.Model(&User{}).Where("id = ?", userID).Updates(map[string]interface{}{ return s.db.Model(&User{}).Where("id = ?", userID).Updates(map[string]interface{}{
"password_hash": passwordHash, "password_hash": passwordHash,
"updated_at": time.Now(), "updated_at": time.Now().UTC(),
}).Error }).Error
} }

View File

@@ -68,7 +68,8 @@ func (t *AsterTrader) SyncOrdersFromAster(traderID string, exchangeID string, ex
// Normalize side for storage // Normalize side for storage
side := strings.ToUpper(trade.Side) side := strings.ToUpper(trade.Side)
// Create order record // Create order record - use UTC time to avoid timezone issues
tradeTimeUTC := trade.Time.UTC()
orderRecord := &store.TraderOrder{ orderRecord := &store.TraderOrder{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -85,9 +86,9 @@ func (t *AsterTrader) SyncOrdersFromAster(traderID string, exchangeID string, ex
FilledQuantity: trade.Quantity, FilledQuantity: trade.Quantity,
AvgFillPrice: trade.Price, AvgFillPrice: trade.Price,
Commission: trade.Fee, Commission: trade.Fee,
FilledAt: trade.Time, FilledAt: tradeTimeUTC,
CreatedAt: trade.Time, CreatedAt: tradeTimeUTC,
UpdatedAt: trade.Time, UpdatedAt: tradeTimeUTC,
} }
// Insert order record // Insert order record
@@ -96,7 +97,7 @@ func (t *AsterTrader) SyncOrdersFromAster(traderID string, exchangeID string, ex
continue continue
} }
// Create fill record // Create fill record - use UTC time
fillRecord := &store.TraderFill{ fillRecord := &store.TraderFill{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -113,7 +114,7 @@ func (t *AsterTrader) SyncOrdersFromAster(traderID string, exchangeID string, ex
CommissionAsset: "USDT", CommissionAsset: "USDT",
RealizedPnL: trade.RealizedPnL, RealizedPnL: trade.RealizedPnL,
IsMaker: false, IsMaker: false,
CreatedAt: trade.Time, CreatedAt: tradeTimeUTC,
} }
if err := orderStore.CreateFill(fillRecord); err != nil { if err := orderStore.CreateFill(fillRecord); err != nil {

View File

@@ -1407,7 +1407,7 @@ func (t *AsterTrader) GetTrades(startTime time.Time, limit int) ([]TradeRecord,
Quantity: qty, Quantity: qty,
RealizedPnL: pnl, RealizedPnL: pnl,
Fee: fee, Fee: fee,
Time: time.UnixMilli(at.Time), Time: time.UnixMilli(at.Time).UTC(),
} }
result = append(result, trade) result = append(result, trade)
} }

View File

@@ -637,7 +637,7 @@ func (at *AutoTrader) runCycle() error {
TakeProfit: d.TakeProfit, TakeProfit: d.TakeProfit,
Confidence: d.Confidence, Confidence: d.Confidence,
Reasoning: d.Reasoning, Reasoning: d.Reasoning,
Timestamp: time.Now(), Timestamp: time.Now().UTC(),
Success: false, Success: false,
} }
@@ -1976,7 +1976,7 @@ func (at *AutoTrader) recordPositionChange(orderID, symbol, side, action string,
Quantity: quantity, Quantity: quantity,
EntryPrice: price, EntryPrice: price,
EntryOrderID: orderID, EntryOrderID: orderID,
EntryTime: time.Now(), EntryTime: time.Now().UTC(),
Leverage: leverage, Leverage: leverage,
Status: "OPEN", Status: "OPEN",
} }
@@ -1996,7 +1996,7 @@ func (at *AutoTrader) recordPositionChange(orderID, symbol, side, action string,
at.id, at.exchangeID, at.exchange, at.id, at.exchangeID, at.exchange,
symbol, side, action, symbol, side, action,
quantity, price, fee, 0, // realizedPnL will be calculated quantity, price, fee, 0, // realizedPnL will be calculated
time.Now(), orderID, time.Now().UTC(), orderID,
); err != nil { ); err != nil {
logger.Infof(" ⚠️ Failed to process close position: %v", err) logger.Infof(" ⚠️ Failed to process close position: %v", err)
} else { } else {
@@ -2049,8 +2049,8 @@ func (at *AutoTrader) createOrderRecord(orderID, symbol, action, positionSide st
ReduceOnly: reduceOnly, ReduceOnly: reduceOnly,
ClosePosition: reduceOnly, ClosePosition: reduceOnly,
OrderAction: orderAction, OrderAction: orderAction,
CreatedAt: time.Now(), CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now(), UpdatedAt: time.Now().UTC(),
} }
} }
@@ -2091,7 +2091,7 @@ func (at *AutoTrader) recordOrderFill(orderRecordID int64, exchangeOrderID, symb
CommissionAsset: "USDT", CommissionAsset: "USDT",
RealizedPnL: 0, // Will be calculated for close orders RealizedPnL: 0, // Will be calculated for close orders
IsMaker: false, // Market orders are usually taker IsMaker: false, // Market orders are usually taker
CreatedAt: time.Now(), CreatedAt: time.Now().UTC(),
} }
// Calculate realized PnL for close orders // Calculate realized PnL for close orders

View File

@@ -1122,7 +1122,7 @@ func (t *FuturesTrader) GetTrades(startTime time.Time, limit int) ([]TradeRecord
TradeID: strconv.FormatInt(income.TranID, 10), TradeID: strconv.FormatInt(income.TranID, 10),
Symbol: income.Symbol, Symbol: income.Symbol,
RealizedPnL: pnl, RealizedPnL: pnl,
Time: time.UnixMilli(income.Time), Time: time.UnixMilli(income.Time).UTC(),
// Note: Income API doesn't provide price, quantity, side, fee // Note: Income API doesn't provide price, quantity, side, fee
// For accurate data, use GetTradesForSymbol with specific symbol // For accurate data, use GetTradesForSymbol with specific symbol
} }
@@ -1167,7 +1167,7 @@ func (t *FuturesTrader) GetTradesForSymbol(symbol string, startTime time.Time, l
Quantity: qty, Quantity: qty,
RealizedPnL: pnl, RealizedPnL: pnl,
Fee: fee, Fee: fee,
Time: time.UnixMilli(at.Time), Time: time.UnixMilli(at.Time).UTC(),
} }
trades = append(trades, trade) trades = append(trades, trade)
} }
@@ -1210,7 +1210,7 @@ func (t *FuturesTrader) GetTradesForSymbolFromID(symbol string, fromID int64, li
Quantity: qty, Quantity: qty,
RealizedPnL: pnl, RealizedPnL: pnl,
Fee: fee, Fee: fee,
Time: time.UnixMilli(at.Time), Time: time.UnixMilli(at.Time).UTC(),
} }
trades = append(trades, trade) trades = append(trades, trade)
} }

View File

@@ -145,7 +145,8 @@ func (t *FuturesTrader) SyncOrdersFromBinance(traderID string, exchangeID string
// Normalize side // Normalize side
side := strings.ToUpper(trade.Side) side := strings.ToUpper(trade.Side)
// Create order record // Create order record - use UTC time to avoid timezone issues
tradeTimeUTC := trade.Time.UTC()
orderRecord := &store.TraderOrder{ orderRecord := &store.TraderOrder{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, ExchangeID: exchangeID,
@@ -162,9 +163,9 @@ func (t *FuturesTrader) SyncOrdersFromBinance(traderID string, exchangeID string
FilledQuantity: trade.Quantity, FilledQuantity: trade.Quantity,
AvgFillPrice: trade.Price, AvgFillPrice: trade.Price,
Commission: trade.Fee, Commission: trade.Fee,
FilledAt: trade.Time, FilledAt: tradeTimeUTC,
CreatedAt: trade.Time, CreatedAt: tradeTimeUTC,
UpdatedAt: trade.Time, UpdatedAt: tradeTimeUTC,
} }
// Insert order record // Insert order record
@@ -173,7 +174,7 @@ func (t *FuturesTrader) SyncOrdersFromBinance(traderID string, exchangeID string
continue continue
} }
// Create fill record // Create fill record - use UTC time
fillRecord := &store.TraderFill{ fillRecord := &store.TraderFill{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, ExchangeID: exchangeID,
@@ -190,7 +191,7 @@ func (t *FuturesTrader) SyncOrdersFromBinance(traderID string, exchangeID string
CommissionAsset: "USDT", CommissionAsset: "USDT",
RealizedPnL: trade.RealizedPnL, RealizedPnL: trade.RealizedPnL,
IsMaker: false, IsMaker: false,
CreatedAt: trade.Time, CreatedAt: tradeTimeUTC,
} }
if err := orderStore.CreateFill(fillRecord); err != nil { if err := orderStore.CreateFill(fillRecord); err != nil {

View File

@@ -110,7 +110,7 @@ func (t *BitgetTrader) GetTrades(startTime time.Time, limit int) ([]BitgetTrade,
FillQty: fillQty, FillQty: fillQty,
Fee: -fee, // Bitget returns negative fee Fee: -fee, // Bitget returns negative fee
FeeAsset: fill.FeeCcy, FeeAsset: fill.FeeCcy,
ExecTime: time.UnixMilli(cTime), ExecTime: time.UnixMilli(cTime).UTC(),
ProfitLoss: profit, ProfitLoss: profit,
OrderType: "MARKET", OrderType: "MARKET",
OrderAction: orderAction, OrderAction: orderAction,
@@ -174,7 +174,8 @@ func (t *BitgetTrader) SyncOrdersFromBitget(traderID string, exchangeID string,
// Normalize side for storage // Normalize side for storage
side := strings.ToUpper(trade.Side) side := strings.ToUpper(trade.Side)
// Create order record // Create order record - use UTC time to avoid timezone issues
execTimeUTC := trade.ExecTime.UTC()
orderRecord := &store.TraderOrder{ orderRecord := &store.TraderOrder{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -191,9 +192,9 @@ func (t *BitgetTrader) SyncOrdersFromBitget(traderID string, exchangeID string,
FilledQuantity: trade.FillQty, FilledQuantity: trade.FillQty,
AvgFillPrice: trade.FillPrice, AvgFillPrice: trade.FillPrice,
Commission: trade.Fee, Commission: trade.Fee,
FilledAt: trade.ExecTime, FilledAt: execTimeUTC,
CreatedAt: trade.ExecTime, CreatedAt: execTimeUTC,
UpdatedAt: trade.ExecTime, UpdatedAt: execTimeUTC,
} }
// Insert order record // Insert order record
@@ -202,7 +203,7 @@ func (t *BitgetTrader) SyncOrdersFromBitget(traderID string, exchangeID string,
continue continue
} }
// Create fill record // Create fill record - use UTC time
fillRecord := &store.TraderFill{ fillRecord := &store.TraderFill{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -219,7 +220,7 @@ func (t *BitgetTrader) SyncOrdersFromBitget(traderID string, exchangeID string,
CommissionAsset: trade.FeeAsset, CommissionAsset: trade.FeeAsset,
RealizedPnL: trade.ProfitLoss, RealizedPnL: trade.ProfitLoss,
IsMaker: false, IsMaker: false,
CreatedAt: trade.ExecTime, CreatedAt: execTimeUTC,
} }
if err := orderStore.CreateFill(fillRecord); err != nil { if err := orderStore.CreateFill(fillRecord); err != nil {

View File

@@ -1069,8 +1069,8 @@ func (t *BitgetTrader) GetClosedPnL(startTime time.Time, limit int) ([]ClosedPnL
cTime, _ := strconv.ParseInt(pos.CTime, 10, 64) cTime, _ := strconv.ParseInt(pos.CTime, 10, 64)
uTime, _ := strconv.ParseInt(pos.UTime, 10, 64) uTime, _ := strconv.ParseInt(pos.UTime, 10, 64)
record.EntryTime = time.UnixMilli(cTime) record.EntryTime = time.UnixMilli(cTime).UTC()
record.ExitTime = time.UnixMilli(uTime) record.ExitTime = time.UnixMilli(uTime).UTC()
record.CloseType = "unknown" record.CloseType = "unknown"
records = append(records, record) records = append(records, record)

View File

@@ -127,7 +127,7 @@ func (t *BybitTrader) parseTradesResult(list []map[string]interface{}) ([]BybitT
closedSize, _ := strconv.ParseFloat(closedSizeStr, 64) closedSize, _ := strconv.ParseFloat(closedSizeStr, 64)
closedPnl, _ := strconv.ParseFloat(closedPnlStr, 64) closedPnl, _ := strconv.ParseFloat(closedPnlStr, 64)
execTimeMs, _ := strconv.ParseInt(execTimeStr, 10, 64) execTimeMs, _ := strconv.ParseInt(execTimeStr, 10, 64)
execTime := time.UnixMilli(execTimeMs) execTime := time.UnixMilli(execTimeMs).UTC()
// Determine order action based on side and closedSize // Determine order action based on side and closedSize
// If closedSize > 0, it's a close trade // If closedSize > 0, it's a close trade
@@ -223,7 +223,8 @@ func (t *BybitTrader) SyncOrdersFromBybit(traderID string, exchangeID string, ex
// Normalize side for storage // Normalize side for storage
side := strings.ToUpper(trade.Side) side := strings.ToUpper(trade.Side)
// Create order record // Create order record - use UTC time to avoid timezone issues
execTimeUTC := trade.ExecTime.UTC()
orderRecord := &store.TraderOrder{ orderRecord := &store.TraderOrder{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -240,9 +241,9 @@ func (t *BybitTrader) SyncOrdersFromBybit(traderID string, exchangeID string, ex
FilledQuantity: trade.ExecQty, FilledQuantity: trade.ExecQty,
AvgFillPrice: trade.ExecPrice, AvgFillPrice: trade.ExecPrice,
Commission: trade.ExecFee, Commission: trade.ExecFee,
FilledAt: trade.ExecTime, FilledAt: execTimeUTC,
CreatedAt: trade.ExecTime, CreatedAt: execTimeUTC,
UpdatedAt: trade.ExecTime, UpdatedAt: execTimeUTC,
} }
// Insert order record // Insert order record
@@ -251,7 +252,7 @@ func (t *BybitTrader) SyncOrdersFromBybit(traderID string, exchangeID string, ex
continue continue
} }
// Create fill record // Create fill record - use UTC time
fillRecord := &store.TraderFill{ fillRecord := &store.TraderFill{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -268,7 +269,7 @@ func (t *BybitTrader) SyncOrdersFromBybit(traderID string, exchangeID string, ex
CommissionAsset: "USDT", CommissionAsset: "USDT",
RealizedPnL: trade.ClosedPnL, RealizedPnL: trade.ClosedPnL,
IsMaker: trade.IsMaker, IsMaker: trade.IsMaker,
CreatedAt: trade.ExecTime, CreatedAt: execTimeUTC,
} }
if err := orderStore.CreateFill(fillRecord); err != nil { if err := orderStore.CreateFill(fillRecord); err != nil {

View File

@@ -1032,8 +1032,8 @@ func (t *BybitTrader) parseClosedPnLResult(resultData interface{}) ([]ClosedPnLR
RealizedPnL: closedPnL, RealizedPnL: closedPnL,
Fee: fee, Fee: fee,
Leverage: int(leverage), Leverage: int(leverage),
EntryTime: time.UnixMilli(createdTime), EntryTime: time.UnixMilli(createdTime).UTC(),
ExitTime: time.UnixMilli(updatedTime), ExitTime: time.UnixMilli(updatedTime).UTC(),
OrderID: orderId, OrderID: orderId,
CloseType: "unknown", // Bybit doesn't provide close type directly CloseType: "unknown", // Bybit doesn't provide close type directly
ExchangeID: orderId, // Use orderId as exchange ID ExchangeID: orderId, // Use orderId as exchange ID

View File

@@ -61,7 +61,8 @@ func (t *HyperliquidTrader) SyncOrdersFromHyperliquid(traderID string, exchangeI
positionSide = "SHORT" positionSide = "SHORT"
} }
// Create order record // Create order record - use UTC time to avoid timezone issues
tradeTimeUTC := trade.Time.UTC()
orderRecord := &store.TraderOrder{ orderRecord := &store.TraderOrder{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -78,9 +79,9 @@ func (t *HyperliquidTrader) SyncOrdersFromHyperliquid(traderID string, exchangeI
FilledQuantity: trade.Quantity, FilledQuantity: trade.Quantity,
AvgFillPrice: trade.Price, AvgFillPrice: trade.Price,
Commission: trade.Fee, Commission: trade.Fee,
FilledAt: trade.Time, FilledAt: tradeTimeUTC,
CreatedAt: trade.Time, CreatedAt: tradeTimeUTC,
UpdatedAt: trade.Time, UpdatedAt: tradeTimeUTC,
} }
// Insert order record // Insert order record
@@ -89,7 +90,7 @@ func (t *HyperliquidTrader) SyncOrdersFromHyperliquid(traderID string, exchangeI
continue continue
} }
// Create fill record // Create fill record - use UTC time
fillRecord := &store.TraderFill{ fillRecord := &store.TraderFill{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -106,7 +107,7 @@ func (t *HyperliquidTrader) SyncOrdersFromHyperliquid(traderID string, exchangeI
CommissionAsset: "USDT", CommissionAsset: "USDT",
RealizedPnL: trade.RealizedPnL, RealizedPnL: trade.RealizedPnL,
IsMaker: false, // Hyperliquid GetTrades doesn't provide maker/taker info IsMaker: false, // Hyperliquid GetTrades doesn't provide maker/taker info
CreatedAt: trade.Time, CreatedAt: tradeTimeUTC,
} }
if err := orderStore.CreateFill(fillRecord); err != nil { if err := orderStore.CreateFill(fillRecord); err != nil {

View File

@@ -2070,7 +2070,7 @@ func (t *HyperliquidTrader) GetTrades(startTime time.Time, limit int) ([]TradeRe
Quantity: qty, Quantity: qty,
RealizedPnL: pnl, RealizedPnL: pnl,
Fee: fee, Fee: fee,
Time: time.UnixMilli(fill.Time), Time: time.UnixMilli(fill.Time).UTC(),
} }
trades = append(trades, trade) trades = append(trades, trade)
} }

View File

@@ -70,7 +70,8 @@ func (t *LighterTraderV2) SyncOrdersFromLighter(traderID string, exchangeID stri
} }
} }
// Create order record // Create order record - use UTC time to avoid timezone issues
tradeTimeUTC := trade.Time.UTC()
orderRecord := &store.TraderOrder{ orderRecord := &store.TraderOrder{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -87,9 +88,9 @@ func (t *LighterTraderV2) SyncOrdersFromLighter(traderID string, exchangeID stri
FilledQuantity: trade.Quantity, FilledQuantity: trade.Quantity,
AvgFillPrice: trade.Price, AvgFillPrice: trade.Price,
Commission: trade.Fee, Commission: trade.Fee,
FilledAt: trade.Time, FilledAt: tradeTimeUTC,
CreatedAt: trade.Time, CreatedAt: tradeTimeUTC,
UpdatedAt: trade.Time, UpdatedAt: tradeTimeUTC,
} }
// Insert order record // Insert order record
@@ -98,7 +99,7 @@ func (t *LighterTraderV2) SyncOrdersFromLighter(traderID string, exchangeID stri
continue continue
} }
// Create fill record // Create fill record - use UTC time
fillRecord := &store.TraderFill{ fillRecord := &store.TraderFill{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -115,7 +116,7 @@ func (t *LighterTraderV2) SyncOrdersFromLighter(traderID string, exchangeID stri
CommissionAsset: "USDT", CommissionAsset: "USDT",
RealizedPnL: trade.RealizedPnL, RealizedPnL: trade.RealizedPnL,
IsMaker: false, IsMaker: false,
CreatedAt: trade.Time, CreatedAt: tradeTimeUTC,
} }
if err := orderStore.CreateFill(fillRecord); err != nil { if err := orderStore.CreateFill(fillRecord); err != nil {

View File

@@ -537,7 +537,7 @@ func (t *LighterTraderV2) GetTrades(startTime time.Time, limit int) ([]TradeReco
// - signChanged with position flip: split into close + open // - signChanged with position flip: split into close + open
const EPSILON = 0.0001 const EPSILON = 0.0001
tradeTime := time.UnixMilli(lt.Timestamp) tradeTime := time.UnixMilli(lt.Timestamp).UTC()
// Calculate position after trade // Calculate position after trade
var posAfter float64 var posAfter float64
@@ -628,7 +628,7 @@ func (t *LighterTraderV2) GetTrades(startTime time.Time, limit int) ([]TradeReco
Quantity: qty, Quantity: qty,
RealizedPnL: 0, // Not available in API RealizedPnL: 0, // Not available in API
Fee: fee, Fee: fee,
Time: time.UnixMilli(lt.Timestamp), Time: time.UnixMilli(lt.Timestamp).UTC(),
} }
result = append(result, trade) result = append(result, trade)
} }

View File

@@ -133,7 +133,7 @@ func (t *OKXTrader) GetTrades(startTime time.Time, limit int) ([]OKXTrade, error
FillQtyBase: fillQtyBase, FillQtyBase: fillQtyBase,
Fee: -fee, // OKX returns negative fee Fee: -fee, // OKX returns negative fee
FeeAsset: fill.FeeCcy, FeeAsset: fill.FeeCcy,
ExecTime: time.UnixMilli(ts), ExecTime: time.UnixMilli(ts).UTC(),
IsMaker: fill.ExecType == "M", IsMaker: fill.ExecType == "M",
OrderType: "MARKET", OrderType: "MARKET",
OrderAction: orderAction, OrderAction: orderAction,
@@ -197,7 +197,8 @@ func (t *OKXTrader) SyncOrdersFromOKX(traderID string, exchangeID string, exchan
// Normalize side for storage // Normalize side for storage
side := strings.ToUpper(trade.Side) side := strings.ToUpper(trade.Side)
// Create order record // Create order record - use UTC time to avoid timezone issues
execTimeUTC := trade.ExecTime.UTC()
orderRecord := &store.TraderOrder{ orderRecord := &store.TraderOrder{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -214,9 +215,9 @@ func (t *OKXTrader) SyncOrdersFromOKX(traderID string, exchangeID string, exchan
FilledQuantity: trade.FillQtyBase, FilledQuantity: trade.FillQtyBase,
AvgFillPrice: trade.FillPrice, AvgFillPrice: trade.FillPrice,
Commission: trade.Fee, Commission: trade.Fee,
FilledAt: trade.ExecTime, FilledAt: execTimeUTC,
CreatedAt: trade.ExecTime, CreatedAt: execTimeUTC,
UpdatedAt: trade.ExecTime, UpdatedAt: execTimeUTC,
} }
// Insert order record // Insert order record
@@ -225,7 +226,7 @@ func (t *OKXTrader) SyncOrdersFromOKX(traderID string, exchangeID string, exchan
continue continue
} }
// Create fill record // Create fill record - use UTC time
fillRecord := &store.TraderFill{ fillRecord := &store.TraderFill{
TraderID: traderID, TraderID: traderID,
ExchangeID: exchangeID, // UUID ExchangeID: exchangeID, // UUID
@@ -242,7 +243,7 @@ func (t *OKXTrader) SyncOrdersFromOKX(traderID string, exchangeID string, exchan
CommissionAsset: trade.FeeAsset, CommissionAsset: trade.FeeAsset,
RealizedPnL: 0, // OKX fills don't include PnL per trade RealizedPnL: 0, // OKX fills don't include PnL per trade
IsMaker: trade.IsMaker, IsMaker: trade.IsMaker,
CreatedAt: trade.ExecTime, CreatedAt: execTimeUTC,
} }
if err := orderStore.CreateFill(fillRecord); err != nil { if err := orderStore.CreateFill(fillRecord); err != nil {

View File

@@ -1366,8 +1366,8 @@ func (t *OKXTrader) GetClosedPnL(startTime time.Time, limit int) ([]ClosedPnLRec
// Times // Times
cTime, _ := strconv.ParseInt(pos.CTime, 10, 64) cTime, _ := strconv.ParseInt(pos.CTime, 10, 64)
uTime, _ := strconv.ParseInt(pos.UTime, 10, 64) uTime, _ := strconv.ParseInt(pos.UTime, 10, 64)
record.EntryTime = time.UnixMilli(cTime) record.EntryTime = time.UnixMilli(cTime).UTC()
record.ExitTime = time.UnixMilli(uTime) record.ExitTime = time.UnixMilli(uTime).UTC()
// Close type // Close type
switch pos.Type { switch pos.Type {

View File

@@ -1,7 +1,6 @@
import { useEffect, useState, useRef } from 'react' import { useEffect, useState } from 'react'
import { motion, AnimatePresence } from 'framer-motion' import { motion, AnimatePresence } from 'framer-motion'
// Force HMR Update - Reliability Fix v3 (Emergency Recovery) import useSWR from 'swr'
import useSWR, { mutate } from 'swr'
import { api } from './lib/api' import { api } from './lib/api'
import { TraderDashboardPage } from './pages/TraderDashboardPage' import { TraderDashboardPage } from './pages/TraderDashboardPage'
@@ -20,13 +19,11 @@ import HeaderBar from './components/HeaderBar'
import { LanguageProvider, useLanguage } from './contexts/LanguageContext' import { LanguageProvider, useLanguage } from './contexts/LanguageContext'
import { AuthProvider, useAuth } from './contexts/AuthContext' import { AuthProvider, useAuth } from './contexts/AuthContext'
import { ConfirmDialogProvider } from './components/ConfirmDialog' import { ConfirmDialogProvider } from './components/ConfirmDialog'
import { t, type Language } from './i18n/translations' import { t } from './i18n/translations'
import { confirmToast, notify } from './lib/notify'
import { useSystemConfig } from './hooks/useSystemConfig' import { useSystemConfig } from './hooks/useSystemConfig'
import { OFFICIAL_LINKS } from './constants/branding' import { OFFICIAL_LINKS } from './constants/branding'
import { BacktestPage } from './components/BacktestPage' import { BacktestPage } from './components/BacktestPage'
import { LogOut, Loader2 } from 'lucide-react'
import type { import type {
SystemStatus, SystemStatus,
AccountInfo, AccountInfo,

View File

@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import { motion } from 'framer-motion'
interface DeepVoidBackgroundProps extends React.HTMLAttributes<HTMLDivElement> { interface DeepVoidBackgroundProps extends React.HTMLAttributes<HTMLDivElement> {
children?: React.ReactNode children?: React.ReactNode

View File

@@ -1,5 +1,4 @@
import { useEffect, useState, useRef } from 'react' import { useEffect, useState, useRef } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import { mutate } from 'swr' import { mutate } from 'swr'
import { api } from '../lib/api' import { api } from '../lib/api'
import { ChartTabs } from '../components/ChartTabs' import { ChartTabs } from '../components/ChartTabs'