import type { Command } from "commander"; /** * OpenFinClaw — Unified financial tools plugin. * Features: * - Strategy tools: publish, validate, fork, leaderboard * - Market data tools: price, K-line, crypto data, compare, search * - SQLite persistence: all tool executions logged; domain tables for strategies and backtests * - Dashboard: embedded HTTP server at http://127.0.0.1: (default 18792) * Supports FEP v2.0 protocol for strategy packages. */ import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; import { registerStrategyCli } from "./src/cli.js"; import { resolvePluginConfig } from "./src/config.js"; import { registerDatahubTools } from "./src/datahub/tools.js"; import { closeDb, getDb } from "./src/db/db.js"; import { createOpenFinclawGatewayProxy } from "./src/http/gateway-proxy.js"; import { startHttpServer } from "./src/http/server.js"; import { OPENFINCLAW_AGENT_GUIDANCE } from "./src/prompt-guidance.js"; import { setupOpenfinclawCronJobs } from "./src/scheduler/cron-setup.js"; import { AggregatedNewsProvider, createNewsProviders } from "./src/scheduler/news-provider.js"; import { registerSchedulerTools } from "./src/scheduler/tools.js"; import { registerStrategyTools } from "./src/strategy/tools.js"; import { setupTournamentCronJob } from "./src/tournament/cron-setup.js"; import { TournamentDb } from "./src/tournament/db.js"; import { buildOrchestratorPrompt } from "./src/tournament/prompts.js"; import { registerTournamentTools } from "./src/tournament/tools.js"; export default definePluginEntry({ id: "openfinclaw-strategy", name: "OpenFinClaw", description: "Unified financial tools: strategy publishing/fork/validation, market data (price/K-line/crypto/compare/search). Single API key for Hub and DataHub.", register(api) { const config = resolvePluginConfig(api); // Register DataHub market data tools (fin_price, fin_kline, fin_crypto, fin_compare, fin_slim_search) // DB init is lazy — getDb is called at tool execution time, not here. registerDatahubTools(api, config, getDb); // Register strategy tools (skill_publish, skill_validate, skill_fork, skill_leaderboard, etc.) registerStrategyTools(api, config, getDb); // Register scheduler tools (strategy_daily_scan, strategy_price_monitor, strategy_scan_history, strategy_periodic_report) const newsProviders = createNewsProviders(config); const newsProvider = new AggregatedNewsProvider(newsProviders); registerSchedulerTools(api, config, getDb, newsProvider); // Register CLI commands api.registerCli( ({ program }: { program: Command }) => registerStrategyCli({ program, config, logger: api.logger, getDb, }), { commands: ["strategy"] }, ); // Start embedded dashboard HTTP server (loopback only, configurable port). // startHttpServer() closes any previous server from a prior hot-reload // before binding, so EADDRINUSE is avoided without a try/catch here. startHttpServer(config.httpPort, api.logger); // Same-origin proxy for Control UI (avoids CSP connect-src to loopback) // auth: "plugin" — no gateway auth required; upstream is loopback-only (127.0.0.1) api.registerHttpRoute({ path: "/plugins/openfinclaw", match: "prefix", auth: "plugin", handler: createOpenFinclawGatewayProxy({ port: config.httpPort, logger: api.logger }), }); // Register tournament tools (tournament_pick, tournament_leaderboard, tournament_result) const getTournamentDb = () => new TournamentDb(getDb()); registerTournamentTools(api.registerTool.bind(api), getTournamentDb); // Inject agent system prompt: prioritise tool calls so data lands in SQLite const tournamentPrompt = buildOrchestratorPrompt(); api.on("before_prompt_build", async () => ({ prependSystemContext: `${OPENFINCLAW_AGENT_GUIDANCE}\n\n${tournamentPrompt}`, })); // Graceful shutdown: close SQLite connection process.once("beforeExit", () => { closeDb(); }); // ── Gateway Cron registration ── // Write cron jobs directly to ~/.openclaw/cron/jobs.json during register(). // This ensures jobs are available immediately on both gateway startup AND // hot-reload (plugin install without restart). The CronService picks up // file changes on its next tick. if (config.schedulerEnabled) { setupOpenfinclawCronJobs(config) .then((result) => { if (result.created > 0 || result.migrated > 0) { api.logger.info( `[OpenFinClaw] Cron jobs: ${result.created} created, ${result.migrated} migrated, ${result.existing} existing`, ); } }) .catch((err) => { api.logger.info( `[OpenFinClaw] Cron setup failed: ${err instanceof Error ? err.message : String(err)}`, ); }); // Register tournament cron job setupTournamentCronJob() .then((result) => { if (result.created) { api.logger.info("[OpenFinClaw] Tournament cron job registered"); } }) .catch((err) => { api.logger.info( `[OpenFinClaw] Tournament cron setup failed: ${err instanceof Error ? err.message : String(err)}`, ); }); } }, });