package main import ( "context" "fmt" "log/slog" "os" "os/signal" "syscall" "time" "github.com/jackc/pgx/v5/pgxpool" workerbacktest "git.toki-labs.com/toki/alt/services/worker/internal/backtest" "git.toki-labs.com/toki/alt/services/worker/internal/config" "git.toki-labs.com/toki/alt/services/worker/internal/jobs" "git.toki-labs.com/toki/alt/services/worker/internal/livetrading" "git.toki-labs.com/toki/alt/services/worker/internal/marketdata/importer" "git.toki-labs.com/toki/alt/services/worker/internal/papertrading" "git.toki-labs.com/toki/alt/services/worker/internal/providers/kis" "git.toki-labs.com/toki/alt/services/worker/internal/socket" "git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres" ) func main() { ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() cfg := config.Load() runner := jobs.NewRunner() jobs.RegisterBuiltins(runner) // Store-backed socket surfaces are optional at process startup. If the pool // cannot be constructed the worker still serves hello, while market/backtest // handlers report unavailable instead of panicking. var deps socket.Deps pool, err := pgxpool.New(ctx, cfg.DatabaseURL) if err != nil { slog.Error("failed to create database pool; store-backed socket surfaces disabled", "error", err) } else { defer pool.Close() deps = storeBackedDeps(runner, postgres.NewStore(pool)) } server := socket.NewServer(cfg, deps) if err := server.Start(ctx); err != nil { slog.Error("failed to start worker socket server", "error", err) os.Exit(1) } slog.Info("worker ready", "redis_key_prefix", cfg.RedisKeyPrefix, "worker_queue", cfg.WorkerQueue, "handlers", runner.Len(), "backtest_read_surface", deps.Analysis != nil, "backtest_start_surface", deps.Starter != nil, "market_read_surface", deps.Instruments != nil && deps.Bars != nil, "market_import_surface", deps.DailyBarImporter != nil, "live_audit_surface", deps.Live != nil, "addr", fmt.Sprintf("%s:%d%s", cfg.Host, cfg.Port, cfg.SocketPath), ) <-ctx.Done() if err := server.Stop(); err != nil { slog.Error("failed to stop worker socket server", "error", err) os.Exit(1) } } // storeBackedDeps wires every store-backed socket dependency around a single // store, including the backtest execution rail. It is factored out of main so a // unit test can assert the backtest start/read surfaces are wired without a live // database. Backtest execution reads imported daily bars from storage and runs // the bundled strategy resolver; the starter records the pending run and hands // execution to the same runner the import jobs use, keeping a single async job // rail rather than a second execution engine. func storeBackedDeps(runner *jobs.Runner, store *postgres.Store) socket.Deps { kisProvider := kis.NewLiveProvider(kis.NewClient(kis.ConfigFromEnv())) // One importer instance backs both the async job handler and the socket // import command so the operator-facing command and scheduled jobs share the // same provider/store wiring. kisImporter := importer.New(kisProvider, store) jobs.RegisterDailyBarImportHandler(runner, kis.Capability(), kisImporter) barSource := workerbacktest.NewStorageBarSource(store, store) strategyPort := workerbacktest.NewBuiltInStrategyPort() engine := workerbacktest.NewEngine(barSource, strategyPort, store) jobs.RegisterRunBacktestHandler(runner, store, engine, time.Now) starter := jobs.NewBacktestStarter(runner, store) // Paper trading reuses the same imported-bar source and bundled strategy // resolver as backtest, but runs synchronously into a process-local // in-memory state keyed by account id (no durable store for readiness). paperEngine := papertrading.NewEngine(barSource, strategyPort) paperService := papertrading.NewService(paperEngine, time.Now) // Live trading service: nil broker means live order operations are unavailable // until a broker adapter is wired. The audit store is always injected so the // audit query surface is available as soon as the database pool is up. liveSvc := livetrading.NewService(nil) liveSvc.SetAuditStore(store) return socket.Deps{ Starter: starter, Analysis: store, Results: store, Instruments: store, Bars: store, DailyBarImporter: kisImporter, Paper: paperService, Live: liveSvc, } }