Skip to content

Commit 0b69e8e

Browse files
committed
feat(plot): include support for indicators
1 parent 0776d00 commit 0b69e8e

File tree

4 files changed

+149
-22
lines changed

4 files changed

+149
-22
lines changed

examples/backtesting/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ func main() {
5656

5757
chart := plot.NewChart(plot.WithIndicators(
5858
plot.EMA(9, "red"),
59-
plot.RSI(14, "purple"),
59+
plot.EMA(80, "orange"),
60+
plot.RSI(14, "green"),
61+
plot.Stoch(8, 3, "red", "blue"),
6062
))
6163

6264
bot, err := ninjabot.NewBot(

plot/assets/chart.html

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,11 @@
1212
</head>
1313
<script defer src="/assets/chart.js"></script>
1414
<style>
15+
body {
16+
margin: 0;
17+
padding: 0;
18+
}
19+
1520
.coin {
1621
background: rgb(68 122 219);
1722
padding: 10px;
@@ -41,11 +46,13 @@
4146
}
4247
</style>
4348
<body>
44-
<ul>
45-
{{range $val := .pairs}}
49+
<nav class="menu">
50+
<ul>
51+
{{range $val := .pairs}}
4652
<li><a class="coin" href="/?pair={{ $val }}">{{ $val }}</a></li>
47-
{{end}}
48-
</ul>
53+
{{end}}
54+
</ul>
55+
</nav>
4956
<div id="graph"></div>
5057
</body>
5158
</html>

plot/assets/chart.js

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ document.addEventListener("DOMContentLoaded", function () {
1818
low: unpack(data.candles, "low"),
1919
high: unpack(data.candles, "high"),
2020
type: "candlestick",
21-
xaxis: "x",
22-
yaxis: "y",
21+
xaxis: "x1",
22+
yaxis: "y1",
2323
};
2424

2525
const points = [];
@@ -42,6 +42,8 @@ document.addEventListener("DOMContentLoaded", function () {
4242
y: candle.low,
4343
xref: "x",
4444
yref: "y",
45+
xaxis: "x1",
46+
yaxis: "y1",
4547
text: "B",
4648
hovertext: `${order.time}
4749
<br>ID: ${order.id}
@@ -70,6 +72,7 @@ document.addEventListener("DOMContentLoaded", function () {
7072
annotation.ay = -20;
7173
annotation.valign = "top";
7274
}
75+
7376
annotations.push(annotation);
7477
});
7578
});
@@ -80,6 +83,8 @@ document.addEventListener("DOMContentLoaded", function () {
8083
name: "Buy Points",
8184
x: unpack(buyPoints, "time"),
8285
y: unpack(buyPoints, "position"),
86+
xaxis: "x1",
87+
yaxis: "y1",
8388
mode: 'markers',
8489
type: 'scatter',
8590
marker: {
@@ -90,31 +95,81 @@ document.addEventListener("DOMContentLoaded", function () {
9095
name: "Sell Points",
9196
x: unpack(sellPoints, "time"),
9297
y: unpack(sellPoints, "position"),
98+
xaxis: "x1",
99+
yaxis: "y1",
93100
mode: 'markers',
94101
type: 'scatter',
95102
marker: {
96103
color: "red",
97104
}
98105
};
99106

100-
var layout = {
101-
dragmode: "pan",
107+
const standaloneIndicators = data.indicators.reduce((total, indicator) => {
108+
if (!indicator.overlay) {
109+
return total + 1;
110+
}
111+
return total;
112+
}, 0);
113+
114+
let layout = {
115+
template: "ggplot2",
116+
dragmode: "zoom",
102117
margin: {
103-
r: 10,
104118
t: 25,
105-
b: 40,
106-
l: 60,
107119
},
108120
showlegend: true,
109121
xaxis: {
110-
autorange: true
122+
autorange: true,
123+
rangeslider: {visible: false},
124+
showline: true,
125+
anchor: standaloneIndicators > 0 ? "y2" : "y1"
111126
},
112127
yaxis: {
113-
autorange: true
128+
domain: standaloneIndicators > 0 ? [0.5, 1] : [0, 1],
129+
autorange: true,
130+
mirror: true,
131+
showline: true,
132+
gridcolor: "#ddd"
114133
},
134+
hovermode: "x unified",
115135
annotations: annotations,
116136
};
117137

118-
Plotly.newPlot("graph", [candleStickData, buyData, sellData], layout);
138+
let plotData = [candleStickData, buyData, sellData];
139+
const indicatorsHeight = 0.49/standaloneIndicators;
140+
let standaloneIndicatorIndex = 0;
141+
data.indicators.forEach((indicator) => {
142+
const axisNumber = standaloneIndicatorIndex+2;
143+
if (!indicator.overlay) {
144+
const heightStart = standaloneIndicatorIndex * indicatorsHeight;
145+
layout["yaxis"+axisNumber] = {
146+
domain: [heightStart, heightStart + indicatorsHeight],
147+
autorange: true,
148+
mirror: true,
149+
showline: true,
150+
linecolor: "black",
151+
title: indicator.name
152+
};
153+
standaloneIndicatorIndex++;
154+
}
155+
156+
indicator.metrics.forEach(metric => {
157+
const data = {
158+
title: indicator.name,
159+
name: indicator.name + (metric.name && " - " + metric.name),
160+
x: metric.time,
161+
y: metric.value,
162+
type: metric.style,
163+
color: metric.color,
164+
xaxis: "x1",
165+
yaxis: "y1",
166+
};
167+
if (!indicator.overlay) {
168+
data.yaxis = "y"+axisNumber;
169+
}
170+
plotData.push(data);
171+
})
172+
});
173+
Plotly.newPlot("graph", plotData, layout);
119174
})
120175
});

plot/indicator.go

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,17 @@ func (e ema) Overlay() bool {
4747
}
4848

4949
func (e *ema) Load(dataframe *model.Dataframe) {
50-
e.Values = talib.Ema(dataframe.Close, e.Period)
51-
e.Time = dataframe.Time
50+
if len(dataframe.Time) < e.Period {
51+
return
52+
}
53+
54+
e.Values = talib.Ema(dataframe.Close, e.Period)[e.Period:]
55+
e.Time = dataframe.Time[e.Period:]
5256
}
5357

5458
func (e ema) Metrics() []Metric {
5559
return []Metric{
5660
{
57-
Name: "value",
5861
Style: "line",
5962
Color: e.Color,
6063
Values: e.Values,
@@ -64,7 +67,7 @@ func (e ema) Metrics() []Metric {
6467
}
6568

6669
func RSI(period int, color string) Indicator {
67-
return &ema{
70+
return &rsi{
6871
Period: period,
6972
Color: color,
7073
}
@@ -86,18 +89,78 @@ func (e rsi) Overlay() bool {
8689
}
8790

8891
func (e *rsi) Load(dataframe *model.Dataframe) {
89-
e.Values = talib.Rsi(dataframe.Close, e.Period)
90-
e.Time = dataframe.Time
92+
if len(dataframe.Time) < e.Period {
93+
return
94+
}
95+
96+
e.Values = talib.Rsi(dataframe.Close, e.Period)[e.Period:]
97+
e.Time = dataframe.Time[e.Period:]
9198
}
9299

93100
func (e rsi) Metrics() []Metric {
94101
return []Metric{
95102
{
96-
Name: "value",
97103
Color: e.Color,
98104
Style: "line",
99105
Values: e.Values,
100106
Time: e.Time,
101107
},
102108
}
103109
}
110+
111+
func Stoch(k, d int, colork, colord string) Indicator {
112+
return &stoch{
113+
PeriodK: k,
114+
PeriodD: d,
115+
ColorK: colork,
116+
ColorD: colord,
117+
}
118+
}
119+
120+
type stoch struct {
121+
PeriodK int
122+
PeriodD int
123+
ColorK string
124+
ColorD string
125+
ValuesK model.Series
126+
ValuesD model.Series
127+
Time []time.Time
128+
}
129+
130+
func (e stoch) Name() string {
131+
return fmt.Sprintf("STOCH(%d, %d)", e.PeriodK, e.PeriodD)
132+
}
133+
134+
func (e stoch) Overlay() bool {
135+
return false
136+
}
137+
138+
func (e *stoch) Load(dataframe *model.Dataframe) {
139+
if len(dataframe.Time) < e.PeriodK+e.PeriodD {
140+
return
141+
}
142+
143+
e.ValuesK, e.ValuesD = talib.Stoch(dataframe.High, dataframe.Low, dataframe.Close, e.PeriodK, e.PeriodD, talib.SMA, e.PeriodD, talib.SMA)
144+
e.ValuesK = e.ValuesK[e.PeriodK+e.PeriodD:]
145+
e.ValuesD = e.ValuesD[e.PeriodK+e.PeriodD:]
146+
e.Time = dataframe.Time[e.PeriodK+e.PeriodD:]
147+
}
148+
149+
func (e stoch) Metrics() []Metric {
150+
return []Metric{
151+
{
152+
Color: e.ColorK,
153+
Name: "K",
154+
Style: "line",
155+
Values: e.ValuesK,
156+
Time: e.Time,
157+
},
158+
{
159+
Color: e.ColorD,
160+
Name: "D",
161+
Style: "line",
162+
Values: e.ValuesD,
163+
Time: e.Time,
164+
},
165+
}
166+
}

0 commit comments

Comments
 (0)