-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathstocks_tickers.go
384 lines (353 loc) · 16.2 KB
/
stocks_tickers.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
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
package models
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"sort"
"strings"
"sync"
"time"
"github.com/MarketDataApp/sdk-go/helpers/dates"
"github.com/iancoleman/orderedmap"
)
// TickersResponse encapsulates the data structure returned by the /v2/stocks/tickers API endpoint. It includes arrays for various stock attributes such as symbols, names, types, currencies, and exchanges. Optional fields are omitted from the JSON output if they are empty.
//
// # Generated By
//
// - StockTickersRequestV2.Packed(): Makes a request for stock ticker data and returns a TickersResponse struct.
//
// # Methods
//
// - IsValid() bool: Checks if the response contains at least one symbol.
// - String() string: Provides a string representation of the TickersResponse.
// - Unpack() ([]Ticker, error): Converts the response into a slice of Ticker structs.
// - UniqueSymbols() ([]string, error): Extracts a slice of unique stock symbols.
// - ToMap() (map[string]Ticker, error): Converts the response into a map for quick access by symbol.
// - MarshalJSON() ([]byte, error): Customizes the JSON encoding of the TickersResponse.
//
// # Notes
//
// - The Unpack method is particularly useful for processing or displaying ticker information on a per-symbol basis.
// - The MarshalJSON method ensures the TickersResponse is encoded in a alphabetical order, which may be required by certain consumers of the JSON output.
type TickersResponse struct {
Symbol []string `json:"symbol"` // Symbol contains the stock symbols.
Name []string `json:"name,omitempty"` // Name contains the names of the stocks. Optional.
Type []string `json:"type,omitempty"` // Type contains the types of the stocks. Optional.
Currency []string `json:"currency,omitempty"` // Currency contains the currencies in which the stocks are traded. Optional.
Exchange []string `json:"exchange,omitempty"` // Exchange contains the stock exchanges on which the stocks are listed. Optional.
FigiShares []string `json:"figiShares,omitempty"` // FigiShares contains the FIGI codes for the shares. Optional.
FigiComposite []string `json:"figiComposite,omitempty"` // FigiComposite contains the composite FIGI codes. Optional.
Cik []string `json:"cik,omitempty"` // Cik contains the Central Index Key (CIK) numbers. Optional.
Updated []int64 `json:"updated,omitempty"` // Updated contains UNIX timestamps of the last updates. Optional.
}
// IsValid checks whether the TickersResponse instance contains at least one symbol. This method is primarily used to quickly verify if the TickersResponse has any stock symbol data before proceeding with operations that require at least one symbol to be present.
//
// # Returns
//
// - bool: Returns true if there is at least one symbol; otherwise, false.
//
// # Notes
//
// - This method is useful for validating the TickersResponse before attempting to access its symbols, preventing errors related to empty symbol data.
func (tr *TickersResponse) IsValid() bool {
return len(tr.Symbol) > 0
}
// String provides a human-readable representation of the TickersResponse struct. This method is primarily used for debugging or logging purposes, where a clear text format of the TickersResponse data is necessary. It includes all available stock attributes such as symbols, names, types, currencies, exchanges, FIGI codes, CIK numbers, and the last update times. For any stock attribute not updated, "nil" is printed instead of the timestamp.
//
// # Returns
//
// - string: A detailed string representation of the TickersResponse, enumerating all contained stock attributes and their values.
//
// # Notes
//
// - This method is particularly useful for debugging or when a quick textual overview of the TickersResponse data is needed.
// - The method ensures that even if certain optional fields are not populated, the output remains clear and understandable.
func (tr *TickersResponse) String() string {
var str strings.Builder
str.WriteString("TickersResponse{\n")
for i, symbol := range tr.Symbol {
updateTime := "nil"
if i < len(tr.Updated) && tr.Updated[i] != 0 {
updateTime = fmt.Sprint(tr.Updated[i])
}
str.WriteString(fmt.Sprintf("Ticker{Symbol: %q, Name: %q, Type: %q, Currency: %q, Exchange: %q, FigiShares: %q, FigiComposite: %q, Cik: %q, Updated: %s}\n", symbol, tr.Name[i], tr.Type[i], tr.Currency[i], tr.Exchange[i], tr.FigiShares[i], tr.FigiComposite[i], tr.Cik[i], updateTime))
}
str.WriteString("}")
return str.String()
}
// Unpack converts a TickersResponse instance into a slice of Ticker structs, allowing for easy access and manipulation of individual ticker data. This method is particularly useful when you need to process or display ticker information on a per-symbol basis, ensuring that all relevant data is correctly associated with each symbol. It also handles the conversion of UNIX timestamps in the 'Updated' field to time.Time objects, providing a more usable format for date and time operations.
//
// # Returns
//
// - []Ticker: A slice of Ticker structs representing the unpacked tickers.
// - error: An error if the TickersResponse is nil or if there is a mismatch in the lengths of the slices within the TickersResponse.
//
// # Notes
//
// - This method is essential for applications that require detailed and individualized ticker information for further processing or display.
func (tr *TickersResponse) Unpack() ([]Ticker, error) {
if tr == nil {
return nil, fmt.Errorf("TickersResponse is nil")
}
var tickerInfos []Ticker
for i := range tr.Symbol {
tickerInfo := Ticker{
Symbol: tr.Symbol[i],
Name: safeIndex(tr.Name, i),
Type: safeIndex(tr.Type, i),
Currency: safeIndex(tr.Currency, i),
Exchange: safeIndex(tr.Exchange, i),
FigiShares: safeIndex(tr.FigiShares, i),
FigiComposite: safeIndex(tr.FigiComposite, i),
Cik: safeIndex(tr.Cik, i),
}
if len(tr.Updated) > i {
tickerInfo.Updated = time.Unix(tr.Updated[i], 0)
} else {
tickerInfo.Updated = time.Time{} // Assign zero value of time.Time if Updated is not present
}
tickerInfos = append(tickerInfos, tickerInfo)
}
return tickerInfos, nil
}
// safeIndex safely retrieves the string at index i from the slice, or returns an empty string if out of range.
func safeIndex(slice []string, i int) string {
if i < len(slice) {
return slice[i]
}
return ""
}
// UniqueSymbols extracts and returns a slice of unique stock symbols from the TickersResponse. This method is primarily used when you need to identify distinct stock symbols within a larger dataset, such as when consolidating data from multiple sources or filtering out duplicates for analysis purposes.
//
// # Returns
//
// - []string: A slice of unique stock symbols.
// - error: An error encountered during the conversion to a map, if any.
//
// # Notes
//
// - This method leverages a map to ensure uniqueness, which may result in a different order of symbols than originally present in the TickersResponse.
func (tr *TickersResponse) UniqueSymbols() ([]string, error) {
tickerMap, err := tr.ToMap()
if err != nil {
return nil, err
}
uniqueSymbols := make([]string, 0, len(tickerMap))
for symbol := range tickerMap {
uniqueSymbols = append(uniqueSymbols, symbol)
}
return uniqueSymbols, nil
}
// ToMap converts a TickersResponse into a map, facilitating quick access to Ticker information by stock symbol. This method is particularly useful when you need to retrieve Ticker details for a specific symbol without iterating through a slice. It simplifies the process of accessing Ticker data by using stock symbols as keys in the resulting map.
//
// # Returns
//
// - map[string]Ticker: A map where each key is a stock symbol and its value is the corresponding Ticker struct, enabling efficient data retrieval.
// - error: An error encountered during the conversion process, if any.
//
// # Notes
//
// - This method is ideal for scenarios where quick lookup of Ticker information is required.
func (tr *TickersResponse) ToMap() (map[string]Ticker, error) {
tickerInfos, err := tr.Unpack()
if err != nil {
return nil, err
}
tickerMap := make(map[string]Ticker)
for _, tickerInfo := range tickerInfos {
tickerMap[tickerInfo.Symbol] = tickerInfo
}
return tickerMap, nil
}
// MarshalJSON customizes the JSON encoding for the TickersResponse struct, providing a tailored representation of the TickersResponse data in JSON format. This method is primarily used when a TickersResponse instance needs to be serialized into JSON, either for storage, transmission over a network, or for use in web APIs.
//
// # Returns
//
// - []byte: The JSON-encoded representation of the TickersResponse.
// - error: An error if the encoding fails, encapsulating details of the failure.
//
// # Notes
//
// - This method ensures that the TickersResponse is encoded in a specific order, which may be required by certain consumers of the JSON output.
func (tr *TickersResponse) MarshalJSON() ([]byte, error) {
if tr == nil {
return nil, fmt.Errorf("TickersResponse is nil")
}
// Create a new ordered map
o := orderedmap.New()
// Set the "s" key to "ok"
o.Set("s", "ok")
// Set the other keys to the corresponding slices in the struct
o.Set("symbol", tr.Symbol)
o.Set("name", tr.Name)
o.Set("type", tr.Type)
o.Set("currency", tr.Currency)
o.Set("exchange", tr.Exchange)
o.Set("figiShares", tr.FigiShares)
o.Set("figiComposite", tr.FigiComposite)
o.Set("cik", tr.Cik)
o.Set("updated", tr.Updated)
// Marshal the ordered map into a JSON object and return the result
return json.Marshal(o)
}
// Ticker represents the detailed information of a stock ticker, including its symbol, name, type, currency, exchange, FIGI codes, CIK number, and the last update time. This struct is designed to encapsulate all relevant data for a single stock ticker, making it easier to manage and access stock information in a structured manner.
//
// # Generated By
//
// - TickersResponse.Unpack(): Converts a TickersResponse instance into a slice of Ticker structs.
//
// # Methods
//
// - String(): Generates a string representation of the Ticker struct, detailing all its properties in a readable format.
//
// # Notes
//
// - The Updated field uses the time.Time type to represent the last update time in a format that can be easily manipulated within Go programs.
// - Optional fields like Name, Type, Currency, Exchange, FigiShares, FigiComposite, and Cik are omitted from JSON output if empty, thanks to the `omitempty` JSON tag.
type Ticker struct {
Symbol string `json:"symbol"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Currency string `json:"currency,omitempty"`
Exchange string `json:"exchange,omitempty"`
FigiShares string `json:"figiShares,omitempty"`
FigiComposite string `json:"figiComposite,omitempty"`
Cik string `json:"cik,omitempty"`
Updated time.Time `json:"updated,omitempty"`
}
// String generates a string representation of the Ticker struct, providing a human-readable format of its properties. This method is primarily used for logging, debugging, or displaying the Ticker's information in a clear and concise manner.
//
// # Returns
//
// - string: A formatted string detailing the Ticker's properties in a readable format.
//
// # Notes
//
// - This method is particularly useful when a quick overview of the Ticker's data is needed without accessing each property individually.
func (ti Ticker) String() string {
updated := "nil"
if !ti.Updated.IsZero() {
updated = dates.TimeString(ti.Updated)
}
return fmt.Sprintf("Ticker{Symbol: %s, Name: %s, Type: %s, Currency: %s, Exchange: %s, FigiShares: %s, FigiComposite: %s, Cik: %s, Updated: %s}", ti.Symbol, ti.Name, ti.Type, ti.Currency, ti.Exchange, ti.FigiShares, ti.FigiComposite, ti.Cik, updated)
}
// MapToTickersResponse aggregates a collection of Ticker structs, indexed by their ticker symbols, into a single TickersResponse struct. This method is primarily used when there is a need to consolidate individual ticker information into a unified response format, suitable for further processing or serialization.
//
// # Parameters
//
// - map[string]Ticker: A map where the key is a string representing the ticker symbol, and the value is a Ticker struct.
//
// # Returns
//
// - *TickersResponse: A pointer to a TickersResponse struct that aggregates the information from the input map.
//
// # Notes
//
// - This method is particularly useful for converting a collection of dispersed ticker data into a structured and easily manageable format.
func MapToTickersResponse(tickerMap map[string]Ticker) *TickersResponse {
var tr TickersResponse
keys := make([]string, 0, len(tickerMap))
for key := range tickerMap {
keys = append(keys, key)
}
sort.Strings(keys)
for _, key := range keys {
tickerInfo := tickerMap[key]
tr.Symbol = append(tr.Symbol, tickerInfo.Symbol)
tr.Name = append(tr.Name, tickerInfo.Name)
tr.Type = append(tr.Type, tickerInfo.Type)
tr.Currency = append(tr.Currency, tickerInfo.Currency)
tr.Exchange = append(tr.Exchange, tickerInfo.Exchange)
tr.FigiShares = append(tr.FigiShares, tickerInfo.FigiShares)
tr.FigiComposite = append(tr.FigiComposite, tickerInfo.FigiComposite)
tr.Cik = append(tr.Cik, tickerInfo.Cik)
tr.Updated = append(tr.Updated, tickerInfo.Updated.Unix())
}
return &tr
}
// SaveToCSV serializes the ticker data from a map into a CSV file. This method is primarily used for exporting ticker information into a structured file format, allowing for easy data sharing and analysis outside the application context.
//
// # Parameters
//
// - map[string]Ticker: A map containing ticker symbols as keys and Ticker structs as values.
// - string: The filename of the CSV file to be created and written to.
//
// # Returns
//
// - error: An error object if the CSV file could not be written; otherwise, nil.
func SaveToCSV(tickerMap map[string]Ticker, filename string) error {
if tickerMap == nil {
return fmt.Errorf("tickerMap is nil")
}
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
writer := csv.NewWriter(file)
defer writer.Flush()
// Write header
err = writer.Write([]string{"Symbol", "Name", "Type", "Currency", "Exchange", "FigiShares", "FigiComposite", "Cik", "Updated"})
if err != nil {
return err
}
// Write data
for _, tickerInfo := range tickerMap {
updated := ""
if !tickerInfo.Updated.IsZero() {
updated = fmt.Sprintf("%v", tickerInfo.Updated.Unix())
}
err = writer.Write([]string{tickerInfo.Symbol, tickerInfo.Name, tickerInfo.Type, tickerInfo.Currency, tickerInfo.Exchange, tickerInfo.FigiShares, tickerInfo.FigiComposite, tickerInfo.Cik, updated})
if err != nil {
return err
}
}
return nil
}
// CombineTickerResponses merges multiple TickersResponse instances into a single map, facilitating the aggregation of ticker data from various sources. This method is particularly useful when consolidating ticker information obtained from different API calls or datasets into a unified structure for easier access and manipulation. It ensures thread safety by using a mutex to manage concurrent access to the resulting map.
//
// # Parameters
//
// - []*TickersResponse: A slice of pointers to TickersResponse structs intended for combination.
//
// # Returns
//
// - map[string]Ticker: A map consolidating ticker symbols and their corresponding Ticker structs.
// - error: An error object indicating failure during the combination process, if any.
//
// # Notes
//
// - This method employs a mutex to prevent race conditions, ensuring the integrity of the combined map in concurrent environments.
func CombineTickerResponses(responses []*TickersResponse) (map[string]Ticker, error) {
tickerMap := make(map[string]Ticker)
var mutex sync.Mutex
var wg sync.WaitGroup
errors := make(chan error)
for _, response := range responses {
wg.Add(1)
go func(response *TickersResponse) {
defer wg.Done()
responseMap, err := response.ToMap()
if err != nil {
errors <- err
return
}
mutex.Lock()
for key, value := range responseMap {
tickerMap[key] = value
}
mutex.Unlock()
}(response)
}
go func() {
wg.Wait()
close(errors)
}()
for err := range errors {
if err != nil {
return nil, err
}
}
return tickerMap, nil
}