250 lines
8.0 KiB
PL/PgSQL
250 lines
8.0 KiB
PL/PgSQL
-- ============================================================================
|
|
-- SCHEMA: trading
|
|
-- TABLE: strategies
|
|
-- DESCRIPTION: Estrategias de trading definidas
|
|
-- VERSION: 1.0.0
|
|
-- CREATED: 2026-01-16
|
|
-- SPRINT: Sprint 3 - DDL Implementation Roadmap Q1-2026
|
|
-- ============================================================================
|
|
|
|
-- Enum para tipo de estrategia
|
|
DO $$ BEGIN
|
|
CREATE TYPE trading.strategy_type AS ENUM (
|
|
'manual', -- Estrategia manual (usuario decide)
|
|
'semi_auto', -- Semi-automatica (senales con confirmacion)
|
|
'fully_auto', -- Totalmente automatizada
|
|
'copy_trading' -- Copy trading de otro usuario/bot
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Enum para metodologia de trading
|
|
DO $$ BEGIN
|
|
CREATE TYPE trading.trading_methodology AS ENUM (
|
|
'trend_following', -- Seguimiento de tendencia
|
|
'mean_reversion', -- Reversion a la media
|
|
'breakout', -- Rupturas
|
|
'scalping', -- Scalping
|
|
'swing', -- Swing trading
|
|
'position', -- Position trading
|
|
'arbitrage', -- Arbitraje
|
|
'news_based', -- Basado en noticias
|
|
'smc_ict', -- Smart Money Concepts / ICT
|
|
'harmonic', -- Patrones armonicos
|
|
'custom' -- Personalizada
|
|
);
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN null;
|
|
END $$;
|
|
|
|
-- Tabla de Estrategias
|
|
CREATE TABLE IF NOT EXISTS trading.strategies (
|
|
-- Identificadores
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,
|
|
created_by UUID REFERENCES users.users(id),
|
|
|
|
-- Informacion basica
|
|
name VARCHAR(100) NOT NULL,
|
|
slug VARCHAR(100) NOT NULL,
|
|
description TEXT,
|
|
short_description VARCHAR(500),
|
|
|
|
-- Clasificacion
|
|
type trading.strategy_type NOT NULL DEFAULT 'manual',
|
|
methodology trading.trading_methodology NOT NULL DEFAULT 'custom',
|
|
|
|
-- Configuracion de riesgo
|
|
risk_config JSONB NOT NULL DEFAULT '{
|
|
"risk_per_trade_percent": 2,
|
|
"max_daily_loss_percent": 5,
|
|
"max_drawdown_percent": 20,
|
|
"max_concurrent_trades": 3,
|
|
"risk_reward_min": 1.5
|
|
}'::JSONB,
|
|
|
|
-- Configuracion de entrada
|
|
entry_config JSONB NOT NULL DEFAULT '{
|
|
"order_type": "market",
|
|
"slippage_pips": 2,
|
|
"max_spread_pips": 5,
|
|
"entry_confirmation_required": false
|
|
}'::JSONB,
|
|
|
|
-- Configuracion de salida
|
|
exit_config JSONB NOT NULL DEFAULT '{
|
|
"use_trailing_stop": false,
|
|
"trailing_stop_pips": 20,
|
|
"partial_close_enabled": false,
|
|
"partial_close_percent": 50,
|
|
"breakeven_enabled": false,
|
|
"breakeven_pips": 10
|
|
}'::JSONB,
|
|
|
|
-- Simbolos y timeframes
|
|
allowed_symbols VARCHAR(20)[], -- NULL = todos
|
|
allowed_timeframes trading.timeframe[],
|
|
primary_timeframe trading.timeframe DEFAULT 'H1',
|
|
|
|
-- Horarios de trading
|
|
trading_hours JSONB DEFAULT '{
|
|
"enabled": false,
|
|
"sessions": ["london", "new_york"],
|
|
"avoid_news": true,
|
|
"news_buffer_minutes": 30
|
|
}'::JSONB,
|
|
|
|
-- Indicadores utilizados
|
|
indicators_config JSONB DEFAULT '[]'::JSONB,
|
|
|
|
-- Reglas de entrada (para estrategias automaticas)
|
|
entry_rules JSONB DEFAULT '[]'::JSONB,
|
|
|
|
-- Reglas de salida
|
|
exit_rules JSONB DEFAULT '[]'::JSONB,
|
|
|
|
-- Estado
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
is_public BOOLEAN NOT NULL DEFAULT FALSE, -- Visible para otros usuarios
|
|
is_premium BOOLEAN NOT NULL DEFAULT FALSE, -- Requiere suscripcion
|
|
|
|
-- Estadisticas
|
|
total_trades INTEGER NOT NULL DEFAULT 0,
|
|
winning_trades INTEGER NOT NULL DEFAULT 0,
|
|
losing_trades INTEGER NOT NULL DEFAULT 0,
|
|
win_rate DECIMAL(5, 2) DEFAULT 0,
|
|
profit_factor DECIMAL(10, 4) DEFAULT 0,
|
|
average_profit DECIMAL(15, 4) DEFAULT 0,
|
|
average_loss DECIMAL(15, 4) DEFAULT 0,
|
|
max_drawdown DECIMAL(15, 4) DEFAULT 0,
|
|
total_profit_loss DECIMAL(15, 4) DEFAULT 0,
|
|
|
|
-- Backtesting
|
|
last_backtest_at TIMESTAMPTZ,
|
|
backtest_results JSONB,
|
|
|
|
-- Version control
|
|
version INTEGER NOT NULL DEFAULT 1,
|
|
previous_version_id UUID,
|
|
|
|
-- Metadata
|
|
tags VARCHAR(50)[],
|
|
metadata JSONB DEFAULT '{}'::JSONB,
|
|
|
|
-- Timestamps
|
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
|
|
-- Constraints
|
|
CONSTRAINT strategies_unique_slug UNIQUE (tenant_id, slug),
|
|
CONSTRAINT strategies_win_rate_check CHECK (win_rate BETWEEN 0 AND 100)
|
|
);
|
|
|
|
COMMENT ON TABLE trading.strategies IS
|
|
'Estrategias de trading con configuracion de riesgo, entrada y salida';
|
|
|
|
COMMENT ON COLUMN trading.strategies.risk_config IS
|
|
'Configuracion de gestion de riesgo de la estrategia';
|
|
|
|
-- Indices
|
|
CREATE INDEX IF NOT EXISTS idx_strategies_tenant
|
|
ON trading.strategies(tenant_id);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_strategies_creator
|
|
ON trading.strategies(created_by);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_strategies_type
|
|
ON trading.strategies(type);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_strategies_methodology
|
|
ON trading.strategies(methodology);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_strategies_active
|
|
ON trading.strategies(tenant_id, is_active)
|
|
WHERE is_active = TRUE;
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_strategies_public
|
|
ON trading.strategies(is_public, win_rate DESC)
|
|
WHERE is_public = TRUE;
|
|
|
|
-- GIN index para tags
|
|
CREATE INDEX IF NOT EXISTS idx_strategies_tags_gin
|
|
ON trading.strategies USING GIN (tags);
|
|
|
|
-- Trigger para updated_at
|
|
DROP TRIGGER IF EXISTS strategy_updated_at ON trading.strategies;
|
|
CREATE TRIGGER strategy_updated_at
|
|
BEFORE UPDATE ON trading.strategies
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION trading.update_trading_timestamp();
|
|
|
|
-- Funcion para calcular estadisticas de estrategia
|
|
CREATE OR REPLACE FUNCTION trading.recalculate_strategy_stats(p_strategy_id UUID)
|
|
RETURNS VOID AS $$
|
|
DECLARE
|
|
v_stats RECORD;
|
|
BEGIN
|
|
SELECT
|
|
COUNT(*) AS total,
|
|
COUNT(*) FILTER (WHERE profit_loss > 0) AS wins,
|
|
COUNT(*) FILTER (WHERE profit_loss < 0) AS losses,
|
|
COALESCE(AVG(profit_loss) FILTER (WHERE profit_loss > 0), 0) AS avg_profit,
|
|
COALESCE(AVG(ABS(profit_loss)) FILTER (WHERE profit_loss < 0), 0) AS avg_loss,
|
|
COALESCE(SUM(profit_loss), 0) AS total_pnl
|
|
INTO v_stats
|
|
FROM trading.positions
|
|
WHERE strategy_id = p_strategy_id
|
|
AND status IN ('closed', 'stopped', 'target_hit');
|
|
|
|
UPDATE trading.strategies
|
|
SET total_trades = v_stats.total,
|
|
winning_trades = v_stats.wins,
|
|
losing_trades = v_stats.losses,
|
|
win_rate = CASE WHEN v_stats.total > 0
|
|
THEN (v_stats.wins::DECIMAL / v_stats.total * 100)
|
|
ELSE 0 END,
|
|
profit_factor = CASE WHEN v_stats.avg_loss > 0
|
|
THEN v_stats.avg_profit / v_stats.avg_loss
|
|
ELSE 0 END,
|
|
average_profit = v_stats.avg_profit,
|
|
average_loss = v_stats.avg_loss,
|
|
total_profit_loss = v_stats.total_pnl
|
|
WHERE id = p_strategy_id;
|
|
END;
|
|
$$ LANGUAGE plpgsql;
|
|
|
|
-- Vista de estrategias publicas
|
|
CREATE OR REPLACE VIEW trading.v_public_strategies AS
|
|
SELECT
|
|
id,
|
|
name,
|
|
description,
|
|
type,
|
|
methodology,
|
|
primary_timeframe,
|
|
total_trades,
|
|
win_rate,
|
|
profit_factor,
|
|
max_drawdown,
|
|
is_premium,
|
|
created_at
|
|
FROM trading.strategies
|
|
WHERE is_public = TRUE
|
|
AND is_active = TRUE
|
|
AND total_trades >= 10 -- Minimo de trades para mostrar
|
|
ORDER BY win_rate DESC, profit_factor DESC;
|
|
|
|
-- RLS Policy para multi-tenancy
|
|
ALTER TABLE trading.strategies ENABLE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY strategies_tenant_isolation ON trading.strategies
|
|
FOR ALL
|
|
USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);
|
|
|
|
-- Grants
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON trading.strategies TO trading_app;
|
|
GRANT SELECT ON trading.strategies TO trading_readonly;
|
|
GRANT SELECT ON trading.v_public_strategies TO trading_app;
|
|
GRANT EXECUTE ON FUNCTION trading.recalculate_strategy_stats TO trading_app;
|