@reefclaw/connect 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/assets/bridge/gateway/event-parser.d.ts +6 -1
  2. package/assets/bridge/gateway/event-parser.js +19 -2
  3. package/assets/bridge/gateway/poller.d.ts +1 -0
  4. package/assets/bridge/gateway/poller.js +14 -2
  5. package/assets/bridge/providers/gateway.d.ts +22 -2
  6. package/assets/bridge/providers/gateway.js +67 -9
  7. package/assets/plugin/ccxt/binance-private.d.ts +21 -0
  8. package/assets/plugin/ccxt/binance-private.js +132 -22
  9. package/assets/plugin/ccxt/public-market-data-api.d.ts +14 -0
  10. package/assets/plugin/ccxt/public-market-data-api.js +15 -1
  11. package/assets/plugin/config/plugin-config-io.d.ts +13 -0
  12. package/assets/plugin/config/plugin-config-io.js +15 -0
  13. package/assets/plugin/config/tool-gate.js +3 -0
  14. package/assets/plugin/exchange-adapter.d.ts +16 -0
  15. package/assets/plugin/index.js +658 -83
  16. package/assets/plugin/ingest/position-auto-capture.d.ts +68 -0
  17. package/assets/plugin/ingest/position-auto-capture.js +321 -23
  18. package/assets/plugin/ingest/position-decisions-client.d.ts +7 -2
  19. package/assets/plugin/ingest/position-decisions-client.js +13 -3
  20. package/assets/plugin/ingest/reconcile-db-vs-exchange.d.ts +39 -1
  21. package/assets/plugin/ingest/reconcile-db-vs-exchange.js +66 -10
  22. package/assets/plugin/lifecycle/trading-operation-lock.d.ts +17 -0
  23. package/assets/plugin/lifecycle/trading-operation-lock.js +14 -0
  24. package/assets/plugin/live/bracket-id.d.ts +2 -3
  25. package/assets/plugin/live/bracket-id.js +22 -9
  26. package/assets/plugin/live/fill-price.d.ts +13 -0
  27. package/assets/plugin/live/fill-price.js +37 -0
  28. package/assets/plugin/live/live-adapter.d.ts +57 -2
  29. package/assets/plugin/live/live-adapter.js +290 -49
  30. package/assets/plugin/live/local-signal-service.js +11 -6
  31. package/assets/plugin/live/local-strategy-evaluator.js +4 -0
  32. package/assets/plugin/live/position-state-store.d.ts +4 -0
  33. package/assets/plugin/live/proposal-decision-listener.d.ts +6 -0
  34. package/assets/plugin/live/proposal-decision-listener.js +4 -0
  35. package/assets/plugin/live/stop-watcher.d.ts +35 -2
  36. package/assets/plugin/live/stop-watcher.js +63 -3
  37. package/assets/plugin/onboarding/runtime.d.ts +19 -0
  38. package/assets/plugin/onboarding/runtime.js +34 -3
  39. package/assets/plugin/openclaw.plugin.json +1 -0
  40. package/assets/plugin/portfolio/reentry-tracker.d.ts +36 -0
  41. package/assets/plugin/portfolio/reentry-tracker.js +127 -0
  42. package/assets/plugin/portfolio/wave9-admission.d.ts +67 -0
  43. package/assets/plugin/portfolio/wave9-admission.js +262 -0
  44. package/assets/plugin/portfolio/wave9-policy.d.ts +36 -0
  45. package/assets/plugin/portfolio/wave9-policy.js +183 -0
  46. package/assets/plugin/signals/conditions/registry.js +61 -2
  47. package/assets/plugin/signals/strategy-adapter.js +17 -7
  48. package/assets/plugin/simulator/exchange-simulator.d.ts +12 -0
  49. package/assets/plugin/simulator/exchange-simulator.js +78 -3
  50. package/assets/plugin/simulator/fill-engine.js +5 -1
  51. package/assets/plugin/simulator/types.d.ts +10 -1
  52. package/assets/plugin/strategy/evaluator.d.ts +3 -0
  53. package/assets/plugin/strategy/evaluator.js +5 -0
  54. package/assets/plugin/tools/assessment-validation.d.ts +23 -0
  55. package/assets/plugin/tools/assessment-validation.js +58 -0
  56. package/assets/plugin/tools/attach-brackets.d.ts +7 -2
  57. package/assets/plugin/tools/attach-brackets.js +201 -0
  58. package/assets/plugin/tools/audit-bracket-protection.js +157 -1
  59. package/assets/plugin/tools/bracket-control.d.ts +12 -0
  60. package/assets/plugin/tools/bracket-control.js +35 -0
  61. package/assets/plugin/tools/cancel-all-orders.d.ts +2 -0
  62. package/assets/plugin/tools/cancel-all-orders.js +4 -1
  63. package/assets/plugin/tools/cancel-order.d.ts +4 -0
  64. package/assets/plugin/tools/cancel-order.js +49 -3
  65. package/assets/plugin/tools/close-position.d.ts +23 -0
  66. package/assets/plugin/tools/close-position.js +286 -13
  67. package/assets/plugin/tools/create-order.d.ts +28 -0
  68. package/assets/plugin/tools/create-order.js +1390 -190
  69. package/assets/plugin/tools/get-analytics.js +2 -2
  70. package/assets/plugin/tools/get-basis.js +2 -2
  71. package/assets/plugin/tools/get-cascade-risk.js +2 -2
  72. package/assets/plugin/tools/get-crypto-metrics.js +14 -4
  73. package/assets/plugin/tools/get-cvd.js +2 -2
  74. package/assets/plugin/tools/get-divergences.js +2 -2
  75. package/assets/plugin/tools/get-funding-context.js +2 -2
  76. package/assets/plugin/tools/get-liquidation-levels.js +2 -2
  77. package/assets/plugin/tools/get-liquidation-pulse.js +2 -2
  78. package/assets/plugin/tools/get-pattern-scan.js +2 -2
  79. package/assets/plugin/tools/get-regime.js +2 -2
  80. package/assets/plugin/tools/get-resting-liquidity.js +2 -2
  81. package/assets/plugin/tools/get-risk-scenario.js +2 -2
  82. package/assets/plugin/tools/get-session-review.js +2 -2
  83. package/assets/plugin/tools/get-setup-detail.js +21 -2
  84. package/assets/plugin/tools/get-signals.js +2 -2
  85. package/assets/plugin/tools/get-sizing.js +2 -2
  86. package/assets/plugin/tools/get-trade-feedback.js +2 -2
  87. package/assets/plugin/tools/get-trade-flow.js +2 -2
  88. package/assets/plugin/tools/get-volume-profile.js +2 -2
  89. package/assets/plugin/tools/get-wave9-status.d.ts +127 -0
  90. package/assets/plugin/tools/get-wave9-status.js +796 -0
  91. package/assets/plugin/tools/intel-api.d.ts +20 -0
  92. package/assets/plugin/tools/intel-api.js +67 -0
  93. package/assets/plugin/tools/intel-cache.d.ts +1 -1
  94. package/assets/plugin/tools/intel-cache.js +20 -5
  95. package/assets/plugin/tools/list-strategies.d.ts +11 -1
  96. package/assets/plugin/tools/list-strategies.js +17 -0
  97. package/assets/plugin/tools/modify-stop.d.ts +4 -0
  98. package/assets/plugin/tools/modify-stop.js +63 -24
  99. package/assets/plugin/tools/modify-target.d.ts +4 -0
  100. package/assets/plugin/tools/modify-target.js +62 -23
  101. package/assets/plugin/tools/scan-pairs.d.ts +4 -0
  102. package/assets/plugin/tools/scan-pairs.js +22 -8
  103. package/assets/plugin/tools/toggle-strategy.js +7 -0
  104. package/assets/plugin/types.d.ts +5 -0
  105. package/assets/plugin/venues/hyperliquid/hl-balance.d.ts +116 -0
  106. package/assets/plugin/venues/hyperliquid/hl-balance.js +145 -0
  107. package/assets/plugin/venues/hyperliquid/hl-bracket-coordinator.d.ts +123 -0
  108. package/assets/plugin/venues/hyperliquid/hl-bracket-coordinator.js +533 -0
  109. package/assets/plugin/venues/hyperliquid/hl-brackets.d.ts +102 -0
  110. package/assets/plugin/venues/hyperliquid/hl-brackets.js +172 -0
  111. package/assets/plugin/venues/hyperliquid/hl-cloid.d.ts +22 -0
  112. package/assets/plugin/venues/hyperliquid/hl-cloid.js +82 -0
  113. package/assets/plugin/venues/hyperliquid/hl-info-cache.d.ts +46 -0
  114. package/assets/plugin/venues/hyperliquid/hl-info-cache.js +125 -0
  115. package/assets/plugin/venues/hyperliquid/hl-live-adapter.d.ts +146 -0
  116. package/assets/plugin/venues/hyperliquid/hl-live-adapter.js +728 -0
  117. package/assets/plugin/venues/hyperliquid/hl-precision.d.ts +61 -0
  118. package/assets/plugin/venues/hyperliquid/hl-precision.js +176 -0
  119. package/assets/plugin/venues/hyperliquid/hl-private.d.ts +88 -0
  120. package/assets/plugin/venues/hyperliquid/hl-private.js +357 -0
  121. package/assets/plugin/venues/hyperliquid/hl-public.d.ts +31 -4
  122. package/assets/plugin/venues/hyperliquid/hl-public.js +163 -12
  123. package/assets/plugin/venues/hyperliquid/hl-rate-gate.d.ts +57 -0
  124. package/assets/plugin/venues/hyperliquid/hl-rate-gate.js +220 -0
  125. package/assets/plugin/venues/hyperliquid/hl-user-stream.d.ts +90 -0
  126. package/assets/plugin/venues/hyperliquid/hl-user-stream.js +220 -0
  127. package/assets/plugin/venues/registry.d.ts +23 -9
  128. package/assets/plugin/venues/registry.js +12 -13
  129. package/assets/plugin/venues/symbols.d.ts +43 -0
  130. package/assets/plugin/venues/symbols.js +107 -0
  131. package/assets/plugin/wave9/live-account-capture.d.ts +67 -0
  132. package/assets/plugin/wave9/live-account-capture.js +435 -0
  133. package/assets/plugin/wave9/live-autonomous-protection.d.ts +39 -0
  134. package/assets/plugin/wave9/live-autonomous-protection.js +112 -0
  135. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.d.ts +33 -0
  136. package/assets/plugin/wave9/live-durable-reconciliation-scheduler.js +115 -0
  137. package/assets/plugin/wave9/live-execution-ledger.d.ts +107 -0
  138. package/assets/plugin/wave9/live-execution-ledger.js +498 -0
  139. package/assets/plugin/wave9/live-position-confirmation.d.ts +18 -0
  140. package/assets/plugin/wave9/live-position-confirmation.js +111 -0
  141. package/assets/plugin/wave9/live-residual-protection.d.ts +18 -0
  142. package/assets/plugin/wave9/live-residual-protection.js +250 -0
  143. package/assets/plugin/wave9/live-startup-reconciliation.d.ts +38 -0
  144. package/assets/plugin/wave9/live-startup-reconciliation.js +454 -0
  145. package/assets/plugin/wave9/live-symbol-ownership.d.ts +20 -0
  146. package/assets/plugin/wave9/live-symbol-ownership.js +132 -0
  147. package/assets/plugin/wave9/paper-admission-guard.d.ts +199 -0
  148. package/assets/plugin/wave9/paper-admission-guard.js +650 -0
  149. package/assets/plugin/wave9/usdm-evidence-provider.d.ts +42 -0
  150. package/assets/plugin/wave9/usdm-evidence-provider.js +133 -0
  151. package/assets/shared/portfolio/wave9-admission.d.ts +67 -0
  152. package/assets/shared/portfolio/wave9-admission.js +260 -0
  153. package/assets/shared/portfolio/wave9-policy.d.ts +36 -0
  154. package/assets/shared/portfolio/wave9-policy.js +176 -0
  155. package/assets/shared/signals/conditions/registry.js +61 -2
  156. package/assets/shared/signals/indicators-extended.d.ts +52 -0
  157. package/assets/shared/signals/indicators-extended.js +284 -0
  158. package/assets/shared/signals/indicators.d.ts +15 -0
  159. package/assets/shared/signals/indicators.js +107 -0
  160. package/assets/shared/signals/strategy-adapter.js +17 -7
  161. package/assets/skill/SKILL.md +2 -0
  162. package/dist/daemon.js +104 -0
  163. package/package.json +1 -1
@@ -0,0 +1,42 @@
1
+ import type { OhlcvBar } from '../signals/types.js';
2
+ export declare const FOUR_HOURS_MS: number;
3
+ export declare const FUNDING_INTERVAL_MS: number;
4
+ /** Binance has emitted historical funding timestamps three milliseconds after
5
+ * the nominal settlement boundary. Accept only tightly bounded source jitter,
6
+ * then canonicalize back to the exact research event identity. */
7
+ export declare const FUNDING_TIME_TOLERANCE_MS = 10;
8
+ export interface Wave9FundingSettlement {
9
+ time: Date;
10
+ rate: number;
11
+ }
12
+ export interface Wave9SymbolEvidence {
13
+ symbol: string;
14
+ venueSymbol: string;
15
+ dailyBars: OhlcvBar[];
16
+ fourHourBars: OhlcvBar[];
17
+ fundingSettlements: Wave9FundingSettlement[];
18
+ }
19
+ export interface Wave9EvidenceBatch {
20
+ venue: 'binance_usdm';
21
+ marketType: 'future';
22
+ eventTime: Date;
23
+ evidenceFromExclusive: Date;
24
+ symbols: Wave9SymbolEvidence[];
25
+ }
26
+ interface UsdmExchange {
27
+ id?: string;
28
+ options?: Record<string, unknown>;
29
+ fetchOHLCV(symbol: string, timeframe: string, since?: number, limit?: number): Promise<unknown[]>;
30
+ fetchFundingRateHistory(symbol: string, since?: number, limit?: number): Promise<unknown[]>;
31
+ }
32
+ export interface Wave9EvidenceProvider {
33
+ fetchAtomicBatch(eventTime: Date): Promise<Wave9EvidenceBatch>;
34
+ }
35
+ export declare class BinanceUsdmWave9EvidenceProvider implements Wave9EvidenceProvider {
36
+ private readonly exchange;
37
+ constructor(exchange: UsdmExchange);
38
+ fetchAtomicBatch(eventTime: Date): Promise<Wave9EvidenceBatch>;
39
+ }
40
+ /** Deliberately lazy: off mode imports this module but never loads/creates CCXT. */
41
+ export declare function createWave9UsdmEvidenceProvider(): Wave9EvidenceProvider;
42
+ export {};
@@ -0,0 +1,133 @@
1
+ import { createRequire } from 'node:module';
2
+ import { DAY_MS, WAVE9_CONTEXT_BARS, WAVE9_SYMBOL_PRIORITY, } from '../portfolio/wave9-policy.js';
3
+ export const FOUR_HOURS_MS = 4 * 60 * 60 * 1_000;
4
+ export const FUNDING_INTERVAL_MS = 8 * 60 * 60 * 1_000;
5
+ /** Binance has emitted historical funding timestamps three milliseconds after
6
+ * the nominal settlement boundary. Accept only tightly bounded source jitter,
7
+ * then canonicalize back to the exact research event identity. */
8
+ export const FUNDING_TIME_TOLERANCE_MS = 10;
9
+ function venueSymbol(symbol) {
10
+ if (!symbol.endsWith('USDT'))
11
+ throw new Error(`unsupported Wave 9 symbol ${symbol}`);
12
+ return `${symbol.slice(0, -4)}/USDT:USDT`;
13
+ }
14
+ function ohlcvRows(rows, label) {
15
+ return rows.map((raw, index) => {
16
+ if (!Array.isArray(raw) || raw.length < 6) {
17
+ throw new Error(`${label} row ${index} is not a complete OHLCV tuple`);
18
+ }
19
+ const values = raw.slice(0, 6).map(Number);
20
+ if (!values.every(Number.isFinite)) {
21
+ throw new Error(`${label} row ${index} contains a non-finite value`);
22
+ }
23
+ const [time, open, high, low, close, volume] = values;
24
+ if (open <= 0 || high <= 0 || low <= 0 || close <= 0 || volume < 0) {
25
+ throw new Error(`${label} row ${index} has invalid market values`);
26
+ }
27
+ if (high < Math.max(open, close) || low > Math.min(open, close)) {
28
+ throw new Error(`${label} row ${index} has invalid OHLC geometry`);
29
+ }
30
+ return { time: new Date(time), open, high, low, close, volume };
31
+ });
32
+ }
33
+ function fundingRows(rows, label) {
34
+ return rows.map((raw, index) => {
35
+ const row = raw;
36
+ const info = row?.info;
37
+ const time = Number(row?.timestamp ?? info?.fundingTime);
38
+ const rate = Number(row?.fundingRate ?? info?.fundingRate);
39
+ if (!Number.isFinite(time) || !Number.isFinite(rate)) {
40
+ throw new Error(`${label} funding row ${index} is incomplete`);
41
+ }
42
+ return { time: new Date(time), rate };
43
+ });
44
+ }
45
+ function requireExactTimes(actual, expected, label) {
46
+ const times = actual.map((row) => row.time.getTime());
47
+ if (times.length !== expected.length || times.some((time, index) => time !== expected[index])) {
48
+ throw new Error(`${label} is partial or stale: expected [${expected.map((time) => new Date(time).toISOString()).join(', ')}], got [${times.map((time) => new Date(time).toISOString()).join(', ')}]`);
49
+ }
50
+ }
51
+ function canonicalizeFundingTimes(actual, expected, label) {
52
+ const times = actual.map((row) => row.time.getTime());
53
+ const mismatched = times.length !== expected.length
54
+ || times.some((time, index) => Math.abs(time - expected[index]) > FUNDING_TIME_TOLERANCE_MS);
55
+ if (mismatched) {
56
+ throw new Error(`${label} is partial or stale: expected [${expected.map((time) => new Date(time).toISOString()).join(', ')}] `
57
+ + `within ${FUNDING_TIME_TOLERANCE_MS}ms, got [${times.map((time) => new Date(time).toISOString()).join(', ')}]`);
58
+ }
59
+ return actual.map((row, index) => ({ ...row, time: new Date(expected[index]) }));
60
+ }
61
+ export class BinanceUsdmWave9EvidenceProvider {
62
+ exchange;
63
+ constructor(exchange) {
64
+ this.exchange = exchange;
65
+ if (exchange.id !== 'binanceusdm' || exchange.options?.defaultType !== 'future') {
66
+ throw new Error('Wave 9 evidence provider requires Binance USD-M futures (binanceusdm/defaultType=future)');
67
+ }
68
+ }
69
+ async fetchAtomicBatch(eventTime) {
70
+ const eventMs = eventTime.getTime();
71
+ if (!Number.isFinite(eventMs) || eventMs % DAY_MS !== 0) {
72
+ throw new Error('Wave 9 event must be a UTC daily boundary');
73
+ }
74
+ const fromMs = eventMs - DAY_MS;
75
+ const expectedDaily = Array.from({ length: WAVE9_CONTEXT_BARS }, (_, index) => eventMs - (WAVE9_CONTEXT_BARS - index) * DAY_MS);
76
+ const expected4h = Array.from({ length: 7 }, (_, index) => fromMs + index * FOUR_HOURS_MS);
77
+ const expectedFunding = [fromMs + FUNDING_INTERVAL_MS, fromMs + 2 * FUNDING_INTERVAL_MS, eventMs];
78
+ // Promise.all is only a transport optimization. No caller sees a batch
79
+ // unless every one of the eight frozen symbols validates in full.
80
+ const symbols = await Promise.all(WAVE9_SYMBOL_PRIORITY.map(async (symbol) => {
81
+ const ccxtSymbol = venueSymbol(symbol);
82
+ const [dailyRaw, fourHourRaw, fundingRaw] = await Promise.all([
83
+ this.exchange.fetchOHLCV(ccxtSymbol, '1d', eventMs - (WAVE9_CONTEXT_BARS + 2) * DAY_MS, WAVE9_CONTEXT_BARS + 2),
84
+ this.exchange.fetchOHLCV(ccxtSymbol, '4h', fromMs, 7),
85
+ this.exchange.fetchFundingRateHistory(ccxtSymbol, fromMs + 1, 16),
86
+ ]);
87
+ const dailyBars = ohlcvRows(dailyRaw, `${symbol} 1d`)
88
+ .filter((bar) => bar.time.getTime() + DAY_MS <= eventMs)
89
+ .slice(-WAVE9_CONTEXT_BARS);
90
+ const fourHourBars = ohlcvRows(fourHourRaw, `${symbol} 4h`)
91
+ .filter((bar) => bar.time.getTime() >= fromMs && bar.time.getTime() <= eventMs)
92
+ .sort((left, right) => left.time.getTime() - right.time.getTime());
93
+ const fundingRowsInWindow = fundingRows(fundingRaw, symbol)
94
+ .filter((row) => row.time.getTime() > fromMs
95
+ && row.time.getTime() <= eventMs + FUNDING_TIME_TOLERANCE_MS)
96
+ .sort((left, right) => left.time.getTime() - right.time.getTime());
97
+ requireExactTimes(dailyBars, expectedDaily, `${symbol} completed 1d evidence`);
98
+ requireExactTimes(fourHourBars, expected4h, `${symbol} 4h evidence`);
99
+ const fundingSettlements = canonicalizeFundingTimes(fundingRowsInWindow, expectedFunding, `${symbol} funding evidence`);
100
+ return {
101
+ symbol,
102
+ venueSymbol: ccxtSymbol,
103
+ dailyBars,
104
+ fourHourBars,
105
+ fundingSettlements,
106
+ };
107
+ }));
108
+ if (symbols.some((row, index) => row.symbol !== WAVE9_SYMBOL_PRIORITY[index])) {
109
+ throw new Error('Wave 9 evidence universe/order mismatch');
110
+ }
111
+ return {
112
+ venue: 'binance_usdm',
113
+ marketType: 'future',
114
+ eventTime: new Date(eventMs),
115
+ evidenceFromExclusive: new Date(fromMs),
116
+ symbols,
117
+ };
118
+ }
119
+ }
120
+ /** Deliberately lazy: off mode imports this module but never loads/creates CCXT. */
121
+ export function createWave9UsdmEvidenceProvider() {
122
+ const require = createRequire(import.meta.url);
123
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
124
+ const ccxt = require('ccxt');
125
+ const BinanceUsdm = ccxt.binanceusdm ?? ccxt.default?.binanceusdm;
126
+ if (!BinanceUsdm)
127
+ throw new Error('CCXT binanceusdm class is unavailable');
128
+ const exchange = new BinanceUsdm({
129
+ enableRateLimit: true,
130
+ options: { defaultType: 'future' },
131
+ });
132
+ return new BinanceUsdmWave9EvidenceProvider(exchange);
133
+ }
@@ -0,0 +1,67 @@
1
+ import type { Direction } from '../signals/types.js';
2
+ import { type Wave9StrategyName } from './wave9-policy.js';
3
+ export interface Wave9AdmissionPolicy {
4
+ riskPct: number;
5
+ maxPositions: number;
6
+ maxOpenRiskPct: number;
7
+ maxGrossExposureMultiple: number;
8
+ maxPositionUsd: number;
9
+ feeBps: number;
10
+ slippageBps: number;
11
+ capEnforcement: 'admission_only_no_forced_deleveraging';
12
+ riskMeasure: 'fixed_initial_stop_risk_usd';
13
+ }
14
+ export declare const WAVE9_ADMISSION_POLICY: Readonly<Wave9AdmissionPolicy>;
15
+ export interface Wave9AdmissionPosition {
16
+ candidateId: string;
17
+ symbol: string;
18
+ strategy: Wave9StrategyName;
19
+ direction: Direction;
20
+ entryPrice: number;
21
+ quantity: number;
22
+ markPrice: number;
23
+ initialStop: number;
24
+ dollarRiskUsd: number;
25
+ accruedFundingUsd: number;
26
+ }
27
+ export interface Wave9AdmissionState {
28
+ cashEquityUsd: number;
29
+ positions: Wave9AdmissionPosition[];
30
+ }
31
+ export interface Wave9AdmissionCandidate {
32
+ id: string;
33
+ eventTime: string;
34
+ symbol: string;
35
+ strategy: Wave9StrategyName;
36
+ direction: Direction;
37
+ referencePrice: number;
38
+ stopPrice: number;
39
+ }
40
+ export type Wave9AdmissionReason = 'accepted' | 'invalidGeometry' | 'symbolAlreadyOpen' | 'maxPositions' | 'maxGrossExposure' | 'maxOpenRisk' | 'invalidSizing';
41
+ export interface Wave9AdmissionLedgerRow extends Wave9AdmissionPosition {
42
+ estimatedExitCostUsd: number;
43
+ }
44
+ export interface Wave9AdmissionDecision extends Wave9AdmissionCandidate {
45
+ accepted: boolean;
46
+ reason: Wave9AdmissionReason;
47
+ entryPrice?: number;
48
+ quantity?: number;
49
+ notionalUsd?: number;
50
+ riskUsd?: number;
51
+ projectedCashEquityUsd?: number;
52
+ projectedLiquidationEquityUsd?: number;
53
+ projectedGrossExposureUsd?: number;
54
+ projectedOpenRiskUsd?: number;
55
+ projectedOpenPositions?: number;
56
+ projectedPositionLedger?: Wave9AdmissionLedgerRow[];
57
+ }
58
+ export interface Wave9AdmissionBatchResult {
59
+ decisions: Wave9AdmissionDecision[];
60
+ state: Wave9AdmissionState;
61
+ }
62
+ /**
63
+ * Apply a same-event candidate batch in the exact frozen priority. The caller
64
+ * must release stops and completed-daily reversals before invoking this
65
+ * function, just as the chronological research engine does.
66
+ */
67
+ export declare function applyWave9AdmissionBatch(inputState: Wave9AdmissionState, rawCandidates: readonly Wave9AdmissionCandidate[], policy?: Readonly<Wave9AdmissionPolicy>): Wave9AdmissionBatchResult;
@@ -0,0 +1,260 @@
1
+ // Pure chronological Wave 9 admission policy. It mirrors the research
2
+ // portfolio's configured-symbol-then-strategy order and liquidation-equity
3
+ // cap arithmetic. Runtime safety gates are intentionally not folded into this
4
+ // function; the shadow observer compares them separately and must never claim
5
+ // behavioral parity when they disagree.
6
+ import { WAVE9_LONG_STRATEGY, WAVE9_STRATEGY_PRIORITY, WAVE9_SYMBOL_PRIORITY, } from './wave9-policy.js';
7
+ export const WAVE9_ADMISSION_POLICY = Object.freeze({
8
+ riskPct: 0.5,
9
+ maxPositions: 4,
10
+ maxOpenRiskPct: 2,
11
+ maxGrossExposureMultiple: 1.5,
12
+ maxPositionUsd: 10_000,
13
+ feeBps: 5,
14
+ slippageBps: 10,
15
+ capEnforcement: 'admission_only_no_forced_deleveraging',
16
+ riskMeasure: 'fixed_initial_stop_risk_usd',
17
+ });
18
+ function finitePositive(value) {
19
+ return Number.isFinite(value) && value > 0;
20
+ }
21
+ function nearlyEqual(left, right) {
22
+ return Math.abs(left - right) <= 1e-9 * Math.max(1, Math.abs(left), Math.abs(right));
23
+ }
24
+ function validatePolicy(policy) {
25
+ for (const [label, value] of Object.entries({
26
+ riskPct: policy.riskPct,
27
+ maxOpenRiskPct: policy.maxOpenRiskPct,
28
+ maxGrossExposureMultiple: policy.maxGrossExposureMultiple,
29
+ maxPositionUsd: policy.maxPositionUsd,
30
+ })) {
31
+ if (!finitePositive(value))
32
+ throw new Error(`${label} must be positive and finite`);
33
+ }
34
+ if (!Number.isInteger(policy.maxPositions) || policy.maxPositions <= 0) {
35
+ throw new Error('maxPositions must be a positive integer');
36
+ }
37
+ for (const [label, value] of Object.entries({
38
+ feeBps: policy.feeBps,
39
+ slippageBps: policy.slippageBps,
40
+ })) {
41
+ if (!Number.isFinite(value) || value < 0) {
42
+ throw new Error(`${label} must be finite and non-negative`);
43
+ }
44
+ }
45
+ if (policy.capEnforcement !== 'admission_only_no_forced_deleveraging'
46
+ || policy.riskMeasure !== 'fixed_initial_stop_risk_usd') {
47
+ throw new Error('unsupported Wave 9 admission policy semantics');
48
+ }
49
+ }
50
+ function rank(value, values, label) {
51
+ const result = values.indexOf(value);
52
+ if (result < 0)
53
+ throw new Error(`${label} is not in the frozen Wave 9 priority`);
54
+ return result;
55
+ }
56
+ function validateCandidateIdentity(id, eventTime, symbol, strategy, direction) {
57
+ const eventMs = Date.parse(eventTime);
58
+ if (!Number.isFinite(eventMs) || new Date(eventMs).toISOString() !== eventTime) {
59
+ throw new Error('candidate eventTime must be a canonical ISO timestamp');
60
+ }
61
+ const expectedId = `${eventTime}|${symbol}|${strategy}|${direction}`;
62
+ if (id !== expectedId) {
63
+ throw new Error(`candidate ID does not match its frozen identity: ${id}`);
64
+ }
65
+ }
66
+ function copyPosition(position) {
67
+ return { ...position };
68
+ }
69
+ function ledgerFor(positions, policy) {
70
+ const exitCostRate = (policy.feeBps + policy.slippageBps) / 10_000;
71
+ return positions.map((position) => ({
72
+ ...copyPosition(position),
73
+ estimatedExitCostUsd: position.markPrice * position.quantity * exitCostRate,
74
+ }));
75
+ }
76
+ function summarize(cashEquityUsd, ledger) {
77
+ let unrealized = 0;
78
+ let funding = 0;
79
+ let exitCosts = 0;
80
+ let gross = 0;
81
+ let risk = 0;
82
+ for (const row of ledger) {
83
+ unrealized += row.direction === 'LONG'
84
+ ? (row.markPrice - row.entryPrice) * row.quantity
85
+ : (row.entryPrice - row.markPrice) * row.quantity;
86
+ funding += row.accruedFundingUsd;
87
+ exitCosts += row.estimatedExitCostUsd;
88
+ gross += Math.abs(row.markPrice * row.quantity);
89
+ risk += row.dollarRiskUsd;
90
+ }
91
+ return { equity: cashEquityUsd + unrealized - funding - exitCosts, gross, risk };
92
+ }
93
+ /**
94
+ * Apply a same-event candidate batch in the exact frozen priority. The caller
95
+ * must release stops and completed-daily reversals before invoking this
96
+ * function, just as the chronological research engine does.
97
+ */
98
+ export function applyWave9AdmissionBatch(inputState, rawCandidates, policy = WAVE9_ADMISSION_POLICY) {
99
+ validatePolicy(policy);
100
+ if (!Number.isFinite(inputState.cashEquityUsd)) {
101
+ throw new Error('cashEquityUsd must be finite');
102
+ }
103
+ const positions = inputState.positions.map(copyPosition);
104
+ const ids = new Set();
105
+ for (const position of positions) {
106
+ if (ids.has(position.candidateId))
107
+ throw new Error('open candidate IDs must be unique');
108
+ ids.add(position.candidateId);
109
+ rank(position.symbol, WAVE9_SYMBOL_PRIORITY, 'open-position symbol');
110
+ rank(position.strategy, WAVE9_STRATEGY_PRIORITY, 'open-position strategy');
111
+ const identity = position.candidateId.split('|');
112
+ if (identity.length !== 4) {
113
+ throw new Error(`open-position candidate ID is malformed: ${position.candidateId}`);
114
+ }
115
+ validateCandidateIdentity(position.candidateId, identity[0], position.symbol, position.strategy, position.direction);
116
+ if (!finitePositive(position.entryPrice)
117
+ || !finitePositive(position.quantity)
118
+ || !finitePositive(position.markPrice)
119
+ || !finitePositive(position.initialStop)
120
+ || !Number.isFinite(position.dollarRiskUsd)
121
+ || position.dollarRiskUsd < 0
122
+ || !Number.isFinite(position.accruedFundingUsd)) {
123
+ throw new Error(`invalid open position ${position.candidateId}`);
124
+ }
125
+ const expectedDirection = position.strategy === WAVE9_LONG_STRATEGY ? 'LONG' : 'SHORT';
126
+ const geometryValid = position.direction === expectedDirection
127
+ && (position.direction === 'LONG'
128
+ ? position.initialStop < position.entryPrice
129
+ : position.initialStop > position.entryPrice);
130
+ if (!geometryValid) {
131
+ throw new Error(`invalid open-position geometry ${position.candidateId}`);
132
+ }
133
+ const expectedRisk = Math.abs(position.entryPrice - position.initialStop)
134
+ * position.quantity;
135
+ if (!nearlyEqual(position.dollarRiskUsd, expectedRisk)) {
136
+ throw new Error(`open-position risk mismatch ${position.candidateId}`);
137
+ }
138
+ }
139
+ if (new Set(positions.map((position) => position.symbol)).size !== positions.length) {
140
+ throw new Error('Wave 9 allows at most one open position per symbol');
141
+ }
142
+ const candidates = rawCandidates.map((candidate) => ({ ...candidate }));
143
+ const candidateIds = new Set();
144
+ let batchEventTime;
145
+ for (const candidate of candidates) {
146
+ if (candidateIds.has(candidate.id))
147
+ throw new Error('candidate IDs must be unique');
148
+ candidateIds.add(candidate.id);
149
+ validateCandidateIdentity(candidate.id, candidate.eventTime, candidate.symbol, candidate.strategy, candidate.direction);
150
+ if (batchEventTime === undefined)
151
+ batchEventTime = candidate.eventTime;
152
+ if (candidate.eventTime !== batchEventTime) {
153
+ throw new Error('a Wave 9 admission batch must contain exactly one eventTime');
154
+ }
155
+ rank(candidate.symbol, WAVE9_SYMBOL_PRIORITY, 'candidate symbol');
156
+ rank(candidate.strategy, WAVE9_STRATEGY_PRIORITY, 'candidate strategy');
157
+ }
158
+ candidates.sort((left, right) => rank(left.symbol, WAVE9_SYMBOL_PRIORITY, 'candidate symbol')
159
+ - rank(right.symbol, WAVE9_SYMBOL_PRIORITY, 'candidate symbol')
160
+ || rank(left.strategy, WAVE9_STRATEGY_PRIORITY, 'candidate strategy')
161
+ - rank(right.strategy, WAVE9_STRATEGY_PRIORITY, 'candidate strategy')
162
+ || left.id.localeCompare(right.id));
163
+ let cashEquityUsd = inputState.cashEquityUsd;
164
+ const decisions = [];
165
+ const feeRate = policy.feeBps / 10_000;
166
+ const slippageRate = policy.slippageBps / 10_000;
167
+ for (const candidate of candidates) {
168
+ const decision = {
169
+ ...candidate,
170
+ accepted: false,
171
+ reason: 'invalidSizing',
172
+ };
173
+ const expectedDirection = candidate.strategy === WAVE9_LONG_STRATEGY ? 'LONG' : 'SHORT';
174
+ if (candidate.direction !== expectedDirection
175
+ || !finitePositive(candidate.referencePrice)
176
+ || !finitePositive(candidate.stopPrice)) {
177
+ decision.reason = 'invalidGeometry';
178
+ decisions.push(decision);
179
+ continue;
180
+ }
181
+ const entryPrice = candidate.direction === 'LONG'
182
+ ? candidate.referencePrice * (1 + slippageRate)
183
+ : candidate.referencePrice * (1 - slippageRate);
184
+ decision.entryPrice = entryPrice;
185
+ if ((candidate.direction === 'LONG' && candidate.stopPrice >= entryPrice)
186
+ || (candidate.direction === 'SHORT' && candidate.stopPrice <= entryPrice)) {
187
+ decision.reason = 'invalidGeometry';
188
+ decisions.push(decision);
189
+ continue;
190
+ }
191
+ if (positions.some((position) => position.symbol === candidate.symbol)) {
192
+ decision.reason = 'symbolAlreadyOpen';
193
+ decisions.push(decision);
194
+ continue;
195
+ }
196
+ if (positions.length >= policy.maxPositions) {
197
+ decision.reason = 'maxPositions';
198
+ decisions.push(decision);
199
+ continue;
200
+ }
201
+ const current = summarize(cashEquityUsd, ledgerFor(positions, policy));
202
+ const riskPerUnit = Math.abs(entryPrice - candidate.stopPrice);
203
+ const riskBudget = current.equity * policy.riskPct / 100;
204
+ const quantity = Math.min(riskBudget / riskPerUnit, policy.maxPositionUsd / entryPrice);
205
+ const notionalUsd = quantity * entryPrice;
206
+ const riskUsd = quantity * riskPerUnit;
207
+ Object.assign(decision, { quantity, notionalUsd, riskUsd });
208
+ if (!finitePositive(quantity) || current.equity <= 0) {
209
+ decision.reason = 'invalidSizing';
210
+ decisions.push(decision);
211
+ continue;
212
+ }
213
+ const position = {
214
+ candidateId: candidate.id,
215
+ symbol: candidate.symbol,
216
+ strategy: candidate.strategy,
217
+ direction: candidate.direction,
218
+ entryPrice,
219
+ quantity,
220
+ markPrice: candidate.referencePrice,
221
+ initialStop: candidate.stopPrice,
222
+ dollarRiskUsd: riskUsd,
223
+ accruedFundingUsd: 0,
224
+ };
225
+ const projectedPositions = [...positions, position];
226
+ const projectedCash = cashEquityUsd - notionalUsd * feeRate;
227
+ const projectedLedger = ledgerFor(projectedPositions, policy);
228
+ const projected = summarize(projectedCash, projectedLedger);
229
+ Object.assign(decision, {
230
+ projectedCashEquityUsd: projectedCash,
231
+ projectedLiquidationEquityUsd: projected.equity,
232
+ projectedGrossExposureUsd: projected.gross,
233
+ projectedOpenRiskUsd: projected.risk,
234
+ projectedOpenPositions: projectedPositions.length,
235
+ projectedPositionLedger: projectedLedger,
236
+ });
237
+ if (projected.equity <= 0) {
238
+ decision.reason = 'invalidSizing';
239
+ }
240
+ else if (projected.gross
241
+ > projected.equity * policy.maxGrossExposureMultiple + 1e-9) {
242
+ decision.reason = 'maxGrossExposure';
243
+ }
244
+ else if (projected.risk
245
+ > projected.equity * policy.maxOpenRiskPct / 100 + 1e-9) {
246
+ decision.reason = 'maxOpenRisk';
247
+ }
248
+ else {
249
+ decision.accepted = true;
250
+ decision.reason = 'accepted';
251
+ cashEquityUsd = projectedCash;
252
+ positions.push(position);
253
+ }
254
+ decisions.push(decision);
255
+ }
256
+ return {
257
+ decisions,
258
+ state: { cashEquityUsd, positions: positions.map(copyPosition) },
259
+ };
260
+ }
@@ -0,0 +1,36 @@
1
+ import type { Direction, OhlcvBar } from '../signals/types.js';
2
+ export declare const DAY_MS: number;
3
+ export declare const WAVE9_LOOKBACK_DAYS = 28;
4
+ export declare const WAVE9_ATR_PERIOD = 14;
5
+ export declare const WAVE9_ATR_MULTIPLE = 3;
6
+ export declare const WAVE9_CONTEXT_BARS = 250;
7
+ export declare const WAVE9_LONG_STRATEGY = "tsmom_28d_long_flat_reversal_1d";
8
+ export declare const WAVE9_SHORT_STRATEGY = "tsmom_28d_short_flat_reversal_1d";
9
+ export type Wave9StrategyName = typeof WAVE9_LONG_STRATEGY | typeof WAVE9_SHORT_STRATEGY;
10
+ export declare const WAVE9_SYMBOL_PRIORITY: readonly ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "LINKUSDT"];
11
+ export declare const WAVE9_STRATEGY_PRIORITY: readonly ["tsmom_28d_long_flat_reversal_1d", "tsmom_28d_short_flat_reversal_1d"];
12
+ export interface Wave9EntrySignal {
13
+ strategy: Wave9StrategyName;
14
+ direction: Direction;
15
+ stopPrice: number;
16
+ }
17
+ export interface Wave9DailyDecision {
18
+ eventTime: string;
19
+ completedDailyOpen: string;
20
+ currentReturn: number;
21
+ previousReturn: number;
22
+ atr14: number;
23
+ referencePrice: number;
24
+ entries: Wave9EntrySignal[];
25
+ exitLong: boolean;
26
+ exitShort: boolean;
27
+ sourceBarCount: number;
28
+ }
29
+ /**
30
+ * Return the exact completed-daily context visible at a UTC daily boundary.
31
+ * The function is intentionally fail-closed: unordered, duplicated, gapped,
32
+ * non-UTC, or stale candles are rejected rather than silently repaired.
33
+ */
34
+ export declare function completedDailyContext(rawBars: readonly OhlcvBar[], eventTime: Date): OhlcvBar[];
35
+ /** Evaluate the exact Wave 9 entry-transition and signal-reversal semantics. */
36
+ export declare function evaluateWave9Daily(rawBars: readonly OhlcvBar[], eventTime: Date): Wave9DailyDecision;