Skip to content

Commit 0075865

Browse files
authored
feat(xignitefeeder): migrate API endpoints from QUICK to MINKABU (#622)
* feat(xignitefeeder): migrate API endpoints from QUICK to MINKABU
1 parent 44f556c commit 0075865

File tree

7 files changed

+121
-41
lines changed

7 files changed

+121
-41
lines changed

contrib/xignitefeeder/README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ bgworkers:
1717
# exchange list
1818
exchanges:
1919
- XTKS # Tokyo Stock Exchange
20-
- XJAS # Jasdaq
2120
#- XNGO # Nagoya Stock Exchange
2221
#- XSAP # Sapporo Stock Exchange
2322
#- XFKA # Fukuoka Stock Exchange
24-
#- XTAM # Tokyo PRO Market
2523
# Xignite feeder also retrieves data of Index Symbols (ex. TOPIX(東証1部株価指数)) every day.
2624
# To get target indices, index groups that the indices belong are necessary.
2725
# (cf. https://www.marketdata-cloud.quick-co.jp/Products/QUICKIndexHistorical/Overview/ListSymbols )
@@ -42,6 +40,17 @@ bgworkers:
4240
update_time: "22:00:00" # (UTC). = every day at 07:00:00 (JST)
4341
# XigniteFeeder writes data to "{identifier}/{timeframe}/TICK" TimeBucketKey
4442
timeframe: "1Sec"
43+
# Base URL of the API
44+
base_url: "https://stg-api.mk-smapi.jp/"
45+
# Endpoint of each Xigntie API
46+
endpoint:
47+
equity_realtime_get_quotes: "MINKABUEquityRealTime.json/GetQuotes"
48+
equity_realtime_list_symbols: "MINKABUEquityRealTime.json/ListSymbols"
49+
equity_realtime_get_bars: "MINKABUEquityRealTime.json/GetBars"
50+
equity_historical_get_quotes_range: "MINKABUEquityHistorical.json/GetQuotesRange"
51+
index_realtime_get_bars: "MINKABUIndexRealTime.json/GetBars"
52+
index_historical_list_symbols: "MINKABUIndexHistorical.json/ListSymbols"
53+
index_historical_get_quotes_range: "MINKABUIndexHistorical.json/GetQuotesRange"
4554
# Auth token for Xignite API
4655
# This config can be manually overridden by "XIGNITE_FEEDER_API_TOKEN" environmental variable.
4756
token: "D***0"

contrib/xignitefeeder/api/client.go

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,17 @@ import (
1515
"github.com/alpacahq/marketstore/v4/utils/log"
1616
)
1717

18-
const (
19-
// XigniteBaseURL is a Base URL for Quick Xignite API
20-
// (https://www.marketdata-cloud.quick-co.jp/Products/)
21-
XigniteBaseURL = "https://api.marketdata-cloud.quick-co.jp"
22-
// GetQuotesURL is the URL of Get Quotes endpoint
23-
// (https://www.marketdata-cloud.quick-co.jp/Products/QUICKEquityRealTime/Overview/GetQuotes)
24-
GetQuotesURL = XigniteBaseURL + "/QUICKEquityRealTime.json/GetQuotes"
25-
// ListSymbolsURL is the URL of List symbols endpoint
26-
// (https://www.marketdata-cloud.quick-co.jp/Products/QUICKEquityRealTime/Overview/ListSymbols)
27-
ListSymbolsURL = XigniteBaseURL + "/QUICKEquityRealTime.json/ListSymbols"
28-
// ListIndexSymbolsURL is the URL of List symbols endpoint
29-
// (https://www.marketdata-cloud.quick-co.jp/Products/QUICKIndexHistorical/Overview/ListSymbols)
30-
// /QUICKEquityRealTime.json/ListSymbols : list symbols for a exchange
31-
// /QUICKIndexHistorical.json/ListSymbols : list index symbols for an index group (ex. TOPIX).
32-
ListIndexSymbolsURL = XigniteBaseURL + "/QUICKIndexHistorical.json/ListSymbols"
33-
// GetBarsURL is the URL of Get Bars endpoint
34-
// (https://www.marketdata-cloud.quick-co.jp/Products/QUICKEquityRealTime/Overview/GetBars)
35-
GetBarsURL = XigniteBaseURL + "/QUICKEquityRealTime.json/GetBars"
36-
// GetIndexBarsURL is the URL of QuickIndexRealTime/GetBars endpoint
37-
// (https://www.marketdata-cloud.quick-co.jp/Products/QUICKIndexRealTime/Overview/GetBars)
38-
GetIndexBarsURL = XigniteBaseURL + "/QUICKIndexRealTime.json/GetBars"
39-
// GetQuotesRangeURL is the URL of Get Quotes Range endpoint
40-
// (https://www.marketdata-cloud.quick-co.jp/Products/QUICKEquityHistorical/Overview/GetQuotesRange)
41-
GetQuotesRangeURL = XigniteBaseURL + "/QUICKEquityHistorical.json/GetQuotesRange"
42-
// GetIndexQuotesRangeURL is the URL of Get Index Quotes Range endpoint
43-
// (https://www.marketdata-cloud.quick-co.jp/Products/QUICKIndexHistorical/Overview/GetQuotesRange)
44-
GetIndexQuotesRangeURL = XigniteBaseURL + "/QUICKIndexHistorical.json/GetQuotesRange"
45-
46-
SuccessOutcome = "Success"
47-
)
18+
const SuccessOutcome = "Success"
19+
20+
type Endpoints struct {
21+
EquityRealTimeGetQuotes string
22+
EquityRealTimeListSymbols string
23+
EquityRealTimeGetBars string
24+
EquityHistoricalGetQuotesRange string
25+
IndexRealTimeGetBars string
26+
IndexHistoricalListSymbols string
27+
IndexHistoricalGetQuotesRange string
28+
}
4829

4930
// Client calls an endpoint and returns the parsed response.
5031
type Client interface {
@@ -62,17 +43,21 @@ type Client interface {
6243
}
6344

6445
// NewDefaultAPIClient initializes Xignite API client with the specified API token and HTTP timeout[sec].
65-
func NewDefaultAPIClient(token string, timeoutSec int) *DefaultClient {
46+
func NewDefaultAPIClient(token string, timeoutSec int, baseURL string, endpoints Endpoints) *DefaultClient {
6647
return &DefaultClient{
6748
httpClient: &http.Client{Timeout: time.Duration(timeoutSec) * time.Second},
6849
token: token,
50+
baseURL: baseURL,
51+
endpoints: endpoints,
6952
}
7053
}
7154

7255
// DefaultClient is the Xignite API client with a default http client.
7356
type DefaultClient struct {
7457
httpClient *http.Client
7558
token string
59+
baseURL string
60+
endpoints Endpoints
7661
}
7762

7863
// GetRealTimeQuotes calls GetQuotes endpoint of Xignite API with specified identifiers
@@ -86,7 +71,8 @@ func (c *DefaultClient) GetRealTimeQuotes(ctx context.Context, identifiers []str
8671
"Identifiers": {strings.Join(identifiers, ",")},
8772
}
8873
req, err := http.NewRequestWithContext(ctx,
89-
"POST", GetQuotesURL, strings.NewReader(form.Encode()))
74+
"POST", fmt.Sprintf("%s%s", c.baseURL, c.endpoints.EquityRealTimeGetQuotes),
75+
strings.NewReader(form.Encode()))
9076
if err != nil {
9177
return response, errors.Wrap(err, "failed to create an http request")
9278
}
@@ -121,7 +107,8 @@ func (c *DefaultClient) GetRealTimeQuotes(ctx context.Context, identifiers []str
121107
// https://www.marketdata-cloud.quick-co.jp/Products/QUICKEquityRealTime/Overview/ListSymbols
122108
// exchange: XTKS, XNGO, XSAP, XFKA, XJAS, XTAM
123109
func (c *DefaultClient) ListSymbols(ctx context.Context, exchange string) (response ListSymbolsResponse, err error) {
124-
apiURL := ListSymbolsURL + fmt.Sprintf("?_token=%s&Exchange=%s", c.token, exchange)
110+
apiURL := fmt.Sprintf("%s%s?_token=%s&Exchange=%s",
111+
c.baseURL, c.endpoints.EquityRealTimeListSymbols, c.token, exchange)
125112
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, http.NoBody)
126113
if err != nil {
127114
return response, errors.Wrap(err, "failed to create an http request")
@@ -145,7 +132,8 @@ func (c *DefaultClient) ListSymbols(ctx context.Context, exchange string) (respo
145132
// indexGroup: INDXJPX, IND_NIKKEI.
146133
func (c *DefaultClient) ListIndexSymbols(ctx context.Context, indexGroup string,
147134
) (response ListIndexSymbolsResponse, err error) {
148-
apiURL := ListIndexSymbolsURL + fmt.Sprintf("?_token=%s&GroupName=%s", c.token, indexGroup)
135+
apiURL := fmt.Sprintf("%s%s?_token=%s&GroupName=%s",
136+
c.baseURL, c.endpoints.IndexHistoricalListSymbols, c.token, indexGroup)
149137
req, err := http.NewRequestWithContext(ctx, "GET", apiURL, http.NoBody)
150138
if err != nil {
151139
return response, errors.Wrap(err, "failed to create an http request")
@@ -182,7 +170,8 @@ func getBarsURLValues(token, identifier string, start, end time.Time) url.Values
182170
func (c *DefaultClient) GetRealTimeBars(ctx context.Context, identifier string, start, end time.Time,
183171
) (response GetBarsResponse, err error) {
184172
form := getBarsURLValues(c.token, identifier, start, end)
185-
req, err := getBarsRequest(ctx, GetBarsURL, form.Encode())
173+
req, err := getBarsRequest(ctx, fmt.Sprintf("%s%s", c.baseURL, c.endpoints.EquityRealTimeGetBars),
174+
form.Encode())
186175
if err != nil {
187176
return response, err
188177
}
@@ -202,7 +191,8 @@ func (c *DefaultClient) GetRealTimeBars(ctx context.Context, identifier string,
202191
func (c *DefaultClient) GetIndexBars(ctx context.Context, identifier string, start, end time.Time,
203192
) (response GetIndexBarsResponse, err error) {
204193
form := getBarsURLValues(c.token, identifier, start, end)
205-
req, err := getBarsRequest(ctx, GetIndexBarsURL, form.Encode())
194+
req, err := getBarsRequest(ctx, fmt.Sprintf("%s%s", c.baseURL, c.endpoints.IndexRealTimeGetBars),
195+
form.Encode())
206196
if err != nil {
207197
return response, err
208198
}
@@ -233,7 +223,8 @@ func getBarsRequest(ctx context.Context, url, body string) (*http.Request, error
233223
func (c *DefaultClient) GetQuotesRange(ctx context.Context, identifier string, startDate, endDate time.Time,
234224
) (response GetQuotesRangeResponse, err error) {
235225
formValues := quotesRangeFormValues(c.token, identifier, startDate, endDate)
236-
req, err := quotesRangeReq(ctx, GetQuotesRangeURL, formValues.Encode())
226+
req, err := quotesRangeReq(ctx, fmt.Sprintf("%s%s", c.baseURL, c.endpoints.EquityHistoricalGetQuotesRange),
227+
formValues.Encode())
237228
if err != nil {
238229
return response, err
239230
}
@@ -257,7 +248,8 @@ func (c *DefaultClient) GetQuotesRange(ctx context.Context, identifier string, s
257248
func (c *DefaultClient) GetIndexQuotesRange(ctx context.Context, identifier string, startDate, endDate time.Time,
258249
) (response GetIndexQuotesRangeResponse, err error) {
259250
formValues := quotesRangeFormValues(c.token, identifier, startDate, endDate)
260-
req, err := quotesRangeReq(ctx, GetIndexQuotesRangeURL, formValues.Encode())
251+
req, err := quotesRangeReq(ctx, fmt.Sprintf("%s%s", c.baseURL, c.endpoints.IndexHistoricalGetQuotesRange),
252+
formValues.Encode())
261253
if err != nil {
262254
return response, err
263255
}

contrib/xignitefeeder/configs/config.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ type DefaultConfig struct {
3030
Timeframe string `json:"timeframe"`
3131
APIToken string `json:"token"`
3232
Timeout int `json:"timeout"`
33+
BaseURL string `json:"base_url"`
34+
Endpoint Endpoint `json:"endpoint"`
3335
OpenTime time.Time
3436
CloseTime time.Time
3537
ClosedDaysOfTheWeek []time.Weekday
@@ -54,6 +56,16 @@ type DefaultConfig struct {
5456
} `json:"recentBackfill"`
5557
}
5658

59+
type Endpoint struct {
60+
EquityRealTimeGetQuotes string `json:"equity_realtime_get_quotes"`
61+
EquityRealTimeListSymbols string `json:"equity_realtime_list_symbols"`
62+
EquityRealTimeGetBars string `json:"equity_realtime_get_bars"`
63+
EquityHistoricalGetQuotesRange string `json:"equity_historical_get_quotes_range"`
64+
IndexRealTimeGetBars string `json:"index_realtime_get_bars"`
65+
IndexHistoricalListSymbols string `json:"index_historical_list_symbols"`
66+
IndexHistoricalGetQuotesRange string `json:"index_historical_get_quotes_range"`
67+
}
68+
5769
// NewConfig casts a map object to Config struct and returns it through json marshal->unmarshal.
5870
func NewConfig(config map[string]interface{}) (*DefaultConfig, error) {
5971
data, err := json.Marshal(config)
@@ -74,10 +86,40 @@ func NewConfig(config map[string]interface{}) (*DefaultConfig, error) {
7486
if err := validate(ret); err != nil {
7587
return nil, fmt.Errorf("config validation error: %w", err)
7688
}
89+
ret.BaseURL, ret.Endpoint = endpointWithDefault(ret.BaseURL, ret.Endpoint)
7790

7891
return ret, nil
7992
}
8093

94+
func endpointWithDefault(baseURL string, endpoint Endpoint) (string, Endpoint) {
95+
if baseURL == "" {
96+
baseURL = "https://api.marketdata-cloud.quick-co.jp/"
97+
}
98+
if endpoint.EquityRealTimeGetQuotes == "" {
99+
endpoint.EquityRealTimeGetQuotes = "QUICKEquityRealTime.json/GetQuotes"
100+
}
101+
if endpoint.EquityRealTimeListSymbols == "" {
102+
endpoint.EquityRealTimeListSymbols = "QUICKEquityRealTime.json/ListSymbols"
103+
}
104+
if endpoint.EquityRealTimeGetBars == "" {
105+
endpoint.EquityRealTimeGetBars = "QUICKEquityRealTime.json/GetBars"
106+
}
107+
if endpoint.EquityHistoricalGetQuotesRange == "" {
108+
endpoint.EquityHistoricalGetQuotesRange = "QUICKEquityHistorical.json/GetQuotesRange"
109+
}
110+
if endpoint.IndexRealTimeGetBars == "" {
111+
endpoint.IndexRealTimeGetBars = "QUICKIndexRealTime.json/GetBars"
112+
}
113+
if endpoint.IndexHistoricalListSymbols == "" {
114+
endpoint.IndexHistoricalListSymbols = "QUICKIndexHistorical.json/ListSymbols"
115+
}
116+
if endpoint.IndexHistoricalGetQuotesRange == "" {
117+
endpoint.IndexHistoricalGetQuotesRange = "QUICKIndexHistorical.json/GetQuotesRange"
118+
}
119+
120+
return baseURL, endpoint
121+
}
122+
81123
func validate(cfg *DefaultConfig) error {
82124
if len(cfg.Exchanges) < 1 && len(cfg.IndexGroups) < 1 {
83125
return errors.New("must have 1 or more stock exchanges or index group in the config file")

contrib/xignitefeeder/configs/config_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,17 @@ func TestNewConfig(t *testing.T) {
4141
ClosedDays: []time.Time{},
4242
UpdateTime: time.Date(0, 1, 1, 20, 0, 0, 0, time.UTC),
4343
APIToken: "ABCDEFGHIJKLMNOPQRSTUVWXYZ789012",
44+
// default urls
45+
BaseURL: "https://api.marketdata-cloud.quick-co.jp/",
46+
Endpoint: configs.Endpoint{
47+
EquityRealTimeGetQuotes: "QUICKEquityRealTime.json/GetQuotes",
48+
EquityRealTimeListSymbols: "QUICKEquityRealTime.json/ListSymbols",
49+
EquityRealTimeGetBars: "QUICKEquityRealTime.json/GetBars",
50+
EquityHistoricalGetQuotesRange: "QUICKEquityHistorical.json/GetQuotesRange",
51+
IndexRealTimeGetBars: "QUICKIndexRealTime.json/GetBars",
52+
IndexHistoricalListSymbols: "QUICKIndexHistorical.json/ListSymbols",
53+
IndexHistoricalGetQuotesRange: "QUICKIndexHistorical.json/GetQuotesRange",
54+
},
4455
},
4556
wantErr: false,
4657
},
@@ -63,6 +74,17 @@ func TestNewConfig(t *testing.T) {
6374
ClosedDays: []time.Time{},
6475
UpdateTime: time.Date(0, 1, 1, 12, 34, 56, 0, time.UTC),
6576
APIToken: "hellohellohellohellohellohello12",
77+
// default urls
78+
BaseURL: "https://api.marketdata-cloud.quick-co.jp/",
79+
Endpoint: configs.Endpoint{
80+
EquityRealTimeGetQuotes: "QUICKEquityRealTime.json/GetQuotes",
81+
EquityRealTimeListSymbols: "QUICKEquityRealTime.json/ListSymbols",
82+
EquityRealTimeGetBars: "QUICKEquityRealTime.json/GetBars",
83+
EquityHistoricalGetQuotesRange: "QUICKEquityHistorical.json/GetQuotesRange",
84+
IndexRealTimeGetBars: "QUICKIndexRealTime.json/GetBars",
85+
IndexHistoricalListSymbols: "QUICKIndexHistorical.json/ListSymbols",
86+
IndexHistoricalGetQuotesRange: "QUICKIndexHistorical.json/GetQuotesRange",
87+
},
6688
},
6789
wantErr: false,
6890
},

contrib/xignitefeeder/symbols/manager.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,12 @@ func (m *ManagerImpl) UpdateSymbols(ctx context.Context) {
8787
var identifiers []string
8888
for _, securityDescription := range resp.ArrayOfSecurityDescription {
8989
symbol := securityDescription.Symbol
90+
if len(symbol) >= 5 {
91+
// ignore 5-digit stock code
92+
log.Info(fmt.Sprintf("Ignore 5-digit stock code: %s", symbol))
93+
continue
94+
}
95+
9096
if _, found := m.NotQuoteStockList[symbol]; found {
9197
// ignore symbols in not_quote_stock_list
9298
continue

contrib/xignitefeeder/symbols/manager_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ func (mac *MockListSymbolsAPIClient) ListSymbols(_ context.Context, exchange str
2121
ArrayOfSecurityDescription: []api.SecurityDescription{
2222
{Symbol: "1234"},
2323
{Symbol: "5678"},
24+
{Symbol: "90123"}, // 5-digit stock code should be ignored
2425
},
2526
}, nil
2627
}

contrib/xignitefeeder/xignitefeeder.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,15 @@ func NewBgWorker(conf map[string]interface{}) (bgworker.BgWorker, error) {
2929
log.Info("loaded Xignite Feeder config...")
3030

3131
// init Xignite API client
32-
apiClient := api.NewDefaultAPIClient(config.APIToken, config.Timeout)
32+
apiClient := api.NewDefaultAPIClient(config.APIToken, config.Timeout, config.BaseURL, api.Endpoints{
33+
EquityRealTimeGetQuotes: config.Endpoint.EquityRealTimeGetQuotes,
34+
EquityRealTimeListSymbols: config.Endpoint.EquityRealTimeListSymbols,
35+
EquityRealTimeGetBars: config.Endpoint.EquityRealTimeGetBars,
36+
EquityHistoricalGetQuotesRange: config.Endpoint.EquityHistoricalGetQuotesRange,
37+
IndexRealTimeGetBars: config.Endpoint.IndexRealTimeGetBars,
38+
IndexHistoricalListSymbols: config.Endpoint.IndexHistoricalListSymbols,
39+
IndexHistoricalGetQuotesRange: config.Endpoint.IndexHistoricalGetQuotesRange,
40+
})
3341

3442
// init Market Time Checker
3543
var timeChecker feed.MarketTimeChecker

0 commit comments

Comments
 (0)