mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-07 03:07:56 +08:00
feat: migrate to CoinAnk API and improve chart UI
- Chart improvements: professional styling, popular symbols quick selection, simplified B/S legend - Data source migration: use CoinAnk API exclusively for all kline data - Code cleanup: remove Binance WebSocket cache and related code (websocket_client.go, combined_streams.go, monitor.go) - Log optimization: reduce hook spam, suppress 404 errors, increase P&L diff threshold - Lighter integration: add order sync functionality, fix market order precision - Remove ticker merge logic for simplicity
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"nofx/store"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var dbPath string
|
||||
var dryRun bool
|
||||
|
||||
flag.StringVar(&dbPath, "db", "./data/data.db", "数据库文件路径")
|
||||
flag.BoolVar(&dryRun, "dry-run", false, "只检查不删除(预览模式)")
|
||||
flag.Parse()
|
||||
|
||||
// 确保数据库文件存在
|
||||
absPath, err := filepath.Abs(dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无效的数据库路径: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||
log.Fatalf("❌ 数据库文件不存在: %s", absPath)
|
||||
}
|
||||
|
||||
fmt.Printf("📂 数据库路径: %s\n", absPath)
|
||||
|
||||
// 打开数据库
|
||||
s, err := store.New(absPath)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无法打开数据库: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
orderStore := s.Order()
|
||||
|
||||
// 1. 检查重复订单数量
|
||||
fmt.Println("\n🔍 检查重复数据...")
|
||||
dupOrders, err := orderStore.GetDuplicateOrdersCount()
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 检查重复订单失败: %v", err)
|
||||
}
|
||||
fmt.Printf(" 📋 重复订单: %d 条\n", dupOrders)
|
||||
|
||||
dupFills, err := orderStore.GetDuplicateFillsCount()
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 检查重复成交失败: %v", err)
|
||||
}
|
||||
fmt.Printf(" 📊 重复成交: %d 条\n", dupFills)
|
||||
|
||||
if dupOrders == 0 && dupFills == 0 {
|
||||
fmt.Println("\n✅ 数据库没有重复记录,无需清理")
|
||||
return
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
fmt.Println("\n⚠️ 预览模式(--dry-run),不会删除数据")
|
||||
fmt.Println(" 运行 'go run scripts/cleanup_duplicates.go' 来执行实际清理")
|
||||
return
|
||||
}
|
||||
|
||||
// 2. 清理重复订单
|
||||
if dupOrders > 0 {
|
||||
fmt.Println("\n🧹 清理重复订单...")
|
||||
deleted, err := orderStore.CleanupDuplicateOrders()
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 清理失败: %v", err)
|
||||
}
|
||||
fmt.Printf(" ✅ 删除了 %d 条重复订单\n", deleted)
|
||||
}
|
||||
|
||||
// 3. 清理重复成交
|
||||
if dupFills > 0 {
|
||||
fmt.Println("\n🧹 清理重复成交...")
|
||||
deleted, err := orderStore.CleanupDuplicateFills()
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 清理失败: %v", err)
|
||||
}
|
||||
fmt.Printf(" ✅ 删除了 %d 条重复成交\n", deleted)
|
||||
}
|
||||
|
||||
// 4. 验证清理结果
|
||||
fmt.Println("\n🔍 验证清理结果...")
|
||||
dupOrdersAfter, _ := orderStore.GetDuplicateOrdersCount()
|
||||
dupFillsAfter, _ := orderStore.GetDuplicateFillsCount()
|
||||
fmt.Printf(" 📋 剩余重复订单: %d 条\n", dupOrdersAfter)
|
||||
fmt.Printf(" 📊 剩余重复成交: %d 条\n", dupFillsAfter)
|
||||
|
||||
if dupOrdersAfter == 0 && dupFillsAfter == 0 {
|
||||
fmt.Println("\n✅ 清理完成!数据库已去重")
|
||||
} else {
|
||||
fmt.Println("\n⚠️ 仍有重复数据,可能需要手动检查")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"nofx/store"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var dbPath string
|
||||
var force bool
|
||||
|
||||
flag.StringVar(&dbPath, "db", "./data/data.db", "数据库文件路径")
|
||||
flag.BoolVar(&force, "force", false, "跳过确认直接删除")
|
||||
flag.Parse()
|
||||
|
||||
// 确保数据库文件存在
|
||||
absPath, err := filepath.Abs(dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无效的数据库路径: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||
log.Fatalf("❌ 数据库文件不存在: %s", absPath)
|
||||
}
|
||||
|
||||
fmt.Printf("📂 数据库路径: %s\n", absPath)
|
||||
|
||||
// 打开数据库
|
||||
s, err := store.New(absPath)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无法打开数据库: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
db := s.DB()
|
||||
|
||||
// 统计当前数据
|
||||
var orderCount, fillCount int
|
||||
db.QueryRow(`SELECT COUNT(*) FROM trader_orders`).Scan(&orderCount)
|
||||
db.QueryRow(`SELECT COUNT(*) FROM trader_fills`).Scan(&fillCount)
|
||||
|
||||
fmt.Printf("\n📊 当前数据统计:\n")
|
||||
fmt.Printf(" trader_orders: %d 条记录\n", orderCount)
|
||||
fmt.Printf(" trader_fills: %d 条记录\n", fillCount)
|
||||
|
||||
if orderCount == 0 && fillCount == 0 {
|
||||
fmt.Println("\n✅ 表已经是空的,无需清空")
|
||||
return
|
||||
}
|
||||
|
||||
// 确认删除
|
||||
if !force {
|
||||
fmt.Println("\n⚠️ 警告: 此操作将删除所有订单和成交记录,无法恢复!")
|
||||
fmt.Print("\n确认删除?请输入 'yes' 继续: ")
|
||||
|
||||
reader := bufio.NewReader(os.Stdin)
|
||||
input, _ := reader.ReadString('\n')
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
if input != "yes" {
|
||||
fmt.Println("\n❌ 操作已取消")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n🗑️ 开始清空表...")
|
||||
|
||||
// 清空 trader_fills 表(先删除,因为有外键约束)
|
||||
result, err := db.Exec(`DELETE FROM trader_fills`)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 清空 trader_fills 失败: %v", err)
|
||||
}
|
||||
fillsDeleted, _ := result.RowsAffected()
|
||||
fmt.Printf(" ✅ 删除了 %d 条成交记录\n", fillsDeleted)
|
||||
|
||||
// 清空 trader_orders 表
|
||||
result, err = db.Exec(`DELETE FROM trader_orders`)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 清空 trader_orders 失败: %v", err)
|
||||
}
|
||||
ordersDeleted, _ := result.RowsAffected()
|
||||
fmt.Printf(" ✅ 删除了 %d 条订单记录\n", ordersDeleted)
|
||||
|
||||
// 重置自增ID(可选,让ID从1重新开始)
|
||||
_, err = db.Exec(`DELETE FROM sqlite_sequence WHERE name IN ('trader_orders', 'trader_fills')`)
|
||||
if err == nil {
|
||||
fmt.Println(" ✅ 重置了自增ID计数器")
|
||||
}
|
||||
|
||||
// 验证清空结果
|
||||
db.QueryRow(`SELECT COUNT(*) FROM trader_orders`).Scan(&orderCount)
|
||||
db.QueryRow(`SELECT COUNT(*) FROM trader_fills`).Scan(&fillCount)
|
||||
|
||||
fmt.Printf("\n🔍 验证结果:\n")
|
||||
fmt.Printf(" trader_orders: %d 条记录\n", orderCount)
|
||||
fmt.Printf(" trader_fills: %d 条记录\n", fillCount)
|
||||
|
||||
if orderCount == 0 && fillCount == 0 {
|
||||
fmt.Println("\n✅ 表已成功清空!")
|
||||
fmt.Println("\n💡 现在可以重新运行 trader 进行测试")
|
||||
fmt.Println(" 新的订单将从 ID=1 开始记录")
|
||||
} else {
|
||||
fmt.Println("\n⚠️ 清空未完成,请检查数据库")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"nofx/store"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var dbPath string
|
||||
var traderID string
|
||||
|
||||
flag.StringVar(&dbPath, "db", "./data/data.db", "数据库文件路径")
|
||||
flag.StringVar(&traderID, "trader", "", "Trader ID(可选)")
|
||||
flag.Parse()
|
||||
|
||||
// 确保数据库文件存在
|
||||
absPath, err := filepath.Abs(dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无效的数据库路径: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||
log.Fatalf("❌ 数据库文件不存在: %s", absPath)
|
||||
}
|
||||
|
||||
fmt.Printf("📂 数据库路径: %s\n", absPath)
|
||||
|
||||
// 打开数据库
|
||||
s, err := store.New(absPath)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无法打开数据库: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
orderStore := s.Order()
|
||||
|
||||
// 如果指定了 traderID,获取该 trader 的订单
|
||||
if traderID == "" {
|
||||
fmt.Println("\n⚠️ 未指定 trader_id,使用: --trader <trader_id>")
|
||||
fmt.Println(" 获取所有 trader 的统计信息...\n")
|
||||
}
|
||||
|
||||
// 获取订单列表
|
||||
orders, err := orderStore.GetTraderOrders(traderID, 100)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 获取订单失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf("\n📋 找到 %d 条订单记录\n\n", len(orders))
|
||||
|
||||
if len(orders) == 0 {
|
||||
fmt.Println("⚠️ 没有订单数据!可能的原因:")
|
||||
fmt.Println(" 1. Trader 还没有执行过交易")
|
||||
fmt.Println(" 2. CreateOrder 插入失败(重复键冲突)")
|
||||
fmt.Println(" 3. 指定的 trader_id 不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// 统计数据
|
||||
var (
|
||||
totalOrders = len(orders)
|
||||
filledOrders = 0
|
||||
withFilledAt = 0
|
||||
withAvgFillPrice = 0
|
||||
withOrderAction = 0
|
||||
missingFilledAt = 0
|
||||
missingAvgPrice = 0
|
||||
missingOrderAction = 0
|
||||
)
|
||||
|
||||
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
fmt.Printf("%-15s %-10s %-10s %-15s %-10s %-15s\n", "订单ID", "状态", "动作", "平均成交价", "成交时间", "问题")
|
||||
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
|
||||
for _, order := range orders {
|
||||
issues := []string{}
|
||||
|
||||
if order.Status == "FILLED" {
|
||||
filledOrders++
|
||||
|
||||
// 检查 filled_at
|
||||
if !order.FilledAt.IsZero() {
|
||||
withFilledAt++
|
||||
} else {
|
||||
missingFilledAt++
|
||||
issues = append(issues, "❌ 缺少成交时间")
|
||||
}
|
||||
|
||||
// 检查 avg_fill_price
|
||||
if order.AvgFillPrice > 0 {
|
||||
withAvgFillPrice++
|
||||
} else {
|
||||
missingAvgPrice++
|
||||
issues = append(issues, "❌ 成交价为0")
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 order_action
|
||||
if order.OrderAction != "" {
|
||||
withOrderAction++
|
||||
} else {
|
||||
missingOrderAction++
|
||||
issues = append(issues, "⚠️ 缺少订单动作")
|
||||
}
|
||||
|
||||
issueStr := "✅ 正常"
|
||||
if len(issues) > 0 {
|
||||
issueStr = ""
|
||||
for i, issue := range issues {
|
||||
if i > 0 {
|
||||
issueStr += ", "
|
||||
}
|
||||
issueStr += issue
|
||||
}
|
||||
}
|
||||
|
||||
filledAtStr := "N/A"
|
||||
if !order.FilledAt.IsZero() {
|
||||
filledAtStr = order.FilledAt.Format("01-02 15:04")
|
||||
}
|
||||
|
||||
fmt.Printf("%-15s %-10s %-10s %-15.2f %-10s %s\n",
|
||||
order.ExchangeOrderID[:min(15, len(order.ExchangeOrderID))],
|
||||
order.Status,
|
||||
order.OrderAction,
|
||||
order.AvgFillPrice,
|
||||
filledAtStr,
|
||||
issueStr,
|
||||
)
|
||||
}
|
||||
|
||||
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
|
||||
// 统计摘要
|
||||
fmt.Printf("\n📊 统计摘要:\n")
|
||||
fmt.Printf(" 总订单数: %d\n", totalOrders)
|
||||
fmt.Printf(" 已成交订单: %d\n", filledOrders)
|
||||
fmt.Printf(" 有成交时间: %d / %d (%.1f%%)\n", withFilledAt, filledOrders, float64(withFilledAt)/float64(max(filledOrders, 1))*100)
|
||||
fmt.Printf(" 有成交价格: %d / %d (%.1f%%)\n", withAvgFillPrice, filledOrders, float64(withAvgFillPrice)/float64(max(filledOrders, 1))*100)
|
||||
fmt.Printf(" 有订单动作: %d / %d (%.1f%%)\n", withOrderAction, totalOrders, float64(withOrderAction)/float64(max(totalOrders, 1))*100)
|
||||
|
||||
fmt.Printf("\n⚠️ 问题订单:\n")
|
||||
if missingFilledAt > 0 {
|
||||
fmt.Printf(" ❌ %d 条订单缺少成交时间 (filled_at)\n", missingFilledAt)
|
||||
}
|
||||
if missingAvgPrice > 0 {
|
||||
fmt.Printf(" ❌ %d 条订单成交价为 0 (avg_fill_price)\n", missingAvgPrice)
|
||||
}
|
||||
if missingOrderAction > 0 {
|
||||
fmt.Printf(" ⚠️ %d 条订单缺少订单动作 (order_action)\n", missingOrderAction)
|
||||
}
|
||||
|
||||
if missingFilledAt > 0 || missingAvgPrice > 0 {
|
||||
fmt.Println("\n💡 这些订单无法在图表上显示,因为:")
|
||||
fmt.Println(" - 缺少成交时间 → 前端无法定位到K线时间轴")
|
||||
fmt.Println(" - 成交价为 0 → 前端会过滤掉 (line 164: if (!orderPrice || orderPrice === 0) return)")
|
||||
fmt.Println("\n🔧 可能的原因:")
|
||||
fmt.Println(" 1. UpdateOrderStatus 没有被正确调用")
|
||||
fmt.Println(" 2. GetOrderStatus 返回的数据缺少 avgPrice 字段")
|
||||
fmt.Println(" 3. Lighter 交易所的订单状态查询有问题")
|
||||
}
|
||||
|
||||
if missingFilledAt == 0 && missingAvgPrice == 0 && missingOrderAction == 0 {
|
||||
fmt.Println("\n✅ 所有订单数据完整!")
|
||||
fmt.Println(" 如果图表仍然没有显示 B/S 标记,检查:")
|
||||
fmt.Println(" 1. 前端是否正确调用了 /api/orders API")
|
||||
fmt.Println(" 2. 浏览器控制台是否有错误")
|
||||
fmt.Println(" 3. 订单时间是否在图表的时间范围内")
|
||||
}
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"nofx/store"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var dbPath string
|
||||
var dryRun bool
|
||||
|
||||
flag.StringVar(&dbPath, "db", "./data/data.db", "数据库文件路径")
|
||||
flag.BoolVar(&dryRun, "dry-run", false, "只检查不修复(预览模式)")
|
||||
flag.Parse()
|
||||
|
||||
// 确保数据库文件存在
|
||||
absPath, err := filepath.Abs(dbPath)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无效的数据库路径: %v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(absPath); os.IsNotExist(err) {
|
||||
log.Fatalf("❌ 数据库文件不存在: %s", absPath)
|
||||
}
|
||||
|
||||
fmt.Printf("📂 数据库路径: %s\n", absPath)
|
||||
|
||||
// 打开数据库
|
||||
s, err := store.New(absPath)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 无法打开数据库: %v", err)
|
||||
}
|
||||
defer s.Close()
|
||||
|
||||
db := s.DB()
|
||||
|
||||
fmt.Println("\n🔍 检查需要修复的订单...")
|
||||
|
||||
// 1. 修复缺少 filled_at 的 FILLED 订单(使用 updated_at 或 created_at)
|
||||
var needFixFilledAt int
|
||||
err = db.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM trader_orders
|
||||
WHERE status = 'FILLED' AND (filled_at IS NULL OR filled_at = '')
|
||||
`).Scan(&needFixFilledAt)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 查询失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf(" 📋 缺少成交时间的订单: %d 条\n", needFixFilledAt)
|
||||
|
||||
// 2. 修复 avg_fill_price = 0 的 FILLED 订单(使用 price 字段)
|
||||
var needFixAvgPrice int
|
||||
err = db.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM trader_orders
|
||||
WHERE status = 'FILLED' AND (avg_fill_price = 0 OR avg_fill_price IS NULL) AND price > 0
|
||||
`).Scan(&needFixAvgPrice)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 查询失败: %v", err)
|
||||
}
|
||||
|
||||
fmt.Printf(" 💰 成交价为0的订单: %d 条\n", needFixAvgPrice)
|
||||
|
||||
if needFixFilledAt == 0 && needFixAvgPrice == 0 {
|
||||
fmt.Println("\n✅ 没有需要修复的订单!")
|
||||
return
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
fmt.Println("\n⚠️ 预览模式(--dry-run),不会修改数据")
|
||||
fmt.Println(" 运行 'go run scripts/fix_order_data.go' 来执行实际修复")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("\n🔧 开始修复...")
|
||||
|
||||
// 修复缺少 filled_at 的订单
|
||||
if needFixFilledAt > 0 {
|
||||
result, err := db.Exec(`
|
||||
UPDATE trader_orders
|
||||
SET filled_at = COALESCE(updated_at, created_at)
|
||||
WHERE status = 'FILLED' AND (filled_at IS NULL OR filled_at = '')
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 修复成交时间失败: %v", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
fmt.Printf(" ✅ 修复了 %d 条订单的成交时间\n", rows)
|
||||
}
|
||||
|
||||
// 修复 avg_fill_price = 0 的订单
|
||||
if needFixAvgPrice > 0 {
|
||||
result, err := db.Exec(`
|
||||
UPDATE trader_orders
|
||||
SET avg_fill_price = price,
|
||||
filled_quantity = quantity
|
||||
WHERE status = 'FILLED'
|
||||
AND (avg_fill_price = 0 OR avg_fill_price IS NULL)
|
||||
AND price > 0
|
||||
`)
|
||||
if err != nil {
|
||||
log.Fatalf("❌ 修复成交价失败: %v", err)
|
||||
}
|
||||
rows, _ := result.RowsAffected()
|
||||
fmt.Printf(" ✅ 修复了 %d 条订单的成交价\n", rows)
|
||||
}
|
||||
|
||||
// 验证修复结果
|
||||
fmt.Println("\n🔍 验证修复结果...")
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
var stillMissingFilledAt int
|
||||
db.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM trader_orders
|
||||
WHERE status = 'FILLED' AND (filled_at IS NULL OR filled_at = '')
|
||||
`).Scan(&stillMissingFilledAt)
|
||||
|
||||
var stillMissingAvgPrice int
|
||||
db.QueryRow(`
|
||||
SELECT COUNT(*)
|
||||
FROM trader_orders
|
||||
WHERE status = 'FILLED' AND (avg_fill_price = 0 OR avg_fill_price IS NULL)
|
||||
`).Scan(&stillMissingAvgPrice)
|
||||
|
||||
fmt.Printf(" 📋 仍缺少成交时间: %d 条\n", stillMissingFilledAt)
|
||||
fmt.Printf(" 💰 仍缺少成交价: %d 条\n", stillMissingAvgPrice)
|
||||
|
||||
if stillMissingFilledAt == 0 && stillMissingAvgPrice == 0 {
|
||||
fmt.Println("\n✅ 修复完成!所有订单数据已完整")
|
||||
fmt.Println("\n💡 现在刷新图表页面,应该能看到 B/S 标记了")
|
||||
} else {
|
||||
fmt.Println("\n⚠️ 仍有部分订单无法修复,可能需要手动检查")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "=================================="
|
||||
echo "NOFX 后端重启和测试脚本"
|
||||
echo "=================================="
|
||||
|
||||
# 1. 停止旧进程
|
||||
echo ""
|
||||
echo "1️⃣ 停止旧进程..."
|
||||
pkill -f "bin/nofx" || echo " 没有运行中的进程"
|
||||
sleep 2
|
||||
|
||||
# 2. 清理旧数据
|
||||
echo ""
|
||||
echo "2️⃣ 清理测试数据..."
|
||||
sqlite3 data/data.db "DELETE FROM trader_fills; DELETE FROM trader_orders;"
|
||||
echo " ✅ trader_orders 和 trader_fills 表已清空"
|
||||
|
||||
# 3. 验证数据库已清空
|
||||
ORDERS_COUNT=$(sqlite3 data/data.db "SELECT COUNT(*) FROM trader_orders")
|
||||
FILLS_COUNT=$(sqlite3 data/data.db "SELECT COUNT(*) FROM trader_fills")
|
||||
echo " 验证: trader_orders=$ORDERS_COUNT, trader_fills=$FILLS_COUNT"
|
||||
|
||||
# 4. 启动新进程
|
||||
echo ""
|
||||
echo "3️⃣ 启动新编译的后端服务..."
|
||||
if [ ! -f "bin/nofx" ]; then
|
||||
echo " ❌ bin/nofx 不存在,请先运行 go build -o bin/nofx ."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
nohup ./bin/nofx > data/nofx_$(date +%Y-%m-%d).log 2>&1 &
|
||||
NOFX_PID=$!
|
||||
echo " ✅ 后端已启动 (PID: $NOFX_PID)"
|
||||
|
||||
# 5. 等待服务启动
|
||||
echo ""
|
||||
echo "4️⃣ 等待服务启动..."
|
||||
sleep 3
|
||||
|
||||
# 6. 验证进程运行
|
||||
if ps -p $NOFX_PID > /dev/null; then
|
||||
echo " ✅ 后端进程运行正常 (PID: $NOFX_PID)"
|
||||
else
|
||||
echo " ❌ 后端进程启动失败,请检查日志"
|
||||
tail -20 data/nofx_$(date +%Y-%m-%d).log
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "=================================="
|
||||
echo "✅ 重启完成!"
|
||||
echo "=================================="
|
||||
echo ""
|
||||
echo "📝 下一步操作:"
|
||||
echo " 1. 访问前端页面"
|
||||
echo " 2. 执行一次平仓操作(手动或AI)"
|
||||
echo " 3. 等待 10 秒(让 pollLighterTradeHistory 完成)"
|
||||
echo " 4. 检查数据库:"
|
||||
echo " sqlite3 data/data.db \"SELECT id, status, avg_fill_price, filled_quantity FROM trader_orders\""
|
||||
echo " 5. 刷新图表页面,应该能看到 B/S 标记"
|
||||
echo ""
|
||||
echo "📊 实时日志查看:"
|
||||
echo " tail -f data/nofx_$(date +%Y-%m-%d).log | grep -E 'Order recorded|Found matching trade|Fill recorded'"
|
||||
echo ""
|
||||
Reference in New Issue
Block a user