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.
- package/README.md +210 -0
- package/dist/builder.d.ts +11 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +179 -0
- package/dist/builder.js.map +1 -0
- package/dist/cli.d.ts +9 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +548 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +51 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +114 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.d.ts +52 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +371 -0
- package/dist/credentials.js.map +1 -0
- package/dist/demo.d.ts +26 -0
- package/dist/demo.d.ts.map +1 -0
- package/dist/demo.js +95 -0
- package/dist/demo.js.map +1 -0
- package/dist/engine.d.ts +91 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +1036 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/dist/investigation-tools/aph-holdings.d.ts +61 -0
- package/dist/investigation-tools/aph-holdings.d.ts.map +1 -0
- package/dist/investigation-tools/aph-holdings.js +459 -0
- package/dist/investigation-tools/aph-holdings.js.map +1 -0
- package/dist/investigation-tools/asic-officer-lookup.d.ts +42 -0
- package/dist/investigation-tools/asic-officer-lookup.d.ts.map +1 -0
- package/dist/investigation-tools/asic-officer-lookup.js +197 -0
- package/dist/investigation-tools/asic-officer-lookup.js.map +1 -0
- package/dist/investigation-tools/asx-calendar-fetcher.d.ts +42 -0
- package/dist/investigation-tools/asx-calendar-fetcher.d.ts.map +1 -0
- package/dist/investigation-tools/asx-calendar-fetcher.js +271 -0
- package/dist/investigation-tools/asx-calendar-fetcher.js.map +1 -0
- package/dist/investigation-tools/asx-parser.d.ts +66 -0
- package/dist/investigation-tools/asx-parser.d.ts.map +1 -0
- package/dist/investigation-tools/asx-parser.js +314 -0
- package/dist/investigation-tools/asx-parser.js.map +1 -0
- package/dist/investigation-tools/bulk-asx-announcements.d.ts +53 -0
- package/dist/investigation-tools/bulk-asx-announcements.d.ts.map +1 -0
- package/dist/investigation-tools/bulk-asx-announcements.js +204 -0
- package/dist/investigation-tools/bulk-asx-announcements.js.map +1 -0
- package/dist/investigation-tools/entity-resolver.d.ts +77 -0
- package/dist/investigation-tools/entity-resolver.d.ts.map +1 -0
- package/dist/investigation-tools/entity-resolver.js +346 -0
- package/dist/investigation-tools/entity-resolver.js.map +1 -0
- package/dist/investigation-tools/hotcopper-scraper.d.ts +73 -0
- package/dist/investigation-tools/hotcopper-scraper.d.ts.map +1 -0
- package/dist/investigation-tools/hotcopper-scraper.js +318 -0
- package/dist/investigation-tools/hotcopper-scraper.js.map +1 -0
- package/dist/investigation-tools/index.d.ts +15 -0
- package/dist/investigation-tools/index.d.ts.map +1 -0
- package/dist/investigation-tools/index.js +15 -0
- package/dist/investigation-tools/index.js.map +1 -0
- package/dist/investigation-tools/insider-graph.d.ts +173 -0
- package/dist/investigation-tools/insider-graph.d.ts.map +1 -0
- package/dist/investigation-tools/insider-graph.js +732 -0
- package/dist/investigation-tools/insider-graph.js.map +1 -0
- package/dist/investigation-tools/insider-suspicion-scorer.d.ts +97 -0
- package/dist/investigation-tools/insider-suspicion-scorer.d.ts.map +1 -0
- package/dist/investigation-tools/insider-suspicion-scorer.js +327 -0
- package/dist/investigation-tools/insider-suspicion-scorer.js.map +1 -0
- package/dist/investigation-tools/multi-forum-scraper.d.ts +104 -0
- package/dist/investigation-tools/multi-forum-scraper.d.ts.map +1 -0
- package/dist/investigation-tools/multi-forum-scraper.js +415 -0
- package/dist/investigation-tools/multi-forum-scraper.js.map +1 -0
- package/dist/investigation-tools/price-fetcher.d.ts +81 -0
- package/dist/investigation-tools/price-fetcher.d.ts.map +1 -0
- package/dist/investigation-tools/price-fetcher.js +268 -0
- package/dist/investigation-tools/price-fetcher.js.map +1 -0
- package/dist/investigation-tools/shared.d.ts +39 -0
- package/dist/investigation-tools/shared.d.ts.map +1 -0
- package/dist/investigation-tools/shared.js +203 -0
- package/dist/investigation-tools/shared.js.map +1 -0
- package/dist/investigation-tools/timeline-linker.d.ts +90 -0
- package/dist/investigation-tools/timeline-linker.d.ts.map +1 -0
- package/dist/investigation-tools/timeline-linker.js +219 -0
- package/dist/investigation-tools/timeline-linker.js.map +1 -0
- package/dist/investigation-tools/volume-scanner.d.ts +70 -0
- package/dist/investigation-tools/volume-scanner.d.ts.map +1 -0
- package/dist/investigation-tools/volume-scanner.js +227 -0
- package/dist/investigation-tools/volume-scanner.js.map +1 -0
- package/dist/model.d.ts +136 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +1071 -0
- package/dist/model.js.map +1 -0
- package/dist/patching.d.ts +45 -0
- package/dist/patching.d.ts.map +1 -0
- package/dist/patching.js +317 -0
- package/dist/patching.js.map +1 -0
- package/dist/prompts.d.ts +15 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +351 -0
- package/dist/prompts.js.map +1 -0
- package/dist/replay-log.d.ts +54 -0
- package/dist/replay-log.d.ts.map +1 -0
- package/dist/replay-log.js +94 -0
- package/dist/replay-log.js.map +1 -0
- package/dist/runtime.d.ts +53 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +259 -0
- package/dist/runtime.js.map +1 -0
- package/dist/settings.d.ts +39 -0
- package/dist/settings.d.ts.map +1 -0
- package/dist/settings.js +146 -0
- package/dist/settings.js.map +1 -0
- package/dist/tool-defs.d.ts +58 -0
- package/dist/tool-defs.d.ts.map +1 -0
- package/dist/tool-defs.js +1029 -0
- package/dist/tool-defs.js.map +1 -0
- package/dist/tools.d.ts +72 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +1454 -0
- package/dist/tools.js.map +1 -0
- package/dist/tui.d.ts +49 -0
- package/dist/tui.d.ts.map +1 -0
- package/dist/tui.js +699 -0
- package/dist/tui.js.map +1 -0
- 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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"}
|