learning_ai_invt_trdg/backend/schema/013_distributed_entry_lock.sql

50 lines
1.8 KiB
PL/PgSQL

-- ============================================================
-- Migration 013: Distributed Entry Locking
-- Date: 2026-02-18
-- Purpose: Add deterministic distributed locking + lifecycle guard for ENTRY flows.
-- ============================================================
-- Determine a stable 64-bit key per profile+symbol pair.
CREATE OR REPLACE FUNCTION fn_entry_lock_key(p_profile uuid, p_symbol text)
RETURNS bigint AS $$
DECLARE
profile_hash bigint;
symbol_hash bigint;
BEGIN
profile_hash := abs(hashtext(coalesce(p_profile::text, ''))::bigint) % 2147483647;
symbol_hash := abs(hashtext(coalesce(lower(p_symbol), ''))::bigint) % 4294967295;
RETURN ((profile_hash << 32) + symbol_hash) % 9223372036854775807;
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- Try to obtain the advisory lock without blocking.
CREATE OR REPLACE FUNCTION fn_try_acquire_entry_lock(p_profile uuid, p_symbol text)
RETURNS boolean AS $$
BEGIN
RETURN pg_try_advisory_lock(fn_entry_lock_key(p_profile, p_symbol));
END;
$$ LANGUAGE plpgsql;
-- Release the advisory lock.
CREATE OR REPLACE FUNCTION fn_release_entry_lock(p_profile uuid, p_symbol text)
RETURNS boolean AS $$
BEGIN
RETURN pg_advisory_unlock(fn_entry_lock_key(p_profile, p_symbol));
END;
$$ LANGUAGE plpgsql;
-- Check for an existing ENTRY/OPEN lifecycle for the same profile+symbol.
CREATE OR REPLACE FUNCTION fn_is_entry_in_progress(p_profile uuid, p_symbol text)
RETURNS boolean AS $$
BEGIN
RETURN EXISTS (
SELECT 1
FROM trade_lifecycle tl
JOIN positions p ON p.profile_id = tl.profile_id AND p.trade_id = tl.trade_id
WHERE tl.profile_id = p_profile
AND lower(p.symbol) = lower(coalesce(p_symbol, ''))
AND tl.current_stage IN ('ENTRY', 'OPEN')
);
END;
$$ LANGUAGE plpgsql STABLE;