first commit of restructured project
This commit is contained in:
151
trahn-trade-backend/cmd/server/main.go
Normal file
151
trahn-trade-backend/cmd/server/main.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/kjannette/trahn-backend/internal/api"
|
||||
"github.com/kjannette/trahn-backend/internal/bot"
|
||||
"github.com/kjannette/trahn-backend/internal/config"
|
||||
"github.com/kjannette/trahn-backend/internal/db"
|
||||
"github.com/kjannette/trahn-backend/internal/external"
|
||||
"github.com/kjannette/trahn-backend/internal/notifications"
|
||||
"github.com/kjannette/trahn-backend/internal/repository"
|
||||
"github.com/kjannette/trahn-backend/internal/scheduler"
|
||||
)
|
||||
|
||||
const banner = `
|
||||
╔══════════════════════════════════════╗
|
||||
║ TRAHN Grid Trading Bot v0.2 ║
|
||||
║ ║
|
||||
╚══════════════════════════════════════╝
|
||||
`
|
||||
|
||||
const apiPort = 3001
|
||||
|
||||
func main() {
|
||||
fmt.Print(banner)
|
||||
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "config load error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := cfg.Validate(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cfg.Print()
|
||||
|
||||
// Database
|
||||
fmt.Printf("\n[DB] Connecting to %s:%d/%s ...\n", cfg.DBHost, cfg.DBPort, cfg.DBName)
|
||||
pool, err := db.Connect(cfg.DSN())
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[DB] Connection failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer func() {
|
||||
pool.Close()
|
||||
fmt.Println("[DB] Connection pool closed")
|
||||
}()
|
||||
|
||||
if err := db.TestConnection(pool); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[DB] Test query failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Repos
|
||||
priceRepo := repository.NewPriceRepo(pool)
|
||||
tradeRepo := repository.NewTradeRepo(pool)
|
||||
srRepo := repository.NewSRRepo(pool)
|
||||
gridRepo := repository.NewGridStateRepo(pool)
|
||||
|
||||
// Shared Dune client (single instance for bot + scheduler)
|
||||
var dune *external.DuneClient
|
||||
if cfg.DuneAPIKey != "" {
|
||||
dune = external.NewDuneClient(cfg.DuneAPIKey, external.DuneOptions{
|
||||
Method: cfg.SRMethod,
|
||||
LookbackDays: cfg.SRLookbackDays,
|
||||
RefreshHours: cfg.SRRefreshHours,
|
||||
})
|
||||
|
||||
// Warm cache from DB if a recent S/R record exists
|
||||
if latest, err := srRepo.GetLatest(context.Background()); err == nil && latest != nil {
|
||||
dune.SeedCache(&external.SRResult{
|
||||
Support: latest.Support,
|
||||
Resistance: latest.Resistance,
|
||||
Midpoint: latest.Midpoint,
|
||||
Method: latest.Method,
|
||||
LookbackDays: latest.LookbackDays,
|
||||
FetchedAt: latest.Timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Notifications
|
||||
notify := notifications.NewSender(cfg.WebhookURL, cfg.BotName)
|
||||
|
||||
// Graceful shutdown context
|
||||
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
// 1. API server
|
||||
srv := api.NewServer(pool, apiPort, cfg.APIKey, cfg.CORSAllowOrigin)
|
||||
go func() {
|
||||
if err := srv.Start(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
fmt.Fprintf(os.Stderr, "[API] Server error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
|
||||
// 2. Grid bot (shares the Dune client)
|
||||
botService := bot.NewService()
|
||||
if err := botService.Start(ctx, cfg, priceRepo, tradeRepo, gridRepo, notify, dune); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[BOT] Start failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// 3. S/R Scheduler (shares the same Dune client)
|
||||
var srSched *scheduler.SRScheduler
|
||||
if dune != nil {
|
||||
srSched = scheduler.NewSRScheduler(dune, srRepo, scheduler.SRSchedulerConfig{
|
||||
CronInterval: 1 * time.Hour,
|
||||
SRChangeThreshold: 5,
|
||||
GetBotState: botService.BotState,
|
||||
OnGridRecalculate: func(sr *external.SRResult) {
|
||||
botService.InitializeGrid(ctx)
|
||||
},
|
||||
})
|
||||
srSched.Start()
|
||||
} else {
|
||||
fmt.Println("[SCHEDULER] Skipped - no Dune API key configured")
|
||||
}
|
||||
|
||||
fmt.Println("\nAll services started successfully")
|
||||
|
||||
// Wait for shutdown signal
|
||||
<-ctx.Done()
|
||||
fmt.Println("\nShutting down gracefully...")
|
||||
|
||||
if srSched != nil {
|
||||
srSched.Stop()
|
||||
}
|
||||
|
||||
botService.Stop()
|
||||
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
if err := srv.Shutdown(shutdownCtx); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "[API] Shutdown error: %v\n", err)
|
||||
}
|
||||
fmt.Println("[API] Server closed")
|
||||
fmt.Println("Shutdown complete")
|
||||
}
|
||||
Reference in New Issue
Block a user