@mathieuc/tradingview 3.5.1 → 4.0.0-beta.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 +112 -51
- package/dist/chart/chart-session.d.ts +114 -0
- package/dist/chart/chart-session.js +339 -0
- package/dist/chart/graphics.d.ts +129 -0
- package/dist/chart/graphics.js +153 -0
- package/dist/chart/index.d.ts +11 -0
- package/dist/chart/index.js +6 -0
- package/dist/chart/strategy.d.ts +100 -0
- package/dist/chart/strategy.js +51 -0
- package/dist/chart/study.d.ts +60 -0
- package/dist/chart/study.js +171 -0
- package/dist/chart/timeframes.d.ts +7 -0
- package/dist/chart/timeframes.js +22 -0
- package/dist/chart/types.d.ts +105 -0
- package/dist/chart/types.js +9 -0
- package/dist/client/client.d.ts +89 -0
- package/dist/client/client.js +254 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +2 -0
- package/dist/client/transport.d.ts +27 -0
- package/dist/client/transport.js +38 -0
- package/dist/data/candles.d.ts +40 -0
- package/dist/data/candles.js +101 -0
- package/dist/data/history.d.ts +61 -0
- package/dist/data/history.js +101 -0
- package/dist/data/index.d.ts +28 -0
- package/dist/data/index.js +15 -0
- package/dist/data/indicators.d.ts +54 -0
- package/dist/data/indicators.js +139 -0
- package/dist/data/operation.d.ts +71 -0
- package/dist/data/operation.js +205 -0
- package/dist/data/provider.d.ts +23 -0
- package/dist/data/provider.js +11 -0
- package/dist/data/quotes.d.ts +33 -0
- package/dist/data/quotes.js +117 -0
- package/dist/data/symbols.d.ts +9 -0
- package/dist/data/symbols.js +19 -0
- package/dist/errors.d.ts +53 -0
- package/dist/errors.js +24 -0
- package/dist/events.d.ts +36 -0
- package/dist/events.js +70 -0
- package/dist/http/account.d.ts +47 -0
- package/dist/http/account.js +127 -0
- package/dist/http/index.d.ts +11 -0
- package/dist/http/index.js +5 -0
- package/dist/http/indicators.d.ts +37 -0
- package/dist/http/indicators.js +144 -0
- package/dist/http/layouts.d.ts +36 -0
- package/dist/http/layouts.js +24 -0
- package/dist/http/market.d.ts +45 -0
- package/dist/http/market.js +59 -0
- package/dist/http/pine-permissions.d.ts +29 -0
- package/dist/http/pine-permissions.js +56 -0
- package/dist/http/request.d.ts +44 -0
- package/dist/http/request.js +83 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +19 -0
- package/dist/indicators/builtin-indicator.d.ts +17 -0
- package/dist/indicators/builtin-indicator.js +75 -0
- package/dist/indicators/index.d.ts +8 -0
- package/dist/indicators/index.js +2 -0
- package/dist/indicators/pine-indicator.d.ts +63 -0
- package/dist/indicators/pine-indicator.js +101 -0
- package/dist/protocol/compression.d.ts +12 -0
- package/dist/protocol/compression.js +83 -0
- package/dist/protocol/framing.d.ts +59 -0
- package/dist/protocol/framing.js +68 -0
- package/dist/protocol/ids.d.ts +2 -0
- package/dist/protocol/ids.js +10 -0
- package/dist/protocol/index.d.ts +4 -0
- package/dist/protocol/index.js +3 -0
- package/dist/quote/fields.d.ts +35 -0
- package/dist/quote/fields.js +21 -0
- package/dist/quote/index.d.ts +4 -0
- package/dist/quote/index.js +2 -0
- package/dist/quote/quote-session.d.ts +73 -0
- package/dist/quote/quote-session.js +205 -0
- package/docs/README.es.md +58 -0
- package/docs/README.fr.md +58 -0
- package/docs/README.pt.md +58 -0
- package/docs/data-api.md +229 -0
- package/docs/low-level-api.md +213 -0
- package/docs/migration-v4.md +128 -0
- package/docs/v4-coverage.md +192 -0
- package/docs/v4-reliability.md +33 -0
- package/package.json +55 -20
- package/.env.sample +0 -2
- package/.eslintrc.js +0 -28
- package/.gitattributes +0 -2
- package/docs/DOCS.md +0 -3
- package/examples/AllPrivateIndicators.js +0 -44
- package/examples/BuiltInIndicator.js +0 -51
- package/examples/CustomChartType.js +0 -123
- package/examples/CustomTimeframe.js +0 -31
- package/examples/Errors.js +0 -155
- package/examples/FakeReplayMode.js +0 -40
- package/examples/FromToData.js +0 -35
- package/examples/GetDrawings.js +0 -27
- package/examples/GraphicIndicator.js +0 -42
- package/examples/MultipleSyncFetch.js +0 -44
- package/examples/PinePermManage.js +0 -67
- package/examples/ReplayMode.js +0 -103
- package/examples/Search.js +0 -14
- package/examples/SimpleChart.js +0 -63
- package/examples/UserLogin.js +0 -16
- package/main.js +0 -11
- package/src/chart/graphicParser.js +0 -308
- package/src/chart/session.js +0 -553
- package/src/chart/study.js +0 -435
- package/src/classes/BuiltInIndicator.js +0 -137
- package/src/classes/PineIndicator.js +0 -132
- package/src/classes/PinePermManager.js +0 -159
- package/src/client.js +0 -294
- package/src/miscRequests.js +0 -607
- package/src/protocol.js +0 -60
- package/src/quote/market.js +0 -132
- package/src/quote/session.js +0 -125
- package/src/types.js +0 -36
- package/src/utils.js +0 -20
- package/vite.config.js +0 -7
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/** Raw graphics as sent by TradingView, indexed by type then drawing ID. */
|
|
2
|
+
export type RawGraphics = Record<string, Record<string, any>>;
|
|
3
|
+
export type ExtendValue = 'right' | 'left' | 'both' | 'none';
|
|
4
|
+
export type YLocValue = 'price' | 'abovebar' | 'belowbar';
|
|
5
|
+
export type LabelStyleValue = 'none' | 'xcross' | 'cross' | 'triangleup' | 'triangledown' | 'flag' | 'circle' | 'arrowup' | 'arrowdown' | 'label_up' | 'label_down' | 'label_left' | 'label_right' | 'label_lower_left' | 'label_lower_right' | 'label_upper_left' | 'label_upper_right' | 'label_center' | 'square' | 'diamond';
|
|
6
|
+
export type LineStyleValue = 'solid' | 'dotted' | 'dashed' | 'arrow_left' | 'arrow_right' | 'arrow_both';
|
|
7
|
+
export type BoxStyleValue = 'solid' | 'dotted' | 'dashed';
|
|
8
|
+
export type SizeValue = 'auto' | 'huge' | 'large' | 'normal' | 'small' | 'tiny';
|
|
9
|
+
export type VAlignValue = 'top' | 'center' | 'bottom';
|
|
10
|
+
export type HAlignValue = 'left' | 'center' | 'right';
|
|
11
|
+
export type TextWrapValue = 'none' | 'auto';
|
|
12
|
+
export type TablePositionValue = 'top_left' | 'top_center' | 'top_right' | 'middle_left' | 'middle_center' | 'middle_right' | 'bottom_left' | 'bottom_center' | 'bottom_right';
|
|
13
|
+
/**
|
|
14
|
+
* X positions are expressed in bars back from the most recent bar loaded on
|
|
15
|
+
* the chart (0 = latest bar). They are `undefined` when the bar is not loaded.
|
|
16
|
+
*/
|
|
17
|
+
export interface GraphicLabel {
|
|
18
|
+
id: number;
|
|
19
|
+
x: number | undefined;
|
|
20
|
+
y: number;
|
|
21
|
+
yLoc: YLocValue;
|
|
22
|
+
text: string;
|
|
23
|
+
style: LabelStyleValue;
|
|
24
|
+
color: number;
|
|
25
|
+
textColor: number;
|
|
26
|
+
size: SizeValue;
|
|
27
|
+
textAlign: HAlignValue;
|
|
28
|
+
toolTip: string;
|
|
29
|
+
}
|
|
30
|
+
export interface GraphicLine {
|
|
31
|
+
id: number;
|
|
32
|
+
x1: number | undefined;
|
|
33
|
+
y1: number;
|
|
34
|
+
x2: number | undefined;
|
|
35
|
+
y2: number;
|
|
36
|
+
extend: ExtendValue;
|
|
37
|
+
style: LineStyleValue;
|
|
38
|
+
color: number;
|
|
39
|
+
width: number;
|
|
40
|
+
}
|
|
41
|
+
export interface GraphicBox {
|
|
42
|
+
id: number;
|
|
43
|
+
x1: number | undefined;
|
|
44
|
+
y1: number;
|
|
45
|
+
x2: number | undefined;
|
|
46
|
+
y2: number;
|
|
47
|
+
color: number;
|
|
48
|
+
bgColor: number;
|
|
49
|
+
extend: ExtendValue;
|
|
50
|
+
style: BoxStyleValue;
|
|
51
|
+
width: number;
|
|
52
|
+
text: string;
|
|
53
|
+
textSize: SizeValue;
|
|
54
|
+
textColor: number;
|
|
55
|
+
textVAlign: VAlignValue;
|
|
56
|
+
textHAlign: HAlignValue;
|
|
57
|
+
textWrap: TextWrapValue;
|
|
58
|
+
}
|
|
59
|
+
export interface TableCell {
|
|
60
|
+
id: number;
|
|
61
|
+
text: string;
|
|
62
|
+
width: number;
|
|
63
|
+
height: number;
|
|
64
|
+
textColor: number;
|
|
65
|
+
textHAlign: HAlignValue;
|
|
66
|
+
textVAlign: VAlignValue;
|
|
67
|
+
textSize: SizeValue;
|
|
68
|
+
bgColor: number;
|
|
69
|
+
}
|
|
70
|
+
export interface GraphicTable {
|
|
71
|
+
id: number;
|
|
72
|
+
position: TablePositionValue;
|
|
73
|
+
rows: number;
|
|
74
|
+
columns: number;
|
|
75
|
+
bgColor: number;
|
|
76
|
+
frameColor: number;
|
|
77
|
+
frameWidth: number;
|
|
78
|
+
borderColor: number;
|
|
79
|
+
borderWidth: number;
|
|
80
|
+
/** Cells as a `[row][column]` matrix. */
|
|
81
|
+
cells: TableCell[][];
|
|
82
|
+
}
|
|
83
|
+
export interface GraphicHorizLine {
|
|
84
|
+
id: number;
|
|
85
|
+
level: number;
|
|
86
|
+
startIndex: number | undefined;
|
|
87
|
+
endIndex: number | undefined;
|
|
88
|
+
extendRight: boolean;
|
|
89
|
+
extendLeft: boolean;
|
|
90
|
+
[key: string]: unknown;
|
|
91
|
+
}
|
|
92
|
+
export interface GraphicPoint {
|
|
93
|
+
index: number | undefined;
|
|
94
|
+
level: number;
|
|
95
|
+
[key: string]: unknown;
|
|
96
|
+
}
|
|
97
|
+
export interface GraphicPolygon {
|
|
98
|
+
id: number;
|
|
99
|
+
points: GraphicPoint[];
|
|
100
|
+
[key: string]: unknown;
|
|
101
|
+
}
|
|
102
|
+
export interface GraphicHorizHist {
|
|
103
|
+
id: number;
|
|
104
|
+
priceLow: number;
|
|
105
|
+
priceHigh: number;
|
|
106
|
+
firstBarTime: number | undefined;
|
|
107
|
+
lastBarTime: number | undefined;
|
|
108
|
+
rate: number[];
|
|
109
|
+
[key: string]: unknown;
|
|
110
|
+
}
|
|
111
|
+
/** Drawings produced by an indicator, grouped by type. */
|
|
112
|
+
export interface GraphicsData {
|
|
113
|
+
labels: GraphicLabel[];
|
|
114
|
+
lines: GraphicLine[];
|
|
115
|
+
boxes: GraphicBox[];
|
|
116
|
+
tables: GraphicTable[];
|
|
117
|
+
polygons: GraphicPolygon[];
|
|
118
|
+
horizLines: GraphicHorizLine[];
|
|
119
|
+
horizHists: GraphicHorizHist[];
|
|
120
|
+
/** Unparsed graphics, including types this parser does not know. */
|
|
121
|
+
raw: RawGraphics;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Converts raw indicator graphics into readable objects.
|
|
125
|
+
* @param indexes Maps graphic X indexes to "bars back" positions.
|
|
126
|
+
*/
|
|
127
|
+
export declare function parseGraphics(raw?: RawGraphics, indexes?: ReadonlyArray<number | undefined>): GraphicsData;
|
|
128
|
+
/** Applies `graphicsCmds` (erase then create) to raw graphics in place. */
|
|
129
|
+
export declare function applyGraphicsCommands(graphics: RawGraphics, commands: any): void;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
const TRANSLATOR = {
|
|
2
|
+
extend: {
|
|
3
|
+
r: 'right', l: 'left', b: 'both', n: 'none',
|
|
4
|
+
},
|
|
5
|
+
yLoc: { pr: 'price', ab: 'abovebar', bl: 'belowbar' },
|
|
6
|
+
labelStyle: {
|
|
7
|
+
n: 'none',
|
|
8
|
+
xcr: 'xcross',
|
|
9
|
+
cr: 'cross',
|
|
10
|
+
tup: 'triangleup',
|
|
11
|
+
tdn: 'triangledown',
|
|
12
|
+
flg: 'flag',
|
|
13
|
+
cir: 'circle',
|
|
14
|
+
aup: 'arrowup',
|
|
15
|
+
adn: 'arrowdown',
|
|
16
|
+
lup: 'label_up',
|
|
17
|
+
ldn: 'label_down',
|
|
18
|
+
llf: 'label_left',
|
|
19
|
+
lrg: 'label_right',
|
|
20
|
+
llwlf: 'label_lower_left',
|
|
21
|
+
llwrg: 'label_lower_right',
|
|
22
|
+
luplf: 'label_upper_left',
|
|
23
|
+
luprg: 'label_upper_right',
|
|
24
|
+
lcn: 'label_center',
|
|
25
|
+
sq: 'square',
|
|
26
|
+
dia: 'diamond',
|
|
27
|
+
},
|
|
28
|
+
lineStyle: {
|
|
29
|
+
sol: 'solid', dot: 'dotted', dsh: 'dashed', al: 'arrow_left', ar: 'arrow_right', ab: 'arrow_both',
|
|
30
|
+
},
|
|
31
|
+
boxStyle: { sol: 'solid', dot: 'dotted', dsh: 'dashed' },
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Converts raw indicator graphics into readable objects.
|
|
35
|
+
* @param indexes Maps graphic X indexes to "bars back" positions.
|
|
36
|
+
*/
|
|
37
|
+
export function parseGraphics(raw = {}, indexes = []) {
|
|
38
|
+
const values = (type) => Object.values(raw[type] ?? {});
|
|
39
|
+
const x = (index) => indexes[index];
|
|
40
|
+
return {
|
|
41
|
+
labels: values('dwglabels').map((l) => ({
|
|
42
|
+
id: l.id,
|
|
43
|
+
x: x(l.x),
|
|
44
|
+
y: l.y,
|
|
45
|
+
yLoc: TRANSLATOR.yLoc[l.yl] ?? l.yl,
|
|
46
|
+
text: l.t,
|
|
47
|
+
style: TRANSLATOR.labelStyle[l.st] ?? l.st,
|
|
48
|
+
color: l.ci,
|
|
49
|
+
textColor: l.tci,
|
|
50
|
+
size: l.sz,
|
|
51
|
+
textAlign: l.ta,
|
|
52
|
+
toolTip: l.tt,
|
|
53
|
+
})),
|
|
54
|
+
lines: values('dwglines').map((l) => ({
|
|
55
|
+
id: l.id,
|
|
56
|
+
x1: x(l.x1),
|
|
57
|
+
y1: l.y1,
|
|
58
|
+
x2: x(l.x2),
|
|
59
|
+
y2: l.y2,
|
|
60
|
+
extend: TRANSLATOR.extend[l.ex] ?? l.ex,
|
|
61
|
+
style: TRANSLATOR.lineStyle[l.st] ?? l.st,
|
|
62
|
+
color: l.ci,
|
|
63
|
+
width: l.w,
|
|
64
|
+
})),
|
|
65
|
+
boxes: values('dwgboxes').map((b) => ({
|
|
66
|
+
id: b.id,
|
|
67
|
+
x1: x(b.x1),
|
|
68
|
+
y1: b.y1,
|
|
69
|
+
x2: x(b.x2),
|
|
70
|
+
y2: b.y2,
|
|
71
|
+
color: b.c,
|
|
72
|
+
bgColor: b.bc,
|
|
73
|
+
extend: TRANSLATOR.extend[b.ex] ?? b.ex,
|
|
74
|
+
style: TRANSLATOR.boxStyle[b.st] ?? b.st,
|
|
75
|
+
width: b.w,
|
|
76
|
+
text: b.t,
|
|
77
|
+
textSize: b.ts,
|
|
78
|
+
textColor: b.tc,
|
|
79
|
+
textVAlign: b.tva,
|
|
80
|
+
textHAlign: b.tha,
|
|
81
|
+
textWrap: b.tw,
|
|
82
|
+
})),
|
|
83
|
+
tables: values('dwgtables').map((t) => {
|
|
84
|
+
const cells = [];
|
|
85
|
+
for (const cell of values('dwgtablecells')) {
|
|
86
|
+
if (cell.tid !== t.id)
|
|
87
|
+
continue;
|
|
88
|
+
cells[cell.row] ??= [];
|
|
89
|
+
cells[cell.row][cell.col] = {
|
|
90
|
+
id: cell.id,
|
|
91
|
+
text: cell.t,
|
|
92
|
+
width: cell.w,
|
|
93
|
+
height: cell.h,
|
|
94
|
+
textColor: cell.tc,
|
|
95
|
+
textHAlign: cell.tha,
|
|
96
|
+
textVAlign: cell.tva,
|
|
97
|
+
textSize: cell.ts,
|
|
98
|
+
bgColor: cell.bgc,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
id: t.id,
|
|
103
|
+
position: t.pos,
|
|
104
|
+
rows: t.rows,
|
|
105
|
+
columns: t.cols,
|
|
106
|
+
bgColor: t.bgc,
|
|
107
|
+
frameColor: t.frmc,
|
|
108
|
+
frameWidth: t.frmw,
|
|
109
|
+
borderColor: t.brdc,
|
|
110
|
+
borderWidth: t.brdw,
|
|
111
|
+
cells,
|
|
112
|
+
};
|
|
113
|
+
}),
|
|
114
|
+
horizLines: values('horizlines').map((h) => ({
|
|
115
|
+
...h,
|
|
116
|
+
startIndex: x(h.startIndex),
|
|
117
|
+
endIndex: x(h.endIndex),
|
|
118
|
+
})),
|
|
119
|
+
polygons: values('polygons').map((p) => ({
|
|
120
|
+
...p,
|
|
121
|
+
points: (p.points ?? []).map((point) => ({ ...point, index: x(point.index) })),
|
|
122
|
+
})),
|
|
123
|
+
horizHists: values('hhists').map((h) => ({
|
|
124
|
+
...h,
|
|
125
|
+
firstBarTime: x(h.firstBarTime),
|
|
126
|
+
lastBarTime: x(h.lastBarTime),
|
|
127
|
+
})),
|
|
128
|
+
raw,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/** Applies `graphicsCmds` (erase then create) to raw graphics in place. */
|
|
132
|
+
export function applyGraphicsCommands(graphics, commands) {
|
|
133
|
+
for (const instruction of commands?.erase ?? []) {
|
|
134
|
+
if (instruction.action === 'all') {
|
|
135
|
+
if (!instruction.type) {
|
|
136
|
+
for (const type of Object.keys(graphics))
|
|
137
|
+
graphics[type] = {};
|
|
138
|
+
}
|
|
139
|
+
else
|
|
140
|
+
delete graphics[instruction.type];
|
|
141
|
+
}
|
|
142
|
+
else if (instruction.action === 'one' && instruction.type) {
|
|
143
|
+
delete graphics[instruction.type]?.[instruction.id];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (const [type, groups] of Object.entries(commands?.create ?? {})) {
|
|
147
|
+
graphics[type] ??= {};
|
|
148
|
+
for (const group of groups ?? []) {
|
|
149
|
+
for (const item of group.data ?? [])
|
|
150
|
+
graphics[type][item.id] = item;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { ChartSession } from './chart-session.js';
|
|
2
|
+
export type { ChartEvents, MarketOptions, SeriesCompletedInfo, } from './chart-session.js';
|
|
3
|
+
export { Study } from './study.js';
|
|
4
|
+
export type { StudyChange, StudyEvents, StudyValue } from './study.js';
|
|
5
|
+
export { applyGraphicsCommands, parseGraphics, } from './graphics.js';
|
|
6
|
+
export type { BoxStyleValue, ExtendValue, GraphicBox, GraphicHorizHist, GraphicHorizLine, GraphicLabel, GraphicLine, GraphicPoint, GraphicPolygon, GraphicTable, GraphicsData, HAlignValue, LabelStyleValue, LineStyleValue, RawGraphics, SizeValue, TableCell, TablePositionValue, TextWrapValue, VAlignValue, YLocValue, } from './graphics.js';
|
|
7
|
+
export { mergeStrategyReport, parseTrades } from './strategy.js';
|
|
8
|
+
export type { FromTo, PerformanceReport, RelAbsValue, StrategyReport, StrategyReportChange, TradeReport, } from './strategy.js';
|
|
9
|
+
export { timeframeSeconds } from './timeframes.js';
|
|
10
|
+
export { CHART_TYPE_STUDIES } from './types.js';
|
|
11
|
+
export type { Adjustment, Candle, ChartType, ChartTypeInputs, MarketSymbol, Subsession, SymbolInfo, Timeframe, Timezone, TradingSession, } from './types.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ChartSession } from './chart-session.js';
|
|
2
|
+
export { Study } from './study.js';
|
|
3
|
+
export { applyGraphicsCommands, parseGraphics, } from './graphics.js';
|
|
4
|
+
export { mergeStrategyReport, parseTrades } from './strategy.js';
|
|
5
|
+
export { timeframeSeconds } from './timeframes.js';
|
|
6
|
+
export { CHART_TYPE_STUDIES } from './types.js';
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/** A value with absolute and percent forms. */
|
|
2
|
+
export interface RelAbsValue {
|
|
3
|
+
v: number;
|
|
4
|
+
p: number;
|
|
5
|
+
}
|
|
6
|
+
export interface TradeReport {
|
|
7
|
+
entry: {
|
|
8
|
+
name: string;
|
|
9
|
+
type: 'long' | 'short';
|
|
10
|
+
value: number;
|
|
11
|
+
time: number;
|
|
12
|
+
};
|
|
13
|
+
/** `name` is '' for exits that are not explicit. */
|
|
14
|
+
exit: {
|
|
15
|
+
name: string;
|
|
16
|
+
value: number;
|
|
17
|
+
time: number;
|
|
18
|
+
};
|
|
19
|
+
quantity: number;
|
|
20
|
+
profit: RelAbsValue;
|
|
21
|
+
cumulative: RelAbsValue;
|
|
22
|
+
runup: RelAbsValue;
|
|
23
|
+
drawdown: RelAbsValue;
|
|
24
|
+
}
|
|
25
|
+
export interface PerformanceReport {
|
|
26
|
+
avgBarsInTrade: number;
|
|
27
|
+
avgBarsInWinTrade: number;
|
|
28
|
+
avgBarsInLossTrade: number;
|
|
29
|
+
avgTrade: number;
|
|
30
|
+
avgTradePercent: number;
|
|
31
|
+
avgLosTrade: number;
|
|
32
|
+
avgLosTradePercent: number;
|
|
33
|
+
avgWinTrade: number;
|
|
34
|
+
avgWinTradePercent: number;
|
|
35
|
+
commissionPaid: number;
|
|
36
|
+
grossLoss: number;
|
|
37
|
+
grossLossPercent: number;
|
|
38
|
+
grossProfit: number;
|
|
39
|
+
grossProfitPercent: number;
|
|
40
|
+
largestLosTrade: number;
|
|
41
|
+
largestLosTradePercent: number;
|
|
42
|
+
largestWinTrade: number;
|
|
43
|
+
largestWinTradePercent: number;
|
|
44
|
+
marginCalls: number;
|
|
45
|
+
maxContractsHeld: number;
|
|
46
|
+
netProfit: number;
|
|
47
|
+
netProfitPercent: number;
|
|
48
|
+
numberOfLosingTrades: number;
|
|
49
|
+
numberOfWiningTrades: number;
|
|
50
|
+
percentProfitable: number;
|
|
51
|
+
profitFactor: number;
|
|
52
|
+
ratioAvgWinAvgLoss: number;
|
|
53
|
+
totalOpenTrades: number;
|
|
54
|
+
totalTrades: number;
|
|
55
|
+
[key: string]: number;
|
|
56
|
+
}
|
|
57
|
+
export interface FromTo {
|
|
58
|
+
from: number;
|
|
59
|
+
to: number;
|
|
60
|
+
}
|
|
61
|
+
/** Report sent by Pine strategies (backtest results computed by TradingView). */
|
|
62
|
+
export interface StrategyReport {
|
|
63
|
+
currency?: string;
|
|
64
|
+
settings?: {
|
|
65
|
+
dateRange?: {
|
|
66
|
+
backtest?: FromTo;
|
|
67
|
+
trade?: FromTo;
|
|
68
|
+
};
|
|
69
|
+
[key: string]: unknown;
|
|
70
|
+
};
|
|
71
|
+
/** Trades, most recent first. */
|
|
72
|
+
trades: TradeReport[];
|
|
73
|
+
history: {
|
|
74
|
+
buyHold?: number[];
|
|
75
|
+
buyHoldPercent?: number[];
|
|
76
|
+
drawDown?: number[];
|
|
77
|
+
drawDownPercent?: number[];
|
|
78
|
+
equity?: number[];
|
|
79
|
+
equityPercent?: number[];
|
|
80
|
+
};
|
|
81
|
+
performance: {
|
|
82
|
+
all?: PerformanceReport;
|
|
83
|
+
long?: PerformanceReport;
|
|
84
|
+
short?: PerformanceReport;
|
|
85
|
+
buyHoldReturn?: number;
|
|
86
|
+
buyHoldReturnPercent?: number;
|
|
87
|
+
maxDrawDown?: number;
|
|
88
|
+
maxDrawDownPercent?: number;
|
|
89
|
+
openPL?: number;
|
|
90
|
+
openPLPercent?: number;
|
|
91
|
+
sharpeRatio?: number;
|
|
92
|
+
sortinoRatio?: number;
|
|
93
|
+
[key: string]: unknown;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export type StrategyReportChange = 'report.currency' | 'report.settings' | 'report.perf' | 'report.trades' | 'report.history';
|
|
97
|
+
/** Converts raw trades (oldest first) into readable trades (most recent first). */
|
|
98
|
+
export declare function parseTrades(trades: any[]): TradeReport[];
|
|
99
|
+
/** Merges a raw report into `target`; returns the changed parts. */
|
|
100
|
+
export declare function mergeStrategyReport(target: StrategyReport, report: any): StrategyReportChange[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** Converts raw trades (oldest first) into readable trades (most recent first). */
|
|
2
|
+
export function parseTrades(trades) {
|
|
3
|
+
return [...trades].reverse().map((t) => ({
|
|
4
|
+
entry: {
|
|
5
|
+
name: t.e?.c,
|
|
6
|
+
type: String(t.e?.tp ?? '').startsWith('s') ? 'short' : 'long',
|
|
7
|
+
value: t.e?.p,
|
|
8
|
+
time: t.e?.tm,
|
|
9
|
+
},
|
|
10
|
+
exit: { name: t.x?.c, value: t.x?.p, time: t.x?.tm },
|
|
11
|
+
quantity: t.q,
|
|
12
|
+
profit: t.tp,
|
|
13
|
+
cumulative: t.cp,
|
|
14
|
+
runup: t.rn,
|
|
15
|
+
drawdown: t.dd,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
/** Merges a raw report into `target`; returns the changed parts. */
|
|
19
|
+
export function mergeStrategyReport(target, report) {
|
|
20
|
+
const changes = [];
|
|
21
|
+
if (!report || typeof report !== 'object')
|
|
22
|
+
return changes;
|
|
23
|
+
if (report.currency) {
|
|
24
|
+
target.currency = report.currency;
|
|
25
|
+
changes.push('report.currency');
|
|
26
|
+
}
|
|
27
|
+
if (report.settings) {
|
|
28
|
+
target.settings = report.settings;
|
|
29
|
+
changes.push('report.settings');
|
|
30
|
+
}
|
|
31
|
+
if (report.performance) {
|
|
32
|
+
target.performance = report.performance;
|
|
33
|
+
changes.push('report.perf');
|
|
34
|
+
}
|
|
35
|
+
if (report.trades) {
|
|
36
|
+
target.trades = parseTrades(report.trades);
|
|
37
|
+
changes.push('report.trades');
|
|
38
|
+
}
|
|
39
|
+
if (report.equity) {
|
|
40
|
+
target.history = {
|
|
41
|
+
buyHold: report.buyHold,
|
|
42
|
+
buyHoldPercent: report.buyHoldPercent,
|
|
43
|
+
drawDown: report.drawDown,
|
|
44
|
+
drawDownPercent: report.drawDownPercent,
|
|
45
|
+
equity: report.equity,
|
|
46
|
+
equityPercent: report.equityPercent,
|
|
47
|
+
};
|
|
48
|
+
changes.push('report.history');
|
|
49
|
+
}
|
|
50
|
+
return changes;
|
|
51
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { TradingViewError } from '../errors.js';
|
|
2
|
+
import { Emitter } from '../events.js';
|
|
3
|
+
import type { Indicator } from '../indicators/index.js';
|
|
4
|
+
import type { ServerPacket } from '../protocol/framing.js';
|
|
5
|
+
import { type GraphicsData } from './graphics.js';
|
|
6
|
+
import { type StrategyReport, type StrategyReportChange } from './strategy.js';
|
|
7
|
+
/** One study row: `$time` (Unix seconds) plus one entry per plot. */
|
|
8
|
+
export interface StudyValue {
|
|
9
|
+
$time: number;
|
|
10
|
+
[plot: string]: number;
|
|
11
|
+
}
|
|
12
|
+
export type StudyChange = 'plots' | 'graphic' | StrategyReportChange;
|
|
13
|
+
export interface StudyEvents {
|
|
14
|
+
[event: string]: unknown[];
|
|
15
|
+
/** The server started (re)computing the study. */
|
|
16
|
+
loading: [];
|
|
17
|
+
/** The study finished computing over the loaded bars. */
|
|
18
|
+
ready: [];
|
|
19
|
+
update: [changes: StudyChange[]];
|
|
20
|
+
error: [error: TradingViewError];
|
|
21
|
+
}
|
|
22
|
+
/** @internal Link between a study and its chart. */
|
|
23
|
+
export interface StudyBridge {
|
|
24
|
+
chartId: string;
|
|
25
|
+
send(method: string, params: unknown[]): void;
|
|
26
|
+
barsBack(): Map<number, number>;
|
|
27
|
+
log(...args: unknown[]): void;
|
|
28
|
+
unregister(id: string): void;
|
|
29
|
+
}
|
|
30
|
+
/** An indicator running on a chart. Create it with `chart.createStudy(indicator)`. */
|
|
31
|
+
export declare class Study extends Emitter<StudyEvents> {
|
|
32
|
+
#private;
|
|
33
|
+
readonly id: string;
|
|
34
|
+
/** @internal Use `chart.createStudy()`. */
|
|
35
|
+
constructor(bridge: StudyBridge, indicator: Indicator);
|
|
36
|
+
/** @internal */
|
|
37
|
+
create(): void;
|
|
38
|
+
/** The indicator definition running in this study. */
|
|
39
|
+
get indicator(): Indicator;
|
|
40
|
+
/** True after the first `study_completed`. */
|
|
41
|
+
get isReady(): boolean;
|
|
42
|
+
/** True after `remove()` or when the chart is gone. */
|
|
43
|
+
get isRemoved(): boolean;
|
|
44
|
+
/** Plot values, oldest first. */
|
|
45
|
+
get values(): StudyValue[];
|
|
46
|
+
/** Drawings (labels, lines, boxes, tables...) produced by the indicator. */
|
|
47
|
+
get graphics(): GraphicsData;
|
|
48
|
+
/** Strategy report (Pine strategies only). */
|
|
49
|
+
get strategyReport(): StrategyReport;
|
|
50
|
+
/** Replaces the indicator (typically the same script with new inputs). */
|
|
51
|
+
setIndicator(indicator: Indicator): void;
|
|
52
|
+
/** Removes the study from the chart. Idempotent. */
|
|
53
|
+
remove(): void;
|
|
54
|
+
/** @internal Called when the chart or connection is gone; `error` is reported if set. */
|
|
55
|
+
dispose(error?: TradingViewError): void;
|
|
56
|
+
/** @internal Packets addressed to this study (`study_*`). */
|
|
57
|
+
onPacket(packet: ServerPacket): void;
|
|
58
|
+
/** @internal Data from `timescale_update` / `du`. */
|
|
59
|
+
onData(data: any): void;
|
|
60
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { TradingViewError } from '../errors.js';
|
|
2
|
+
import { Emitter } from '../events.js';
|
|
3
|
+
import { BuiltInIndicator } from '../indicators/builtin-indicator.js';
|
|
4
|
+
import { PineIndicator } from '../indicators/pine-indicator.js';
|
|
5
|
+
import { decodeCompressed } from '../protocol/compression.js';
|
|
6
|
+
import { createSessionId } from '../protocol/ids.js';
|
|
7
|
+
import { applyGraphicsCommands, parseGraphics, } from './graphics.js';
|
|
8
|
+
import { mergeStrategyReport, } from './strategy.js';
|
|
9
|
+
function assertIndicator(indicator) {
|
|
10
|
+
if (!(indicator instanceof PineIndicator) && !(indicator instanceof BuiltInIndicator)) {
|
|
11
|
+
throw new TradingViewError('INVALID_ARGUMENT', 'The indicator must be a PineIndicator (see getIndicator) or a BuiltInIndicator');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Formats a `study_error` payload into a readable message. */
|
|
15
|
+
function studyErrorMessage(error) {
|
|
16
|
+
if (error && typeof error === 'object' && 'error' in error) {
|
|
17
|
+
const { error: template, ctx } = error;
|
|
18
|
+
if (typeof template === 'string') {
|
|
19
|
+
return template.replace(/\{(\w+)\}/g, (all, key) => (ctx && key in ctx ? String(ctx[key]) : all));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return typeof error === 'string' ? error : JSON.stringify(error);
|
|
23
|
+
}
|
|
24
|
+
/** An indicator running on a chart. Create it with `chart.createStudy(indicator)`. */
|
|
25
|
+
export class Study extends Emitter {
|
|
26
|
+
id = createSessionId('st');
|
|
27
|
+
#bridge;
|
|
28
|
+
#values = new Map();
|
|
29
|
+
#graphics = {};
|
|
30
|
+
#graphicIndexes = [];
|
|
31
|
+
#strategyReport = { trades: [], history: {}, performance: {} };
|
|
32
|
+
#indicator;
|
|
33
|
+
#removed = false;
|
|
34
|
+
#ready = false;
|
|
35
|
+
/** @internal Use `chart.createStudy()`. */
|
|
36
|
+
constructor(bridge, indicator) {
|
|
37
|
+
super();
|
|
38
|
+
assertIndicator(indicator);
|
|
39
|
+
this.#bridge = bridge;
|
|
40
|
+
this.#indicator = indicator;
|
|
41
|
+
}
|
|
42
|
+
/** @internal */
|
|
43
|
+
create() {
|
|
44
|
+
this.#bridge.send('create_study', [
|
|
45
|
+
this.#bridge.chartId, this.id, 'st1', '$prices', this.#indicator.type, this.#indicator.toStudyInputs(),
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
/** The indicator definition running in this study. */
|
|
49
|
+
get indicator() {
|
|
50
|
+
return this.#indicator;
|
|
51
|
+
}
|
|
52
|
+
/** True after the first `study_completed`. */
|
|
53
|
+
get isReady() {
|
|
54
|
+
return this.#ready;
|
|
55
|
+
}
|
|
56
|
+
/** True after `remove()` or when the chart is gone. */
|
|
57
|
+
get isRemoved() {
|
|
58
|
+
return this.#removed;
|
|
59
|
+
}
|
|
60
|
+
/** Plot values, oldest first. */
|
|
61
|
+
get values() {
|
|
62
|
+
return [...this.#values.values()].sort((a, b) => a.$time - b.$time);
|
|
63
|
+
}
|
|
64
|
+
/** Drawings (labels, lines, boxes, tables...) produced by the indicator. */
|
|
65
|
+
get graphics() {
|
|
66
|
+
const barsBack = this.#bridge.barsBack();
|
|
67
|
+
return parseGraphics(this.#graphics, this.#graphicIndexes.map((index) => barsBack.get(index)));
|
|
68
|
+
}
|
|
69
|
+
/** Strategy report (Pine strategies only). */
|
|
70
|
+
get strategyReport() {
|
|
71
|
+
return this.#strategyReport;
|
|
72
|
+
}
|
|
73
|
+
/** Replaces the indicator (typically the same script with new inputs). */
|
|
74
|
+
setIndicator(indicator) {
|
|
75
|
+
assertIndicator(indicator);
|
|
76
|
+
if (this.#removed)
|
|
77
|
+
throw new TradingViewError('INVALID_STATE', 'The study is removed');
|
|
78
|
+
this.#indicator = indicator;
|
|
79
|
+
this.#bridge.send('modify_study', [this.#bridge.chartId, this.id, 'st1', indicator.toStudyInputs()]);
|
|
80
|
+
}
|
|
81
|
+
/** Removes the study from the chart. Idempotent. */
|
|
82
|
+
remove() {
|
|
83
|
+
if (this.#removed)
|
|
84
|
+
return;
|
|
85
|
+
this.#bridge.send('remove_study', [this.#bridge.chartId, this.id]);
|
|
86
|
+
this.#removed = true;
|
|
87
|
+
this.#bridge.unregister(this.id);
|
|
88
|
+
}
|
|
89
|
+
/** @internal Called when the chart or connection is gone; `error` is reported if set. */
|
|
90
|
+
dispose(error) {
|
|
91
|
+
if (this.#removed)
|
|
92
|
+
return;
|
|
93
|
+
this.#removed = true;
|
|
94
|
+
if (error)
|
|
95
|
+
this.emit('error', error);
|
|
96
|
+
}
|
|
97
|
+
/** @internal Packets addressed to this study (`study_*`). */
|
|
98
|
+
onPacket(packet) {
|
|
99
|
+
this.#bridge.log('study', this.id, packet);
|
|
100
|
+
switch (packet.m) {
|
|
101
|
+
case 'study_loading':
|
|
102
|
+
this.emit('loading');
|
|
103
|
+
return;
|
|
104
|
+
case 'study_completed':
|
|
105
|
+
this.#ready = true;
|
|
106
|
+
this.emit('ready');
|
|
107
|
+
return;
|
|
108
|
+
case 'study_error':
|
|
109
|
+
this.emit('error', new TradingViewError('STUDY_ERROR', `Study error: ${studyErrorMessage(packet.p[3])}`, {
|
|
110
|
+
details: packet.p.slice(3),
|
|
111
|
+
}));
|
|
112
|
+
return;
|
|
113
|
+
default:
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** @internal Data from `timescale_update` / `du`. */
|
|
117
|
+
onData(data) {
|
|
118
|
+
if (!data || typeof data !== 'object')
|
|
119
|
+
return;
|
|
120
|
+
const changes = [];
|
|
121
|
+
if (Array.isArray(data.st) && data.st.length > 0) {
|
|
122
|
+
const { plots } = this.#indicator instanceof PineIndicator ? this.#indicator : { plots: undefined };
|
|
123
|
+
for (const row of data.st) {
|
|
124
|
+
const value = {};
|
|
125
|
+
row.v.forEach((plot, i) => {
|
|
126
|
+
if (i === 0) {
|
|
127
|
+
value.$time = plot;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const name = plots?.[`plot_${i - 1}`];
|
|
131
|
+
if (name && !(name in value))
|
|
132
|
+
value[name] = plot;
|
|
133
|
+
else
|
|
134
|
+
value[`plot_${i - 1}`] = plot;
|
|
135
|
+
});
|
|
136
|
+
this.#values.set(value.$time, value);
|
|
137
|
+
}
|
|
138
|
+
changes.push('plots');
|
|
139
|
+
}
|
|
140
|
+
const ns = data.ns;
|
|
141
|
+
if (ns?.d) {
|
|
142
|
+
let parsed;
|
|
143
|
+
try {
|
|
144
|
+
parsed = JSON.parse(ns.d);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
this.emit('error', new TradingViewError('PARSE_ERROR', 'Unable to parse study data', { cause: error }));
|
|
148
|
+
}
|
|
149
|
+
if (parsed?.graphicsCmds) {
|
|
150
|
+
applyGraphicsCommands(this.#graphics, parsed.graphicsCmds);
|
|
151
|
+
changes.push('graphic');
|
|
152
|
+
}
|
|
153
|
+
if (parsed?.dataCompressed) {
|
|
154
|
+
try {
|
|
155
|
+
const decoded = decodeCompressed(parsed.dataCompressed);
|
|
156
|
+
changes.push(...mergeStrategyReport(this.#strategyReport, decoded?.report));
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
this.emit('error', new TradingViewError('PARSE_ERROR', 'Unable to parse compressed strategy report', {
|
|
160
|
+
cause: error,
|
|
161
|
+
}));
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (parsed?.data?.report)
|
|
165
|
+
changes.push(...mergeStrategyReport(this.#strategyReport, parsed.data.report));
|
|
166
|
+
}
|
|
167
|
+
if (Array.isArray(ns?.indexes))
|
|
168
|
+
this.#graphicIndexes = ns.indexes;
|
|
169
|
+
this.emit('update', changes);
|
|
170
|
+
}
|
|
171
|
+
}
|