Initial commit: btcdata — BTC data backend service

Split from btcprice monolith. Owns DB, migrations, Binance WebSocket,
Kraken OHLC pollers, and exposes a JSON API with SSE price stream.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 20:07:30 +01:00
co-authored by Claude Opus 4.6
commit f64aa98b04
35 changed files with 1079 additions and 0 deletions
@@ -0,0 +1 @@
DROP TABLE binance_price;
@@ -0,0 +1,5 @@
CREATE TABLE binance_price (
id BIGSERIAL PRIMARY KEY,
price NUMERIC(18,8) NOT NULL,
recorded_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
@@ -0,0 +1 @@
DROP TABLE kraken_hour;
@@ -0,0 +1,11 @@
CREATE TABLE kraken_hour (
ts BIGINT NOT NULL PRIMARY KEY,
open NUMERIC(18,8) NOT NULL,
high NUMERIC(18,8) NOT NULL,
low NUMERIC(18,8) NOT NULL,
close NUMERIC(18,8) NOT NULL,
vwap NUMERIC(18,8) NOT NULL,
volume NUMERIC(24,8) NOT NULL,
trade_count INTEGER NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
@@ -0,0 +1 @@
DROP TABLE kraken_day;
@@ -0,0 +1,11 @@
CREATE TABLE kraken_day (
ts BIGINT NOT NULL PRIMARY KEY,
open NUMERIC(18,8) NOT NULL,
high NUMERIC(18,8) NOT NULL,
low NUMERIC(18,8) NOT NULL,
close NUMERIC(18,8) NOT NULL,
vwap NUMERIC(18,8) NOT NULL,
volume NUMERIC(24,8) NOT NULL,
trade_count INTEGER NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
+44
View File
@@ -0,0 +1,44 @@
-- queries for btcdata
-- :name insert-binance-price! :! :n
-- :doc Insert a Binance BTC price
INSERT INTO binance_price (price) VALUES (:price)
-- :name get-latest-binance-price :? :1
-- :doc Get the most recent Binance BTC price
SELECT price, recorded_at FROM binance_price ORDER BY id DESC LIMIT 1
-- :name upsert-kraken-hour! :! :n
-- :doc Upsert a Kraken hourly OHLC candle
INSERT INTO kraken_hour (ts, open, high, low, close, vwap, volume, trade_count)
VALUES (:ts, :open, :high, :low, :close, :vwap, :volume, :trade-count)
ON CONFLICT (ts) DO UPDATE
SET open = EXCLUDED.open,
high = EXCLUDED.high,
low = EXCLUDED.low,
close = EXCLUDED.close,
vwap = EXCLUDED.vwap,
volume = EXCLUDED.volume,
trade_count = EXCLUDED.trade_count
-- :name get-latest-kraken-hour :? :1
-- :doc Get the most recent Kraken hourly candle by timestamp
SELECT ts, open, high, low, close, vwap, volume, trade_count, created_at
FROM kraken_hour ORDER BY ts DESC LIMIT 1
-- :name upsert-kraken-day! :! :n
-- :doc Upsert a Kraken daily OHLC candle
INSERT INTO kraken_day (ts, open, high, low, close, vwap, volume, trade_count)
VALUES (:ts, :open, :high, :low, :close, :vwap, :volume, :trade-count)
ON CONFLICT (ts) DO UPDATE
SET open = EXCLUDED.open,
high = EXCLUDED.high,
low = EXCLUDED.low,
close = EXCLUDED.close,
vwap = EXCLUDED.vwap,
volume = EXCLUDED.volume,
trade_count = EXCLUDED.trade_count
-- :name get-latest-kraken-day :? :1
-- :doc Get the most recent Kraken daily candle by timestamp
SELECT ts FROM kraken_day ORDER BY ts DESC LIMIT 1
+59
View File
@@ -0,0 +1,59 @@
{:system/env #profile {:dev :dev :test :test :prod :prod}
:server/http
{:port #long #or [#env BTCDATA_PORT 4100]
:host #or [#env HTTP_HOST "0.0.0.0"]
:handler #ig/ref :handler/ring}
:handler/ring
{:router #ig/ref :router/core
:api-path "/api"
:cors-origin #or [#env CORS_ORIGIN "*"]
:site-defaults-config
{:params {:keywordize true
:multipart true
:nested true}
:cookies false
:session false
:security {:anti-forgery false
:xss-protection {:enable? true :mode :block}
:frame-options :sameorigin}
:responses {:not-modified-responses true
:absolute-redirects true
:content-types true
:default-charset "utf-8"}}}
:router/routes
{:routes #ig/refset :reitit/routes}
:router/core
{:routes #ig/ref :router/routes
:env #ig/ref :system/env}
:db.sql/connection
{:jdbc-url #env JDBC_URL}
:db.sql/query-fn
{:conn #ig/ref :db.sql/connection
:options {}
:filename "queries.sql"}
:db.sql/migrations
{:store :database
:db {:datasource #ig/ref :db.sql/connection}
:migrate-on-init? true}
:reitit.routes/api
{:base-path "/api"
:query-fn #ig/ref :db.sql/query-fn
:binance #ig/ref :ws/binance}
:ws/binance
{:query-fn #ig/ref :db.sql/query-fn
:uri "wss://stream.binance.com:9443/ws/btcusdt@trade"}
:kraken/ohlc
{:query-fn #ig/ref :db.sql/query-fn}
:kraken/ohlc-day
{:query-fn #ig/ref :db.sql/query-fn}}