230 lines
7.1 KiB
PL/PgSQL
230 lines
7.1 KiB
PL/PgSQL
-- ============================================================================
|
|
-- SCHEMA: trading
|
|
-- TABLE: symbols
|
|
-- DESCRIPTION: Simbolos/instrumentos habilitados para trading por tenant
|
|
-- VERSION: 1.0.0
|
|
-- CREATED: 2026-01-16
|
|
-- SPRINT: Sprint 3 - DDL Implementation Roadmap Q1-2026
|
|
-- ============================================================================
|
|
|
|
-- Crear schema si no existe
|
|
CREATE SCHEMA IF NOT EXISTS trading;
|
|
|
|
-- Grant usage
|
|
GRANT USAGE ON SCHEMA trading TO trading_app;
|
|
GRANT USAGE ON SCHEMA trading TO trading_readonly;
|
|
|
|
-- ============================================================================
|
|
-- ENUMS COMPARTIDOS DEL SCHEMA TRADING
|
|
-- ============================================================================
|
|
|
|
-- Enum para direccion de trade
|
|
DO $$ BEGIN
|
|
CREATE TYPE trading.trade_direction AS ENUM (
|
|
'long', -- Compra / Buy
|
|
'short' -- Venta / Sell
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Enum para estado de posicion
|
|
DO $$ BEGIN
|
|
CREATE TYPE trading.position_status AS ENUM (
|
|
'pending', -- Pendiente de ejecucion
|
|
'open', -- Posicion abierta
|
|
'closed', -- Cerrada manualmente
|
|
'stopped', -- Cerrada por stop loss
|
|
'target_hit', -- Cerrada por take profit
|
|
'expired', -- Expirada
|
|
'cancelled' -- Cancelada antes de ejecutar
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Enum para tipo de orden
|
|
DO $$ BEGIN
|
|
CREATE TYPE trading.order_type AS ENUM (
|
|
'market', -- Orden de mercado
|
|
'limit', -- Orden limitada
|
|
'stop', -- Stop order
|
|
'stop_limit' -- Stop limit order
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Enum para timeframe
|
|
DO $$ BEGIN
|
|
CREATE TYPE trading.timeframe AS ENUM (
|
|
'M1', 'M5', 'M15', 'M30', -- Minutos
|
|
'H1', 'H4', -- Horas
|
|
'D1', 'W1', 'MN' -- Dia, Semana, Mes
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- ============================================================================
|
|
-- TABLA: symbols
|
|
-- Simbolos habilitados por tenant (copia de market_data.tickers con config tenant)
|
|
-- ============================================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS trading.symbols (
|
|
-- Identificadores
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
ticker_id UUID NOT NULL REFERENCES market_data.tickers(id),
|
|
|
|
-- Datos del simbolo (denormalizados para performance)
|
|
symbol VARCHAR(20) NOT NULL,
|
|
name VARCHAR(200),
|
|
type market_data.instrument_type,
|
|
|
|
-- Configuracion por tenant
|
|
is_enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
is_tradeable BOOLEAN NOT NULL DEFAULT TRUE,
|
|
is_visible BOOLEAN NOT NULL DEFAULT TRUE,
|
|
|
|
-- Limites especificos del tenant
|
|
min_lot_size DECIMAL(10, 4),
|
|
max_lot_size DECIMAL(10, 4),
|
|
max_position_size DECIMAL(15, 2), -- Tamaño maximo de posicion en USD
|
|
max_daily_volume DECIMAL(15, 2), -- Volumen diario maximo
|
|
|
|
-- Spread markup (si el tenant agrega spread)
|
|
spread_markup_pips DECIMAL(10, 4) DEFAULT 0,
|
|
|
|
-- Comisiones del tenant
|
|
commission_per_lot DECIMAL(10, 4) DEFAULT 0,
|
|
commission_percent DECIMAL(5, 4) DEFAULT 0,
|
|
|
|
-- Margen requerido (override del default)
|
|
margin_required_percent DECIMAL(5, 2),
|
|
max_leverage INTEGER,
|
|
|
|
-- Trading hours override
|
|
custom_trading_hours JSONB,
|
|
|
|
-- Categorias/tags del tenant
|
|
categories VARCHAR(50)[],
|
|
tags VARCHAR(50)[],
|
|
display_order INTEGER DEFAULT 999,
|
|
|
|
-- Estadisticas del tenant
|
|
trade_count INTEGER NOT NULL DEFAULT 0,
|
|
total_volume DECIMAL(20, 4) NOT NULL DEFAULT 0,
|
|
last_trade_at TIMESTAMPTZ,
|
|
|
|
-- Metadata
|
|
metadata JSONB DEFAULT '{}'::JSONB,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Constraints
|
|
CONSTRAINT symbols_unique_per_tenant UNIQUE (tenant_id, ticker_id),
|
|
CONSTRAINT symbols_unique_symbol_per_tenant UNIQUE (tenant_id, symbol)
|
|
);
|
|
|
|
COMMENT ON TABLE trading.symbols IS
|
|
'Simbolos habilitados para trading por tenant con configuracion personalizada';
|
|
|
|
COMMENT ON COLUMN trading.symbols.spread_markup_pips IS
|
|
'Pips adicionales de spread que el tenant agrega al precio del broker';
|
|
|
|
-- Indices
|
|
CREATE INDEX IF NOT EXISTS idx_symbols_tenant
|
|
ON trading.symbols(tenant_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_symbols_ticker
|
|
ON trading.symbols(ticker_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_symbols_symbol
|
|
ON trading.symbols(tenant_id, symbol);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_symbols_enabled
|
|
ON trading.symbols(tenant_id, is_enabled, is_tradeable)
|
|
WHERE is_enabled = TRUE AND is_tradeable = TRUE;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_symbols_type
|
|
ON trading.symbols(tenant_id, type);
|
|
|
|
-- GIN index para categorias
|
|
CREATE INDEX IF NOT EXISTS idx_symbols_categories_gin
|
|
ON trading.symbols USING GIN (categories);
|
|
|
|
-- Trigger para updated_at
|
|
CREATE OR REPLACE FUNCTION trading.update_trading_timestamp()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = NOW();
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS symbol_updated_at ON trading.symbols;
|
|
CREATE TRIGGER symbol_updated_at
|
|
BEFORE UPDATE ON trading.symbols
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION trading.update_trading_timestamp();
|
|
|
|
-- Trigger para sincronizar datos del ticker
|
|
CREATE OR REPLACE FUNCTION trading.sync_symbol_from_ticker()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
SELECT symbol, name, type
|
|
INTO NEW.symbol, NEW.name, NEW.type
|
|
FROM market_data.tickers
|
|
WHERE id = NEW.ticker_id;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
DROP TRIGGER IF EXISTS symbol_sync ON trading.symbols;
|
|
CREATE TRIGGER symbol_sync
|
|
BEFORE INSERT OR UPDATE OF ticker_id ON trading.symbols
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION trading.sync_symbol_from_ticker();
|
|
|
|
-- Vista de simbolos activos
|
|
CREATE OR REPLACE VIEW trading.v_active_symbols AS
|
|
SELECT
|
|
s.id,
|
|
s.tenant_id,
|
|
s.ticker_id,
|
|
s.symbol,
|
|
s.name,
|
|
s.type,
|
|
t.pip_size,
|
|
t.typical_spread_pips,
|
|
s.spread_markup_pips,
|
|
(COALESCE(t.typical_spread_pips, 0) + COALESCE(s.spread_markup_pips, 0)) AS total_spread_pips,
|
|
t.current_bid,
|
|
t.current_ask,
|
|
s.min_lot_size,
|
|
s.max_lot_size,
|
|
COALESCE(s.max_leverage, t.max_leverage) AS max_leverage,
|
|
s.display_order
|
|
FROM trading.symbols s
|
|
JOIN market_data.tickers t ON s.ticker_id = t.id
|
|
WHERE s.is_enabled = TRUE
|
|
AND s.is_tradeable = TRUE
|
|
AND t.status = 'active'
|
|
ORDER BY s.display_order, s.symbol;
|
|
|
|
-- RLS Policy para multi-tenancy
|
|
ALTER TABLE trading.symbols ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY symbols_tenant_isolation ON trading.symbols
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Grants
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON trading.symbols TO trading_app;
|
|
GRANT SELECT ON trading.symbols TO trading_readonly;
|
|
GRANT SELECT ON trading.v_active_symbols TO trading_app;
|