thevoidforge 21.0.11 → 21.0.12

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 (107) hide show
  1. package/dist/.claude/commands/ai.md +69 -0
  2. package/dist/.claude/commands/architect.md +121 -0
  3. package/dist/.claude/commands/assemble.md +201 -0
  4. package/dist/.claude/commands/assess.md +75 -0
  5. package/dist/.claude/commands/blueprint.md +135 -0
  6. package/dist/.claude/commands/build.md +116 -0
  7. package/dist/.claude/commands/campaign.md +201 -0
  8. package/dist/.claude/commands/cultivation.md +166 -0
  9. package/dist/.claude/commands/current.md +128 -0
  10. package/dist/.claude/commands/dangerroom.md +74 -0
  11. package/dist/.claude/commands/debrief.md +178 -0
  12. package/dist/.claude/commands/deploy.md +99 -0
  13. package/dist/.claude/commands/devops.md +143 -0
  14. package/dist/.claude/commands/gauntlet.md +140 -0
  15. package/dist/.claude/commands/git.md +104 -0
  16. package/dist/.claude/commands/grow.md +146 -0
  17. package/dist/.claude/commands/imagine.md +126 -0
  18. package/dist/.claude/commands/portfolio.md +50 -0
  19. package/dist/.claude/commands/prd.md +113 -0
  20. package/dist/.claude/commands/qa.md +107 -0
  21. package/dist/.claude/commands/review.md +151 -0
  22. package/dist/.claude/commands/security.md +100 -0
  23. package/dist/.claude/commands/test.md +96 -0
  24. package/dist/.claude/commands/thumper.md +116 -0
  25. package/dist/.claude/commands/treasury.md +100 -0
  26. package/dist/.claude/commands/ux.md +118 -0
  27. package/dist/.claude/commands/vault.md +189 -0
  28. package/dist/.claude/commands/void.md +108 -0
  29. package/dist/CHANGELOG.md +1918 -0
  30. package/dist/CLAUDE.md +250 -0
  31. package/dist/HOLOCRON.md +856 -0
  32. package/dist/VERSION.md +123 -0
  33. package/dist/docs/NAMING_REGISTRY.md +478 -0
  34. package/dist/docs/methods/AI_INTELLIGENCE.md +276 -0
  35. package/dist/docs/methods/ASSEMBLER.md +142 -0
  36. package/dist/docs/methods/BACKEND_ENGINEER.md +165 -0
  37. package/dist/docs/methods/BUILD_JOURNAL.md +185 -0
  38. package/dist/docs/methods/BUILD_PROTOCOL.md +426 -0
  39. package/dist/docs/methods/CAMPAIGN.md +568 -0
  40. package/dist/docs/methods/CONTEXT_MANAGEMENT.md +189 -0
  41. package/dist/docs/methods/DEEP_CURRENT.md +184 -0
  42. package/dist/docs/methods/DEVOPS_ENGINEER.md +295 -0
  43. package/dist/docs/methods/FIELD_MEDIC.md +261 -0
  44. package/dist/docs/methods/FORGE_ARTIST.md +108 -0
  45. package/dist/docs/methods/FORGE_KEEPER.md +268 -0
  46. package/dist/docs/methods/GAUNTLET.md +344 -0
  47. package/dist/docs/methods/GROWTH_STRATEGIST.md +466 -0
  48. package/dist/docs/methods/HEARTBEAT.md +168 -0
  49. package/dist/docs/methods/MCP_INTEGRATION.md +139 -0
  50. package/dist/docs/methods/MUSTER.md +148 -0
  51. package/dist/docs/methods/PRD_GENERATOR.md +186 -0
  52. package/dist/docs/methods/PRODUCT_DESIGN_FRONTEND.md +250 -0
  53. package/dist/docs/methods/QA_ENGINEER.md +337 -0
  54. package/dist/docs/methods/RELEASE_MANAGER.md +145 -0
  55. package/dist/docs/methods/SECURITY_AUDITOR.md +320 -0
  56. package/dist/docs/methods/SUB_AGENTS.md +335 -0
  57. package/dist/docs/methods/SYSTEMS_ARCHITECT.md +171 -0
  58. package/dist/docs/methods/TESTING.md +359 -0
  59. package/dist/docs/methods/THUMPER.md +175 -0
  60. package/dist/docs/methods/TIME_VAULT.md +120 -0
  61. package/dist/docs/methods/TREASURY.md +184 -0
  62. package/dist/docs/methods/TROUBLESHOOTING.md +265 -0
  63. package/dist/docs/patterns/README.md +52 -0
  64. package/dist/docs/patterns/ad-billing-adapter.ts +537 -0
  65. package/dist/docs/patterns/ad-platform-adapter.ts +421 -0
  66. package/dist/docs/patterns/ai-classifier.ts +195 -0
  67. package/dist/docs/patterns/ai-eval.ts +272 -0
  68. package/dist/docs/patterns/ai-orchestrator.ts +341 -0
  69. package/dist/docs/patterns/ai-router.ts +194 -0
  70. package/dist/docs/patterns/ai-tool-schema.ts +237 -0
  71. package/dist/docs/patterns/api-route.ts +241 -0
  72. package/dist/docs/patterns/backtest-engine.ts +499 -0
  73. package/dist/docs/patterns/browser-review.ts +292 -0
  74. package/dist/docs/patterns/combobox.tsx +300 -0
  75. package/dist/docs/patterns/component.tsx +262 -0
  76. package/dist/docs/patterns/daemon-process.ts +338 -0
  77. package/dist/docs/patterns/data-pipeline.ts +297 -0
  78. package/dist/docs/patterns/database-migration.ts +466 -0
  79. package/dist/docs/patterns/e2e-test.ts +629 -0
  80. package/dist/docs/patterns/error-handling.ts +312 -0
  81. package/dist/docs/patterns/execution-safety.ts +601 -0
  82. package/dist/docs/patterns/financial-transaction.ts +342 -0
  83. package/dist/docs/patterns/funding-plan.ts +462 -0
  84. package/dist/docs/patterns/game-entity.ts +137 -0
  85. package/dist/docs/patterns/game-loop.ts +113 -0
  86. package/dist/docs/patterns/game-state.ts +143 -0
  87. package/dist/docs/patterns/job-queue.ts +225 -0
  88. package/dist/docs/patterns/kongo-integration.ts +164 -0
  89. package/dist/docs/patterns/middleware.ts +363 -0
  90. package/dist/docs/patterns/mobile-screen.tsx +139 -0
  91. package/dist/docs/patterns/mobile-service.ts +167 -0
  92. package/dist/docs/patterns/multi-tenant.ts +382 -0
  93. package/dist/docs/patterns/oauth-token-lifecycle.ts +223 -0
  94. package/dist/docs/patterns/outbound-rate-limiter.ts +260 -0
  95. package/dist/docs/patterns/prompt-template.ts +195 -0
  96. package/dist/docs/patterns/revenue-source-adapter.ts +311 -0
  97. package/dist/docs/patterns/service.ts +224 -0
  98. package/dist/docs/patterns/sse-endpoint.ts +118 -0
  99. package/dist/docs/patterns/stablecoin-adapter.ts +511 -0
  100. package/dist/docs/patterns/third-party-script.ts +68 -0
  101. package/dist/scripts/thumper/gom-jabbar.sh +241 -0
  102. package/dist/scripts/thumper/relay.sh +610 -0
  103. package/dist/scripts/thumper/scan.sh +359 -0
  104. package/dist/scripts/thumper/thumper.sh +190 -0
  105. package/dist/scripts/thumper/water-rings.sh +76 -0
  106. package/package.json +1 -1
  107. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -0,0 +1,601 @@
1
+ /**
2
+ * Pattern: Execution Safety (Trading / Financial Order Management)
3
+ *
4
+ * Key principles:
5
+ * - Exchange precision (tick size, lot size, min notional) fetched from API — NEVER hardcoded
6
+ * - Every order validated before submission: size limits, price bounds, rate limits
7
+ * - Paper/live toggle via interface — same code path, different backend
8
+ * - Reconciliation: compare local state vs exchange fills — detect drift immediately
9
+ * - Circuit breaker: auto-pause after consecutive losses or drawdown threshold
10
+ * - Audit trail: every order logged with timestamp, reason, fill, and result
11
+ * - Position limits enforced at portfolio level, not just per-order
12
+ *
13
+ * Agents: Stark (backend), Kenobi (security), L (monitoring)
14
+ *
15
+ * Framework adaptations:
16
+ * TypeScript: This file (generic execution backend interface)
17
+ * Python/CCXT: Crypto exchanges (see bottom)
18
+ * Python/Alpaca: US equities (see bottom)
19
+ * Any financial API: Implement ExecutionBackend interface
20
+ */
21
+
22
+ // ── Order Types ─────────────────────────────────────────
23
+
24
+ type OrderSide = 'buy' | 'sell';
25
+ type OrderType = 'market' | 'limit' | 'stop' | 'stop-limit';
26
+ type OrderStatus = 'pending' | 'submitted' | 'partial' | 'filled' | 'cancelled' | 'rejected';
27
+
28
+ interface OrderRequest {
29
+ symbol: string;
30
+ side: OrderSide;
31
+ type: OrderType;
32
+ /** Quantity in base units (e.g., shares, coins) */
33
+ size: number;
34
+ /** Required for limit and stop-limit orders */
35
+ price?: number;
36
+ /** Required for stop and stop-limit orders */
37
+ stopPrice?: number;
38
+ /** Strategy-provided reason for audit trail */
39
+ reason: string;
40
+ /** Client-generated ID for idempotent submission */
41
+ clientOrderId: string;
42
+ }
43
+
44
+ interface OrderResult {
45
+ orderId: string;
46
+ clientOrderId: string;
47
+ status: OrderStatus;
48
+ filledSize: number;
49
+ avgFillPrice: number;
50
+ commission: number;
51
+ timestamp: string;
52
+ rawResponse: Record<string, unknown>;
53
+ }
54
+
55
+ // ── Exchange Precision ──────────────────────────────────
56
+ // NEVER hardcode these values. Always fetch from the exchange API.
57
+ // Precision rules change — hardcoded values WILL cause order rejections.
58
+
59
+ interface ExchangePrecision {
60
+ symbol: string;
61
+ /** Minimum price increment (e.g., 0.01 for USD stocks) */
62
+ tickSize: number;
63
+ /** Minimum quantity increment (e.g., 0.001 for BTC) */
64
+ lotSize: number;
65
+ /** Minimum order value in quote currency (e.g., $10) */
66
+ minNotional: number;
67
+ /** Maximum order size */
68
+ maxSize: number;
69
+ /** Decimal places for price */
70
+ pricePrecision: number;
71
+ /** Decimal places for quantity */
72
+ quantityPrecision: number;
73
+ /** When this precision data was fetched — refetch if stale */
74
+ fetchedAt: string;
75
+ }
76
+
77
+ function roundToTickSize(price: number, tickSize: number): number {
78
+ return Math.round(price / tickSize) * tickSize;
79
+ }
80
+
81
+ function roundToLotSize(quantity: number, lotSize: number): number {
82
+ return Math.floor(quantity / lotSize) * lotSize;
83
+ }
84
+
85
+ function roundToPrecision(value: number, decimals: number): number {
86
+ const factor = 10 ** decimals;
87
+ return Math.round(value * factor) / factor;
88
+ }
89
+
90
+ // ── Order Validation ────────────────────────────────────
91
+
92
+ interface ValidationError {
93
+ field: string;
94
+ message: string;
95
+ }
96
+
97
+ function validateOrder(
98
+ order: OrderRequest,
99
+ precision: ExchangePrecision,
100
+ portfolio: { cash: number; maxOrderSize: number }
101
+ ): ValidationError[] {
102
+ const errors: ValidationError[] = [];
103
+
104
+ // Size checks
105
+ if (order.size <= 0) {
106
+ errors.push({ field: 'size', message: 'Order size must be positive' });
107
+ }
108
+ if (order.size < precision.lotSize) {
109
+ errors.push({ field: 'size', message: `Size ${order.size} below lot size ${precision.lotSize}` });
110
+ }
111
+ if (order.size > precision.maxSize) {
112
+ errors.push({ field: 'size', message: `Size ${order.size} exceeds max ${precision.maxSize}` });
113
+ }
114
+ if (order.size > portfolio.maxOrderSize) {
115
+ errors.push({ field: 'size', message: `Size exceeds portfolio max order size ${portfolio.maxOrderSize}` });
116
+ }
117
+
118
+ // Lot size alignment
119
+ const rounded = roundToLotSize(order.size, precision.lotSize);
120
+ if (rounded !== order.size) {
121
+ errors.push({ field: 'size', message: `Size ${order.size} not aligned to lot size ${precision.lotSize}` });
122
+ }
123
+
124
+ // Price checks (for limit/stop orders)
125
+ if (order.type === 'limit' || order.type === 'stop-limit') {
126
+ if (order.price == null) {
127
+ errors.push({ field: 'price', message: 'Limit orders require a price' });
128
+ } else {
129
+ if (order.price <= 0) {
130
+ errors.push({ field: 'price', message: 'Price must be positive' });
131
+ }
132
+ const roundedPrice = roundToTickSize(order.price, precision.tickSize);
133
+ if (roundedPrice !== order.price) {
134
+ errors.push({ field: 'price', message: `Price not aligned to tick size ${precision.tickSize}` });
135
+ }
136
+ }
137
+ }
138
+
139
+ // Stop price check
140
+ if ((order.type === 'stop' || order.type === 'stop-limit') && order.stopPrice == null) {
141
+ errors.push({ field: 'stopPrice', message: 'Stop orders require a stop price' });
142
+ }
143
+
144
+ // Minimum notional check
145
+ const estimatedValue = order.size * (order.price ?? 0);
146
+ if (order.type !== 'market' && estimatedValue < precision.minNotional) {
147
+ errors.push({ field: 'notional', message: `Order value ${estimatedValue} below minimum ${precision.minNotional}` });
148
+ }
149
+
150
+ // Cash sufficiency (buy orders only)
151
+ if (order.side === 'buy' && estimatedValue > portfolio.cash) {
152
+ errors.push({ field: 'cash', message: 'Insufficient cash for order' });
153
+ }
154
+
155
+ return errors;
156
+ }
157
+
158
+ // ── Execution Backend Interface ─────────────────────────
159
+ // Paper and live trading implement the same interface.
160
+ // Switch between them via configuration — never via code branching.
161
+
162
+ interface ExecutionBackend {
163
+ name: string;
164
+ /** Fetch exchange precision rules — call on startup and periodically */
165
+ fetchPrecision(symbol: string): Promise<ExchangePrecision>;
166
+ /** Submit an order */
167
+ submitOrder(order: OrderRequest): Promise<OrderResult>;
168
+ /** Cancel an order */
169
+ cancelOrder(orderId: string): Promise<void>;
170
+ /** Get current open orders */
171
+ getOpenOrders(symbol?: string): Promise<OrderResult[]>;
172
+ /** Get fills for reconciliation */
173
+ getFills(since: string): Promise<OrderResult[]>;
174
+ }
175
+
176
+ // ── Paper Trading Backend ───────────────────────────────
177
+
178
+ class PaperBackend implements ExecutionBackend {
179
+ name = 'paper';
180
+ private orders: Map<string, OrderResult> = new Map();
181
+ private nextId = 1;
182
+
183
+ async fetchPrecision(symbol: string): Promise<ExchangePrecision> {
184
+ // Paper mode uses reasonable defaults — but real precision should
185
+ // still be fetched from the target exchange for realistic simulation
186
+ return {
187
+ symbol,
188
+ tickSize: 0.01,
189
+ lotSize: 1,
190
+ minNotional: 10,
191
+ maxSize: 100000,
192
+ pricePrecision: 2,
193
+ quantityPrecision: 0,
194
+ fetchedAt: new Date().toISOString(),
195
+ };
196
+ }
197
+
198
+ async submitOrder(order: OrderRequest): Promise<OrderResult> {
199
+ const result: OrderResult = {
200
+ orderId: `paper-${this.nextId++}`,
201
+ clientOrderId: order.clientOrderId,
202
+ status: 'filled',
203
+ filledSize: order.size,
204
+ avgFillPrice: order.price ?? 0,
205
+ commission: 0,
206
+ timestamp: new Date().toISOString(),
207
+ rawResponse: { simulated: true },
208
+ };
209
+ this.orders.set(result.orderId, result);
210
+ return result;
211
+ }
212
+
213
+ async cancelOrder(orderId: string): Promise<void> {
214
+ const order = this.orders.get(orderId);
215
+ if (order) order.status = 'cancelled';
216
+ }
217
+
218
+ async getOpenOrders(): Promise<OrderResult[]> {
219
+ return [...this.orders.values()].filter(o => o.status === 'pending' || o.status === 'submitted');
220
+ }
221
+
222
+ async getFills(since: string): Promise<OrderResult[]> {
223
+ return [...this.orders.values()].filter(o => o.status === 'filled' && o.timestamp >= since);
224
+ }
225
+ }
226
+
227
+ // ── Position Manager ────────────────────────────────────
228
+
229
+ interface PositionLimits {
230
+ /** Maximum total exposure as fraction of equity (0.0 - 1.0) */
231
+ maxExposure: number;
232
+ /** Maximum exposure per symbol as fraction of equity */
233
+ maxPerSymbol: number;
234
+ /** Maximum number of concurrent positions */
235
+ maxPositions: number;
236
+ /** Stop-loss percentage — auto-close at this drawdown */
237
+ stopLossPct: number;
238
+ }
239
+
240
+ interface ManagedPosition {
241
+ symbol: string;
242
+ side: OrderSide;
243
+ quantity: number;
244
+ entryPrice: number;
245
+ currentPrice: number;
246
+ stopLossPrice: number;
247
+ unrealizedPnl: number;
248
+ openedAt: string;
249
+ }
250
+
251
+ class PositionManager {
252
+ private positions: Map<string, ManagedPosition> = new Map();
253
+ private limits: PositionLimits;
254
+ private equity: number;
255
+
256
+ constructor(limits: PositionLimits, initialEquity: number) {
257
+ this.limits = limits;
258
+ this.equity = initialEquity;
259
+ }
260
+
261
+ canOpenPosition(symbol: string, size: number, price: number): ValidationError[] {
262
+ const errors: ValidationError[] = [];
263
+ const orderValue = size * price;
264
+
265
+ // Position count limit
266
+ if (!this.positions.has(symbol) && this.positions.size >= this.limits.maxPositions) {
267
+ errors.push({ field: 'positions', message: `At maximum position count (${this.limits.maxPositions})` });
268
+ }
269
+
270
+ // Per-symbol exposure limit
271
+ const existingExposure = this.getSymbolExposure(symbol);
272
+ if ((existingExposure + orderValue) / this.equity > this.limits.maxPerSymbol) {
273
+ errors.push({ field: 'exposure', message: `Would exceed per-symbol limit (${this.limits.maxPerSymbol * 100}%)` });
274
+ }
275
+
276
+ // Total portfolio exposure limit
277
+ const totalExposure = this.getTotalExposure();
278
+ if ((totalExposure + orderValue) / this.equity > this.limits.maxExposure) {
279
+ errors.push({ field: 'exposure', message: `Would exceed total exposure limit (${this.limits.maxExposure * 100}%)` });
280
+ }
281
+
282
+ return errors;
283
+ }
284
+
285
+ /** Check all positions for stop-loss triggers. Returns symbols to close. */
286
+ checkStopLosses(currentPrices: Map<string, number>): string[] {
287
+ const triggered: string[] = [];
288
+ for (const [symbol, pos] of this.positions) {
289
+ const currentPrice = currentPrices.get(symbol);
290
+ if (currentPrice == null) continue;
291
+
292
+ pos.currentPrice = currentPrice;
293
+ pos.unrealizedPnl = (currentPrice - pos.entryPrice) * pos.quantity * (pos.side === 'buy' ? 1 : -1);
294
+
295
+ if (pos.side === 'buy' && currentPrice <= pos.stopLossPrice) {
296
+ triggered.push(symbol);
297
+ } else if (pos.side === 'sell' && currentPrice >= pos.stopLossPrice) {
298
+ triggered.push(symbol);
299
+ }
300
+ }
301
+ return triggered;
302
+ }
303
+
304
+ private getSymbolExposure(symbol: string): number {
305
+ const pos = this.positions.get(symbol);
306
+ return pos ? pos.quantity * pos.currentPrice : 0;
307
+ }
308
+
309
+ private getTotalExposure(): number {
310
+ let total = 0;
311
+ for (const pos of this.positions.values()) {
312
+ total += pos.quantity * pos.currentPrice;
313
+ }
314
+ return total;
315
+ }
316
+ }
317
+
318
+ // ── Circuit Breaker ─────────────────────────────────────
319
+
320
+ interface CircuitBreakerConfig {
321
+ /** Pause after this many consecutive losses */
322
+ maxConsecutiveLosses: number;
323
+ /** Pause if drawdown exceeds this percentage (0.0 - 1.0) */
324
+ maxDrawdownPct: number;
325
+ /** Cooldown period before resuming (milliseconds) */
326
+ cooldownMs: number;
327
+ /** Callback when circuit breaker trips */
328
+ onTrip: (reason: string) => void;
329
+ }
330
+
331
+ class CircuitBreaker {
332
+ private config: CircuitBreakerConfig;
333
+ private consecutiveLosses = 0;
334
+ private peakEquity: number;
335
+ private trippedAt: number | null = null;
336
+
337
+ constructor(config: CircuitBreakerConfig, initialEquity: number) {
338
+ this.config = config;
339
+ this.peakEquity = initialEquity;
340
+ }
341
+
342
+ /** Record a trade result. Returns true if trading should continue. */
343
+ recordTrade(pnl: number): boolean {
344
+ if (pnl < 0) {
345
+ this.consecutiveLosses++;
346
+ if (this.consecutiveLosses >= this.config.maxConsecutiveLosses) {
347
+ this.trip(`${this.consecutiveLosses} consecutive losses`);
348
+ return false;
349
+ }
350
+ } else {
351
+ this.consecutiveLosses = 0;
352
+ }
353
+ return !this.isTripped();
354
+ }
355
+
356
+ /** Update equity and check drawdown. Returns true if trading should continue. */
357
+ updateEquity(currentEquity: number): boolean {
358
+ if (currentEquity > this.peakEquity) {
359
+ this.peakEquity = currentEquity;
360
+ }
361
+ const drawdown = (this.peakEquity - currentEquity) / this.peakEquity;
362
+ if (drawdown >= this.config.maxDrawdownPct) {
363
+ this.trip(`Drawdown ${(drawdown * 100).toFixed(1)}% exceeds limit ${(this.config.maxDrawdownPct * 100).toFixed(1)}%`);
364
+ return false;
365
+ }
366
+ return !this.isTripped();
367
+ }
368
+
369
+ isTripped(): boolean {
370
+ if (this.trippedAt == null) return false;
371
+ if (Date.now() - this.trippedAt >= this.config.cooldownMs) {
372
+ this.reset();
373
+ return false;
374
+ }
375
+ return true;
376
+ }
377
+
378
+ private trip(reason: string): void {
379
+ this.trippedAt = Date.now();
380
+ this.config.onTrip(reason);
381
+ }
382
+
383
+ private reset(): void {
384
+ this.trippedAt = null;
385
+ this.consecutiveLosses = 0;
386
+ }
387
+ }
388
+
389
+ // ── Audit Trail ─────────────────────────────────────────
390
+
391
+ interface AuditEntry {
392
+ timestamp: string;
393
+ action: 'submit' | 'fill' | 'cancel' | 'reject' | 'stop-loss' | 'circuit-break';
394
+ order: OrderRequest | null;
395
+ result: OrderResult | null;
396
+ reason: string;
397
+ portfolioState: { cash: number; equity: number; positionCount: number };
398
+ }
399
+
400
+ class AuditTrail {
401
+ private entries: AuditEntry[] = [];
402
+ private onLog?: (entry: AuditEntry) => void;
403
+
404
+ constructor(onLog?: (entry: AuditEntry) => void) {
405
+ this.onLog = onLog;
406
+ }
407
+
408
+ record(entry: Omit<AuditEntry, 'timestamp'>): void {
409
+ const full: AuditEntry = { ...entry, timestamp: new Date().toISOString() };
410
+ this.entries.push(full);
411
+ this.onLog?.(full);
412
+
413
+ // Structured JSON logging — never log PII or API keys
414
+ console.log(JSON.stringify({
415
+ event: `order.${full.action}`,
416
+ symbol: full.order?.symbol,
417
+ side: full.order?.side,
418
+ size: full.order?.size,
419
+ reason: full.reason,
420
+ orderId: full.result?.orderId,
421
+ fillPrice: full.result?.avgFillPrice,
422
+ equity: full.portfolioState.equity,
423
+ }));
424
+ }
425
+
426
+ getEntries(): readonly AuditEntry[] {
427
+ return this.entries;
428
+ }
429
+ }
430
+
431
+ // ── Reconciliation ──────────────────────────────────────
432
+
433
+ interface ReconciliationResult {
434
+ matched: number;
435
+ mismatched: number;
436
+ missingLocal: string[]; // Orders on exchange not tracked locally
437
+ missingExchange: string[]; // Local orders not found on exchange
438
+ priceDifferences: Array<{ orderId: string; localPrice: number; exchangePrice: number }>;
439
+ }
440
+
441
+ function reconcile(
442
+ localOrders: OrderResult[],
443
+ exchangeFills: OrderResult[]
444
+ ): ReconciliationResult {
445
+ const exchangeMap = new Map(exchangeFills.map(f => [f.clientOrderId, f]));
446
+ const localMap = new Map(localOrders.map(o => [o.clientOrderId, o]));
447
+
448
+ let matched = 0;
449
+ let mismatched = 0;
450
+ const missingExchange: string[] = [];
451
+ const priceDifferences: Array<{ orderId: string; localPrice: number; exchangePrice: number }> = [];
452
+
453
+ for (const [clientId, local] of localMap) {
454
+ const exchange = exchangeMap.get(clientId);
455
+ if (!exchange) {
456
+ missingExchange.push(clientId);
457
+ continue;
458
+ }
459
+ if (Math.abs(local.avgFillPrice - exchange.avgFillPrice) > 0.0001) {
460
+ priceDifferences.push({
461
+ orderId: clientId,
462
+ localPrice: local.avgFillPrice,
463
+ exchangePrice: exchange.avgFillPrice,
464
+ });
465
+ mismatched++;
466
+ } else {
467
+ matched++;
468
+ }
469
+ }
470
+
471
+ const missingLocal = exchangeFills
472
+ .filter(f => !localMap.has(f.clientOrderId))
473
+ .map(f => f.clientOrderId);
474
+
475
+ return { matched, mismatched, missingLocal, missingExchange, priceDifferences };
476
+ }
477
+
478
+ export type {
479
+ OrderRequest, OrderResult, OrderSide, OrderType, OrderStatus,
480
+ ExchangePrecision, ExecutionBackend, ValidationError,
481
+ PositionLimits, ManagedPosition, CircuitBreakerConfig,
482
+ AuditEntry, ReconciliationResult,
483
+ };
484
+ export {
485
+ roundToTickSize, roundToLotSize, roundToPrecision,
486
+ validateOrder, reconcile,
487
+ PaperBackend, PositionManager, CircuitBreaker, AuditTrail,
488
+ };
489
+
490
+ // ── Framework Adaptations ───────────────────────────────
491
+ //
492
+ // === Python / CCXT (Crypto Exchanges) ===
493
+ //
494
+ // import ccxt
495
+ //
496
+ // exchange = ccxt.binance({"apiKey": "...", "secret": "..."})
497
+ // exchange.set_sandbox_mode(True) # Paper trading toggle
498
+ //
499
+ // # Fetch precision from exchange — NEVER hardcode
500
+ // markets = exchange.load_markets()
501
+ // info = markets["BTC/USDT"]
502
+ // tick_size = info["precision"]["price"]
503
+ // lot_size = info["precision"]["amount"]
504
+ // min_notional = info["limits"]["cost"]["min"]
505
+ //
506
+ // # Round to exchange precision
507
+ // price = exchange.price_to_precision("BTC/USDT", raw_price)
508
+ // amount = exchange.amount_to_precision("BTC/USDT", raw_amount)
509
+ //
510
+ // # Submit order
511
+ // order = exchange.create_limit_buy_order("BTC/USDT", amount, price)
512
+ //
513
+ // # Reconciliation
514
+ // fills = exchange.fetch_my_trades("BTC/USDT", since=timestamp)
515
+ // # Compare fills against local order log
516
+ //
517
+ // # CCXT handles 40+ exchanges with the same interface — same as ExecutionBackend pattern
518
+ //
519
+ // === Python / Alpaca (US Equities) ===
520
+ //
521
+ // from alpaca.trading.client import TradingClient
522
+ // from alpaca.trading.requests import MarketOrderRequest, LimitOrderRequest
523
+ // from alpaca.trading.enums import OrderSide, TimeInForce
524
+ //
525
+ // # Paper vs live: just change the base_url
526
+ // client = TradingClient(api_key, secret_key, paper=True)
527
+ //
528
+ // # Submit order
529
+ // order = client.submit_order(
530
+ // MarketOrderRequest(
531
+ // symbol="AAPL", qty=10, side=OrderSide.BUY,
532
+ // time_in_force=TimeInForce.DAY,
533
+ // client_order_id="my-unique-id" # Idempotency
534
+ // )
535
+ // )
536
+ //
537
+ // # Position management
538
+ // positions = client.get_all_positions()
539
+ // account = client.get_account()
540
+ // buying_power = float(account.buying_power)
541
+ //
542
+ // # Reconciliation: compare client.get_orders() vs local state
543
+ //
544
+ // === IBKR (Interactive Brokers) ===
545
+ //
546
+ // # Use ib_insync (Python) or official TWS API
547
+ // from ib_insync import IB, Stock, LimitOrder
548
+ //
549
+ // ib = IB()
550
+ // ib.connect("127.0.0.1", 7497, clientId=1) # 7497=paper, 7496=live
551
+ //
552
+ // contract = Stock("AAPL", "SMART", "USD")
553
+ // ib.qualifyContracts(contract)
554
+ //
555
+ // # Precision: contract details include min tick, lot size
556
+ // details = ib.reqContractDetails(contract)[0]
557
+ // min_tick = details.minTick
558
+ //
559
+ // order = LimitOrder("BUY", 10, round(price / min_tick) * min_tick)
560
+ // trade = ib.placeOrder(contract, order)
561
+ //
562
+ // # IBKR supports paper trading on port 7497 — same code, different port
563
+
564
+ // ── Anti-Patterns ──────────────────────────────────────
565
+ //
566
+ // === Never raw transfer() to smart contracts ===
567
+ //
568
+ // ERC20 `transfer(address, amount)` to a smart contract deposits funds with
569
+ // no guarantee of recovery. Smart contracts may lack withdrawal functions,
570
+ // absorb tokens into settlement pools, or have no rescue mechanism.
571
+ //
572
+ // Before any on-chain transfer:
573
+ // 1. Read the contract's ABI — verify the correct deposit/funding function
574
+ // 2. Check if the contract has a withdrawal or recovery function
575
+ // 3. Use the contract's own deposit method, not raw transfer()
576
+ // 4. For amounts >$100, simulate the transaction first (eth_call)
577
+ //
578
+ // This applies to any on-chain execution — Ethereum, L2s, Solana (via CPI).
579
+ //
580
+ // === Derive Don't Accumulate ===
581
+ //
582
+ // Never maintain a running balance by incrementing/decrementing on each event.
583
+ // Running totals drift due to: missed events, duplicate processing, rounding
584
+ // errors, and partial fills that update one side but not the other.
585
+ //
586
+ // Instead, derive the current state from the source of truth:
587
+ //
588
+ // // WRONG: running accumulator
589
+ // this.totalPnl += trade.pnl;
590
+ // this.positionSize += trade.filledSize;
591
+ //
592
+ // // RIGHT: derive from complete history
593
+ // const fills = await backend.getFills(since);
594
+ // const totalPnl = fills.reduce((sum, f) => sum + f.pnl, 0);
595
+ // const positionSize = fills.reduce(
596
+ // (sum, f) => sum + f.filledSize * (f.side === 'buy' ? 1 : -1), 0
597
+ // );
598
+ //
599
+ // For reconciliation, the derived value IS the value. If the derived value
600
+ // disagrees with an accumulator, the accumulator is wrong — always.
601
+ // (Field reports #271, #274, #275)