-
Notifications
You must be signed in to change notification settings - Fork 2
/
quotes.go
315 lines (280 loc) · 9.2 KB
/
quotes.go
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
package flux
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
"github.com/Jeffail/gabs/v2"
jsonpatch "github.com/evanphx/json-patch"
)
// QuoteField is a custom type for quote fields (backed by string)
type QuoteField string
const (
// Mark field
Mark = QuoteField("MARK")
// MarkChange field
MarkChange = QuoteField("MARK_CHANGE")
// MarkPercentChange field
MarkPercentChange = QuoteField("MARK_PERCENT_CHANGE")
// NetChange field
NetChange = QuoteField("NET_CHANGE")
// NetPercentChange field
NetPercentChange = QuoteField("NET_CHANGE_PERCENT")
// Bid field
Bid = QuoteField("BID")
// BidExchange field
BidExchange = QuoteField("BID_EXCHANGE")
// Ask field
Ask = QuoteField("ASK")
// AskExchange field
AskExchange = QuoteField("ASK_EXCHANGE")
// BidSize field
BidSize = QuoteField("BID_SIZE")
// AskSize field
AskSize = QuoteField("ASK_SIZE")
// Volume field
Volume = QuoteField("VOLUME")
// Open field
Open = QuoteField("OPEN")
// High52 field
High52 = QuoteField("HIGH52")
// Low52 field
Low52 = QuoteField("LOW52")
// High field
High = QuoteField("HIGH")
// Low field
Low = QuoteField("LOW")
// VWAP field
VWAP = QuoteField("VWAP")
// VolIdx field
VolIdx = QuoteField("VOLATILITY_INDEX")
// ImplVol field
ImplVol = QuoteField("IMPLIED_VOLATILITY")
// MMMove field
MMMove = QuoteField("MARKET_MAKER_MOVE")
// PercentIV field
PercentIV = QuoteField("PERCENT_IV")
// HistoricalVol30 field
HistoricalVol30 = QuoteField("HISTORICAL_VOLATILITY_30_DAYS")
// MktCap field
MktCap = QuoteField("MARKET_CAP")
// Beta field
Beta = QuoteField("BETA")
// PE field
PE = QuoteField("PE")
// InitMargin field
InitMargin = QuoteField("INITIAL_MARGIN")
// Last field
Last = QuoteField("LAST")
// LastSize field
LastSize = QuoteField("LAST_SIZE")
// LastExchange field
LastExchange = QuoteField("LAST_EXCHANGE")
// Rho field
Rho = QuoteField("RHO")
// BorrowStatus field
BorrowStatus = QuoteField("BORROW_STATUS")
// Delta field
Delta = QuoteField("DELTA")
// Gamma field
Gamma = QuoteField("GAMMA")
// Theta field
Theta = QuoteField("THETA")
// Vega field
Vega = QuoteField("VEGA")
// DivAmount field
DivAmount = QuoteField("DIV_AMOUNT")
// EPS field
EPS = QuoteField("EPS")
// ExdDivDate field
ExdDivDate = QuoteField("EXD_DIV_DATE")
// Yield field
Yield = QuoteField("YIELD")
// FrontVol field
FrontVol = QuoteField("FRONT_VOLATILITY")
// BackVol field
BackVol = QuoteField("BACK_VOLATILITY")
// VolDiff field
VolDiff = QuoteField("VOLATILITY_DIFFERENCE")
// Close field
Close = QuoteField("CLOSE")
)
// QuoteRequestSignature is the parameter for a quote request
type QuoteRequestSignature struct {
// ticker for the content of a quote request
Ticker string
// how frequently to get a quote (TODO: useless for flux)
RefreshRate int
// field properties (detailed in documentation)
Fields []QuoteField
}
func (q *QuoteRequestSignature) shortName() string {
return fmt.Sprintf("QUOTE#%s@%d(W:%d)", q.Ticker, q.RefreshRate, len(q.Fields))
}
// QuoteStoredCache is the response from a quote request
type QuoteStoredCache struct {
Items []QuoteItem `json:"items"`
Service string `json:"service"`
RequestID string `json:"requestId"`
Ver int `json:"ver"`
}
// QuoteItem is a single item in a QuoteResponse
type QuoteItem struct {
Symbol string `json:"symbol"`
Values quoteValues `json:"values"`
}
type quoteValues struct {
ASK float64 `json:"ASK,omitempty"`
ASKEXCHANGE string `json:"ASK_EXCHANGE,omitempty"`
ASKSIZE int `json:"ASK_SIZE,omitempty"`
BACKVOLATILITY float64 `json:"BACK_VOLATILITY,omitempty"`
BETA float64 `json:"BETA,omitempty"`
BID float64 `json:"BID,omitempty"`
BIDEXCHANGE string `json:"BID_EXCHANGE,omitempty"`
BIDSIZE int `json:"BID_SIZE,omitempty"`
BORROWSTATUS string `json:"BORROW_STATUS,omitempty"`
CLOSE float64 `json:"CLOSE,omitempty"`
DELTA float64 `json:"DELTA,omitempty"`
FRONTVOLATILITY float64 `json:"FRONT_VOLATILITY,omitempty"`
GAMMA float64 `json:"GAMMA,omitempty"`
HIGH float64 `json:"HIGH,omitempty"`
HISTORICALVOLATILITY30DAYS float64 `json:"HISTORICAL_VOLATILITY_30_DAYS,omitempty"`
IMPLIEDVOLATILITY float64 `json:"IMPLIED_VOLATILITY,omitempty"`
INITIALMARGIN float64 `json:"INITIAL_MARGIN,omitempty"`
LAST float64 `json:"LAST,omitempty"`
LASTEXCHANGE string `json:"LAST_EXCHANGE,omitempty"`
LASTSIZE int `json:"LAST_SIZE,omitempty"`
LOW float64 `json:"LOW,omitempty"`
MARK float64 `json:"MARK,omitempty"`
MARKETCAP int `json:"MARKET_CAP,omitempty"`
MARKCHANGE float64 `json:"MARK_CHANGE,omitempty"`
MARKPERCENTCHANGE float64 `json:"MARK_PERCENT_CHANGE,omitempty"`
NETCHANGE float64 `json:"NET_CHANGE,omitempty"`
NETCHANGEPERCENT float64 `json:"NET_CHANGE_PERCENT,omitempty"`
OPEN float64 `json:"OPEN,omitempty"`
PERCENTILEIV float64 `json:"PERCENTILE_IV,omitempty"`
RHO float64 `json:"RHO,omitempty"`
THETA float64 `json:"THETA,omitempty"`
VEGA float64 `json:"VEGA,omitempty"`
VOLATILITYDIFFERENCE float64 `json:"VOLATILITY_DIFFERENCE,omitempty"`
VOLATILITYINDEX float64 `json:"VOLATILITY_INDEX,omitempty"`
VOLUME int `json:"VOLUME,omitempty"`
VWAP float64 `json:"VWAP,omitempty"`
}
type newQuoteObject struct {
Op string `json:"op"`
Path string `json:"path"`
Value QuoteStoredCache `json:"value"`
}
type updatedQuoteObject struct {
Op string `json:"op"`
Path string `json:"path"`
Value float64 `json:"value"`
}
func (s *Session) quoteHandler(msg []byte, gab *gabs.Container) {
// get the request id from the header
rID := gab.Search("header", "id").String()
rID = rID[1 : len(rID)-1]
rService := gab.Search("header", "service").String()
rVerStr := gab.Search("header", "ver").String()
rVer, _ := strconv.Atoi(rVerStr)
rService = rService[1 : len(rService)-1]
newState := s.CurrentState
// get the patches
patches := gab.S("body", "patches").Children()
for _, patch := range patches {
// fmt.Printf("%d - %s (%s)\n", rVer, patch.S("path").String(), patch.S("value").String())
if patch.S("path").String() == "/error" {
continue
}
path := patch.S("path").String()
if path == "\"\"" {
newState.Quote = QuoteStoredCache{}
}
patch.Set(fmt.Sprintf("/quote%s", path[1:len(path)-1]), "path")
bytesJSON, _ := patch.MarshalJSON()
patchStr := "[" + string(bytesJSON) + "]"
jspatch, err := jsonpatch.DecodePatch([]byte(patchStr))
if err != nil {
continue
}
byteState, _ := json.Marshal(newState)
byteState, err = jspatch.Apply(byteState)
if err != nil {
continue
}
json.Unmarshal(byteState, &newState)
newState.Quote.RequestID = rID
newState.Quote.Service = rService
newState.Quote.Ver = int(rVer)
}
s.CurrentState = newState
s.NotificationChannel <- true
s.TransactionChannel <- newState
}
// RequestQuote returns the quote for the relevant spec with the fields requested
func (s *Session) RequestQuote(specs QuoteRequestSignature) (*QuoteStoredCache, error) {
// force capitalization of tickers, since the socket is case sensitive
specs.Ticker = strings.ToUpper(specs.Ticker)
uniqueID := fmt.Sprintf("%s-%d", specs.shortName(), s.QuoteRequestVers[specs.shortName()])
if len(s.CurrentState.Quote.Items) != 0 &&
s.CurrentState.Quote.Ver == s.specHash(fmt.Sprintf("%s-%d", specs.shortName(), s.QuoteRequestVers[specs.shortName()]-1)) {
return &s.CurrentState.Quote, nil
}
hash := s.specHash(uniqueID)
payload := gatewayRequestLoad{
Payload: []gatewayRequest{
{
Header: gatewayHeader{
Service: "quotes",
ID: "fluxQuotes",
Ver: hash,
},
Params: gatewayParams{
Account: "COMBINED ACCOUNT",
// supports multi-quoting (see comments on #16)
Symbols: strings.Split(specs.Ticker, ","),
RefreshRate: specs.RefreshRate,
QuoteFields: specs.Fields,
},
},
},
}
s.QuoteMu.Lock()
defer s.QuoteMu.Unlock()
s.QuoteRequestVers[specs.shortName()]++
s.sendJSON(payload)
ctx, ctxCancel := context.WithTimeout(context.Background(), 1000*time.Millisecond)
defer ctxCancel()
for {
select {
case <-ctx.Done():
return nil, ErrNotReceivedInTime
case <-s.NotificationChannel:
if s.CurrentState.Quote.Ver == hash {
cl := time.Now()
for {
if len(s.CurrentState.Quote.Items) != 0 {
flag := false
for _, val := range s.CurrentState.Quote.Items {
if (val == QuoteItem{}) {
flag = true
}
}
if !flag {
time.Sleep(500 * time.Millisecond)
return &s.CurrentState.Quote, nil
}
} else if time.Now().Sub(cl).Milliseconds() > 1000 {
if len(s.CurrentState.Quote.Items) == 0 {
return nil, ErrNotReceivedInTime
}
return &s.CurrentState.Quote, nil
}
}
}
}
}
}