/** * @fileoverview Trading Domain - IG Spread Betting Execution * Clean separation: Live trading != Portfolio holdings * Purpose: Execute spread bets via IG Trading API */ import { z } from 'zod'; import { Timeframe } from './market.js'; import { GitHubUsername } from './portfolio.js'; // Trading-specific primitives const UUID = z.string().uuid(); const ISODateTime = z.string().datetime({ offset: true }); const PositiveNumber = z.number().positive(); const NonNegativeNumber = z.number().nonnegative(); // Trading direction for spread betting export const Direction = z.enum(['BUY', 'SELL']); // Position size for spread betting export const PositionSize = z.number().positive(); // IG Trading Position (Spread Betting) - CLEAN & FOCUSED export const TradingPosition = z.object({ // Core identification id: UUID, owner: GitHubUsername, // GitHub username ig_position_id: z.string(), // IG's position ID // Position details symbol: z.string(), // EURUSD, GBPUSD, XAUUSD, etc. direction: Direction, // BUY or SELL size: PositiveNumber, // Position size (£ per point) // Price levels open_price: PositiveNumber, // Entry price current_price: PositiveNumber.optional(), // Current market price stop_loss: PositiveNumber.optional(), // Stop loss level take_profit: PositiveNumber.optional(), // Take profit level // P&L tracking unrealized_pnl: z.number().optional(), // Current P&L (can be negative) realized_pnl: z.number().optional(), // Final P&L when closed // Timing opened_at: ISODateTime, closed_at: ISODateTime.optional(), updated_at: ISODateTime, // Last update timestamp // Status status: z.enum(['open', 'closed', 'pending']).default('open') }).strict(); // IG Trade Execution (Individual trades) export const Trade = z.object({ id: UUID, owner: GitHubUsername, position_id: UUID.optional(), // Links to TradingPosition ig_deal_id: z.string(), // IG's deal reference // Trade action and details action: z.enum(['OPEN', 'CLOSE', 'MODIFY']), // Trade action type symbol: z.string(), direction: Direction, size: PositiveNumber, price: PositiveNumber, // Signal attribution (links to ML) signal_id: z.string().optional(), // Signal reference (can be string ID) model_id: z.string().optional(), // Which model generated the signal // Execution details executed_at: ISODateTime, pnl: z.number().optional(), // Trade P&L (can be negative) fees: z.number().optional(), // Trading fees // Status status: z.enum(['executed', 'pending', 'rejected', 'cancelled']) }).strict(); // Enhanced Trading Account (Infrastructure Migration: 20250916000004) export const TradingAccount = z.object({ id: UUID, owner: GitHubUsername, ig_account_id: z.string(), // IG provider ID // Account details (Enhanced) account_name: z.string(), // Display name account_type: z.enum(['SIPP', 'ISA', 'GIA', 'CFD', 'SPREAD_BETTING', 'DEMO', 'LIVE']), // Enhanced types currency: z.string().default('GBP'), // Balance tracking (Infrastructure aligned) balance: z.number(), // Current balance (can be negative) available_funds: NonNegativeNumber, // Available for trading margin_used: NonNegativeNumber, // Currently used margin // Status and sync tracking status: z.enum(['active', 'inactive', 'suspended']).default('active'), last_synced: ISODateTime, // Last sync timestamp created_at: ISODateTime, updated_at: ISODateTime }).strict(); // Request schemas for trading operations export const OpenPositionRequest = z.object({ symbol: z.string(), direction: Direction, size: PositiveNumber, stop_loss: PositiveNumber.optional(), take_profit: PositiveNumber.optional(), signal_id: UUID.optional() // Link to triggering signal }).strict(); export const ClosePositionRequest = z.object({ ig_position_id: z.string(), size: PositiveNumber.optional() // Partial close if specified }).strict(); // Response schemas export const TradingPositionResponse = z.object({ success: z.literal(true), data: TradingPosition, requestId: z.string(), timestamp: ISODateTime }).strict(); export const TradeResponse = z.object({ success: z.literal(true), data: Trade, requestId: z.string(), timestamp: ISODateTime }).strict(); // New envelope types for missing routes export const TradingAccountsResponse = z.object({ success: z.literal(true), data: z.object({ accounts: z.array(TradingAccount) }).strict(), requestId: z.string(), timestamp: ISODateTime }).strict(); export const TradesOpenResponse = z.object({ success: z.literal(true), data: z.object({ positions: z.array(TradingPosition.extend({ status: z.literal('open') })) }).strict(), requestId: z.string(), timestamp: ISODateTime }).strict(); export const TradesClosedResponse = z.object({ success: z.literal(true), data: z.object({ positions: z.array(TradingPosition.extend({ status: z.literal('closed'), closed_at: ISODateTime })) }).strict(), requestId: z.string(), timestamp: ISODateTime }).strict(); export const TradingHistoryResponse = z.object({ success: z.literal(true), data: z.object({ trades: z.array(Trade), total_count: z.number().int().nonnegative(), page: z.number().int().nonnegative().optional(), limit: z.number().int().positive().optional() }).strict(), requestId: z.string(), timestamp: ISODateTime }).strict(); export const OrdersResponse = z.object({ success: z.literal(true), data: z.object({ orders: z.array(z.object({ id: UUID, owner: GitHubUsername, symbol: z.string(), direction: Direction, size: PositiveNumber, order_type: z.enum(['market', 'limit', 'stop']), price: PositiveNumber.optional(), status: z.enum(['pending', 'filled', 'cancelled', 'rejected']), created_at: ISODateTime, updated_at: ISODateTime }).strict()) }).strict(), requestId: z.string(), timestamp: ISODateTime }).strict(); // Type exports export type DirectionType = z.infer; export type PositionSizeType = z.infer; export type TradingPositionType = z.infer; export type TradeType = z.infer; export type TradingAccountType = z.infer; export type OpenPositionRequestType = z.infer; export type ClosePositionRequestType = z.infer; export type TradingPositionResponseType = z.infer; export type TradeResponseType = z.infer; export type TradingAccountsResponseType = z.infer; export type TradesOpenResponseType = z.infer; export type TradesClosedResponseType = z.infer; export type TradingHistoryResponseType = z.infer; export type OrdersResponseType = z.infer;