/** * @fileoverview ML Domain - Signals & Consensus for Live Trading * Clean separation: ML signals != Portfolio holdings * Purpose: Generate and store ML trading signals for spread betting */ import { z } from 'zod'; import { Timeframe } from './market.js'; // Core primitives for ML domain export const Decision = z.enum(["BUY","SELL","HOLD"]); export type DecisionType = z.infer; // Enhanced ML Signal (Infrastructure Migration: 20250910000001) export const Signal = z.object({ // Core signal data owner: z.string(), // "pacey999" model_id: z.string(), // "baseline-rsi" symbol: z.string(), timeframe: Timeframe, bar_ts: z.string(), // ISO8601 decision: Decision, confidence: z.number().min(0).max(1), dedup_key: z.string(), // Helper builds this // Model metadata model_version: z.string().optional(), features_used: z.array(z.string()).optional(), // Enhanced reproducibility tracking (Infrastructure aligned) bar_id: z.string().uuid().optional(), // Join to OHLCV/TA row (CONFIRMED in DB) ta_fingerprint: z.string().optional(), // Hash of selected indicators (CONFIRMED in DB) // Extended metadata for Infrastructure flexibility meta: z.record(z.any()).optional(), // Database audit fields (Infrastructure requirement) created_at: z.string().datetime().optional(), updated_at: z.string().datetime().optional() }).strict(); // ML Consensus (Aggregated Signals) - CLEAN & FOCUSED export const Consensus = z.object({ // Core identification owner: z.string(), // Market context symbol: z.string(), timeframe: Timeframe, bar_ts: z.string(), // Consensus decision decision: Decision, // Aggregated decision confidence: z.number().min(0).max(1), // Weighted confidence // Contributing models contributors: z.array(z.object({ model_id: z.string(), decision: Decision, confidence: z.number().min(0).max(1), weight: z.number().min(0).max(1) }).strict()).min(1), // Deduplication dedup_key: z.string(), // Timestamps timestamp: z.string(), created_at: z.string().optional() }).strict(); // ML Model Registration (Simple) export const Model = z.object({ id: z.string(), // "baseline-rsi", "sma-crossover" owner: z.string(), name: z.string(), // "Baseline RSI Model" description: z.string().optional(), version: z.string(), // "1.0.0" requires_gpu: z.boolean().default(false), created_at: z.string() }).strict(); // Request schemas export const StoreSignalRequest = z.object({ owner: z.string(), model_id: z.string(), symbol: z.string(), timeframe: Timeframe, bar_ts: z.string(), decision: Decision, confidence: z.number().min(0).max(1), model_version: z.string().optional(), features_used: z.array(z.string()).optional(), dedup_key: z.string() }).strict(); export const StoreConsensusRequest = z.object({ owner: z.string(), symbol: z.string(), timeframe: Timeframe, bar_ts: z.string(), decision: Decision, confidence: z.number().min(0).max(1), contributors: z.array(z.object({ model_id: z.string(), decision: Decision, confidence: z.number().min(0).max(1), weight: z.number().min(0).max(1) }).strict()), dedup_key: z.string() }).strict(); export const RegisterModelRequest = z.object({ id: z.string(), owner: z.string(), name: z.string(), description: z.string().optional(), version: z.string(), requires_gpu: z.boolean().default(false) }).strict(); // Response schemas export const SignalResponse = z.object({ success: z.literal(true), data: Signal, requestId: z.string(), timestamp: z.string() }).strict(); export const SignalsListResponse = z.object({ success: z.literal(true), data: z.object({ signals: z.array(Signal), count: z.number().int().nonnegative(), symbol: z.string(), timeframe: Timeframe }).strict(), requestId: z.string(), timestamp: z.string() }).strict(); // New envelope types for missing routes export const ConsensusLatestResponse = z.object({ success: z.literal(true), data: z.object({ symbol: z.string(), timeframe: Timeframe, consensus: Decision, confidence: z.number().min(0).max(1), indicators: z.record(z.number()).optional(), contributors: z.array(z.object({ model_id: z.string(), decision: Decision, confidence: z.number().min(0).max(1), weight: z.number().min(0).max(1) }).strict()), timestamp: z.string().datetime(), bar_ts: z.string() }).strict(), requestId: z.string(), timestamp: z.string() }).strict(); export const BacktestResponse = z.object({ success: z.literal(true), data: z.object({ model_id: z.string(), symbol: z.string(), timeframe: Timeframe, start_date: z.string(), end_date: z.string(), total_trades: z.number().int().nonnegative(), win_rate: z.number().min(0).max(1), total_pnl: z.number(), max_drawdown: z.number(), sharpe_ratio: z.number().optional(), trades: z.array(z.object({ entry_date: z.string(), exit_date: z.string(), direction: Decision, entry_price: z.number(), exit_price: z.number(), pnl: z.number(), confidence: z.number().min(0).max(1) }).strict()).optional() }).strict(), requestId: z.string(), timestamp: z.string() }).strict(); export const PredictResponse = z.object({ success: z.literal(true), data: z.object({ model_id: z.string(), symbol: z.string(), timeframe: Timeframe, prediction: Decision, confidence: z.number().min(0).max(1), features: z.record(z.number()).optional(), model_version: z.string().optional(), timestamp: z.string().datetime() }).strict(), requestId: z.string(), timestamp: z.string() }).strict(); // Type exports export type SignalType = z.infer; export type ConsensusType = z.infer; export type ModelType = z.infer; export type StoreSignalRequestType = z.infer; export type StoreConsensusRequestType = z.infer; export type RegisterModelRequestType = z.infer; export type SignalResponseType = z.infer; export type SignalsListResponseType = z.infer; export type ConsensusLatestResponseType = z.infer; export type BacktestResponseType = z.infer; export type PredictResponseType = z.infer;