-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdnsePublic.ts
More file actions
117 lines (107 loc) · 3.08 KB
/
Copy pathdnsePublic.ts
File metadata and controls
117 lines (107 loc) · 3.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import { request } from "undici";
import { asOfClock, nowSec, isAsOfOverridden } from "../../agent/clock.js";
const DNSE_BASE = "https://services.entrade.com.vn";
export type Resolution = "1" | "5" | "15" | "30" | "1H" | "1D" | "1W" | "1M";
export interface OhlcvSeries {
t: number[]; // unix seconds
o: number[];
h: number[];
l: number[];
c: number[];
v: number[];
nextTime?: number;
}
export interface Bar {
time: number;
open: number;
high: number;
low: number;
close: number;
volume: number;
}
async function fetchOhlcs(
kind: "stock" | "index",
symbol: string,
resolution: Resolution,
from: number,
to: number,
): Promise<OhlcvSeries> {
const url = `${DNSE_BASE}/chart-api/v2/ohlcs/${kind}?symbol=${encodeURIComponent(
symbol,
)}&resolution=${resolution}&from=${from}&to=${to}`;
const { statusCode, body } = await request(url, {
method: "GET",
headers: { accept: "application/json" },
});
if (statusCode !== 200) {
const text = await body.text();
throw new Error(`DNSE ${kind} ${statusCode}: ${text.slice(0, 200)}`);
}
return (await body.json()) as OhlcvSeries;
}
/**
* Binary search to find the index of the last bar that occurs on or before the given time.
* Assumes the bars array is chronologically sorted.
* O(log n) time complexity.
*/
export function findLastBarIndex(bars: Bar[], time: number): number {
let low = 0;
let high = bars.length - 1;
let ans = -1;
while (low <= high) {
const mid = Math.floor((low + high) / 2);
if (bars[mid]!.time <= time) {
ans = mid;
low = mid + 1;
} else {
high = mid - 1;
}
}
return ans;
}
export function seriesToBars(s: OhlcvSeries): Bar[] {
const out: Bar[] = [];
for (let i = 0; i < s.t.length; i++) {
if (s.c[i] == null) continue; // skip empty intraday slots
out.push({
time: s.t[i]!,
open: s.o[i]!,
high: s.h[i]!,
low: s.l[i]!,
close: s.c[i]!,
volume: s.v[i]!,
});
}
return out;
}
function clipBars(bars: Bar[]): Bar[] {
// Clip when an as-of clock is active (ALS or module override). When neither
// is set, fall through unchanged — DNSE only returns historical data anyway.
const hasOverride =
asOfClock.getStore()?.asOfSec != null || isAsOfOverridden();
if (!hasOverride) return bars;
const asOf = nowSec();
// Optimization: use O(log n) binary search rather than O(n) filtering
// to avoid allocating arrays inside tight loops
const idx = findLastBarIndex(bars, asOf);
if (idx < 0) return [];
return idx === bars.length - 1 ? bars : bars.slice(0, idx + 1);
}
export async function getStockOhlcv(
symbol: string,
resolution: Resolution,
from: number,
to: number,
): Promise<Bar[]> {
const series = await fetchOhlcs("stock", symbol.toUpperCase(), resolution, from, to);
return clipBars(seriesToBars(series));
}
export async function getIndexOhlcv(
symbol: string,
resolution: Resolution,
from: number,
to: number,
): Promise<Bar[]> {
const series = await fetchOhlcs("index", symbol.toUpperCase(), resolution, from, to);
return clipBars(seriesToBars(series));
}