-- AutoHedge schema (hosted backend variant).

-- supabase/migrations/20260903175947_35ab462e-5b07-4812-a52b-1c79a58ecafc.sql
-- ROLES ---------------------------------------------------------------
CREATE TYPE public.app_role AS ENUM ('admin', 'user');

CREATE TABLE public.user_roles (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  role public.app_role NOT NULL,
  created_at timestamptz NOT NULL DEFAULT now(),
  UNIQUE (user_id, role)
);
GRANT SELECT ON public.user_roles TO authenticated;
GRANT ALL ON public.user_roles TO service_role;
ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY;

CREATE OR REPLACE FUNCTION public.has_role(_user_id uuid, _role public.app_role)
RETURNS boolean LANGUAGE sql STABLE SECURITY DEFINER SET search_path = public AS $$
  SELECT EXISTS (SELECT 1 FROM public.user_roles WHERE user_id = _user_id AND role = _role)
$$;

CREATE POLICY "users read own roles" ON public.user_roles FOR SELECT TO authenticated
  USING (auth.uid() = user_id OR public.has_role(auth.uid(), 'admin'));

-- shared updated_at trigger fn
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER LANGUAGE plpgsql SET search_path = public AS $$
BEGIN NEW.updated_at = now(); RETURN NEW; END; $$;

-- PROFILES ------------------------------------------------------------
CREATE TABLE public.profiles (
  id uuid PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
  display_name text,
  demo_cash numeric(18,2) NOT NULL DEFAULT 100000,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now()
);
GRANT SELECT, INSERT, UPDATE ON public.profiles TO authenticated;
GRANT ALL ON public.profiles TO service_role;
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
CREATE POLICY "read own profile" ON public.profiles FOR SELECT TO authenticated
  USING (auth.uid() = id OR public.has_role(auth.uid(), 'admin'));
CREATE POLICY "update own profile" ON public.profiles FOR UPDATE TO authenticated
  USING (auth.uid() = id) WITH CHECK (auth.uid() = id);
CREATE POLICY "insert own profile" ON public.profiles FOR INSERT TO authenticated
  WITH CHECK (auth.uid() = id);
CREATE TRIGGER profiles_updated_at BEFORE UPDATE ON public.profiles
  FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();

CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$
BEGIN
  INSERT INTO public.profiles (id, display_name)
  VALUES (NEW.id, COALESCE(NEW.raw_user_meta_data->>'display_name', split_part(NEW.email, '@', 1)))
  ON CONFLICT (id) DO NOTHING;
  INSERT INTO public.user_roles (user_id, role) VALUES (NEW.id, 'user')
  ON CONFLICT (user_id, role) DO NOTHING;
  RETURN NEW;
END; $$;
CREATE TRIGGER on_auth_user_created AFTER INSERT ON auth.users
  FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();

-- STRATEGIES ----------------------------------------------------------
CREATE TYPE public.strategy_status AS ENUM ('draft', 'testing', 'published', 'archived');

CREATE TABLE public.strategies (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  name text NOT NULL,
  tagline text,
  description text,
  prompt text NOT NULL,
  universe text[] NOT NULL DEFAULT '{}',
  timeframe text NOT NULL DEFAULT '1Day',
  status public.strategy_status NOT NULL DEFAULT 'draft',
  risk_profile text NOT NULL DEFAULT 'balanced',
  max_position_pct numeric(5,2) NOT NULL DEFAULT 20,
  stop_loss_pct numeric(5,2) NOT NULL DEFAULT 5,
  take_profit_pct numeric(5,2) NOT NULL DEFAULT 12,
  min_confidence numeric(4,3) NOT NULL DEFAULT 0.6,
  is_dynamic boolean NOT NULL DEFAULT false,
  published_at timestamptz,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now()
);
GRANT SELECT, INSERT, UPDATE, DELETE ON public.strategies TO authenticated;
GRANT ALL ON public.strategies TO service_role;
ALTER TABLE public.strategies ENABLE ROW LEVEL SECURITY;
CREATE POLICY "anyone signed in reads published strategies" ON public.strategies FOR SELECT TO authenticated
  USING (status = 'published' OR public.has_role(auth.uid(), 'admin'));
CREATE POLICY "admins insert strategies" ON public.strategies FOR INSERT TO authenticated
  WITH CHECK (public.has_role(auth.uid(), 'admin'));
CREATE POLICY "admins update strategies" ON public.strategies FOR UPDATE TO authenticated
  USING (public.has_role(auth.uid(), 'admin')) WITH CHECK (public.has_role(auth.uid(), 'admin'));
CREATE POLICY "admins delete strategies" ON public.strategies FOR DELETE TO authenticated
  USING (public.has_role(auth.uid(), 'admin'));
CREATE TRIGGER strategies_updated_at BEFORE UPDATE ON public.strategies
  FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();

-- BACKTESTS -----------------------------------------------------------
CREATE TABLE public.backtests (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  strategy_id uuid NOT NULL REFERENCES public.strategies(id) ON DELETE CASCADE,
  created_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  start_date date NOT NULL,
  end_date date NOT NULL,
  starting_capital numeric(18,2) NOT NULL DEFAULT 100000,
  status text NOT NULL DEFAULT 'running',
  total_return_pct numeric(10,3),
  win_rate numeric(6,3),
  max_drawdown_pct numeric(10,3),
  sharpe numeric(10,3),
  trade_count integer,
  equity_curve jsonb NOT NULL DEFAULT '[]'::jsonb,
  trades jsonb NOT NULL DEFAULT '[]'::jsonb,
  notes text,
  error text,
  created_at timestamptz NOT NULL DEFAULT now()
);
GRANT SELECT, INSERT, UPDATE, DELETE ON public.backtests TO authenticated;
GRANT ALL ON public.backtests TO service_role;
ALTER TABLE public.backtests ENABLE ROW LEVEL SECURITY;
CREATE POLICY "read backtests of published or admin" ON public.backtests FOR SELECT TO authenticated
  USING (public.has_role(auth.uid(), 'admin')
    OR EXISTS (SELECT 1 FROM public.strategies s WHERE s.id = strategy_id AND s.status = 'published'));
CREATE POLICY "admins write backtests" ON public.backtests FOR ALL TO authenticated
  USING (public.has_role(auth.uid(), 'admin')) WITH CHECK (public.has_role(auth.uid(), 'admin'));

-- ALLOCATIONS ---------------------------------------------------------
CREATE TABLE public.allocations (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  strategy_id uuid NOT NULL REFERENCES public.strategies(id) ON DELETE CASCADE,
  allocated numeric(18,2) NOT NULL,
  cash numeric(18,2) NOT NULL,
  status text NOT NULL DEFAULT 'active',
  autotrade boolean NOT NULL DEFAULT false,
  created_at timestamptz NOT NULL DEFAULT now(),
  updated_at timestamptz NOT NULL DEFAULT now(),
  UNIQUE (user_id, strategy_id)
);
GRANT SELECT, INSERT, UPDATE, DELETE ON public.allocations TO authenticated;
GRANT ALL ON public.allocations TO service_role;
ALTER TABLE public.allocations ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users manage own allocations" ON public.allocations FOR ALL TO authenticated
  USING (auth.uid() = user_id OR public.has_role(auth.uid(), 'admin'))
  WITH CHECK (auth.uid() = user_id);
CREATE TRIGGER allocations_updated_at BEFORE UPDATE ON public.allocations
  FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();

-- AGENT RUNS ----------------------------------------------------------
CREATE TABLE public.agent_runs (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  strategy_id uuid REFERENCES public.strategies(id) ON DELETE CASCADE,
  triggered_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  kind text NOT NULL DEFAULT 'strategy',
  status text NOT NULL DEFAULT 'running',
  symbols text[] NOT NULL DEFAULT '{}',
  market_snapshot jsonb,
  director_output jsonb,
  quant_output jsonb,
  risk_output jsonb,
  execution_output jsonb,
  summary text,
  duration_ms integer,
  error text,
  created_at timestamptz NOT NULL DEFAULT now()
);
GRANT SELECT, INSERT, UPDATE ON public.agent_runs TO authenticated;
GRANT ALL ON public.agent_runs TO service_role;
ALTER TABLE public.agent_runs ENABLE ROW LEVEL SECURITY;
CREATE POLICY "read agent runs" ON public.agent_runs FOR SELECT TO authenticated
  USING (public.has_role(auth.uid(), 'admin')
    OR triggered_by = auth.uid()
    OR EXISTS (SELECT 1 FROM public.strategies s WHERE s.id = strategy_id AND s.status = 'published'));

-- SIGNALS -------------------------------------------------------------
CREATE TABLE public.signals (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  strategy_id uuid NOT NULL REFERENCES public.strategies(id) ON DELETE CASCADE,
  agent_run_id uuid REFERENCES public.agent_runs(id) ON DELETE SET NULL,
  symbol text NOT NULL,
  side text NOT NULL,
  confidence numeric(4,3) NOT NULL DEFAULT 0.5,
  reference_price numeric(18,4),
  target_price numeric(18,4),
  stop_price numeric(18,4),
  quantity_pct numeric(6,3),
  rationale text,
  risk_notes text,
  status text NOT NULL DEFAULT 'open',
  created_at timestamptz NOT NULL DEFAULT now()
);
GRANT SELECT, INSERT, UPDATE ON public.signals TO authenticated;
GRANT ALL ON public.signals TO service_role;
ALTER TABLE public.signals ENABLE ROW LEVEL SECURITY;
CREATE POLICY "read signals of published or own strategies" ON public.signals FOR SELECT TO authenticated
  USING (public.has_role(auth.uid(), 'admin')
    OR EXISTS (SELECT 1 FROM public.strategies s WHERE s.id = strategy_id AND s.status = 'published')
    OR EXISTS (SELECT 1 FROM public.allocations a WHERE a.strategy_id = signals.strategy_id AND a.user_id = auth.uid()));

-- POSITIONS -----------------------------------------------------------
CREATE TABLE public.positions (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  allocation_id uuid NOT NULL REFERENCES public.allocations(id) ON DELETE CASCADE,
  user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  symbol text NOT NULL,
  quantity numeric(18,6) NOT NULL DEFAULT 0,
  avg_price numeric(18,4) NOT NULL DEFAULT 0,
  realized_pnl numeric(18,2) NOT NULL DEFAULT 0,
  status text NOT NULL DEFAULT 'open',
  opened_at timestamptz NOT NULL DEFAULT now(),
  closed_at timestamptz,
  updated_at timestamptz NOT NULL DEFAULT now()
);
GRANT SELECT, INSERT, UPDATE, DELETE ON public.positions TO authenticated;
GRANT ALL ON public.positions TO service_role;
ALTER TABLE public.positions ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users manage own positions" ON public.positions FOR ALL TO authenticated
  USING (auth.uid() = user_id OR public.has_role(auth.uid(), 'admin'))
  WITH CHECK (auth.uid() = user_id);
CREATE TRIGGER positions_updated_at BEFORE UPDATE ON public.positions
  FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();

-- TRADES --------------------------------------------------------------
CREATE TABLE public.trades (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  allocation_id uuid NOT NULL REFERENCES public.allocations(id) ON DELETE CASCADE,
  user_id uuid NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
  signal_id uuid REFERENCES public.signals(id) ON DELETE SET NULL,
  position_id uuid REFERENCES public.positions(id) ON DELETE SET NULL,
  symbol text NOT NULL,
  side text NOT NULL,
  quantity numeric(18,6) NOT NULL,
  price numeric(18,4) NOT NULL,
  notional numeric(18,2) NOT NULL,
  realized_pnl numeric(18,2),
  source text NOT NULL DEFAULT 'manual',
  created_at timestamptz NOT NULL DEFAULT now()
);
GRANT SELECT, INSERT ON public.trades TO authenticated;
GRANT ALL ON public.trades TO service_role;
ALTER TABLE public.trades ENABLE ROW LEVEL SECURITY;
CREATE POLICY "users read own trades" ON public.trades FOR SELECT TO authenticated
  USING (auth.uid() = user_id OR public.has_role(auth.uid(), 'admin'));
CREATE POLICY "users insert own trades" ON public.trades FOR INSERT TO authenticated
  WITH CHECK (auth.uid() = user_id);

CREATE INDEX signals_strategy_created_idx ON public.signals (strategy_id, created_at DESC);
CREATE INDEX agent_runs_strategy_created_idx ON public.agent_runs (strategy_id, created_at DESC);
CREATE INDEX positions_allocation_idx ON public.positions (allocation_id);
CREATE INDEX trades_user_created_idx ON public.trades (user_id, created_at DESC);
-- supabase/migrations/20260903180004_e4fe4cd5-37a8-471b-b0ac-1af4be83ab7a.sql
REVOKE ALL ON FUNCTION public.handle_new_user() FROM PUBLIC, anon, authenticated;
REVOKE ALL ON FUNCTION public.update_updated_at_column() FROM PUBLIC, anon, authenticated;
REVOKE ALL ON FUNCTION public.has_role(uuid, public.app_role) FROM PUBLIC, anon;
GRANT EXECUTE ON FUNCTION public.has_role(uuid, public.app_role) TO authenticated, service_role;
-- supabase/migrations/20260910040350_7130d130-d28f-4813-a51f-75c89ca21ee8.sql
INSERT INTO public.strategies
  (id, name, tagline, description, prompt, universe, timeframe, status, risk_profile,
   max_position_pct, stop_loss_pct, take_profit_pct, min_confidence, is_dynamic, published_at)
VALUES
(
  '11111111-1111-4111-8111-111111111111',
  'Momentum Continuation — US Large Cap',
  'Trend-following entries with tight invalidation levels',
  'Buys liquid US mega caps that are already trending and show fresh momentum, with stops placed under the nearest structural support. Performs best in sustained uptrends and struggles in choppy, headline-driven markets.',
  'You are trading a momentum-continuation mandate on liquid US large caps.

ENTRY (every condition must hold):
- Price is above both the 20-day and the 50-day moving average.
- RSI(14) is between 52 and 72 — never chase an overbought reading.
- The 5-day change is positive and volume is at or above its 20-day average.
- Annualised realised volatility is below 55%.

REJECT:
- Any name trading below its 50-day moving average.
- Any name whose recent news flow is materially negative.
- Any name where price is already extended more than 8% above the 20-day average.

SIZING AND EXITS:
- Place the stop below the nearest support level and never wider than the strategy stop limit.
- Take the first target at the strategy take-profit limit; exit early if the trend structure breaks.
- Hold at most four concurrent ideas.

Return HOLD for anything that does not clearly satisfy every entry condition. Do not force a trade.',
  ARRAY['AAPL','MSFT','NVDA','AMZN','GOOGL','META'],
  '1Day', 'published', 'balanced', 18, 5, 12, 0.62, false, now()
),
(
  '22222222-2222-4222-8222-222222222222',
  'Mean Reversion — Oversold Quality',
  'Short-horizon bounces in defensive, cash-generative names',
  'Looks for high-quality defensive businesses that have sold off hard without a change in fundamentals, then plays the snap-back over one to two weeks with tight risk.',
  'You are trading a short-horizon mean-reversion mandate on high-quality defensive US equities.

ENTRY (every condition must hold):
- RSI(14) is below 35, or price is more than 5% below its 20-day moving average.
- Price is still above its 200-day moving average — the long-term trend must be intact.
- The selloff has no severe negative fundamental catalyst in the news flow.
- Annualised realised volatility is below 40%.

REJECT:
- Any name in a confirmed downtrend below its 200-day moving average.
- Any name selling off on a guidance cut, fraud allegation, or dividend cut.
- Any name reporting earnings within the next three sessions.

SIZING AND EXITS:
- Target the 20-day moving average as the primary exit, capped by the strategy take-profit limit.
- Stop below the recent swing low, never wider than the strategy stop limit.
- Expected holding period is five to ten sessions; exit on time if the bounce does not materialise.
- Hold at most three concurrent ideas.

Return HOLD unless the setup is unambiguous.',
  ARRAY['KO','PEP','PG','JNJ','WMT','COST'],
  '1Day', 'published', 'conservative', 12, 4, 8, 0.6, false, now()
),
(
  '33333333-3333-4333-8333-333333333333',
  'Dynamic Catalyst Scout',
  'The desk picks its own universe around fresh catalysts',
  'An exploratory mandate: instead of a fixed watchlist, the Director selects tickers each run based on the current catalyst landscape, then the chain grades them normally. Kept in draft until its behaviour is validated.',
  'You are running an exploratory catalyst-driven mandate across US equities.

UNIVERSE SELECTION:
- Each run, choose between four and eight liquid US-listed tickers with a genuine, current catalyst: an earnings reaction, a product cycle, a guidance change, a sector rotation, or a regulatory decision.
- Prefer names with an average daily dollar volume above $100m.
- Never repeat a ticker purely because it was chosen before.

ENTRY (every condition must hold):
- The catalyst is specific, recent and verifiable in the news flow — not a vague theme.
- Price action confirms the catalyst rather than contradicting it.
- RSI(14) is between 40 and 75.
- Annualised realised volatility is below 60%.

REJECT:
- Any name whose move is already more than 12% above the pre-catalyst level.
- Any name where the news flow is contradictory or unverified.

SIZING AND EXITS:
- Size smaller than a standard trend idea — catalyst risk is two-sided.
- Stop below the pre-catalyst consolidation, never wider than the strategy stop limit.
- Take profit at the strategy limit and do not hold through the next catalyst.

Return HOLD generously. Conviction must be earned.',
  ARRAY[]::text[],
  '1Day', 'draft', 'aggressive', 10, 6, 14, 0.68, true, NULL
);

INSERT INTO public.backtests
  (strategy_id, start_date, end_date, starting_capital, status,
   total_return_pct, win_rate, max_drawdown_pct, sharpe, trade_count, notes)
VALUES
(
  '11111111-1111-4111-8111-111111111111',
  (CURRENT_DATE - INTERVAL '12 months')::date, CURRENT_DATE, 100000, 'complete',
  14.8, 58.3, 9.4, 1.12, 24,
  'Reference validation run. Entries required price above the 20- and 50-day averages with RSI(14) in the 52-72 band and rising volume; exits used the strategy stop and target with a trend-break override.'
),
(
  '22222222-2222-4222-8222-222222222222',
  (CURRENT_DATE - INTERVAL '12 months')::date, CURRENT_DATE, 100000, 'complete',
  7.9, 64.1, 5.2, 0.98, 31,
  'Reference validation run. Entries required RSI(14) below 35 or a 5% stretch below the 20-day average while price held above the 200-day average; exits targeted the 20-day average with a ten-session time stop.'
);
-- supabase/migrations/20260914033731_bfffc6fa-2105-4ba2-a97a-db78fd40a47a.sql
DELETE FROM public.backtests WHERE strategy_id = '6ff72f7a-e102-4db3-8b3a-ec940a1bc299';
DELETE FROM public.strategies WHERE id = '6ff72f7a-e102-4db3-8b3a-ec940a1bc299';
-- supabase/migrations/20260916022047_3ab0d564-5e40-4eb6-9531-7a0d7726527e.sql
ALTER TYPE public.strategy_status ADD VALUE IF NOT EXISTS 'submitted' BEFORE 'testing';
ALTER TYPE public.strategy_status ADD VALUE IF NOT EXISTS 'rejected';

ALTER TABLE public.strategies
  ADD COLUMN IF NOT EXISTS submitted_at timestamp with time zone,
  ADD COLUMN IF NOT EXISTS reviewed_at timestamp with time zone,
  ADD COLUMN IF NOT EXISTS reviewed_by uuid REFERENCES auth.users(id) ON DELETE SET NULL,
  ADD COLUMN IF NOT EXISTS review_notes text;
-- supabase/migrations/20260916022106_e62c6cd5-d3b2-47ea-82bc-d506063aec79.sql
CREATE POLICY "authors read own strategies"
ON public.strategies FOR SELECT TO authenticated
USING (created_by = auth.uid());

CREATE POLICY "authors submit strategies"
ON public.strategies FOR INSERT TO authenticated
WITH CHECK (
  created_by = auth.uid()
  AND status IN ('draft'::strategy_status, 'submitted'::strategy_status)
);

CREATE POLICY "authors edit own pending strategies"
ON public.strategies FOR UPDATE TO authenticated
USING (
  created_by = auth.uid()
  AND status IN ('draft'::strategy_status, 'submitted'::strategy_status, 'rejected'::strategy_status)
)
WITH CHECK (
  created_by = auth.uid()
  AND status IN ('draft'::strategy_status, 'submitted'::strategy_status, 'rejected'::strategy_status)
);

CREATE POLICY "authors delete own pending strategies"
ON public.strategies FOR DELETE TO authenticated
USING (
  created_by = auth.uid()
  AND status IN ('draft'::strategy_status, 'submitted'::strategy_status, 'rejected'::strategy_status)
);
-- supabase/migrations/20260919040235_6e3f1d11-cac4-413b-9fe0-c1425dd2c261.sql
INSERT INTO public.strategies (id, created_by, name, tagline, description, prompt, universe, timeframe, status, risk_profile, max_position_pct, stop_loss_pct, take_profit_pct, min_confidence, is_dynamic, submitted_at, reviewed_at, reviewed_by, review_notes)
VALUES
('44444444-4444-4444-8444-444444444444', '482afa54-3d19-4b56-aebd-472e2a60758b',
 'Trend Ride — 50/200 Golden Cross', 'Ride confirmed medium-term uptrends in mega-cap tech.',
 'A patient trend-following program that only holds names whose medium-term trend is confirmed by both moving averages and momentum.',
 'You are a disciplined trend-following trader. BUY a symbol only when ALL of the following hold: the 50-day simple moving average is above the 200-day simple moving average; the last close is above the 50-day SMA; RSI(14) is between 50 and 70; and the 20-day average volume is above its 60-day average. SELL (exit the position) when the close falls below the 50-day SMA, or RSI(14) drops below 45, or the 50-day SMA crosses back below the 200-day SMA. Otherwise HOLD. Never add to a losing position. Maximum holding period 60 trading days.',
 ARRAY['AAPL','MSFT','NVDA','GOOGL','AMZN'], '1Day', 'testing', 'balanced', 18, 6, 15, 0.6, false,
 now() - interval '3 days', now() - interval '2 days', '482afa54-3d19-4b56-aebd-472e2a60758b', 'Clear rules, approved for historical validation.'),

('55555555-5555-4555-8555-555555555555', '482afa54-3d19-4b56-aebd-472e2a60758b',
 'RSI Snapback — Quality Dips', 'Buy short-term panic in financially strong large caps.',
 'A mean-reversion program buying sharp oversold dislocations in high quality large caps and exiting on the bounce back to the mean.',
 'You are a mean-reversion trader operating on daily bars. BUY when RSI(14) is below 30 AND the close is at least 5 percent below the 20-day simple moving average AND the 200-day SMA is still rising (close above the 200-day SMA). SELL when RSI(14) rises above 55, or the close reaches the 20-day SMA, or the trade has been open 12 trading days. Otherwise HOLD. Skip any symbol whose 14-day ATR exceeds 6 percent of price.',
 ARRAY['JNJ','PG','KO','JPM','UNH','HD'], '1Day', 'testing', 'conservative', 15, 5, 9, 0.6, false,
 now() - interval '3 days', now() - interval '2 days', '482afa54-3d19-4b56-aebd-472e2a60758b', 'Approved for historical validation.'),

('66666666-6666-4666-8666-666666666666', '482afa54-3d19-4b56-aebd-472e2a60758b',
 'Breakout Momentum — 20-Day High', 'Enter on decisive range breakouts with volume confirmation.',
 'A breakout program taking positions when price clears its recent trading range on expanding volume, with an ATR-based trailing exit.',
 'You are a breakout momentum trader. BUY when the close makes a new 20-day high AND that day''s volume is at least 1.5 times the 20-day average volume AND the close is above the 50-day simple moving average AND MACD is above its signal line. SELL when the close falls more than 2 times the 14-day ATR below the highest close achieved since entry, or the close drops below the 20-day SMA. Otherwise HOLD. Maximum holding period 40 trading days.',
 ARRAY['TSLA','AMD','META','NFLX','CRM'], '1Day', 'testing', 'aggressive', 20, 8, 20, 0.65, false,
 now() - interval '3 days', now() - interval '2 days', '482afa54-3d19-4b56-aebd-472e2a60758b', 'Approved for historical validation.'),

('77777777-7777-4777-8777-777777777777', '482afa54-3d19-4b56-aebd-472e2a60758b',
 'Low Volatility Carry — Defensive Sleeve', 'Hold steady low-beta names while volatility stays compressed.',
 'A defensive program that holds low volatility large caps while realized volatility is compressed and steps aside when it expands.',
 'You are a low-volatility trader. BUY when 20-day realized volatility is below 20 percent annualised AND the close is above both the 50-day and 200-day simple moving averages AND RSI(14) is between 45 and 65. SELL when 20-day realized volatility rises above 30 percent annualised, or the close falls below the 50-day SMA. Otherwise HOLD. Maximum holding period 90 trading days.',
 ARRAY['PEP','MRK','WMT','COST','MCD'], '1Day', 'submitted', 'conservative', 12, 4, 8, 0.55, false,
 now() - interval '10 hours', NULL, NULL, NULL);
-- supabase/migrations/20260920055408_86e21c45-7fbe-4683-b115-8356572f8d61.sql
ALTER TABLE public.strategies
  ADD COLUMN IF NOT EXISTS admin_rating smallint CHECK (admin_rating BETWEEN 1 AND 5),
  ADD COLUMN IF NOT EXISTS admin_rating_note text,
  ADD COLUMN IF NOT EXISTS rated_at timestamp with time zone,
  ADD COLUMN IF NOT EXISTS rated_by uuid REFERENCES auth.users(id);
-- supabase/migrations/20260930034600_273f72dc-f890-4147-be26-d781cb1d1013.sql
CREATE TABLE public.provider_keys (
  name text PRIMARY KEY,
  value text NOT NULL,
  updated_by uuid,
  updated_at timestamptz NOT NULL DEFAULT now()
);
GRANT ALL ON public.provider_keys TO service_role;
ALTER TABLE public.provider_keys ENABLE ROW LEVEL SECURITY;
