RISE Framework Specification
Spec ID: 71
Version: 1.0
Document ID: caishen-rise-pdsp-schema-v1.0
Last Updated: 2026-08-01
Source of truth: src/Caishen/Databases/SpiderDbDatabase/gia-mssql/PDSDb.script-221031.sql (UTF-16LE)
What This Schema Enables:
Desired Outcomes:
The price store (PDSDb) holds seven tables in two clusters:
Price cluster — the core, written by the refresh engine:
PDSPPrices — OHLC barsPDSPInstrumentProperties — trading metadataAnalysis cluster — derived, written by the indicator/analysis engines:
PanoIndicators — per-bar computed indicator panelSequencialPanoDatas — per-range aggregate statisticsPanoSegments — labelled sub-sequences of a rangeCasans — case analyses (a named study over a period)TradedSignalExamples — annotated example signals for learningThe strategy/ordering domain lives in a separate database (SpiderDb,
spec 76). The two are joined only in application code, never by a cross-database
foreign key. Preserve that separation: price data has a fundamentally different
write pattern, retention need, and blast radius than strategy data.
PDSPPricesThe central table.
TABLE PricePoints
povTlid varchar(32) NOT NULL -- PRIMARY KEY
instrument varchar(16) NOT NULL
timeframe varchar(4) NULL -- see note
askO, askH, askL, askC float NOT NULL
bidO, bidH, bidL, bidC float NOT NULL
volume int NOT NULL
dt datetime NOT NULL -- period START
isIncompleted bit NULL -- see note
instrumentPropertyRef varchar(16) NOT NULL -- FK
PRIMARY KEY CLUSTERED (povTlid)
FOREIGN KEY (instrumentPropertyRef) REFERENCES InstrumentProperties(instrument)
Key derivation: povTlid is computed, not assigned. See spec 70,
Bar Identity. The clustered primary key on a content-derived string is the
schema’s defining choice — it makes every write idempotent and every identity
lookup a single seek.
Schema corrections required for re-implementation:
| Issue | Original | Required |
|---|---|---|
timeframe nullable |
NULL allowed |
NOT NULL — every bar has a timeframe; the key already encodes it |
isIncompleted nullable |
NULL allowed, readers treat null as false |
NOT NULL DEFAULT false |
instrument duplicated |
Both instrument and instrumentPropertyRef hold the same value |
Collapse to one column that is both the FK and the filter column |
dt zone |
Naive datetime | Timestamp with timezone, stored UTC (spec 70, Time semantics) |
| Missing index | Only the clustered PK exists | Add the covering index below |
Required index — this is the load-bearing one. Stated as a requirement, with DDL only as an illustration:
Requirement I1. Reads of the form
(instrument, timeframe)+dtdescending must be served by an index that covers the bar columns — i.e. satisfiable without touching the base table.
-- Illustration in one dialect; express it however the target store covers.
INDEX ix_series_time ON PricePoints (instrument, timeframe, dt DESC)
INCLUDE (askO, askH, askL, askC, bidO, bidH, bidL, bidC, volume, isIncompleted)
Targets without an INCLUDE-style payload satisfy I1 differently — a composite
index with the payload columns in the key, a clustering/sort key on
(instrument, timeframe, dt), or a columnar layout. The requirement is coverage
and ordering, not this syntax.
Every read in the system has the shape
WHERE instrument = ? AND timeframe = ? AND dt <= ? ORDER BY dt DESC LIMIT n.
Without this index that is a full scan of the clustered key, which is ordered by
key string — i.e. by instrument name then by lexical timestamp, which is not
chronological across timeframe formats. The original had no such index and
compensated with periodic index reorganization, which does not address the
problem. This index is not an optimization; it is a correctness-of-performance
requirement.
Second index, for the incremental path:
Requirement I2. Locating the forming bar for a series must be a bounded lookup, not a scan.
INDEX ix_incomplete ON PricePoints (instrument, timeframe, isIncompleted, dt DESC)
WHERE isIncompleted = true -- filtered/partial index where supported
The incremental algorithm (spec 72) probes for the anchor on every run, for every
series. Where filtered/partial indexes are unavailable, the unfiltered composite
still satisfies I2; the filter is an optimization that keeps the index to at
most one row per active series.
PDSPInstrumentPropertiesTABLE InstrumentProperties
instrument varchar(16) NOT NULL -- PRIMARY KEY
pipSize float NOT NULL
precision int NOT NULL
mmr float NOT NULL -- minimum margin requirement
lmr float NOT NULL -- liquidation margin requirement
quantityMinimim int NOT NULL -- [sic] original spelling
quantityMaximim int NOT NULL -- [sic] original spelling
baseUnitSize int NOT NULL
contractMultiplier float NOT NULL
contractCurrency varchar(8000) NOT NULL -- see note
trailingStepMinimum int NOT NULL
trailingStepMaximum int NOT NULL
subscriptionStatus bit NOT NULL
marketCode varchar(64) NOT NULL
PRIMARY KEY CLUSTERED (instrument)
Corrections required: fix the Maximim/Minimim misspellings; size
contractCurrency to an actual currency code (varchar(8)) rather than
varchar(8000); make marketCode a constrained enumeration
(FOREX | COMMODITY | INDICE | TREASURY).
The varchar(8000) widths appear throughout both databases — they are an
artifact of the code generator defaulting to maximum width, not a design intent.
Size every string column to its actual domain.
These tables are written by the indicator and analysis engines (specs 05, 04), not by the price refresher. They are documented here because they live in the same database and key off the same identity.
PanoIndicatorsOne row per bar, holding the full computed indicator panel. Keyed by the same
povTlid as the bar it describes — a strict 1:1 extension of PricePoints.
~64 columns. Grouped by concern:
| Group | Columns |
|---|---|
| Alligator (Bill Williams) | Lips, Teeth, Jaw, Throat, GatorLower, GatorUpper, GatorSupreme, NbBarMouthIsOpen, NbBarPriceOutMouth |
| Awesome / Accelerator oscillators | AO, AOF, AC, ACD, SAO, Zone, ZLC, Saucer, StDevAO |
| Fractals | Fractal, Fractal5/8/13/21/34/55, FractalBuy, FractalSell, FractalDimension, FractalInitBuySignal, FractalInitSellSignal |
| Divergent bars | BDB (bullish), FDB (fractal divergent bar) |
| Distances | distGLRL, distGLBL, distRLBL, distRLPrice, distGLPrice, distBLPrice |
| Divergence | MPDivergence, DivergenceIndicator, DivergenceStrenght [sic], LabelDivergence, LabelAngulation |
| Other | Squat, MFI, IsExtreme, BarType, StDevClose, IsEntryPoint, IsExitPoint |
| Range link | PovTlidRange → SequencialPanoDatas |
| Scratch | int1..int3, double1..double3, bool1..bool3, string1..string3 |
The scratch columns are a schema smell to remove. They appear in four tables
(PanoIndicators, SequencialPanoDatas, Casans, PanoSegments) and exist
because adding a column to a generated model was expensive. In a re-implementation
use a typed extension mechanism (a JSON/document column with a declared schema,
or a proper migration workflow). Carrying int1..int3 forward guarantees the
same undocumented-meaning problem recurs.
Foreign key defect to fix: the original declares six separate foreign keys
all on the single column PovTlid — pointing at Casans, PanoSegments,
PanoSegments1, PDSPPrices, TradedSignalExamples, and
TradedSignalExamples1 — plus a seventh on PovTlidRange to
SequencialPanoDatas. One column cannot meaningfully reference six parents, and
two of the six are duplicate constraints against the same target table under a
1-suffixed name.
Only the PDSPPrices reference (and the PovTlidRange one) is real; the rest
are generator artifacts and should be dropped. If a genuine association to
segments or cases is needed, model it with its own column or a join table.
SequencialPanoDatasAggregate statistics over a range of bars rather than a single bar.
TABLE RangeStatistics
povTlidRange varchar(64) NOT NULL -- PRIMARY KEY
instrument varchar(16) NOT NULL
timeframe varchar(3) NOT NULL
nbPeriods int NULL
dtStart, dtEnd datetime NULL
bdbAvgHeight float NULL
stDevAO, stDevClose float NOT NULL
dist{GLRL,GLBL,RLBL,RLPrice,GLPrice,BLPrice}Max float NOT NULL
dist{...}Min float NOT NULL
dist{...}Stdev float NOT NULL
(+ scratch columns)
Range identity: povTlidRange extends the bar key format with a second
timestamp — <instrument>_<tf>__<tlidFrom>__<tlidTo> (see
BarTlider.MkPovTimerangedTlid). Same virtue as the bar key: a range is named
by its content, so the same range computed twice collides rather than duplicates.
Note timeframe varchar(3) here versus varchar(4) in PDSPPrices — an
inconsistency that truncates 4-character codes. Unify.
Casans (Case Analyses)A named study over a period, self-referencing to form a hierarchy.
TABLE CaseAnalyses
cid varchar(32) NOT NULL -- PRIMARY KEY
label varchar(160) NOT NULL
category varchar(64) NULL
typeName varchar(64) NULL
dtStart, dtEnd datetime NOT NULL
stepsText varchar(8000) NULL
notes varchar(8000) NULL
binLabel bit NULL
profitPerContract float NULL
profitPips float NULL
parentCasanCid varchar(32) NULL -- FK → self (hierarchy)
(+ scratch columns)
PanoSegmentsA labelled sub-sequence within a case analysis; also self-referencing.
TABLE Segments
idug varchar(32) NOT NULL -- PRIMARY KEY
tagName varchar(128) NOT NULL
transformType varchar(128) NOT NULL
category varchar(64) NULL
eventName varchar(8000) NULL
timeOrigin int NULL
desired bit NULL -- the intended vs actual flag
isCasanPanoPoint bit NULL
notes varchar(8000) NULL
parentSegmentIdug varchar(32) NULL -- FK → self
parentCid varchar(32) NOT NULL -- FK → CaseAnalyses
desired marks a segment as representing an intended outcome rather than an
observed one — the structural-tension pattern that recurs across this platform.
TradedSignalExamplesAnnotated example signals, keyed by idug, linked to a case analysis via
parentCid, carrying tagName, entryType, exitType, and notes. Used as
training/reference material for signal recognition.
The original defines 45 views. They fall into four families, three of which should not be reproduced.
vPDSPricesNProp -- bars joined to their instrument's pipSize and precision
vPDSPPricesDt -- (dt, instrument, timeframe) only: the cheap existence probe
vPDSPrices -- bars plus a computed "instrument_timeframe" label
vPDSPPricesDt is genuinely load-bearing: it backs GetPeriodTimestamps, which
backs the range fast-path in spec 72.
vIsIncompleted__ALL -- all bars still forming
vIsIncompleted_{M1,W1,H4,D1} -- per-timeframe variants
The __ALL view is the useful one; the per-timeframe copies are the same query
with a literal. Replace all five with one parameterized query.
Note the original predicate is WHERE IsIncompleted LIKE '1' — a string
comparison against a bit column. Use a boolean comparison.
vAUDUSD_H4, vAUDUSD_H1, vAUDUSD_W1, vAUDUSD_m5, vAUDUSD_m15, vAUDUSD_M1,
vEURUSD_*, vGBPUSD_*, vUSDCAD_*, ohlcvAUDUSD_*, vEURUSD_H4__202207, ...
Roughly 35 views, each hardcoding one instrument and one timeframe — some hardcoding a specific month. They exist because the tooling of the day made ad-hoc parameterized queries awkward. They are pure duplication and they rot: adding an instrument means adding seven views.
Replace with a single parameterized query or view function:
GetOHLC(instrument, timeframe, dtFrom, dtTo, priceSide)
priceSide ∈ { ask, bid, mid, median }
Note the derivations the originals encode, because they are the real content:
ohlcvAUDUSD_D1 — ask side, aliased to Date/Open/High/Low/Close/VolumeohlcvAUDUSD_H4 — same plus Median = (askH + askL + bidH + bidL) / 4sp_csv_avg_exporter (spec 75) — mid side, (ask + bid) / 2 per OHLC componentSeveral also carry SELECT TOP (100) PERCENT ... ORDER BY 'Date'. That orders by
a string literal, not by the column — the quotes make it a constant. The
TOP (100) PERCENT prefix is a legacy trick to allow ORDER BY in a view, and
it does not guarantee ordering anyway. Never rely on view-level ordering.
Order in the query that consumes it.
x__Indicator_AVG -- 15-period centred moving average of the mid bar
ohlcvAUDUSD_D1_xAVG2206 -- three overlapping asymmetric moving averages
x__Indicator_AVG computes:
babar = (askH + askL + bidH + bidL) / 4
avg = AVG(babar) OVER (ORDER BY dt ROWS BETWEEN 7 PRECEDING AND 7 FOLLOWING)
ohlcvAUDUSD_D1_xAVG2206 computes three of them with asymmetric windows —
(5 preceding, 3 following), (8, 5), (13, 8). Those are Fibonacci-spaced
lookback/lookahead pairs, and they are the SQL expression of the Alligator’s
displaced moving averages.
Two things matter here:
Keep the formulas. Compute them in the indicator layer (spec 05), where the displacement is explicit and testable.
PDSDb defines 24 procedures in four groups:
| Group | Procedures | Disposition |
|---|---|---|
| Query | sp_pov_select, sp_pov_get_last, sp_dt_pov_select, sp_pov_delete |
Fold into the data layer (spec 70) |
| Export | sp_csv_avg_exporter*, sp_csv_nb_suffix*, sp_csv_dt_nb_suffix*, sp_csv_gz_nb_suffix, sp_csv_pov_exporter*, sp_ic_csv_pov_exporter |
See spec 75 |
| Compute | GetChartNormalized, sp_get_pov_norm_range |
See spec 75 |
| Admin | sp_adm_index_rebuild, sp_adm_index_organize, adm__chk__r__version, adm__ls__default_lib, adm_get_pandas_version, adm_get_sklearn_version, sp_ls_py_pack |
Operational; replace with platform tooling |
sp_pov_get_lastSELECT TOP(1) <bar columns>
FROM PricePoints
WHERE instrument = @i AND timeframe = @tf
ORDER BY dt DESC
Straightforward and correct. It is the only procedure whose logic must be preserved verbatim in the data layer.
sp_pov_deleteDeletes a whole series. Destructive and unguarded — the repo contains several
ad-hoc deletion scripts alongside it (debug220720__delete_*, delete__M1.sql,
Delete__All__InstrumentProperty.sql) that were clearly used interactively.
Requirement: any series-deletion capability in the re-implementation must be
explicit, audited, and require a confirmation token. The original’s
ConfirmDangerousActionForm in the test project shows the author reached the
same conclusion.
sp_adm_index_rebuild / sp_adm_index_organize rebuild or reorganize the
table’s indexes. The CLI exposes them as reorg and rebuild subcommands and
optionally runs a reorg automatically at the end of a refresh (driven by a
REORGINDEX environment setting).
This was necessary because the clustered key is a non-monotonic string — new
bars for AUD/USD insert in the middle of the key order, fragmenting pages
continuously. With the covering index specified above and a monotonic physical
ordering, the maintenance pressure largely disappears.
Requirement: treat automatic index maintenance as a symptom to be designed out, not a feature to port. If the target store still needs it, schedule it as platform maintenance rather than as a step inside the data refresh.
One trigger exists, in an incomplete state: lastUpdated on PDSPPrices
AFTER UPDATE. It executes Python inside the database engine and appends to a
file on disk. Both the create and alter scripts show a hardcoded literal
('NAS100') where the affected instrument should be, and the subprocess.call
that would have notified downstream systems is commented out.
This was an experiment toward database-level change notification (spec 73) that was never finished.
Requirement: do not implement change notification as a database trigger that shells out. Use the application-level event contract in spec 73, or the target store’s native change-feed/logical-replication mechanism if one exists. The intent — “downstream systems learn that a series changed, without polling” — is right; the mechanism is not.
The original ran both databases in a single SQL Server 2019 container (codename ZEUS), with:
/data/bak, then copied off-host/data/exports/csvPDSDb; SpiderDb is on 120Container definitions, volume backup/restore scripts, and the attach/detach
runbook are catalogued in README.md of this spec set.
Requirements carried forward:
| Object | Source |
|---|---|
| Full schema export | gia-mssql/PDSDb.script-221031.sql (UTF-16LE, 7,620 lines) |
| Earlier exports (for diffing) | gia-mssql/PDSDb__scriptExport__{2208091244,220907,220922}.sql |
| Trigger experiment | gia-mssql/PDSPPrices.Trigger-ON-lastUpdated__designing220919{,.altering}.sql |
| Index maintenance | gia-mssql/adm__INDEX_{Rebuild,Reorg}.sql |
| Table creation fragments | gia-mssql/cd__Bars__TableCreate.sql, gia-mssql/cd__POVData__TableCreate.sql |
| UTC validation | gia-mssql/tests/220925-validate_UTC_fixed.sql |
| Known period defect | gia-mssql/issue220810__Extra_Period_compared_to_FXTradeStation.sql |
| Backup/restore runbook | gia-mssql/README.md |