110 lines
3.8 KiB
PL/PgSQL
110 lines
3.8 KiB
PL/PgSQL
-- ============================================================
|
|
-- Migration 011: Capital Ledger Hardening
|
|
-- Date: 2026-02-16
|
|
-- Purpose: Introduce deterministic per-profile capital ledger and helper RPCs.
|
|
-- ============================================================
|
|
|
|
CREATE TABLE IF NOT EXISTS capital_ledgers (
|
|
profile_id uuid PRIMARY KEY REFERENCES trade_profiles(id) ON DELETE CASCADE,
|
|
allocated_capital numeric NOT NULL DEFAULT 0,
|
|
reserved_for_orders numeric NOT NULL DEFAULT 0,
|
|
reserved_for_positions numeric NOT NULL DEFAULT 0,
|
|
realized_pnl numeric NOT NULL DEFAULT 0,
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
COMMENT ON TABLE capital_ledgers IS 'Deterministic ledger for profile capital isolation.';
|
|
COMMENT ON COLUMN capital_ledgers.realized_pnl IS 'Cumulative realized profit/loss for the profile.';
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_capital_ledgers_profile ON capital_ledgers(profile_id);
|
|
|
|
ALTER TABLE capital_ledgers ENABLE ROW LEVEL SECURITY;
|
|
|
|
DROP POLICY IF EXISTS "Users can manage own ledger" ON capital_ledgers;
|
|
CREATE POLICY "Users can manage own ledger" ON capital_ledgers
|
|
FOR ALL
|
|
USING (auth.uid() = (SELECT user_id FROM trade_profiles WHERE id = capital_ledgers.profile_id))
|
|
WITH CHECK (auth.uid() = (SELECT user_id FROM trade_profiles WHERE id = capital_ledgers.profile_id));
|
|
|
|
CREATE OR REPLACE FUNCTION fn_reserve_for_order(p_profile uuid, p_amount numeric)
|
|
RETURNS capital_ledgers AS $$
|
|
DECLARE
|
|
ledger capital_ledgers;
|
|
BEGIN
|
|
UPDATE capital_ledgers
|
|
SET reserved_for_orders = reserved_for_orders + p_amount,
|
|
updated_at = now()
|
|
WHERE profile_id = p_profile
|
|
AND (allocated_capital - reserved_for_orders - reserved_for_positions) >= p_amount
|
|
RETURNING * INTO ledger;
|
|
|
|
IF NOT FOUND THEN
|
|
RAISE EXCEPTION 'Insufficient capital for profile %', p_profile;
|
|
END IF;
|
|
|
|
RETURN ledger;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
CREATE OR REPLACE FUNCTION fn_release_order_reservation(p_profile uuid, p_amount numeric)
|
|
RETURNS capital_ledgers AS $$
|
|
DECLARE
|
|
ledger capital_ledgers;
|
|
BEGIN
|
|
UPDATE capital_ledgers
|
|
SET reserved_for_orders = GREATEST(reserved_for_orders - p_amount, 0),
|
|
updated_at = now()
|
|
WHERE profile_id = p_profile
|
|
RETURNING * INTO ledger;
|
|
|
|
RETURN ledger;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
CREATE OR REPLACE FUNCTION fn_adjust_position_reservation(p_profile uuid, p_delta numeric)
|
|
RETURNS capital_ledgers AS $$
|
|
DECLARE
|
|
ledger capital_ledgers;
|
|
BEGIN
|
|
UPDATE capital_ledgers
|
|
SET reserved_for_positions = GREATEST(reserved_for_positions + p_delta, 0),
|
|
updated_at = now()
|
|
WHERE profile_id = p_profile
|
|
RETURNING * INTO ledger;
|
|
|
|
RETURN ledger;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
CREATE OR REPLACE FUNCTION fn_record_realized_pnl(p_profile uuid, p_delta numeric)
|
|
RETURNS capital_ledgers AS $$
|
|
DECLARE
|
|
ledger capital_ledgers;
|
|
BEGIN
|
|
UPDATE capital_ledgers
|
|
SET realized_pnl = realized_pnl + COALESCE(p_delta, 0),
|
|
updated_at = now()
|
|
WHERE profile_id = p_profile
|
|
RETURNING * INTO ledger;
|
|
|
|
RETURN ledger;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
CREATE OR REPLACE FUNCTION fn_rebuild_ledger(p_profile uuid, p_reserved_orders numeric, p_reserved_positions numeric)
|
|
RETURNS capital_ledgers AS $$
|
|
DECLARE
|
|
ledger capital_ledgers;
|
|
BEGIN
|
|
INSERT INTO capital_ledgers (profile_id, allocated_capital, reserved_for_orders, reserved_for_positions, updated_at)
|
|
VALUES (p_profile, (SELECT allocated_capital FROM trade_profiles WHERE id = p_profile), p_reserved_orders, p_reserved_positions, now())
|
|
ON CONFLICT (profile_id) DO UPDATE
|
|
SET reserved_for_orders = p_reserved_orders,
|
|
reserved_for_positions = p_reserved_positions,
|
|
updated_at = now()
|
|
RETURNING * INTO ledger;
|
|
|
|
RETURN ledger;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|