Major Changes: - Migrated from 3-action TradingAction to 45-action FactoredAction - 45 actions: 5 exposure × 3 order types × 3 urgency levels - Absolute exposure model (target positions -1.0 to +1.0) - Transaction cost differentiation (Market 0.15%, LimitMaker 0.05%, IoC 0.10%) - Fixed action diversity threshold (1.11% → 0.5% for 45-action space) Bug Fixes: - Bug #15: Incomplete FactoredAction integration (code existed but unused) - Bug #16: Runtime crash in action diversity checking (hardcoded 3-action match) Code Changes (13 files, ~464 lines): - ml/src/dqn/action_space.rs: Core FactoredAction + 4 helper methods - ml/src/trainers/dqn.rs: Action diversity refactored (3→45 dynamic) - ml/src/dqn/reward.rs: calculate_reward() signature updated - ml/src/dqn/portfolio_tracker.rs: execute_action() absolute exposure - ml/src/dqn/dqn.rs: WorkingDQN action selection migrated - ml/tests/*.rs: 9 test files updated with FactoredAction assertions Test Results: - 1-epoch smoke test: 100% action diversity (45/45 actions, 80.2s) - 10-epoch production: 87.8% readiness (79/90 scorecard, 14.0 min) - Loss convergence: 96.9% reduction (119K → 3.6K) - Action diversity: 100% → 44% (healthy specialization) - Checkpoint reliability: 12/12 files saved (100%) - DQN tests: 195/195 passing (100%) - ML baseline: 1,514/1,515 passing (99.93%) Production Status: ✅ CERTIFIED (87.8% readiness) Go/No-Go: ✅ GO FOR 100-EPOCH PRODUCTION TRAINING 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
559 lines
28 KiB
PL/PgSQL
559 lines
28 KiB
PL/PgSQL
-- ================================================================================================
|
|
-- Migration 047: Broker Gateway Tables for AMP Futures FIX Integration
|
|
-- Purpose: Database schema for FIX protocol broker integration with session management,
|
|
-- order tracking, execution reports, and position reconciliation
|
|
-- Date: 2025-11-09
|
|
-- Agent: 1 (Broker Gateway Database Schema)
|
|
-- ================================================================================================
|
|
|
|
-- ================================================================================================
|
|
-- TABLE 1: BROKER_SESSIONS
|
|
-- Tracks FIX session state for recovery after disconnects
|
|
-- ================================================================================================
|
|
|
|
CREATE TABLE broker_sessions (
|
|
-- Primary identifier
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- FIX session identifiers
|
|
session_id VARCHAR(128) NOT NULL UNIQUE, -- Composite: "FOXHUNT_CLIENT-CQG"
|
|
sender_comp_id VARCHAR(64) NOT NULL, -- Tag 49 (SenderCompID)
|
|
target_comp_id VARCHAR(64) NOT NULL, -- Tag 56 (TargetCompID)
|
|
|
|
-- FIX sequence numbers (critical for message ordering)
|
|
sender_seq_num BIGINT NOT NULL DEFAULT 1, -- Our outgoing sequence number
|
|
target_seq_num BIGINT NOT NULL DEFAULT 1, -- Expected incoming sequence number
|
|
|
|
-- Session state
|
|
session_state VARCHAR(32) NOT NULL, -- DISCONNECTED, CONNECTED, LOGGING_IN, ACTIVE, LOGGING_OUT, RECONNECTING
|
|
|
|
-- Heartbeat monitoring
|
|
last_heartbeat_sent TIMESTAMPTZ, -- Last heartbeat we sent (Tag 35=0)
|
|
last_heartbeat_received TIMESTAMPTZ, -- Last heartbeat we received
|
|
|
|
-- Connection lifecycle
|
|
connected_at TIMESTAMPTZ, -- TCP connection established
|
|
disconnected_at TIMESTAMPTZ, -- TCP connection closed
|
|
|
|
-- Audit timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Session first created
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Last state update
|
|
|
|
-- Constraints
|
|
CONSTRAINT chk_broker_session_state CHECK (
|
|
session_state IN ('DISCONNECTED', 'CONNECTED', 'LOGGING_IN', 'ACTIVE', 'LOGGING_OUT', 'RECONNECTING')
|
|
),
|
|
CONSTRAINT chk_broker_session_sequences CHECK (
|
|
sender_seq_num >= 1 AND target_seq_num >= 1
|
|
)
|
|
);
|
|
|
|
-- Add table comment
|
|
COMMENT ON TABLE broker_sessions IS 'FIX session state tracking for broker gateway - manages sequence numbers and connection lifecycle';
|
|
|
|
-- Add column comments
|
|
COMMENT ON COLUMN broker_sessions.session_id IS 'Unique FIX session identifier (SenderCompID-TargetCompID)';
|
|
COMMENT ON COLUMN broker_sessions.sender_comp_id IS 'FIX SenderCompID (Tag 49) - our client identifier';
|
|
COMMENT ON COLUMN broker_sessions.target_comp_id IS 'FIX TargetCompID (Tag 56) - broker gateway identifier';
|
|
COMMENT ON COLUMN broker_sessions.sender_seq_num IS 'Outgoing FIX message sequence number (Tag 34) - persisted for recovery';
|
|
COMMENT ON COLUMN broker_sessions.target_seq_num IS 'Expected incoming FIX message sequence number - persisted for gap detection';
|
|
COMMENT ON COLUMN broker_sessions.session_state IS 'Current FIX session state (DISCONNECTED, ACTIVE, etc.)';
|
|
COMMENT ON COLUMN broker_sessions.last_heartbeat_sent IS 'Timestamp of last Heartbeat message sent (Tag 35=0)';
|
|
COMMENT ON COLUMN broker_sessions.last_heartbeat_received IS 'Timestamp of last Heartbeat message received - used for timeout detection';
|
|
|
|
-- ================================================================================================
|
|
-- TABLE 2: BROKER_ORDERS
|
|
-- Audit trail for all orders routed to broker via FIX protocol
|
|
-- ================================================================================================
|
|
|
|
CREATE TABLE broker_orders (
|
|
-- Primary identifier
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- FIX order identifiers
|
|
client_order_id VARCHAR(64) NOT NULL UNIQUE, -- Our ClOrdID (Tag 11) - UUID format
|
|
broker_order_id VARCHAR(64), -- Broker OrderID (Tag 37) - filled after ExecutionReport
|
|
|
|
-- Integration with internal order tracking
|
|
internal_order_id UUID, -- Optional FK to orders table
|
|
|
|
-- Trading context
|
|
account_id VARCHAR(64) NOT NULL, -- AMP account identifier
|
|
symbol VARCHAR(32) NOT NULL, -- Instrument symbol (ES, NQ, etc.)
|
|
side VARCHAR(4) NOT NULL, -- BUY, SELL
|
|
order_type VARCHAR(16) NOT NULL, -- MARKET, LIMIT, STOP, STOP_LIMIT
|
|
|
|
-- Order quantities
|
|
quantity NUMERIC(18, 8) NOT NULL, -- Order quantity in contracts
|
|
filled_quantity NUMERIC(18, 8) NOT NULL DEFAULT 0, -- Quantity filled so far
|
|
|
|
-- Pricing (NULL for market orders)
|
|
price NUMERIC(18, 8), -- Limit price (Tag 44)
|
|
stop_price NUMERIC(18, 8), -- Stop price (Tag 99) for stop orders
|
|
avg_fill_price NUMERIC(18, 8), -- Average fill price (Tag 6)
|
|
|
|
-- Order parameters
|
|
time_in_force VARCHAR(8) NOT NULL DEFAULT 'DAY', -- DAY, GTC, IOC, FOK, GTD
|
|
|
|
-- Order lifecycle status
|
|
status VARCHAR(32) NOT NULL, -- PENDING_SUBMIT, SUBMITTED, PARTIALLY_FILLED, FILLED, REJECTED, CANCELLED, EXPIRED
|
|
|
|
-- Financial tracking
|
|
commission NUMERIC(18, 8), -- Commission charged by broker
|
|
|
|
-- Metadata
|
|
metadata JSONB DEFAULT '{}'::jsonb, -- Strategy, model, additional context
|
|
|
|
-- Timing
|
|
submitted_at TIMESTAMPTZ, -- When order was submitted to broker
|
|
filled_at TIMESTAMPTZ, -- When order was fully filled
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- When record was created
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Last update
|
|
|
|
-- Constraints
|
|
CONSTRAINT chk_broker_order_side CHECK (side IN ('BUY', 'SELL')),
|
|
CONSTRAINT chk_broker_order_type CHECK (
|
|
order_type IN ('MARKET', 'LIMIT', 'STOP', 'STOP_LIMIT')
|
|
),
|
|
CONSTRAINT chk_broker_order_tif CHECK (
|
|
time_in_force IN ('DAY', 'GTC', 'IOC', 'FOK', 'GTD')
|
|
),
|
|
CONSTRAINT chk_broker_order_status CHECK (
|
|
status IN ('PENDING_SUBMIT', 'SUBMITTED', 'PARTIALLY_FILLED', 'FILLED',
|
|
'REJECTED', 'CANCELLED', 'EXPIRED', 'CANCEL_PENDING')
|
|
),
|
|
CONSTRAINT chk_broker_order_quantity CHECK (quantity > 0),
|
|
CONSTRAINT chk_broker_order_filled CHECK (
|
|
filled_quantity >= 0 AND filled_quantity <= quantity
|
|
),
|
|
CONSTRAINT chk_broker_order_price CHECK (
|
|
(order_type = 'MARKET' AND price IS NULL) OR
|
|
(order_type IN ('LIMIT', 'STOP_LIMIT') AND price IS NOT NULL)
|
|
),
|
|
CONSTRAINT chk_broker_order_stop_price CHECK (
|
|
(order_type IN ('STOP', 'STOP_LIMIT') AND stop_price IS NOT NULL) OR
|
|
(order_type IN ('MARKET', 'LIMIT') AND stop_price IS NULL)
|
|
)
|
|
);
|
|
|
|
-- Add table comment
|
|
COMMENT ON TABLE broker_orders IS 'Broker order audit trail - tracks all orders routed to broker via FIX protocol';
|
|
|
|
-- Add column comments
|
|
COMMENT ON COLUMN broker_orders.client_order_id IS 'FIX ClOrdID (Tag 11) - unique client order identifier (UUID format)';
|
|
COMMENT ON COLUMN broker_orders.broker_order_id IS 'FIX OrderID (Tag 37) - broker-assigned order identifier (filled after ExecutionReport)';
|
|
COMMENT ON COLUMN broker_orders.internal_order_id IS 'Optional foreign key to internal orders table for tracking';
|
|
COMMENT ON COLUMN broker_orders.account_id IS 'AMP Futures account identifier';
|
|
COMMENT ON COLUMN broker_orders.symbol IS 'Trading symbol (ES, NQ, CL, etc.)';
|
|
COMMENT ON COLUMN broker_orders.side IS 'Order side: BUY or SELL (FIX Tag 54: 1=Buy, 2=Sell)';
|
|
COMMENT ON COLUMN broker_orders.order_type IS 'Order type: MARKET, LIMIT, STOP, STOP_LIMIT (FIX Tag 40)';
|
|
COMMENT ON COLUMN broker_orders.quantity IS 'Order quantity in contracts (FIX Tag 38)';
|
|
COMMENT ON COLUMN broker_orders.filled_quantity IS 'Cumulative filled quantity (FIX Tag 14)';
|
|
COMMENT ON COLUMN broker_orders.price IS 'Limit price for LIMIT/STOP_LIMIT orders (FIX Tag 44)';
|
|
COMMENT ON COLUMN broker_orders.stop_price IS 'Stop price for STOP/STOP_LIMIT orders (FIX Tag 99)';
|
|
COMMENT ON COLUMN broker_orders.avg_fill_price IS 'Average fill price across all executions (FIX Tag 6)';
|
|
COMMENT ON COLUMN broker_orders.time_in_force IS 'Time in force: DAY, GTC, IOC, FOK (FIX Tag 59)';
|
|
COMMENT ON COLUMN broker_orders.status IS 'Order status in lifecycle (mapped from FIX Tag 39)';
|
|
COMMENT ON COLUMN broker_orders.metadata IS 'Additional order metadata (strategy, model, risk checks, etc.)';
|
|
|
|
-- ================================================================================================
|
|
-- TABLE 3: BROKER_FILLS
|
|
-- Stores every ExecutionReport (FIX Tag 35=8) received from broker
|
|
-- ================================================================================================
|
|
|
|
CREATE TABLE broker_fills (
|
|
-- Primary identifier
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- FIX execution identifiers
|
|
execution_id VARCHAR(64) NOT NULL UNIQUE, -- ExecID (Tag 17) - unique per fill
|
|
broker_order_id VARCHAR(64) NOT NULL, -- OrderID (Tag 37)
|
|
client_order_id VARCHAR(64) NOT NULL, -- ClOrdID (Tag 11) - links to broker_orders
|
|
|
|
-- Integration with internal execution tracking
|
|
internal_execution_id UUID, -- Optional FK to executions table
|
|
|
|
-- Trade details
|
|
symbol VARCHAR(32) NOT NULL, -- Instrument symbol
|
|
side VARCHAR(4) NOT NULL, -- BUY, SELL
|
|
|
|
-- Execution type and status
|
|
exec_type VARCHAR(16) NOT NULL, -- NEW, TRADE, CANCELED, REJECTED (Tag 150)
|
|
order_status VARCHAR(32) NOT NULL, -- Order status after this execution (Tag 39)
|
|
|
|
-- Fill quantities and pricing
|
|
last_qty NUMERIC(18, 8), -- Quantity filled in this report (Tag 32)
|
|
last_price NUMERIC(18, 8), -- Fill price for this report (Tag 31)
|
|
cum_qty NUMERIC(18, 8), -- Total filled quantity (Tag 14)
|
|
avg_price NUMERIC(18, 8), -- Average fill price (Tag 6)
|
|
leaves_qty NUMERIC(18, 8), -- Remaining unfilled quantity (Tag 151)
|
|
|
|
-- Financial tracking
|
|
commission NUMERIC(18, 8), -- Commission charged (if provided)
|
|
|
|
-- Timing
|
|
transact_time TIMESTAMPTZ NOT NULL, -- Exchange execution time (Tag 60)
|
|
received_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- When we received the ExecutionReport
|
|
|
|
-- Audit trail
|
|
text TEXT, -- Reject reason or notes (Tag 58)
|
|
raw_fix_message TEXT, -- Full FIX message for regulatory compliance
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Record creation time
|
|
|
|
-- Constraints
|
|
CONSTRAINT chk_broker_fill_side CHECK (side IN ('BUY', 'SELL')),
|
|
CONSTRAINT chk_broker_fill_exec_type CHECK (
|
|
exec_type IN ('NEW', 'TRADE', 'CANCELED', 'REJECTED', 'PENDING_CANCEL',
|
|
'REPLACED', 'PENDING_REPLACE', 'STOPPED', 'SUSPENDED',
|
|
'RESTATED', 'EXPIRED')
|
|
),
|
|
CONSTRAINT chk_broker_fill_order_status CHECK (
|
|
order_status IN ('NEW', 'PARTIALLY_FILLED', 'FILLED', 'CANCELED',
|
|
'REJECTED', 'EXPIRED', 'PENDING_CANCEL', 'PENDING_REPLACE')
|
|
),
|
|
CONSTRAINT chk_broker_fill_quantities CHECK (
|
|
(last_qty IS NULL OR last_qty >= 0) AND
|
|
(cum_qty IS NULL OR cum_qty >= 0) AND
|
|
(leaves_qty IS NULL OR leaves_qty >= 0)
|
|
)
|
|
);
|
|
|
|
-- Add table comment
|
|
COMMENT ON TABLE broker_fills IS 'Broker execution reports - stores every ExecutionReport (FIX Tag 35=8) for audit and compliance';
|
|
|
|
-- Add column comments
|
|
COMMENT ON COLUMN broker_fills.execution_id IS 'FIX ExecID (Tag 17) - unique execution identifier';
|
|
COMMENT ON COLUMN broker_fills.broker_order_id IS 'FIX OrderID (Tag 37) - broker-assigned order identifier';
|
|
COMMENT ON COLUMN broker_fills.client_order_id IS 'FIX ClOrdID (Tag 11) - links to broker_orders table';
|
|
COMMENT ON COLUMN broker_fills.exec_type IS 'FIX ExecType (Tag 150) - execution type (NEW, TRADE, CANCELED, REJECTED)';
|
|
COMMENT ON COLUMN broker_fills.order_status IS 'FIX OrdStatus (Tag 39) - order status after this execution';
|
|
COMMENT ON COLUMN broker_fills.last_qty IS 'FIX LastQty (Tag 32) - quantity filled in this report';
|
|
COMMENT ON COLUMN broker_fills.last_price IS 'FIX LastPx (Tag 31) - fill price for this report';
|
|
COMMENT ON COLUMN broker_fills.cum_qty IS 'FIX CumQty (Tag 14) - total filled quantity across all fills';
|
|
COMMENT ON COLUMN broker_fills.avg_price IS 'FIX AvgPx (Tag 6) - average fill price';
|
|
COMMENT ON COLUMN broker_fills.leaves_qty IS 'FIX LeavesQty (Tag 151) - remaining unfilled quantity';
|
|
COMMENT ON COLUMN broker_fills.transact_time IS 'FIX TransactTime (Tag 60) - exchange execution timestamp';
|
|
COMMENT ON COLUMN broker_fills.text IS 'FIX Text (Tag 58) - reject reason or notes';
|
|
COMMENT ON COLUMN broker_fills.raw_fix_message IS 'Complete FIX message for regulatory compliance and debugging';
|
|
|
|
-- ================================================================================================
|
|
-- TABLE 4: BROKER_POSITIONS
|
|
-- Real-time position reconciliation with broker
|
|
-- ================================================================================================
|
|
|
|
CREATE TABLE broker_positions (
|
|
-- Primary identifier
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Position identifiers
|
|
account_id VARCHAR(64) NOT NULL, -- AMP account identifier
|
|
symbol VARCHAR(32) NOT NULL, -- Instrument symbol
|
|
|
|
-- Position details
|
|
quantity NUMERIC(18, 8) NOT NULL DEFAULT 0, -- Signed: positive=long, negative=short
|
|
avg_entry_price NUMERIC(18, 8), -- Average entry price
|
|
|
|
-- Market value and P&L
|
|
market_value NUMERIC(18, 8), -- Current market value
|
|
unrealized_pnl NUMERIC(18, 8), -- Unrealized profit/loss
|
|
realized_pnl NUMERIC(18, 8), -- Realized profit/loss (closed trades)
|
|
|
|
-- Timing
|
|
last_updated TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Last position update from broker
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Position first opened
|
|
|
|
-- Constraints
|
|
CONSTRAINT uk_broker_positions_account_symbol UNIQUE (account_id, symbol)
|
|
);
|
|
|
|
-- Add table comment
|
|
COMMENT ON TABLE broker_positions IS 'Real-time position cache from broker - used for reconciliation and risk checks';
|
|
|
|
-- Add column comments
|
|
COMMENT ON COLUMN broker_positions.account_id IS 'AMP Futures account identifier';
|
|
COMMENT ON COLUMN broker_positions.symbol IS 'Trading symbol (ES, NQ, CL, etc.)';
|
|
COMMENT ON COLUMN broker_positions.quantity IS 'Position quantity (signed: positive=long, negative=short)';
|
|
COMMENT ON COLUMN broker_positions.avg_entry_price IS 'Average entry price across all trades';
|
|
COMMENT ON COLUMN broker_positions.market_value IS 'Current market value of position';
|
|
COMMENT ON COLUMN broker_positions.unrealized_pnl IS 'Unrealized profit/loss on open position';
|
|
COMMENT ON COLUMN broker_positions.realized_pnl IS 'Realized profit/loss from closed trades';
|
|
COMMENT ON COLUMN broker_positions.last_updated IS 'Last update timestamp from broker';
|
|
|
|
-- ================================================================================================
|
|
-- HIGH-PERFORMANCE INDEXES
|
|
-- ================================================================================================
|
|
|
|
-- Broker sessions indexes
|
|
CREATE INDEX idx_broker_sessions_state ON broker_sessions(session_state);
|
|
CREATE INDEX idx_broker_sessions_updated_at ON broker_sessions(updated_at DESC);
|
|
|
|
COMMENT ON INDEX idx_broker_sessions_state IS 'Fast lookups for active FIX sessions';
|
|
COMMENT ON INDEX idx_broker_sessions_updated_at IS 'Time-series queries for session lifecycle';
|
|
|
|
-- Broker orders indexes
|
|
CREATE INDEX idx_broker_orders_broker_order_id ON broker_orders(broker_order_id);
|
|
CREATE INDEX idx_broker_orders_account_id ON broker_orders(account_id);
|
|
CREATE INDEX idx_broker_orders_symbol ON broker_orders(symbol);
|
|
CREATE INDEX idx_broker_orders_status ON broker_orders(status);
|
|
CREATE INDEX idx_broker_orders_created_at ON broker_orders(created_at DESC);
|
|
CREATE INDEX idx_broker_orders_internal_order_id ON broker_orders(internal_order_id);
|
|
|
|
-- Composite indexes for common queries
|
|
CREATE INDEX idx_broker_orders_symbol_status ON broker_orders(symbol, status);
|
|
CREATE INDEX idx_broker_orders_account_status ON broker_orders(account_id, status);
|
|
|
|
COMMENT ON INDEX idx_broker_orders_broker_order_id IS 'Fast lookups by broker-assigned OrderID (Tag 37)';
|
|
COMMENT ON INDEX idx_broker_orders_account_id IS 'Account-based queries for order history';
|
|
COMMENT ON INDEX idx_broker_orders_symbol_status IS 'Composite index for active orders per symbol';
|
|
COMMENT ON INDEX idx_broker_orders_internal_order_id IS 'Link to internal orders table';
|
|
|
|
-- Broker fills indexes
|
|
CREATE INDEX idx_broker_fills_broker_order_id ON broker_fills(broker_order_id);
|
|
CREATE INDEX idx_broker_fills_client_order_id ON broker_fills(client_order_id);
|
|
CREATE INDEX idx_broker_fills_transact_time ON broker_fills(transact_time DESC);
|
|
CREATE INDEX idx_broker_fills_symbol ON broker_fills(symbol);
|
|
CREATE INDEX idx_broker_fills_internal_execution_id ON broker_fills(internal_execution_id);
|
|
|
|
COMMENT ON INDEX idx_broker_fills_broker_order_id IS 'Fast lookups of fills by broker OrderID';
|
|
COMMENT ON INDEX idx_broker_fills_client_order_id IS 'Fast lookups of fills by client ClOrdID';
|
|
COMMENT ON INDEX idx_broker_fills_transact_time IS 'Time-series queries for execution history';
|
|
COMMENT ON INDEX idx_broker_fills_internal_execution_id IS 'Link to internal executions table';
|
|
|
|
-- Broker positions indexes
|
|
CREATE INDEX idx_broker_positions_account_id ON broker_positions(account_id);
|
|
CREATE INDEX idx_broker_positions_symbol ON broker_positions(symbol);
|
|
CREATE INDEX idx_broker_positions_last_updated ON broker_positions(last_updated DESC);
|
|
|
|
COMMENT ON INDEX idx_broker_positions_account_id IS 'Fast lookups of all positions for an account';
|
|
COMMENT ON INDEX idx_broker_positions_symbol IS 'Fast lookups of positions by symbol';
|
|
COMMENT ON INDEX idx_broker_positions_last_updated IS 'Detect stale position data';
|
|
|
|
-- ================================================================================================
|
|
-- FOREIGN KEY CONSTRAINTS
|
|
-- ================================================================================================
|
|
|
|
-- broker_fills → broker_orders (required relationship)
|
|
ALTER TABLE broker_fills
|
|
ADD CONSTRAINT fk_broker_fills_client_order_id
|
|
FOREIGN KEY (client_order_id) REFERENCES broker_orders(client_order_id)
|
|
ON DELETE CASCADE;
|
|
|
|
COMMENT ON CONSTRAINT fk_broker_fills_client_order_id ON broker_fills IS 'Links execution reports to parent orders - CASCADE delete removes fills when order is deleted';
|
|
|
|
-- broker_orders → orders (optional relationship, only if orders table exists)
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'orders') THEN
|
|
ALTER TABLE broker_orders
|
|
ADD CONSTRAINT fk_broker_orders_internal_order_id
|
|
FOREIGN KEY (internal_order_id) REFERENCES orders(id)
|
|
ON DELETE SET NULL;
|
|
|
|
RAISE NOTICE 'Created foreign key: broker_orders.internal_order_id → orders.id';
|
|
ELSE
|
|
RAISE NOTICE 'Skipped foreign key: orders table does not exist (will be added later)';
|
|
END IF;
|
|
END $$;
|
|
|
|
-- broker_fills → executions (optional relationship, only if executions table exists)
|
|
DO $$
|
|
BEGIN
|
|
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'executions') THEN
|
|
ALTER TABLE broker_fills
|
|
ADD CONSTRAINT fk_broker_fills_internal_execution_id
|
|
FOREIGN KEY (internal_execution_id) REFERENCES executions(id)
|
|
ON DELETE SET NULL;
|
|
|
|
RAISE NOTICE 'Created foreign key: broker_fills.internal_execution_id → executions.id';
|
|
ELSE
|
|
RAISE NOTICE 'Skipped foreign key: executions table does not exist (will be added later)';
|
|
END IF;
|
|
END $$;
|
|
|
|
-- ================================================================================================
|
|
-- TRIGGER FUNCTIONS FOR DATA INTEGRITY
|
|
-- ================================================================================================
|
|
|
|
-- Function to auto-update updated_at timestamp
|
|
CREATE OR REPLACE FUNCTION update_broker_orders_timestamp()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at := NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Trigger to auto-update broker_orders.updated_at
|
|
CREATE TRIGGER tg_broker_orders_update_timestamp
|
|
BEFORE UPDATE ON broker_orders
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_broker_orders_timestamp();
|
|
|
|
-- Function to auto-update broker_sessions.updated_at
|
|
CREATE OR REPLACE FUNCTION update_broker_sessions_timestamp()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at := NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Trigger to auto-update broker_sessions.updated_at
|
|
CREATE TRIGGER tg_broker_sessions_update_timestamp
|
|
BEFORE UPDATE ON broker_sessions
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_broker_sessions_timestamp();
|
|
|
|
COMMENT ON FUNCTION update_broker_orders_timestamp() IS 'Auto-updates broker_orders.updated_at on modification';
|
|
COMMENT ON FUNCTION update_broker_sessions_timestamp() IS 'Auto-updates broker_sessions.updated_at on modification';
|
|
|
|
-- ================================================================================================
|
|
-- ANALYTICAL VIEWS FOR REPORTING
|
|
-- ================================================================================================
|
|
|
|
-- View for active broker orders
|
|
CREATE OR REPLACE VIEW v_active_broker_orders AS
|
|
SELECT
|
|
bo.id,
|
|
bo.client_order_id,
|
|
bo.broker_order_id,
|
|
bo.account_id,
|
|
bo.symbol,
|
|
bo.side,
|
|
bo.order_type,
|
|
bo.quantity,
|
|
bo.filled_quantity,
|
|
bo.price,
|
|
bo.avg_fill_price,
|
|
bo.status,
|
|
bo.time_in_force,
|
|
bo.submitted_at,
|
|
bo.created_at,
|
|
EXTRACT(EPOCH FROM (NOW() - bo.created_at)) AS age_seconds,
|
|
(bo.quantity - bo.filled_quantity) AS remaining_quantity
|
|
FROM broker_orders bo
|
|
WHERE bo.status IN ('PENDING_SUBMIT', 'SUBMITTED', 'PARTIALLY_FILLED', 'CANCEL_PENDING')
|
|
ORDER BY bo.created_at DESC;
|
|
|
|
COMMENT ON VIEW v_active_broker_orders IS 'Active broker orders (not yet fully filled, cancelled, or rejected)';
|
|
|
|
-- View for broker order fill summary
|
|
CREATE OR REPLACE VIEW v_broker_order_fill_summary AS
|
|
SELECT
|
|
bo.client_order_id,
|
|
bo.broker_order_id,
|
|
bo.symbol,
|
|
bo.side,
|
|
bo.quantity AS order_qty,
|
|
bo.filled_quantity AS total_filled,
|
|
bo.avg_fill_price,
|
|
bo.status,
|
|
bo.created_at,
|
|
COUNT(bf.id) AS num_fills,
|
|
MIN(bf.transact_time) AS first_fill_time,
|
|
MAX(bf.transact_time) AS last_fill_time,
|
|
SUM(bf.commission) AS total_commission
|
|
FROM broker_orders bo
|
|
LEFT JOIN broker_fills bf ON bo.client_order_id = bf.client_order_id AND bf.exec_type = 'TRADE'
|
|
GROUP BY bo.client_order_id, bo.broker_order_id, bo.symbol, bo.side, bo.quantity, bo.filled_quantity, bo.avg_fill_price, bo.status, bo.created_at
|
|
ORDER BY bo.created_at DESC;
|
|
|
|
COMMENT ON VIEW v_broker_order_fill_summary IS 'Summary of fills per order - useful for fill rate analysis';
|
|
|
|
-- View for position reconciliation
|
|
CREATE OR REPLACE VIEW v_broker_position_reconciliation AS
|
|
SELECT
|
|
bp.account_id,
|
|
bp.symbol,
|
|
bp.quantity AS broker_qty,
|
|
COALESCE(p.quantity / 100000000, 0) AS internal_qty, -- Convert from base units (cents)
|
|
(bp.quantity - COALESCE(p.quantity / 100000000, 0)) AS delta,
|
|
bp.unrealized_pnl AS broker_unrealized_pnl,
|
|
p.unrealized_pnl / 100 AS internal_unrealized_pnl, -- Convert from cents
|
|
bp.last_updated AS broker_last_updated,
|
|
p.last_updated AS internal_last_updated
|
|
FROM broker_positions bp
|
|
LEFT JOIN positions p ON bp.symbol = p.symbol AND bp.account_id = p.account_id
|
|
ORDER BY ABS(bp.quantity - COALESCE(p.quantity / 100000000, 0)) DESC;
|
|
|
|
COMMENT ON VIEW v_broker_position_reconciliation IS 'Position reconciliation between broker and internal tracking - highlights mismatches';
|
|
|
|
-- ================================================================================================
|
|
-- GRANT PERMISSIONS
|
|
-- ================================================================================================
|
|
|
|
-- Grant permissions to foxhunt role
|
|
GRANT SELECT, INSERT, UPDATE ON broker_sessions TO foxhunt;
|
|
GRANT SELECT, INSERT, UPDATE ON broker_orders TO foxhunt;
|
|
GRANT SELECT, INSERT ON broker_fills TO foxhunt;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON broker_positions TO foxhunt;
|
|
|
|
-- Grant view access
|
|
GRANT SELECT ON v_active_broker_orders TO foxhunt;
|
|
GRANT SELECT ON v_broker_order_fill_summary TO foxhunt;
|
|
GRANT SELECT ON v_broker_position_reconciliation TO foxhunt;
|
|
|
|
-- ================================================================================================
|
|
-- FINAL VALIDATION
|
|
-- ================================================================================================
|
|
|
|
DO $$
|
|
BEGIN
|
|
-- Verify tables exist
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'broker_sessions') THEN
|
|
RAISE EXCEPTION 'Migration 047 failed: broker_sessions table not created';
|
|
END IF;
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'broker_orders') THEN
|
|
RAISE EXCEPTION 'Migration 047 failed: broker_orders table not created';
|
|
END IF;
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'broker_fills') THEN
|
|
RAISE EXCEPTION 'Migration 047 failed: broker_fills table not created';
|
|
END IF;
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'broker_positions') THEN
|
|
RAISE EXCEPTION 'Migration 047 failed: broker_positions table not created';
|
|
END IF;
|
|
|
|
-- Verify views exist
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.views WHERE table_name = 'v_active_broker_orders') THEN
|
|
RAISE EXCEPTION 'Migration 047 failed: v_active_broker_orders view not created';
|
|
END IF;
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.views WHERE table_name = 'v_broker_order_fill_summary') THEN
|
|
RAISE EXCEPTION 'Migration 047 failed: v_broker_order_fill_summary view not created';
|
|
END IF;
|
|
|
|
IF NOT EXISTS (SELECT 1 FROM information_schema.views WHERE table_name = 'v_broker_position_reconciliation') THEN
|
|
RAISE EXCEPTION 'Migration 047 failed: v_broker_position_reconciliation view not created';
|
|
END IF;
|
|
|
|
-- Success message
|
|
RAISE NOTICE '========================================';
|
|
RAISE NOTICE 'Migration 047 completed successfully!';
|
|
RAISE NOTICE 'Created 4 tables: broker_sessions, broker_orders, broker_fills, broker_positions';
|
|
RAISE NOTICE 'Created 3 views: v_active_broker_orders, v_broker_order_fill_summary, v_broker_position_reconciliation';
|
|
RAISE NOTICE 'Created 14 indexes for high-performance queries';
|
|
RAISE NOTICE 'Created 2 trigger functions for automatic timestamp updates';
|
|
RAISE NOTICE 'Broker Gateway database schema ready for AMP Futures FIX integration';
|
|
RAISE NOTICE '========================================';
|
|
END $$;
|
|
|
|
-- ================================================================================================
|
|
-- ROLLBACK INSTRUCTIONS
|
|
-- ================================================================================================
|
|
|
|
-- To rollback this migration, run the following commands in order:
|
|
-- DROP VIEW IF EXISTS v_broker_position_reconciliation CASCADE;
|
|
-- DROP VIEW IF EXISTS v_broker_order_fill_summary CASCADE;
|
|
-- DROP VIEW IF EXISTS v_active_broker_orders CASCADE;
|
|
-- DROP TRIGGER IF EXISTS tg_broker_sessions_update_timestamp ON broker_sessions;
|
|
-- DROP TRIGGER IF EXISTS tg_broker_orders_update_timestamp ON broker_orders;
|
|
-- DROP FUNCTION IF EXISTS update_broker_sessions_timestamp() CASCADE;
|
|
-- DROP FUNCTION IF EXISTS update_broker_orders_timestamp() CASCADE;
|
|
-- DROP TABLE IF EXISTS broker_fills CASCADE;
|
|
-- DROP TABLE IF EXISTS broker_positions CASCADE;
|
|
-- DROP TABLE IF EXISTS broker_orders CASCADE;
|
|
-- DROP TABLE IF EXISTS broker_sessions CASCADE;
|