feat(quant): WBS-FE-BE-100 complete Vue3 Vite8 SPA & .NET10 FastEndpoints refactoring
This commit is contained in:
+120
@@ -0,0 +1,120 @@
|
||||
-- V8__PostgreSQL_History_First_Schema.sql
|
||||
-- PostgreSQL History-First Operating Model Canonical Database Schema
|
||||
-- Standard: 3NF Relational Core + JSONB Provenance Payload + Audit Logging
|
||||
|
||||
-- 1. Create Schema if not exists
|
||||
CREATE SCHEMA IF NOT EXISTS quantengine;
|
||||
|
||||
-- 2. Market Raw History Time-Series
|
||||
CREATE TABLE IF NOT EXISTS quantengine.market_raw_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
ticker VARCHAR(32) NOT NULL,
|
||||
as_of_date VARCHAR(10) NOT NULL,
|
||||
open_price NUMERIC(18, 4),
|
||||
high_price NUMERIC(18, 4),
|
||||
low_price NUMERIC(18, 4),
|
||||
close_price NUMERIC(18, 4) NOT NULL,
|
||||
volume BIGINT,
|
||||
nav_price NUMERIC(18, 4),
|
||||
disparate_ratio NUMERIC(10, 6),
|
||||
tracking_error NUMERIC(10, 6),
|
||||
aum_krw NUMERIC(20, 2),
|
||||
raw_payload JSONB NOT NULL,
|
||||
provenance JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
CONSTRAINT uk_market_raw_ticker_date UNIQUE (ticker, as_of_date)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_market_raw_ticker_date ON quantengine.market_raw_history (ticker, as_of_date DESC);
|
||||
|
||||
-- 3. Factor Version & Definition History
|
||||
CREATE TABLE IF NOT EXISTS quantengine.factor_version_history (
|
||||
factor_id VARCHAR(64) PRIMARY KEY,
|
||||
formula_name VARCHAR(128) NOT NULL,
|
||||
version VARCHAR(32) NOT NULL,
|
||||
category VARCHAR(64) NOT NULL,
|
||||
calibration_state VARCHAR(32) NOT NULL DEFAULT 'UNTESTED',
|
||||
threshold_params JSONB NOT NULL,
|
||||
description TEXT,
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 4. Factor Output History (Calculation Store)
|
||||
CREATE TABLE IF NOT EXISTS quantengine.factor_output_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
run_id VARCHAR(64) NOT NULL,
|
||||
ticker VARCHAR(32) NOT NULL,
|
||||
as_of_date VARCHAR(10) NOT NULL,
|
||||
factor_id VARCHAR(64) NOT NULL,
|
||||
score NUMERIC(10, 4),
|
||||
calculation_state VARCHAR(32) NOT NULL,
|
||||
provenance JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
CONSTRAINT fk_factor_version FOREIGN KEY (factor_id) REFERENCES quantengine.factor_version_history (factor_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_factor_output_run_ticker ON quantengine.factor_output_history (run_id, ticker);
|
||||
|
||||
-- 5. Decision Result History (Signal & Risk Engine Output)
|
||||
CREATE TABLE IF NOT EXISTS quantengine.decision_result_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
run_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
as_of_date VARCHAR(10) NOT NULL,
|
||||
market_regime VARCHAR(32) NOT NULL,
|
||||
portfolio_health VARCHAR(32) NOT NULL,
|
||||
rebalance_required BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
mid_check_required BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
total_asset_krw NUMERIC(20, 2) NOT NULL,
|
||||
d2_cash_krw NUMERIC(20, 2) NOT NULL,
|
||||
decision_packet_json JSONB NOT NULL,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- 6. Order Waterfall Execution History
|
||||
CREATE TABLE IF NOT EXISTS quantengine.order_waterfall_execution_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
run_id VARCHAR(64) NOT NULL,
|
||||
ticker VARCHAR(32) NOT NULL,
|
||||
sell_priority_rank INT NOT NULL,
|
||||
waterfall_stage VARCHAR(64) NOT NULL,
|
||||
action VARCHAR(16) NOT NULL,
|
||||
target_qty INT NOT NULL,
|
||||
executed_qty INT DEFAULT 0,
|
||||
target_price NUMERIC(18, 4),
|
||||
executed_price NUMERIC(18, 4),
|
||||
bid_ask_spread_bps NUMERIC(10, 2),
|
||||
slippage_bps NUMERIC(10, 2),
|
||||
status VARCHAR(32) NOT NULL,
|
||||
rationale TEXT,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
CONSTRAINT fk_decision_waterfall FOREIGN KEY (run_id) REFERENCES quantengine.decision_result_history (run_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 7. Shadow Ledger History (Blocked/Gate Orders Audit)
|
||||
CREATE TABLE IF NOT EXISTS quantengine.shadow_ledger_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
run_id VARCHAR(64) NOT NULL,
|
||||
ticker VARCHAR(32) NOT NULL,
|
||||
blocked_gate VARCHAR(64) NOT NULL,
|
||||
blocked_reason TEXT NOT NULL,
|
||||
shadow_price NUMERIC(18, 4) NOT NULL,
|
||||
shadow_qty INT NOT NULL,
|
||||
shadow_tp_price NUMERIC(18, 4),
|
||||
shadow_sl_price NUMERIC(18, 4),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
CONSTRAINT fk_decision_shadow FOREIGN KEY (run_id) REFERENCES quantengine.decision_result_history (run_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 8. Scheduler State Machine History
|
||||
CREATE TABLE IF NOT EXISTS quantengine.scheduler_state_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
task_name VARCHAR(64) NOT NULL,
|
||||
execution_id VARCHAR(64) NOT NULL UNIQUE,
|
||||
state VARCHAR(32) NOT NULL,
|
||||
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
finished_at TIMESTAMPTZ,
|
||||
error_message TEXT,
|
||||
lock_token VARCHAR(64)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_scheduler_state_task ON quantengine.scheduler_state_history (task_name, state);
|
||||
@@ -6,6 +6,10 @@ using QuantEngine.Core.Interfaces;
|
||||
|
||||
namespace QuantEngine.Infrastructure.Repositories
|
||||
{
|
||||
/// <summary>
|
||||
/// PostgreSQL Dapper Repository implementation for History-First Operating Model.
|
||||
/// Manages 3NF Data Integrity, Waterfall Auditing, and Shadow Ledger persistence.
|
||||
/// </summary>
|
||||
public class PostgresqlHistoryStore : IPostgresqlHistoryStore
|
||||
{
|
||||
private readonly IDbConnectionFactory _connectionFactory;
|
||||
@@ -68,5 +72,35 @@ namespace QuantEngine.Infrastructure.Repositories
|
||||
var rows = await conn.QueryAsync(sql, new { Limit = limit });
|
||||
return rows.Select(row => (IDictionary<string, object?>)row).ToList();
|
||||
}
|
||||
|
||||
public async Task<long> RecordWaterfallExecutionAsync(string runId, string ticker, int rank, string stage, string action, int targetQty, decimal? targetPrice, decimal? bidAskSpreadBps, decimal? slippageBps, string status, string rationale)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
conn.Open();
|
||||
|
||||
const string sql = @"
|
||||
INSERT INTO quantengine.order_waterfall_execution_history
|
||||
(run_id, ticker, sell_priority_rank, waterfall_stage, action, target_qty, target_price, bid_ask_spread_bps, slippage_bps, status, rationale)
|
||||
VALUES
|
||||
(@runId, @ticker, @rank, @stage, @action, @targetQty, @targetPrice, @bidAskSpreadBps, @slippageBps, @status, @rationale)
|
||||
RETURNING id;";
|
||||
|
||||
return await conn.ExecuteScalarAsync<long>(sql, new { runId, ticker, rank, stage, action, targetQty, targetPrice, bidAskSpreadBps, slippageBps, status, rationale });
|
||||
}
|
||||
|
||||
public async Task<long> RecordShadowLedgerAsync(string runId, string ticker, string blockedGate, string blockedReason, decimal shadowPrice, int shadowQty, decimal? shadowTpPrice, decimal? shadowSlPrice)
|
||||
{
|
||||
using var conn = _connectionFactory.CreateConnection();
|
||||
conn.Open();
|
||||
|
||||
const string sql = @"
|
||||
INSERT INTO quantengine.shadow_ledger_history
|
||||
(run_id, ticker, blocked_gate, blocked_reason, shadow_price, shadow_qty, shadow_tp_price, shadow_sl_price)
|
||||
VALUES
|
||||
(@runId, @ticker, @blockedGate, @blockedReason, @shadowPrice, @shadowQty, @shadowTpPrice, @shadowSlPrice)
|
||||
RETURNING id;";
|
||||
|
||||
return await conn.ExecuteScalarAsync<long>(sql, new { runId, ticker, blockedGate, blockedReason, shadowPrice, shadowQty, shadowTpPrice, shadowSlPrice });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user