-- ===================================================== -- ORBIQUANT IA - CURRENCY EXCHANGE RATES TABLE -- ===================================================== -- Description: Historical exchange rates for multi-currency support -- Schema: financial -- ===================================================== CREATE TABLE financial.currency_exchange_rates ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- Par de monedas from_currency financial.currency_code NOT NULL, to_currency financial.currency_code NOT NULL, -- Tasa de cambio rate DECIMAL(18,8) NOT NULL, -- Fuente de datos source VARCHAR(100) NOT NULL DEFAULT 'manual', -- manual, api, stripe, coinbase, etc. provider VARCHAR(100), -- nombre del proveedor si es API -- Validez temporal valid_from TIMESTAMPTZ NOT NULL DEFAULT NOW(), valid_to TIMESTAMPTZ, -- Metadata metadata JSONB DEFAULT '{}', -- Timestamps created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), -- Constraints CONSTRAINT positive_rate CHECK (rate > 0), CONSTRAINT different_currencies CHECK (from_currency != to_currency), CONSTRAINT valid_dates_order CHECK ( valid_to IS NULL OR valid_to > valid_from ), CONSTRAINT unique_rate_period UNIQUE(from_currency, to_currency, valid_from) ); -- Indexes CREATE INDEX idx_cer_currencies ON financial.currency_exchange_rates(from_currency, to_currency); CREATE INDEX idx_cer_valid_from ON financial.currency_exchange_rates(valid_from DESC); CREATE INDEX idx_cer_valid_period ON financial.currency_exchange_rates(from_currency, to_currency, valid_from DESC) WHERE valid_to IS NULL OR valid_to > NOW(); CREATE INDEX idx_cer_source ON financial.currency_exchange_rates(source); -- Comments COMMENT ON TABLE financial.currency_exchange_rates IS 'Historical exchange rates for currency conversion'; COMMENT ON COLUMN financial.currency_exchange_rates.rate IS 'Exchange rate: 1 from_currency = rate * to_currency'; COMMENT ON COLUMN financial.currency_exchange_rates.source IS 'Source of exchange rate data'; COMMENT ON COLUMN financial.currency_exchange_rates.valid_from IS 'Start of rate validity period'; COMMENT ON COLUMN financial.currency_exchange_rates.valid_to IS 'End of rate validity period (NULL = currently valid)'; COMMENT ON COLUMN financial.currency_exchange_rates.metadata IS 'Additional rate metadata (bid, ask, spread, etc.)'; -- Función helper para obtener tasa de cambio actual CREATE OR REPLACE FUNCTION financial.get_exchange_rate( p_from_currency financial.currency_code, p_to_currency financial.currency_code, p_at_time TIMESTAMPTZ DEFAULT NOW() ) RETURNS DECIMAL(18,8) LANGUAGE plpgsql STABLE AS $$ DECLARE v_rate DECIMAL(18,8); BEGIN -- Si son la misma moneda, retornar 1 IF p_from_currency = p_to_currency THEN RETURN 1.0; END IF; -- Buscar tasa de cambio válida SELECT rate INTO v_rate FROM financial.currency_exchange_rates WHERE from_currency = p_from_currency AND to_currency = p_to_currency AND valid_from <= p_at_time AND (valid_to IS NULL OR valid_to > p_at_time) ORDER BY valid_from DESC LIMIT 1; -- Si no se encuentra, intentar inversa IF v_rate IS NULL THEN SELECT 1.0 / rate INTO v_rate FROM financial.currency_exchange_rates WHERE from_currency = p_to_currency AND to_currency = p_from_currency AND valid_from <= p_at_time AND (valid_to IS NULL OR valid_to > p_at_time) ORDER BY valid_from DESC LIMIT 1; END IF; -- Si aún no hay tasa, retornar NULL RETURN v_rate; END; $$; COMMENT ON FUNCTION financial.get_exchange_rate IS 'Get exchange rate between currencies at specific time'; -- Función para convertir montos CREATE OR REPLACE FUNCTION financial.convert_currency( p_amount DECIMAL, p_from_currency financial.currency_code, p_to_currency financial.currency_code, p_at_time TIMESTAMPTZ DEFAULT NOW() ) RETURNS DECIMAL(20,8) LANGUAGE plpgsql STABLE AS $$ DECLARE v_rate DECIMAL(18,8); BEGIN -- Obtener tasa de cambio v_rate := financial.get_exchange_rate(p_from_currency, p_to_currency, p_at_time); -- Si no hay tasa, retornar NULL IF v_rate IS NULL THEN RETURN NULL; END IF; -- Convertir y retornar RETURN p_amount * v_rate; END; $$; COMMENT ON FUNCTION financial.convert_currency IS 'Convert amount between currencies at specific time';