openplanter 0.1.0

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 (126) hide show
  1. package/README.md +210 -0
  2. package/dist/builder.d.ts +11 -0
  3. package/dist/builder.d.ts.map +1 -0
  4. package/dist/builder.js +179 -0
  5. package/dist/builder.js.map +1 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +548 -0
  9. package/dist/cli.js.map +1 -0
  10. package/dist/config.d.ts +51 -0
  11. package/dist/config.d.ts.map +1 -0
  12. package/dist/config.js +114 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/credentials.d.ts +52 -0
  15. package/dist/credentials.d.ts.map +1 -0
  16. package/dist/credentials.js +371 -0
  17. package/dist/credentials.js.map +1 -0
  18. package/dist/demo.d.ts +26 -0
  19. package/dist/demo.d.ts.map +1 -0
  20. package/dist/demo.js +95 -0
  21. package/dist/demo.js.map +1 -0
  22. package/dist/engine.d.ts +91 -0
  23. package/dist/engine.d.ts.map +1 -0
  24. package/dist/engine.js +1036 -0
  25. package/dist/engine.js.map +1 -0
  26. package/dist/index.d.ts +30 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +39 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/investigation-tools/aph-holdings.d.ts +61 -0
  31. package/dist/investigation-tools/aph-holdings.d.ts.map +1 -0
  32. package/dist/investigation-tools/aph-holdings.js +459 -0
  33. package/dist/investigation-tools/aph-holdings.js.map +1 -0
  34. package/dist/investigation-tools/asic-officer-lookup.d.ts +42 -0
  35. package/dist/investigation-tools/asic-officer-lookup.d.ts.map +1 -0
  36. package/dist/investigation-tools/asic-officer-lookup.js +197 -0
  37. package/dist/investigation-tools/asic-officer-lookup.js.map +1 -0
  38. package/dist/investigation-tools/asx-calendar-fetcher.d.ts +42 -0
  39. package/dist/investigation-tools/asx-calendar-fetcher.d.ts.map +1 -0
  40. package/dist/investigation-tools/asx-calendar-fetcher.js +271 -0
  41. package/dist/investigation-tools/asx-calendar-fetcher.js.map +1 -0
  42. package/dist/investigation-tools/asx-parser.d.ts +66 -0
  43. package/dist/investigation-tools/asx-parser.d.ts.map +1 -0
  44. package/dist/investigation-tools/asx-parser.js +314 -0
  45. package/dist/investigation-tools/asx-parser.js.map +1 -0
  46. package/dist/investigation-tools/bulk-asx-announcements.d.ts +53 -0
  47. package/dist/investigation-tools/bulk-asx-announcements.d.ts.map +1 -0
  48. package/dist/investigation-tools/bulk-asx-announcements.js +204 -0
  49. package/dist/investigation-tools/bulk-asx-announcements.js.map +1 -0
  50. package/dist/investigation-tools/entity-resolver.d.ts +77 -0
  51. package/dist/investigation-tools/entity-resolver.d.ts.map +1 -0
  52. package/dist/investigation-tools/entity-resolver.js +346 -0
  53. package/dist/investigation-tools/entity-resolver.js.map +1 -0
  54. package/dist/investigation-tools/hotcopper-scraper.d.ts +73 -0
  55. package/dist/investigation-tools/hotcopper-scraper.d.ts.map +1 -0
  56. package/dist/investigation-tools/hotcopper-scraper.js +318 -0
  57. package/dist/investigation-tools/hotcopper-scraper.js.map +1 -0
  58. package/dist/investigation-tools/index.d.ts +15 -0
  59. package/dist/investigation-tools/index.d.ts.map +1 -0
  60. package/dist/investigation-tools/index.js +15 -0
  61. package/dist/investigation-tools/index.js.map +1 -0
  62. package/dist/investigation-tools/insider-graph.d.ts +173 -0
  63. package/dist/investigation-tools/insider-graph.d.ts.map +1 -0
  64. package/dist/investigation-tools/insider-graph.js +732 -0
  65. package/dist/investigation-tools/insider-graph.js.map +1 -0
  66. package/dist/investigation-tools/insider-suspicion-scorer.d.ts +97 -0
  67. package/dist/investigation-tools/insider-suspicion-scorer.d.ts.map +1 -0
  68. package/dist/investigation-tools/insider-suspicion-scorer.js +327 -0
  69. package/dist/investigation-tools/insider-suspicion-scorer.js.map +1 -0
  70. package/dist/investigation-tools/multi-forum-scraper.d.ts +104 -0
  71. package/dist/investigation-tools/multi-forum-scraper.d.ts.map +1 -0
  72. package/dist/investigation-tools/multi-forum-scraper.js +415 -0
  73. package/dist/investigation-tools/multi-forum-scraper.js.map +1 -0
  74. package/dist/investigation-tools/price-fetcher.d.ts +81 -0
  75. package/dist/investigation-tools/price-fetcher.d.ts.map +1 -0
  76. package/dist/investigation-tools/price-fetcher.js +268 -0
  77. package/dist/investigation-tools/price-fetcher.js.map +1 -0
  78. package/dist/investigation-tools/shared.d.ts +39 -0
  79. package/dist/investigation-tools/shared.d.ts.map +1 -0
  80. package/dist/investigation-tools/shared.js +203 -0
  81. package/dist/investigation-tools/shared.js.map +1 -0
  82. package/dist/investigation-tools/timeline-linker.d.ts +90 -0
  83. package/dist/investigation-tools/timeline-linker.d.ts.map +1 -0
  84. package/dist/investigation-tools/timeline-linker.js +219 -0
  85. package/dist/investigation-tools/timeline-linker.js.map +1 -0
  86. package/dist/investigation-tools/volume-scanner.d.ts +70 -0
  87. package/dist/investigation-tools/volume-scanner.d.ts.map +1 -0
  88. package/dist/investigation-tools/volume-scanner.js +227 -0
  89. package/dist/investigation-tools/volume-scanner.js.map +1 -0
  90. package/dist/model.d.ts +136 -0
  91. package/dist/model.d.ts.map +1 -0
  92. package/dist/model.js +1071 -0
  93. package/dist/model.js.map +1 -0
  94. package/dist/patching.d.ts +45 -0
  95. package/dist/patching.d.ts.map +1 -0
  96. package/dist/patching.js +317 -0
  97. package/dist/patching.js.map +1 -0
  98. package/dist/prompts.d.ts +15 -0
  99. package/dist/prompts.d.ts.map +1 -0
  100. package/dist/prompts.js +351 -0
  101. package/dist/prompts.js.map +1 -0
  102. package/dist/replay-log.d.ts +54 -0
  103. package/dist/replay-log.d.ts.map +1 -0
  104. package/dist/replay-log.js +94 -0
  105. package/dist/replay-log.js.map +1 -0
  106. package/dist/runtime.d.ts +53 -0
  107. package/dist/runtime.d.ts.map +1 -0
  108. package/dist/runtime.js +259 -0
  109. package/dist/runtime.js.map +1 -0
  110. package/dist/settings.d.ts +39 -0
  111. package/dist/settings.d.ts.map +1 -0
  112. package/dist/settings.js +146 -0
  113. package/dist/settings.js.map +1 -0
  114. package/dist/tool-defs.d.ts +58 -0
  115. package/dist/tool-defs.d.ts.map +1 -0
  116. package/dist/tool-defs.js +1029 -0
  117. package/dist/tool-defs.js.map +1 -0
  118. package/dist/tools.d.ts +72 -0
  119. package/dist/tools.d.ts.map +1 -0
  120. package/dist/tools.js +1454 -0
  121. package/dist/tools.js.map +1 -0
  122. package/dist/tui.d.ts +49 -0
  123. package/dist/tui.d.ts.map +1 -0
  124. package/dist/tui.js +699 -0
  125. package/dist/tui.js.map +1 -0
  126. package/package.json +126 -0
@@ -0,0 +1,219 @@
1
+ /**
2
+ * timeline-linker.ts — Trade-vs-Event Timeline Correlation Tool
3
+ *
4
+ * Correlates politician / insider trade dates against ASX announcements,
5
+ * reports, and trading halts to build evidence chains for potential
6
+ * insider-trading investigations.
7
+ *
8
+ * Scoring algorithm (deterministic, 0–100):
9
+ * 1. timing_score = max(0, (window - gap) / window) × 40
10
+ * 2. alignment_score = 30 (buy→positive / sell→negative), else 10
11
+ * 3. size_score = min(value / $100k, 1) × 20
12
+ * 4. impact_score = 10 if event impact is non-neutral
13
+ * TOTAL = timing + alignment + size + impact (clamped 0–100)
14
+ */
15
+ import { parseDate } from "./shared.js";
16
+ // ── Constants ───────────────────────────────────────────────────
17
+ export const DEFAULT_WINDOW_DAYS = 14;
18
+ export const DEFAULT_MIN_SCORE = 0;
19
+ /** Trade value at which size_score maxes out. */
20
+ export const SIZE_NORMALISER = 100_000.0;
21
+ /** Weight for timing component. */
22
+ export const W_TIMING = 40;
23
+ /** Weight for alignment component. */
24
+ export const W_ALIGNMENT = 30;
25
+ /** Weight for size component. */
26
+ export const W_SIZE = 20;
27
+ /** Weight for impact component. */
28
+ export const W_IMPACT = 10;
29
+ // ── Helpers ─────────────────────────────────────────────────────
30
+ const MS_PER_DAY = 86_400_000;
31
+ function roundTo(value, decimals) {
32
+ const factor = Math.pow(10, decimals);
33
+ return Math.round(value * factor) / factor;
34
+ }
35
+ function formatCurrencyValue(value) {
36
+ return Math.round(value)
37
+ .toFixed(0)
38
+ .replace(/\B(?=(\d{3})+(?!\d))/g, ",");
39
+ }
40
+ // ── Scoring ─────────────────────────────────────────────────────
41
+ /**
42
+ * Return [score 0–100, pattern_label] for a trade→event pair.
43
+ *
44
+ * *daysGap* is the number of days between the trade and the later event
45
+ * (0 = same day).
46
+ */
47
+ export function computeSuspicionScore(trade, event, daysGap, window) {
48
+ // 1. Timing score — closer is more suspicious
49
+ const timingScore = Math.max(0.0, (window - daysGap) / window) * W_TIMING;
50
+ // 2. Alignment score — does the trade direction match the news impact?
51
+ const action = (trade.action ?? "").toLowerCase();
52
+ const impact = (event.impact ?? "neutral").toLowerCase();
53
+ let alignmentScore;
54
+ let pattern;
55
+ if (action === "buy" && impact === "positive") {
56
+ alignmentScore = W_ALIGNMENT;
57
+ pattern = "buy_before_positive";
58
+ }
59
+ else if (action === "sell" && impact === "negative") {
60
+ alignmentScore = W_ALIGNMENT;
61
+ pattern = "sell_before_negative";
62
+ }
63
+ else {
64
+ alignmentScore = 10.0; // timing-only correlation
65
+ pattern = "timing_only";
66
+ }
67
+ // 3. Size score — larger trades are more suspicious
68
+ const tradeValue = trade.value ?? 0;
69
+ const sizeScore = Math.min(tradeValue / SIZE_NORMALISER, 1.0) * W_SIZE;
70
+ // 4. Impact score — non-neutral events are more noteworthy
71
+ const impactScore = impact === "positive" || impact === "negative" ? W_IMPACT : 0.0;
72
+ let total = timingScore + alignmentScore + sizeScore + impactScore;
73
+ total = Math.max(0.0, Math.min(100.0, total));
74
+ return [roundTo(total, 1), pattern];
75
+ }
76
+ // ── Evidence Summary Generation ─────────────────────────────────
77
+ /** Return a plain-English sentence describing the correlation. */
78
+ export function buildEvidenceSummary(trade, event, daysGap, pattern) {
79
+ const person = trade.person ?? "Unknown person";
80
+ const action = (trade.action ?? "traded").toLowerCase();
81
+ const ticker = trade.ticker ?? "???";
82
+ const value = trade.value;
83
+ const valueStr = value != null ? ` ($${formatCurrencyValue(value)})` : "";
84
+ const eventType = event.type ?? "event";
85
+ const desc = event.description ?? "an event";
86
+ const impact = event.impact ?? "neutral";
87
+ const timing = daysGap > 0
88
+ ? `${daysGap} day${daysGap !== 1 ? "s" : ""}`
89
+ : "same day";
90
+ if (pattern === "buy_before_positive") {
91
+ return (`${person} bought ${ticker}${valueStr} ${timing} before ` +
92
+ `a ${impact} ${eventType}: "${desc}". ` +
93
+ `The buy-before-positive-news pattern suggests possible foreknowledge.`);
94
+ }
95
+ else if (pattern === "sell_before_negative") {
96
+ return (`${person} sold ${ticker}${valueStr} ${timing} before ` +
97
+ `a ${impact} ${eventType}: "${desc}". ` +
98
+ `The sell-before-negative-news pattern suggests possible foreknowledge.`);
99
+ }
100
+ else {
101
+ return (`${person} ${action} ${ticker}${valueStr} ${timing} before ` +
102
+ `a ${impact} ${eventType}: "${desc}". ` +
103
+ `Timing correlation noted but trade direction does not align with news impact.`);
104
+ }
105
+ }
106
+ // ── Core Correlation Engine ─────────────────────────────────────
107
+ /**
108
+ * Return a list of evidence-chain objects linking trades to later events.
109
+ */
110
+ export function correlate(trades, events, window = DEFAULT_WINDOW_DAYS, minScore = DEFAULT_MIN_SCORE, dateFrom = null, dateTo = null) {
111
+ // Pre-parse event dates and group by ticker for efficient lookup
112
+ const eventsByTicker = new Map();
113
+ for (const ev of events) {
114
+ const evDate = parseDate(ev.date);
115
+ if (!evDate)
116
+ continue;
117
+ const ticker = (ev.ticker ?? "").trim().toUpperCase();
118
+ if (!ticker)
119
+ continue;
120
+ const list = eventsByTicker.get(ticker);
121
+ if (list) {
122
+ list.push([evDate, ev]);
123
+ }
124
+ else {
125
+ eventsByTicker.set(ticker, [[evDate, ev]]);
126
+ }
127
+ }
128
+ // Sort each ticker's events by date for consistent output
129
+ for (const [, list] of eventsByTicker) {
130
+ list.sort((a, b) => a[0].getTime() - b[0].getTime());
131
+ }
132
+ const results = [];
133
+ for (const trade of trades) {
134
+ const tradeDate = parseDate(trade.date);
135
+ if (!tradeDate)
136
+ continue;
137
+ // Apply date-range filter on the trade date
138
+ if (dateFrom && tradeDate.getTime() < dateFrom.getTime())
139
+ continue;
140
+ if (dateTo && tradeDate.getTime() > dateTo.getTime())
141
+ continue;
142
+ const ticker = (trade.ticker ?? "").trim().toUpperCase();
143
+ if (!ticker)
144
+ continue;
145
+ const candidateEvents = eventsByTicker.get(ticker) ?? [];
146
+ const correlated = [];
147
+ for (const [evDate, ev] of candidateEvents) {
148
+ const daysGap = Math.round((evDate.getTime() - tradeDate.getTime()) / MS_PER_DAY);
149
+ // Event must be on or after the trade, within the window
150
+ if (daysGap < 0 || daysGap > window)
151
+ continue;
152
+ const [score, pattern] = computeSuspicionScore(trade, ev, daysGap, window);
153
+ if (score < minScore)
154
+ continue;
155
+ const summary = buildEvidenceSummary(trade, ev, daysGap, pattern);
156
+ correlated.push({
157
+ event: ev,
158
+ suspicionScore: score,
159
+ pattern,
160
+ daysBeforeEvent: daysGap,
161
+ evidenceSummary: summary,
162
+ });
163
+ }
164
+ if (correlated.length === 0)
165
+ continue;
166
+ // Sort matches: highest score first, then by proximity
167
+ correlated.sort((a, b) => {
168
+ if (b.suspicionScore !== a.suspicionScore) {
169
+ return b.suspicionScore - a.suspicionScore;
170
+ }
171
+ return a.daysBeforeEvent - b.daysBeforeEvent;
172
+ });
173
+ // Top-level suspicion_score is the max among correlated events
174
+ const top = correlated[0];
175
+ results.push({
176
+ trade,
177
+ correlatedEvents: correlated.map((c) => c.event),
178
+ suspicionScore: top.suspicionScore,
179
+ pattern: top.pattern,
180
+ daysBeforeEvent: top.daysBeforeEvent,
181
+ evidenceSummary: top.evidenceSummary,
182
+ allCorrelations: correlated,
183
+ });
184
+ }
185
+ // Sort final results by suspicion score descending
186
+ results.sort((a, b) => b.suspicionScore - a.suspicionScore);
187
+ return results;
188
+ }
189
+ // ── Summary Generation ──────────────────────────────────────────
190
+ /**
191
+ * Generate a structured summary of correlation results.
192
+ */
193
+ export function generateSummary(results, totalTrades) {
194
+ const patternCounts = {};
195
+ for (const r of results) {
196
+ patternCounts[r.pattern] = (patternCounts[r.pattern] ?? 0) + 1;
197
+ }
198
+ // Sort patterns by count descending
199
+ const patternBreakdown = {};
200
+ const sortedPatterns = Object.entries(patternCounts).sort((a, b) => b[1] - a[1]);
201
+ for (const [pattern, count] of sortedPatterns) {
202
+ patternBreakdown[pattern] = count;
203
+ }
204
+ const topSuspicious = results.slice(0, 5).map((r) => ({
205
+ score: r.suspicionScore,
206
+ person: r.trade.person ?? "?",
207
+ action: r.trade.action ?? "?",
208
+ ticker: r.trade.ticker ?? "?",
209
+ daysBeforeEvent: r.daysBeforeEvent,
210
+ pattern: r.pattern,
211
+ }));
212
+ return {
213
+ totalTrades,
214
+ tradesWithCorrelations: results.length,
215
+ patternBreakdown,
216
+ topSuspicious,
217
+ };
218
+ }
219
+ //# sourceMappingURL=timeline-linker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeline-linker.js","sourceRoot":"","sources":["../../src/investigation-tools/timeline-linker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /**
2
+ * volume-scanner.ts – ASX Volume & Price Anomaly Scanner.
3
+ *
4
+ * Detects unusual volume and price activity for ASX tickers and
5
+ * optionally correlates anomalies with known report dates.
6
+ */
7
+ /** Raw OHLCV bar from Yahoo Finance. */
8
+ export interface OhlcvBar {
9
+ date: string;
10
+ open: number;
11
+ high: number;
12
+ low: number;
13
+ close: number;
14
+ volume: number;
15
+ }
16
+ /** Report-date entry used for anomaly → event correlation. */
17
+ export interface ReportDateEntry {
18
+ date: string;
19
+ description?: string;
20
+ }
21
+ /** Anomaly classification. */
22
+ export type AnomalyType = "volume" | "price" | "both";
23
+ /** A single detected anomaly record. */
24
+ export interface AnomalyRecord {
25
+ ticker: string;
26
+ date: string;
27
+ volume: number;
28
+ avgVolume: number;
29
+ volumeRatio: number;
30
+ close: number;
31
+ priceChangePct: number;
32
+ anomalyType: AnomalyType;
33
+ correlatedReports: string;
34
+ }
35
+ /** Number of days for the rolling average-volume baseline. */
36
+ export declare const ROLLING_WINDOW = 20;
37
+ /** Minimum absolute daily price move (%) to flag as a price anomaly. */
38
+ export declare const PRICE_MOVE_THRESHOLD = 3;
39
+ /** Default lookback window in calendar days. */
40
+ export declare const DEFAULT_LOOKBACK_DAYS = 30;
41
+ /** Default volume multiplier vs 20-day average to flag anomalies. */
42
+ export declare const DEFAULT_VOLUME_THRESHOLD = 2;
43
+ /** Anomaly must fall within this many days *before* a report date to correlate. */
44
+ export declare const CORRELATION_WINDOW_DAYS = 5;
45
+ /**
46
+ * Fetch historical OHLCV data for a single ASX ticker.
47
+ *
48
+ * Requests extra history so the rolling window is fully populated
49
+ * before the actual lookback window begins.
50
+ */
51
+ export declare function fetchHistory(ticker: string, days?: number): Promise<OhlcvBar[] | null>;
52
+ /**
53
+ * Scan OHLCV bars for volume and price anomalies.
54
+ *
55
+ * @param ticker - ASX ticker (with or without .AX suffix)
56
+ * @param bars - OHLCV bars (will be sorted chronologically)
57
+ * @param lookbackDays - Number of calendar days to scan
58
+ * @param volThreshold - Volume multiplier vs 20-day average to flag
59
+ * @param reportDates - Optional report dates for correlation
60
+ * @returns Anomaly records sorted by volume_ratio descending.
61
+ */
62
+ export declare function detectAnomalies(ticker: string, bars: OhlcvBar[], lookbackDays?: number, volThreshold?: number, reportDates?: ReportDateEntry[]): AnomalyRecord[];
63
+ /**
64
+ * Fetch history and detect anomalies for multiple tickers.
65
+ *
66
+ * Convenience wrapper that calls {@link fetchHistory} and
67
+ * {@link detectAnomalies} for each ticker, then merges and sorts results.
68
+ */
69
+ export declare function scanTickers(tickers: string[], days?: number, volThreshold?: number, reportDates?: ReportDateEntry[]): Promise<AnomalyRecord[]>;
70
+ //# sourceMappingURL=volume-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volume-scanner.d.ts","sourceRoot":"","sources":["../../src/investigation-tools/volume-scanner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAUH,wCAAwC;AACxC,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,8DAA8D;AAC9D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,8BAA8B;AAC9B,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,CAAC;AAEtD,wCAAwC;AACxC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,WAAW,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAuBD,8DAA8D;AAC9D,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,wEAAwE;AACxE,eAAO,MAAM,oBAAoB,IAAM,CAAC;AAExC,gDAAgD;AAChD,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,qEAAqE;AACrE,eAAO,MAAM,wBAAwB,IAAM,CAAC;AAE5C,mFAAmF;AACnF,eAAO,MAAM,uBAAuB,IAAI,CAAC;AAmEzC;;;;;GAKG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,MAA8B,GACnC,OAAO,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,CAgC5B;AAID;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,QAAQ,EAAE,EAChB,YAAY,GAAE,MAA8B,EAC5C,YAAY,GAAE,MAAiC,EAC/C,WAAW,CAAC,EAAE,eAAe,EAAE,GAC9B,aAAa,EAAE,CAyGjB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EAAE,EACjB,IAAI,GAAE,MAA8B,EACpC,YAAY,GAAE,MAAiC,EAC/C,WAAW,CAAC,EAAE,eAAe,EAAE,GAC9B,OAAO,CAAC,aAAa,EAAE,CAAC,CAqB1B"}
@@ -0,0 +1,227 @@
1
+ /**
2
+ * volume-scanner.ts – ASX Volume & Price Anomaly Scanner.
3
+ *
4
+ * Detects unusual volume and price activity for ASX tickers and
5
+ * optionally correlates anomalies with known report dates.
6
+ */
7
+ import { fetchJson, yahooTicker, normalizeTicker, } from "./shared.js";
8
+ // ── Constants ────────────────────────────────────────────────────
9
+ /** Number of days for the rolling average-volume baseline. */
10
+ export const ROLLING_WINDOW = 20;
11
+ /** Minimum absolute daily price move (%) to flag as a price anomaly. */
12
+ export const PRICE_MOVE_THRESHOLD = 3.0;
13
+ /** Default lookback window in calendar days. */
14
+ export const DEFAULT_LOOKBACK_DAYS = 30;
15
+ /** Default volume multiplier vs 20-day average to flag anomalies. */
16
+ export const DEFAULT_VOLUME_THRESHOLD = 2.0;
17
+ /** Anomaly must fall within this many days *before* a report date to correlate. */
18
+ export const CORRELATION_WINDOW_DAYS = 5;
19
+ // ── Rolling window helper ────────────────────────────────────────
20
+ /**
21
+ * Compute the rolling mean of the last `window` entries ending at `index`.
22
+ *
23
+ * Requires the full window to be populated (returns null when fewer than
24
+ * `window` valid entries are available), matching the behaviour of
25
+ * `pandas.DataFrame.rolling(window=N).mean()` without `min_periods`.
26
+ */
27
+ function rollingMean(values, index, window) {
28
+ const start = index - window + 1;
29
+ if (start < 0)
30
+ return null;
31
+ let sum = 0;
32
+ let count = 0;
33
+ for (let i = start; i <= index; i++) {
34
+ const v = values[i];
35
+ if (v !== null && isFinite(v)) {
36
+ sum += v;
37
+ count++;
38
+ }
39
+ }
40
+ return count === window ? sum / count : null;
41
+ }
42
+ // ── Data fetching ────────────────────────────────────────────────
43
+ /**
44
+ * Parse a Yahoo Finance chart API JSON response into an array of OhlcvBar.
45
+ */
46
+ function parseChartResponse(data) {
47
+ const result = data.chart.result;
48
+ if (!result || result.length === 0)
49
+ return [];
50
+ const { timestamp, indicators } = result[0];
51
+ const quote = indicators.quote[0];
52
+ const bars = [];
53
+ for (let i = 0; i < timestamp.length; i++) {
54
+ const o = quote.open[i];
55
+ const h = quote.high[i];
56
+ const l = quote.low[i];
57
+ const c = quote.close[i];
58
+ const v = quote.volume[i];
59
+ if (o == null || h == null || l == null || c == null || v == null)
60
+ continue;
61
+ const dt = new Date(timestamp[i] * 1000);
62
+ bars.push({
63
+ date: dt.toISOString().slice(0, 10),
64
+ open: o,
65
+ high: h,
66
+ low: l,
67
+ close: c,
68
+ volume: v,
69
+ });
70
+ }
71
+ return bars;
72
+ }
73
+ /**
74
+ * Fetch historical OHLCV data for a single ASX ticker.
75
+ *
76
+ * Requests extra history so the rolling window is fully populated
77
+ * before the actual lookback window begins.
78
+ */
79
+ export async function fetchHistory(ticker, days = DEFAULT_LOOKBACK_DAYS) {
80
+ const symbol = yahooTicker(ticker);
81
+ // Request extra history so the rolling window is fully populated
82
+ const totalDays = days + ROLLING_WINDOW + 10;
83
+ const endTs = Math.floor(Date.now() / 1000);
84
+ const startTs = endTs - totalDays * 24 * 60 * 60;
85
+ const url = `https://query1.finance.yahoo.com/v8/finance/chart/${encodeURIComponent(symbol)}` +
86
+ `?period1=${startTs}&period2=${endTs}&interval=1d`;
87
+ try {
88
+ const data = await fetchJson(url, { delay: 0.5 });
89
+ if (data.chart.error) {
90
+ console.warn(`[warn] Yahoo API error for ${symbol}: ${data.chart.error.description}`);
91
+ return null;
92
+ }
93
+ const bars = parseChartResponse(data);
94
+ if (bars.length === 0) {
95
+ console.warn(`[warn] No data returned for ${symbol}.`);
96
+ return null;
97
+ }
98
+ return bars;
99
+ }
100
+ catch (err) {
101
+ console.warn(`[warn] Failed to fetch ${symbol}: ${err}`);
102
+ return null;
103
+ }
104
+ }
105
+ // ── Anomaly detection ────────────────────────────────────────────
106
+ /**
107
+ * Scan OHLCV bars for volume and price anomalies.
108
+ *
109
+ * @param ticker - ASX ticker (with or without .AX suffix)
110
+ * @param bars - OHLCV bars (will be sorted chronologically)
111
+ * @param lookbackDays - Number of calendar days to scan
112
+ * @param volThreshold - Volume multiplier vs 20-day average to flag
113
+ * @param reportDates - Optional report dates for correlation
114
+ * @returns Anomaly records sorted by volume_ratio descending.
115
+ */
116
+ export function detectAnomalies(ticker, bars, lookbackDays = DEFAULT_LOOKBACK_DAYS, volThreshold = DEFAULT_VOLUME_THRESHOLD, reportDates) {
117
+ const displayTicker = normalizeTicker(ticker);
118
+ // Ensure bars are sorted chronologically
119
+ const sorted = [...bars].sort((a, b) => a.date.localeCompare(b.date));
120
+ // Pre-compute volume array for rolling average
121
+ const volumes = sorted.map((b) => b.volume);
122
+ // Pre-compute rolling average volumes (strict window — requires full N entries)
123
+ const avgVolumes = sorted.map((_, i) => rollingMean(volumes, i, ROLLING_WINDOW));
124
+ // Pre-compute daily price change percentages
125
+ const priceChangePcts = sorted.map((bar, i) => {
126
+ if (i === 0)
127
+ return null;
128
+ const prev = sorted[i - 1].close;
129
+ return prev !== 0 ? ((bar.close - prev) / prev) * 100.0 : null;
130
+ });
131
+ // Determine cutoff date for lookback window
132
+ const cutoff = new Date();
133
+ cutoff.setDate(cutoff.getDate() - lookbackDays);
134
+ const cutoffStr = cutoff.toISOString().slice(0, 10);
135
+ // Parse report dates once
136
+ const parsedReportDates = [];
137
+ if (reportDates) {
138
+ for (const entry of reportDates) {
139
+ const rd = new Date(entry.date);
140
+ if (!isNaN(rd.getTime())) {
141
+ parsedReportDates.push({
142
+ date: rd,
143
+ description: entry.description ?? "",
144
+ });
145
+ }
146
+ }
147
+ }
148
+ const results = [];
149
+ for (let i = 0; i < sorted.length; i++) {
150
+ const bar = sorted[i];
151
+ // Only consider bars within the lookback window
152
+ if (bar.date < cutoffStr)
153
+ continue;
154
+ const avgVol = avgVolumes[i];
155
+ const priceChg = priceChangePcts[i];
156
+ // Skip rows where rolling window or price change isn't available
157
+ if (avgVol === null || priceChg === null)
158
+ continue;
159
+ const volumeRatio = avgVol > 0 ? bar.volume / avgVol : 0;
160
+ const volAnomaly = volumeRatio >= volThreshold;
161
+ const priceAnomaly = Math.abs(priceChg) >= PRICE_MOVE_THRESHOLD;
162
+ if (!volAnomaly && !priceAnomaly)
163
+ continue;
164
+ let anomalyType;
165
+ if (volAnomaly && priceAnomaly) {
166
+ anomalyType = "both";
167
+ }
168
+ else if (volAnomaly) {
169
+ anomalyType = "volume";
170
+ }
171
+ else {
172
+ anomalyType = "price";
173
+ }
174
+ // Correlate with report dates
175
+ let correlatedReports = "";
176
+ if (parsedReportDates.length > 0) {
177
+ const anomalyDate = new Date(bar.date);
178
+ const correlated = [];
179
+ for (const { date: rd, description } of parsedReportDates) {
180
+ const deltaMs = rd.getTime() - anomalyDate.getTime();
181
+ const deltaDays = Math.round(deltaMs / (1000 * 60 * 60 * 24));
182
+ if (deltaDays >= 0 && deltaDays <= CORRELATION_WINDOW_DAYS) {
183
+ let label = rd.toISOString().slice(0, 10);
184
+ if (description) {
185
+ label += ` (${description})`;
186
+ }
187
+ correlated.push(label);
188
+ }
189
+ }
190
+ correlatedReports = correlated.join("; ");
191
+ }
192
+ results.push({
193
+ ticker: displayTicker,
194
+ date: bar.date,
195
+ volume: Math.round(bar.volume),
196
+ avgVolume: Math.round(avgVol),
197
+ volumeRatio: Math.round(volumeRatio * 100) / 100,
198
+ close: Math.round(bar.close * 10000) / 10000,
199
+ priceChangePct: Math.round(priceChg * 100) / 100,
200
+ anomalyType,
201
+ correlatedReports,
202
+ });
203
+ }
204
+ // Sort by volume_ratio descending (most anomalous first)
205
+ results.sort((a, b) => b.volumeRatio - a.volumeRatio);
206
+ return results;
207
+ }
208
+ /**
209
+ * Fetch history and detect anomalies for multiple tickers.
210
+ *
211
+ * Convenience wrapper that calls {@link fetchHistory} and
212
+ * {@link detectAnomalies} for each ticker, then merges and sorts results.
213
+ */
214
+ export async function scanTickers(tickers, days = DEFAULT_LOOKBACK_DAYS, volThreshold = DEFAULT_VOLUME_THRESHOLD, reportDates) {
215
+ const allAnomalies = [];
216
+ for (const raw of tickers) {
217
+ const bars = await fetchHistory(raw, days);
218
+ if (!bars)
219
+ continue;
220
+ const anomalies = detectAnomalies(raw, bars, days, volThreshold, reportDates);
221
+ allAnomalies.push(...anomalies);
222
+ }
223
+ // Sort all by volume_ratio descending
224
+ allAnomalies.sort((a, b) => b.volumeRatio - a.volumeRatio);
225
+ return allAnomalies;
226
+ }
227
+ //# sourceMappingURL=volume-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volume-scanner.js","sourceRoot":"","sources":["../../src/investigation-tools/volume-scanner.ts"],"names":[],"mappings":"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"}