@@ -10,19 +10,27 @@ import (
10
10
"time"
11
11
12
12
"github.com/rodrigo-brito/ninjabot/model"
13
+
14
+ "github.com/StudioSol/set"
15
+ "github.com/evanw/esbuild/pkg/api"
13
16
log "github.com/sirupsen/logrus"
14
17
)
15
18
16
- //go:embed assets
17
- var staticFiles embed.FS
19
+ var (
20
+ //go:embed assets
21
+ staticFiles embed.FS
22
+ )
18
23
19
24
type Chart struct {
20
25
sync.Mutex
21
- port int
22
- candles map [string ][]Candle
23
- dataframe map [string ]* model.Dataframe
24
- orders map [string ][]* Order
25
- indicators []Indicator
26
+ port int
27
+ debug bool
28
+ candles map [string ][]Candle
29
+ dataframe map [string ]* model.Dataframe
30
+ ordersByPair map [string ]* set.LinkedHashSetINT64
31
+ orderByID map [int64 ]* Order
32
+ indicators []Indicator
33
+ scriptContent string
26
34
}
27
35
28
36
type Candle struct {
@@ -35,14 +43,29 @@ type Candle struct {
35
43
Orders []Order `json:"orders"`
36
44
}
37
45
46
+ type Shape struct {
47
+ StartX time.Time `json:"x0"`
48
+ EndX time.Time `json:"x1"`
49
+ StartY float64 `json:"y0"`
50
+ EndY float64 `json:"y1"`
51
+ Color string `json:"color"`
52
+ }
53
+
38
54
type Order struct {
39
- ID int64 `json:"id"`
40
- Time time.Time `json:"time"`
41
- Price float64 `json:"price"`
42
- Quantity float64 `json:"quantity"`
43
- Type string `json:"type"`
44
- Side string `json:"side"`
45
- Profit float64 `json:"profit"`
55
+ ID int64 `json:"id"`
56
+ CreatedAt time.Time `json:"created_at"`
57
+ UpdatedAt time.Time `json:"updated_at"`
58
+ Status string `json:"status"`
59
+ Price float64 `json:"price"`
60
+ Quantity float64 `json:"quantity"`
61
+ Type string `json:"type"`
62
+ Side string `json:"side"`
63
+ Profit float64 `json:"profit"`
64
+
65
+ // Only for OCO Orders
66
+ Stop * float64 `json:"stop"`
67
+ OCOGroup * int64 `json:"oco_group"`
68
+ RefPrice float64 `json:"ref_price"`
46
69
}
47
70
48
71
type indicatorMetric struct {
@@ -78,23 +101,24 @@ func (c *Chart) OnOrder(order model.Order) {
78
101
c .Lock ()
79
102
defer c .Unlock ()
80
103
81
- if order .Status == model .OrderStatusTypeFilled {
82
- item := & Order {
83
- ID : order .ID ,
84
- Time : order .UpdatedAt ,
85
- Price : order .Price ,
86
- Quantity : order .Quantity ,
87
- Type : string (order .Type ),
88
- Side : string (order .Side ),
89
- Profit : order .Profit ,
90
- }
104
+ item := & Order {
105
+ ID : order .ID ,
106
+ CreatedAt : order .CreatedAt ,
107
+ UpdatedAt : order .UpdatedAt ,
108
+ Status : string (order .Status ),
109
+ Price : order .Price ,
110
+ Quantity : order .Quantity ,
111
+ Type : string (order .Type ),
112
+ Side : string (order .Side ),
113
+ Profit : order .Profit ,
114
+ Stop : order .Stop ,
115
+ OCOGroup : order .GroupID ,
116
+ RefPrice : order .RefPrice ,
117
+ }
91
118
92
- if order .Type == model .OrderTypeStopLoss || order .Type == model .OrderTypeStopLossLimit {
93
- item .Price = * order .Stop
94
- }
119
+ c .ordersByPair [order .Pair ].Add (order .ID )
120
+ c .orderByID [order .ID ] = item
95
121
96
- c .orders [order .Pair ] = append (c .orders [order .Pair ], item )
97
- }
98
122
}
99
123
100
124
func (c * Chart ) OnCandle (candle model.Candle ) {
@@ -119,6 +143,7 @@ func (c *Chart) OnCandle(candle model.Candle) {
119
143
Pair : candle .Pair ,
120
144
Metadata : make (map [string ]model.Series ),
121
145
}
146
+ c .ordersByPair [candle .Pair ] = set .NewLinkedHashSetINT64 ()
122
147
}
123
148
124
149
c .dataframe [candle .Pair ].Close = append (c .dataframe [candle .Pair ].Close , candle .Close )
@@ -157,29 +182,50 @@ func (c *Chart) indicatorsByPair(pair string) []plotIndicator {
157
182
}
158
183
159
184
func (c * Chart ) candlesByPair (pair string ) []Candle {
185
+ candles := make ([]Candle , len (c .candles [pair ]))
160
186
for i := range c .candles [pair ] {
161
- for j , order := range c .orders [pair ] {
162
- if order == nil {
163
- continue
164
- }
187
+ candles [i ] = c.candles [pair ][i ]
188
+ for id := range c .ordersByPair [pair ].Iter () {
189
+ order := c .orderByID [id ]
165
190
166
191
if i < len (c .candles [pair ])- 1 &&
167
- (order .Time .After (c.candles [pair ][i ].Time ) &&
168
- order .Time .Before (c .candles [pair ][i + 1 ].Time )) ||
169
- order .Time .Equal (c.candles [pair ][i ].Time ) {
170
- c.candles [pair ][i ].Orders = append (c.candles [pair ][i ].Orders , * order )
171
- c.orders [pair ][j ] = nil
192
+ (order .UpdatedAt .After (c.candles [pair ][i ].Time ) &&
193
+ order .UpdatedAt .Before (c .candles [pair ][i + 1 ].Time )) ||
194
+ order .UpdatedAt .Equal (c.candles [pair ][i ].Time ) {
195
+ candles [i ].Orders = append (candles [i ].Orders , * order )
172
196
}
173
197
}
174
198
}
175
199
176
- for _ , order := range c .orders [pair ] {
177
- if order != nil {
178
- log .Warnf ("orders without candle data: %v" , order )
200
+ return candles
201
+ }
202
+
203
+ func (c * Chart ) shapesByPair (pair string ) []Shape {
204
+ shapes := make ([]Shape , 0 )
205
+ for id := range c .ordersByPair [pair ].Iter () {
206
+ order := c .orderByID [id ]
207
+
208
+ if order .Type != string (model .OrderTypeStopLoss ) &&
209
+ order .Type != string (model .OrderTypeLimitMaker ) {
210
+ continue
211
+ }
212
+
213
+ shape := Shape {
214
+ StartX : order .CreatedAt ,
215
+ EndX : order .UpdatedAt ,
216
+ StartY : order .RefPrice ,
217
+ EndY : order .Price ,
218
+ Color : "rgba(0, 255, 0, 0.3)" ,
219
+ }
220
+
221
+ if order .Type == string (model .OrderTypeStopLoss ) {
222
+ shape .Color = "rgba(255, 0, 0, 0.3)"
179
223
}
224
+
225
+ shapes = append (shapes , shape )
180
226
}
181
227
182
- return c . candles [ pair ]
228
+ return shapes
183
229
}
184
230
185
231
func (c * Chart ) Start () error {
@@ -198,6 +244,11 @@ func (c *Chart) Start() error {
198
244
pairs = append (pairs , pair )
199
245
}
200
246
247
+ http .HandleFunc ("/assets/chart.js" , func (w http.ResponseWriter , req * http.Request ) {
248
+ w .Header ().Set ("Content-type" , "application/javascript" )
249
+ fmt .Fprint (w , c .scriptContent )
250
+ })
251
+
201
252
http .HandleFunc ("/data" , func (w http.ResponseWriter , req * http.Request ) {
202
253
pair := req .URL .Query ().Get ("pair" )
203
254
if pair == "" {
@@ -209,6 +260,7 @@ func (c *Chart) Start() error {
209
260
err := json .NewEncoder (w ).Encode (map [string ]interface {}{
210
261
"candles" : c .candlesByPair (pair ),
211
262
"indicators" : c .indicatorsByPair (pair ),
263
+ "shapes" : c .shapesByPair (pair ),
212
264
})
213
265
if err != nil {
214
266
log .Error (err )
@@ -243,21 +295,49 @@ func WithPort(port int) Option {
243
295
}
244
296
}
245
297
298
+ // WithDebug starts chart without compress
299
+ func WithDebug () Option {
300
+ return func (chart * Chart ) {
301
+ chart .debug = true
302
+ }
303
+ }
304
+
246
305
func WithIndicators (indicators ... Indicator ) Option {
247
306
return func (chart * Chart ) {
248
307
chart .indicators = indicators
249
308
}
250
309
}
251
310
252
- func NewChart (options ... Option ) * Chart {
311
+ func NewChart (options ... Option ) ( * Chart , error ) {
253
312
chart := & Chart {
254
- port : 8080 ,
255
- candles : make (map [string ][]Candle ),
256
- dataframe : make (map [string ]* model.Dataframe ),
257
- orders : make (map [string ][]* Order ),
313
+ port : 8080 ,
314
+ candles : make (map [string ][]Candle ),
315
+ dataframe : make (map [string ]* model.Dataframe ),
316
+ ordersByPair : make (map [string ]* set.LinkedHashSetINT64 ),
317
+ orderByID : make (map [int64 ]* Order ),
258
318
}
259
319
for _ , option := range options {
260
320
option (chart )
261
321
}
262
- return chart
322
+
323
+ content , err := staticFiles .ReadFile ("assets/chart.js" )
324
+ if err != nil {
325
+ return nil , err
326
+ }
327
+
328
+ result := api .Transform (string (content ), api.TransformOptions {
329
+ Loader : api .LoaderJS ,
330
+ Target : api .ES2015 ,
331
+ MinifySyntax : ! chart .debug ,
332
+ MinifyIdentifiers : ! chart .debug ,
333
+ MinifyWhitespace : ! chart .debug ,
334
+ })
335
+
336
+ if len (result .Errors ) > 0 {
337
+ return nil , fmt .Errorf ("chart script faild with: %v" , result .Errors )
338
+ }
339
+
340
+ chart .scriptContent = string (result .Code )
341
+
342
+ return chart , nil
263
343
}
0 commit comments