source: server/db/schema_creation.sql@ fe28254

main
Last change on this file since fe28254 was b715712, checked in by Stefan <trsunovstefan@…>, 8 weeks ago

Add the server side and configuration

  • Property mode set to 100644
File size: 8.8 KB
Line 
1-- schema_creation.sql
2-- EduBerza - crypto exchange simulation database
3-- Course: Databases 2025/2026 Winter, FINKI UKIM
4--
5-- This script is idempotent. It drops the `project` schema and all contained
6-- objects, then recreates them from scratch. Safe to run on an empty database
7-- or on a database where the schema already exists.
8
9DROP SCHEMA IF EXISTS project CASCADE;
10CREATE SCHEMA project;
11
12CREATE EXTENSION IF NOT EXISTS pgcrypto;
13
14SET search_path TO project, public;
15
16-- ============================================================================
17-- USERS
18-- Platform users. Each user has virtual (prop) balances used for simulation.
19-- ============================================================================
20CREATE TABLE project.users (
21 id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
22 username varchar(50) NOT NULL UNIQUE,
23 email varchar(255) NOT NULL UNIQUE,
24 full_name varchar(200),
25 password_hash varchar(255) NOT NULL,
26 available_balance numeric(18,4) NOT NULL DEFAULT 0 CHECK (available_balance >= 0),
27 invested_balance numeric(18,4) NOT NULL DEFAULT 0 CHECK (invested_balance >= 0),
28 created_at timestamptz NOT NULL DEFAULT now(),
29 updated_at timestamptz
30);
31
32-- ============================================================================
33-- CRYPTO
34-- Catalog of crypto assets available on the platform.
35-- ============================================================================
36CREATE TABLE project.crypto (
37 id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
38 symbol varchar(20) NOT NULL UNIQUE,
39 name varchar(255) NOT NULL,
40 created_at timestamptz NOT NULL DEFAULT now()
41);
42
43-- ============================================================================
44-- MARKETS
45-- A market is a (crypto, quote_currency) pair, e.g. BTC/USD.
46-- ============================================================================
47CREATE TABLE project.markets (
48 id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
49 crypto_id uuid NOT NULL REFERENCES project.crypto(id),
50 quote_currency char(3) NOT NULL DEFAULT 'USD',
51 is_active boolean NOT NULL DEFAULT true,
52 created_at timestamptz NOT NULL DEFAULT now(),
53 CONSTRAINT uq_markets UNIQUE (crypto_id, quote_currency)
54);
55
56-- ============================================================================
57-- HOLDINGS
58-- Per-user crypto position with running weighted average entry price.
59-- ============================================================================
60CREATE TABLE project.holdings (
61 id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
62 user_id uuid NOT NULL REFERENCES project.users(id) ON DELETE CASCADE,
63 crypto_id uuid NOT NULL REFERENCES project.crypto(id),
64 quantity numeric(20,4) NOT NULL CHECK (quantity >= 0),
65 -- Weighted-average entry price. NOT NULL so that the P/L arithmetic in
66 -- v_portfolio can never silently produce NULL for an existing position.
67 avg_price numeric(18,6) NOT NULL DEFAULT 0 CHECK (avg_price >= 0),
68 created_at timestamptz NOT NULL DEFAULT now(),
69 updated_at timestamptz,
70 CONSTRAINT uq_holdings_user_crypto UNIQUE (user_id, crypto_id)
71);
72
73-- ============================================================================
74-- ORDERS
75-- Orders placed by users on a market.
76-- ============================================================================
77CREATE TABLE project.orders (
78 id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
79 user_id uuid NOT NULL REFERENCES project.users(id) ON DELETE CASCADE,
80 market_id uuid NOT NULL REFERENCES project.markets(id),
81 side varchar(4) NOT NULL CHECK (side IN ('buy', 'sell')),
82 type varchar(20) NOT NULL CHECK (type IN ('market', 'limit')),
83 status varchar(20) NOT NULL CHECK (status IN ('open', 'executed', 'cancelled')),
84 quantity numeric(20,4) NOT NULL CHECK (quantity > 0),
85 price numeric(18,6),
86 placed_at timestamptz NOT NULL DEFAULT now(),
87 executed_at timestamptz
88);
89
90CREATE INDEX idx_orders_user ON project.orders(user_id);
91CREATE INDEX idx_orders_market ON project.orders(market_id);
92CREATE INDEX idx_orders_status ON project.orders(status);
93
94-- ============================================================================
95-- TRANSACTIONS
96-- Financial ledger: deposits, buys, sells, fees.
97-- ============================================================================
98CREATE TABLE project.transactions (
99 id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
100 user_id uuid NOT NULL REFERENCES project.users(id) ON DELETE CASCADE,
101 type varchar(50) NOT NULL CHECK (type IN ('deposit', 'buy', 'sell', 'fee')),
102 amount numeric(18,4) NOT NULL,
103 currency char(3) NOT NULL DEFAULT 'USD',
104 related_order uuid REFERENCES project.orders(id),
105 created_at timestamptz NOT NULL DEFAULT now(),
106 description text
107);
108
109CREATE INDEX idx_transactions_user ON project.transactions(user_id, created_at DESC);
110
111-- ============================================================================
112-- MARKET TRADES
113-- Raw executed trades on a market. Source of truth for current price.
114-- ============================================================================
115CREATE TABLE project.market_trades (
116 id bigserial PRIMARY KEY,
117 market_id uuid NOT NULL REFERENCES project.markets(id),
118 executed_at timestamptz NOT NULL,
119 price numeric(18,6) NOT NULL CHECK (price > 0),
120 quantity numeric(20,6) NOT NULL CHECK (quantity > 0),
121 side varchar(4) CHECK (side IN ('buy', 'sell')),
122 source varchar(50) NOT NULL DEFAULT 'simulation'
123);
124
125CREATE INDEX idx_market_trades_market_time ON project.market_trades(market_id, executed_at DESC);
126
127-- ============================================================================
128-- MARKET CANDLES
129-- OHLCV aggregates over standard timeframes.
130-- ============================================================================
131CREATE TABLE project.market_candles (
132 id bigserial PRIMARY KEY,
133 market_id uuid NOT NULL REFERENCES project.markets(id),
134 timeframe varchar(5) NOT NULL CHECK (timeframe IN ('1m', '5m', '1h', '1d')),
135 open numeric(18,6) NOT NULL,
136 high numeric(18,6) NOT NULL,
137 low numeric(18,6) NOT NULL,
138 close numeric(18,6) NOT NULL,
139 volume numeric(20,6) NOT NULL,
140 candle_time timestamptz NOT NULL,
141 CONSTRAINT uq_candle UNIQUE (market_id, timeframe, candle_time)
142);
143
144CREATE INDEX idx_market_candles_market_tf_time ON project.market_candles(market_id, timeframe, candle_time DESC);
145
146-- ============================================================================
147-- WATCHLISTS
148-- ============================================================================
149CREATE TABLE project.watchlists (
150 id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
151 user_id uuid NOT NULL REFERENCES project.users(id) ON DELETE CASCADE,
152 name varchar(100) NOT NULL,
153 created_at timestamptz NOT NULL DEFAULT now()
154);
155
156CREATE TABLE project.watchlist_items (
157 id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
158 watchlist_id uuid NOT NULL REFERENCES project.watchlists(id) ON DELETE CASCADE,
159 crypto_id uuid NOT NULL REFERENCES project.crypto(id),
160 added_at timestamptz NOT NULL DEFAULT now(),
161 CONSTRAINT uq_watchlist_crypto UNIQUE (watchlist_id, crypto_id)
162);
163
164-- ============================================================================
165-- VIEWS
166-- ============================================================================
167
168-- Latest trade price per market (current price).
169CREATE OR REPLACE VIEW project.v_latest_prices AS
170SELECT DISTINCT ON (t.market_id)
171 t.market_id,
172 c.symbol,
173 m.quote_currency,
174 t.price,
175 t.executed_at
176FROM project.market_trades t
177JOIN project.markets m ON m.id = t.market_id
178JOIN project.crypto c ON c.id = m.crypto_id
179ORDER BY t.market_id, t.executed_at DESC;
180
181-- Portfolio valuation per user (holdings x latest price).
182CREATE OR REPLACE VIEW project.v_portfolio AS
183SELECT h.user_id,
184 c.symbol,
185 h.quantity,
186 h.avg_price,
187 lp.price AS current_price,
188 (h.quantity * lp.price) AS market_value,
189 (h.quantity * (lp.price - h.avg_price)) AS unrealized_pnl
190FROM project.holdings h
191JOIN project.crypto c ON c.id = h.crypto_id
192LEFT JOIN project.markets m ON m.crypto_id = c.id AND m.quote_currency = 'USD'
193LEFT JOIN project.v_latest_prices lp ON lp.market_id = m.id;
Note: See TracBrowser for help on using the repository browser.