|
|
@ -6,13 +6,16 @@ package mop |
|
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
import ( |
|
|
|
`bytes` |
|
|
|
`bytes` |
|
|
|
|
|
|
|
`encoding/json` |
|
|
|
`fmt` |
|
|
|
`fmt` |
|
|
|
`io/ioutil` |
|
|
|
`io/ioutil` |
|
|
|
`net/http` |
|
|
|
`net/http` |
|
|
|
`reflect` |
|
|
|
`reflect` |
|
|
|
|
|
|
|
`strconv` |
|
|
|
`strings` |
|
|
|
`strings` |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// const quotesURL = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=sl1c1p2oghjkva2r2rdyj3j1`
|
|
|
|
const quotesURLv7 = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=%s` |
|
|
|
const quotesURLv7 = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=%s` |
|
|
|
const quotesURLv7QueryParts = `&range=1d&interval=5m&indicators=close&includeTimestamps=false&includePrePost=false&corsDomain=finance.yahoo.com&.tsrc=finance` |
|
|
|
const quotesURLv7QueryParts = `&range=1d&interval=5m&indicators=close&includeTimestamps=false&includePrePost=false&corsDomain=finance.yahoo.com&.tsrc=finance` |
|
|
|
|
|
|
|
|
|
|
@ -21,23 +24,23 @@ const noDataIndicator = `N/A` |
|
|
|
// Stock stores quote information for the particular stock ticker. The data
|
|
|
|
// Stock stores quote information for the particular stock ticker. The data
|
|
|
|
// for all the fields except 'Advancing' is fetched using Yahoo market API.
|
|
|
|
// for all the fields except 'Advancing' is fetched using Yahoo market API.
|
|
|
|
type Stock struct { |
|
|
|
type Stock struct { |
|
|
|
Ticker string // Stock ticker.
|
|
|
|
Ticker string `json:"symbol"` // Stock ticker.
|
|
|
|
LastTrade string // l1: last trade.
|
|
|
|
LastTrade string `json:"regularMarketPrice"` // l1: last trade.
|
|
|
|
Change string // c6: change real time.
|
|
|
|
Change string `json:"regularMarketChange"` // c6: change real time.
|
|
|
|
ChangePct string // k2: percent change real time.
|
|
|
|
ChangePct string `json:"regularMarketChangePercent"` // k2: percent change real time.
|
|
|
|
Open string // o: market open price.
|
|
|
|
Open string `json:"regularMarketOpen"` // o: market open price.
|
|
|
|
Low string // g: day's low.
|
|
|
|
Low string `json:"regularMarketDayLow"` // g: day's low.
|
|
|
|
High string // h: day's high.
|
|
|
|
High string `json:"regularMarketDayHigh"` // h: day's high.
|
|
|
|
Low52 string // j: 52-weeks low.
|
|
|
|
Low52 string `json:"fiftyTwoWeekLow"` // j: 52-weeks low.
|
|
|
|
High52 string // k: 52-weeks high.
|
|
|
|
High52 string `json:"fiftyTwoWeekHigh"` // k: 52-weeks high.
|
|
|
|
Volume string // v: volume.
|
|
|
|
Volume string `json:"regularMarketVolume"` // v: volume.
|
|
|
|
AvgVolume string // a2: average volume.
|
|
|
|
AvgVolume string `json:"averageDailyVolume10Day"` // a2: average volume.
|
|
|
|
PeRatio string // r2: P/E ration real time.
|
|
|
|
PeRatio string `json:"trailingPE"` // r2: P/E ration real time.
|
|
|
|
PeRatioX string // r: P/E ration (fallback when real time is N/A).
|
|
|
|
PeRatioX string `json:"trailingPE"` // r: P/E ration (fallback when real time is N/A).
|
|
|
|
Dividend string // d: dividend.
|
|
|
|
Dividend string `json:"trailingAnnualDividendRate"` // d: dividend.
|
|
|
|
Yield string // y: dividend yield.
|
|
|
|
Yield string `json:"trailingAnnualDividendYield"` // y: dividend yield.
|
|
|
|
MarketCap string // j3: market cap real time.
|
|
|
|
MarketCap string `json:"marketCap"` // j3: market cap real time.
|
|
|
|
MarketCapX string // j1: market cap (fallback when real time is N/A).
|
|
|
|
MarketCapX string `json:"marketCap"` // j1: market cap (fallback when real time is N/A).
|
|
|
|
Advancing bool // True when change is >= $0.
|
|
|
|
Advancing bool // True when change is >= $0.
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -70,8 +73,8 @@ func (quotes *Quotes) Fetch() (self *Quotes) { |
|
|
|
} |
|
|
|
} |
|
|
|
}() |
|
|
|
}() |
|
|
|
|
|
|
|
|
|
|
|
url := fmt.Sprintf(quotesURL, strings.Join(quotes.profile.Tickers, `+`)) |
|
|
|
url := fmt.Sprintf(quotesURLv7, strings.Join(quotes.profile.Tickers, `,`)) |
|
|
|
response, err := http.Get(url) |
|
|
|
response, err := http.Get(url + quotesURLv7QueryParts) |
|
|
|
if err != nil { |
|
|
|
if err != nil { |
|
|
|
panic(err) |
|
|
|
panic(err) |
|
|
|
} |
|
|
|
} |
|
|
@ -82,7 +85,7 @@ func (quotes *Quotes) Fetch() (self *Quotes) { |
|
|
|
panic(err) |
|
|
|
panic(err) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
quotes.parse(sanitize(body)) |
|
|
|
quotes.parse2(body) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return quotes |
|
|
|
return quotes |
|
|
@ -121,6 +124,70 @@ func (quotes *Quotes) isReady() bool { |
|
|
|
return (quotes.stocks == nil || !quotes.market.IsClosed) && len(quotes.profile.Tickers) > 0 |
|
|
|
return (quotes.stocks == nil || !quotes.market.IsClosed) && len(quotes.profile.Tickers) > 0 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// this will parse the json objects
|
|
|
|
|
|
|
|
func (quotes *Quotes) parse2(body []byte) (*Quotes, error) { |
|
|
|
|
|
|
|
// response -> quoteResponse -> result|error (array) -> map[string]interface{}
|
|
|
|
|
|
|
|
// Stocks has non-int things
|
|
|
|
|
|
|
|
// d := map[string]map[string][]Stock{}
|
|
|
|
|
|
|
|
// some of these are numbers vs strings
|
|
|
|
|
|
|
|
// d := map[string]map[string][]map[string]string{}
|
|
|
|
|
|
|
|
d := map[string]map[string][]map[string]interface{}{} |
|
|
|
|
|
|
|
err := json.Unmarshal(body, &d) |
|
|
|
|
|
|
|
if err != nil { |
|
|
|
|
|
|
|
return nil, err |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
results := d["quoteResponse"]["result"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
quotes.stocks = make([]Stock, len(results)) |
|
|
|
|
|
|
|
for i, raw := range results { |
|
|
|
|
|
|
|
result := map[string]string{} |
|
|
|
|
|
|
|
for k, v := range raw { |
|
|
|
|
|
|
|
switch v.(type) { |
|
|
|
|
|
|
|
case string: |
|
|
|
|
|
|
|
result[k] = v.(string) |
|
|
|
|
|
|
|
case float64: |
|
|
|
|
|
|
|
result[k] = float2Str(v.(float64)) |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
result[k] = fmt.Sprintf("%v", v) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
quotes.stocks[i].Ticker = result["symbol"] |
|
|
|
|
|
|
|
quotes.stocks[i].LastTrade = result["regularMarketPrice"] |
|
|
|
|
|
|
|
quotes.stocks[i].Change = result["regularMarketChange"] |
|
|
|
|
|
|
|
quotes.stocks[i].ChangePct = result["regularMarketChangePercent"] |
|
|
|
|
|
|
|
quotes.stocks[i].Open = result["regularMarketOpen"] |
|
|
|
|
|
|
|
quotes.stocks[i].Low = result["regularMarketDayLow"] |
|
|
|
|
|
|
|
quotes.stocks[i].High = result["regularMarketDayHigh"] |
|
|
|
|
|
|
|
quotes.stocks[i].Low52 = result["fiftyTwoWeekLow"] |
|
|
|
|
|
|
|
quotes.stocks[i].High52 = result["fiftyTwoWeekHigh"] |
|
|
|
|
|
|
|
quotes.stocks[i].Volume = result["regularMarketVolume"] |
|
|
|
|
|
|
|
quotes.stocks[i].AvgVolume = result["averageDailyVolume10Day"] |
|
|
|
|
|
|
|
quotes.stocks[i].PeRatio = result["trailingPE"] |
|
|
|
|
|
|
|
// TODO calculate rt
|
|
|
|
|
|
|
|
quotes.stocks[i].PeRatioX = result["trailingPE"] |
|
|
|
|
|
|
|
quotes.stocks[i].Dividend = result["trailingAnnualDividendRate"] |
|
|
|
|
|
|
|
quotes.stocks[i].Yield = result["trailingAnnualDividendYield"] |
|
|
|
|
|
|
|
quotes.stocks[i].MarketCap = result["marketCap"] |
|
|
|
|
|
|
|
// TODO calculate rt?
|
|
|
|
|
|
|
|
quotes.stocks[i].MarketCapX = result["marketCap"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* |
|
|
|
|
|
|
|
fmt.Println(i) |
|
|
|
|
|
|
|
fmt.Println("-------------------") |
|
|
|
|
|
|
|
for k, v := range result { |
|
|
|
|
|
|
|
fmt.Println(k, v) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
fmt.Println("-------------------") |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
adv, err := strconv.ParseFloat(quotes.stocks[i].Change, 64) |
|
|
|
|
|
|
|
if err == nil { |
|
|
|
|
|
|
|
quotes.stocks[i].Advancing = adv >= 0.0 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return quotes, nil |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Use reflection to parse and assign the quotes data fetched using the Yahoo
|
|
|
|
// Use reflection to parse and assign the quotes data fetched using the Yahoo
|
|
|
|
// market API.
|
|
|
|
// market API.
|
|
|
|
func (quotes *Quotes) parse(body []byte) *Quotes { |
|
|
|
func (quotes *Quotes) parse(body []byte) *Quotes { |
|
|
@ -165,3 +232,26 @@ func (quotes *Quotes) parse(body []byte) *Quotes { |
|
|
|
func sanitize(body []byte) []byte { |
|
|
|
func sanitize(body []byte) []byte { |
|
|
|
return bytes.Replace(bytes.TrimSpace(body), []byte{'"'}, []byte{}, -1) |
|
|
|
return bytes.Replace(bytes.TrimSpace(body), []byte{'"'}, []byte{}, -1) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func float2Str(v float64) string { |
|
|
|
|
|
|
|
unit := "" |
|
|
|
|
|
|
|
switch { |
|
|
|
|
|
|
|
case v > 1.0e12: |
|
|
|
|
|
|
|
v = v / 1.0e12 |
|
|
|
|
|
|
|
unit = "T" |
|
|
|
|
|
|
|
case v > 1.0e9: |
|
|
|
|
|
|
|
v = v / 1.0e9 |
|
|
|
|
|
|
|
unit = "B" |
|
|
|
|
|
|
|
case v > 1.0e6: |
|
|
|
|
|
|
|
v = v / 1.0e6 |
|
|
|
|
|
|
|
unit = "M" |
|
|
|
|
|
|
|
case v > 1.0e5: |
|
|
|
|
|
|
|
v = v / 1.0e3 |
|
|
|
|
|
|
|
unit = "K" |
|
|
|
|
|
|
|
default: |
|
|
|
|
|
|
|
unit = "" |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
// parse
|
|
|
|
|
|
|
|
return fmt.Sprintf("%0.3f%s", v, unit) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|