|
|
|
// Copyright (c) 2013-2019 by Michael Dvorkin and contributors. All Rights Reserved.
|
|
|
|
// Use of this source code is governed by a MIT-style license that can
|
|
|
|
// be found in the LICENSE file.
|
|
|
|
|
|
|
|
package mop
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
//"io"
|
|
|
|
"time"
|
|
|
|
"log"
|
|
|
|
|
|
|
|
"easyquotation/stock"
|
|
|
|
mqtt "github.com/eclipse/paho.mqtt.golang"
|
|
|
|
)
|
|
|
|
|
|
|
|
// 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 quotesURLv7QueryParts = `&range=1d&interval=5m&indicators=close&includeTimestamps=false&includePrePost=false&corsDomain=finance.yahoo.com&.tsrc=finance`
|
|
|
|
|
|
|
|
const noDataIndicator = `N/A`
|
|
|
|
|
|
|
|
// Stock stores quote information for the particular stock ticker. The data
|
|
|
|
// for all the fields except 'Direction' is fetched using Yahoo market API.
|
|
|
|
type Stock struct {
|
|
|
|
Ticker string `json:"symbol"` // Stock ticker.
|
|
|
|
LastTrade string `json:"regularMarketPrice"` // l1: last trade.
|
|
|
|
Change string `json:"regularMarketChange"` // c6: change real time.
|
|
|
|
ChangePct string `json:"regularMarketChangePercent"` // k2: percent change real time.
|
|
|
|
Open string `json:"regularMarketOpen"` // o: market open price.
|
|
|
|
Low string `json:"regularMarketDayLow"` // g: day's low.
|
|
|
|
High string `json:"regularMarketDayHigh"` // h: day's high.
|
|
|
|
Low52 string `json:"fiftyTwoWeekLow"` // j: 52-weeks low.
|
|
|
|
High52 string `json:"fiftyTwoWeekHigh"` // k: 52-weeks high.
|
|
|
|
Volume string `json:"regularMarketVolume"` // v: volume.
|
|
|
|
AvgVolume string `json:"averageDailyVolume10Day"` // a2: average volume.
|
|
|
|
PeRatio string `json:"trailingPE"` // r2: P/E ration real time.
|
|
|
|
PeRatioX string `json:"trailingPE"` // r: P/E ration (fallback when real time is N/A).
|
|
|
|
Dividend string `json:"trailingAnnualDividendRate"` // d: dividend.
|
|
|
|
Yield string `json:"trailingAnnualDividendYield"` // y: dividend yield.
|
|
|
|
MarketCap string `json:"marketCap"` // j3: market cap real time.
|
|
|
|
MarketCapX string `json:"marketCap"` // j1: market cap (fallback when real time is N/A).
|
|
|
|
Currency string `json:"currency"` // String code for currency of stock.
|
|
|
|
Direction int // -1 when change is < $0, 0 when change is = $0, 1 when change is > $0.
|
|
|
|
PreOpen string `json:"preMarketChangePercent,omitempty"`
|
|
|
|
AfterHours string `json:"postMarketChangePercent,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type stockinfo struct {
|
|
|
|
Scode string
|
|
|
|
Sname string
|
|
|
|
Ft string
|
|
|
|
Upt string
|
|
|
|
}
|
|
|
|
|
|
|
|
type recordinfo struct {
|
|
|
|
Date string
|
|
|
|
Time string
|
|
|
|
Totalstocks string
|
|
|
|
}
|
|
|
|
// Quotes stores relevant pointers as well as the array of stock quotes for
|
|
|
|
// the tickers we are tracking.
|
|
|
|
type Quotes struct {
|
|
|
|
market *Market // Pointer to Market.
|
|
|
|
profile *Profile // Pointer to Profile.
|
|
|
|
stocks []Stock // Array of stock quote data.
|
|
|
|
errors string // Error string if any.
|
|
|
|
res map[string]*stock.Stock
|
|
|
|
|
|
|
|
watchlist *Watchlist
|
|
|
|
client mqtt.Client
|
|
|
|
upstocks map[string]string
|
|
|
|
totalstocks []stockinfo
|
|
|
|
needrefresh bool
|
|
|
|
addedstocks []string
|
|
|
|
Allflag bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sets the initial values and returns new Quotes struct.
|
|
|
|
func NewQuotes(market *Market, profile *Profile, res map[string]*stock.Stock, watchlist *Watchlist, client mqtt.Client) *Quotes {
|
|
|
|
/*var watchlist Watchlist
|
|
|
|
err := json.NewDecoder(respbody).Decode(&watchlist)
|
|
|
|
if err != nil {
|
|
|
|
// Handle error
|
|
|
|
fmt.Println(err)
|
|
|
|
}*/
|
|
|
|
return &Quotes{
|
|
|
|
market: market,
|
|
|
|
profile: profile,
|
|
|
|
errors: ``,
|
|
|
|
res: res,
|
|
|
|
watchlist: watchlist,
|
|
|
|
client: client,
|
|
|
|
totalstocks: []stockinfo{},
|
|
|
|
upstocks: map[string]string{},
|
|
|
|
needrefresh: true,
|
|
|
|
Allflag: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Define a struct that matches the structure of the JSON
|
|
|
|
type WatchlistItem struct {
|
|
|
|
Scode string `json:"scode"`
|
|
|
|
AnalyseFrom string `json:"analyse_from"`
|
|
|
|
AnalyseDay string `json:"analyse_day"`
|
|
|
|
Enterprice string `json:"enterprice"`
|
|
|
|
Enterdays int `json:"enterdays"`
|
|
|
|
Exchangerate string `json:"exchangerate"`
|
|
|
|
Uplist []int `json:"uplist"`
|
|
|
|
Last3days string `json:"last3days"`
|
|
|
|
Daysback int `json:"daysback"`
|
|
|
|
Inhklist bool `json:"inhklist"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type Watchlist struct {
|
|
|
|
Baseon string `json:"baseon"`
|
|
|
|
Pdate string `json:"pdate"`
|
|
|
|
Preparam int `json:"preparam"`
|
|
|
|
Dates []string `json:"dates"`
|
|
|
|
Total int `json:"total"`
|
|
|
|
Watchlist []WatchlistItem `json:"watchlist"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) Setquotes(){
|
|
|
|
quotes.market.quotes = quotes
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fetch the latest stock quotes and parse raw fetched data into array of
|
|
|
|
// []Stock structs.
|
|
|
|
func (quotes *Quotes) Fetch() (self *Quotes) {
|
|
|
|
self = quotes // <-- This ensures we return correct quotes after recover() from panic().
|
|
|
|
if quotes.isReady() {
|
|
|
|
defer func() {
|
|
|
|
if err := recover(); err != nil {
|
|
|
|
quotes.errors = fmt.Sprintf("\n\n\n\nError fetching stock quotes...\n%s", err)
|
|
|
|
} else {
|
|
|
|
quotes.errors = ""
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
if quotes.profile.mode == "review" {
|
|
|
|
//fmt.Println("review mode")
|
|
|
|
if quotes.res["sh600000"].Market.Open == 0 {
|
|
|
|
return quotes
|
|
|
|
}
|
|
|
|
//fmt.Println("review mode")
|
|
|
|
var watchlist_selected []WatchlistItem
|
|
|
|
var baseonlist []string
|
|
|
|
|
|
|
|
//date_json := []string{"2023-04-24"}
|
|
|
|
for _, date := range quotes.profile.date_json {
|
|
|
|
url := fmt.Sprintf("http://119.29.166.226/q/dayjson/%sml.json", date)
|
|
|
|
response, err := http.Get(url)
|
|
|
|
if err != nil {
|
|
|
|
// Handle error
|
|
|
|
fmt.Println(err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
defer response.Body.Close()
|
|
|
|
|
|
|
|
var watchlist Watchlist
|
|
|
|
error := json.NewDecoder(response.Body).Decode(&watchlist)
|
|
|
|
if error != nil {
|
|
|
|
// Handle error
|
|
|
|
fmt.Println(error)
|
|
|
|
}
|
|
|
|
for _, item := range watchlist.Watchlist {
|
|
|
|
dayinfo := item.Last3days
|
|
|
|
if _, ok := quotes.res[item.Scode]; ok {
|
|
|
|
if dayinfo[4] == '|' {
|
|
|
|
item.AnalyseFrom = date
|
|
|
|
watchlist_selected = append(watchlist_selected, item)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
baseonlist = append(baseonlist, watchlist.Baseon)
|
|
|
|
}
|
|
|
|
|
|
|
|
quotes.watchlist.Watchlist = watchlist_selected
|
|
|
|
if len(baseonlist) > 0 {
|
|
|
|
quotes.watchlist.Baseon = strings.Join(baseonlist, ",")
|
|
|
|
}else{
|
|
|
|
quotes.watchlist.Baseon = ""
|
|
|
|
}
|
|
|
|
quotes.parsereview(quotes.res)
|
|
|
|
}else{
|
|
|
|
url := fmt.Sprintf(quotesURLv7, strings.Join(quotes.profile.Tickers, `,`))
|
|
|
|
response, err := http.Get(url + quotesURLv7QueryParts)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
defer response.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(response.Body)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
res := quotes.res
|
|
|
|
quotes.parse2(body, res)
|
|
|
|
//fmt.Println(res["sh600111"])
|
|
|
|
//fmt.Println("mode:", quotes.profile.mode)
|
|
|
|
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
fmt.Println("***not ready***")
|
|
|
|
}
|
|
|
|
|
|
|
|
return quotes
|
|
|
|
}
|
|
|
|
|
|
|
|
//write a function to save the slice of quotes.stocks to file
|
|
|
|
func (quotes *Quotes) SaveStocks() {
|
|
|
|
if quotes.profile.mode == "review"{
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
filedata, err := json.MarshalIndent(quotes.totalstocks, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ioutil.WriteFile("stocklist.json", filedata, 0644)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//read stocks
|
|
|
|
func (quotes *Quotes) ReadStocks() {
|
|
|
|
filedata, err := ioutil.ReadFile("stocklist.json")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var stocks []stockinfo
|
|
|
|
err = json.Unmarshal(filedata, &stocks)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes)Addstockcodetofile(codestoadd []string) {
|
|
|
|
var stockcode []string
|
|
|
|
for _, item := range quotes.watchlist.Watchlist {
|
|
|
|
stockcode = append(stockcode, item.Scode)
|
|
|
|
}
|
|
|
|
|
|
|
|
stockcode = append(stockcode, quotes.profile.Tickers...)
|
|
|
|
|
|
|
|
if len(codestoadd) > 0 {
|
|
|
|
stockcode = append(stockcode, codestoadd...)
|
|
|
|
if len(quotes.addedstocks) > 1 {
|
|
|
|
quotes.addedstocks = append(quotes.addedstocks, codestoadd...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Marshal the combined array to a JSON-encoded byte slice
|
|
|
|
data, err := json.Marshal(stockcode)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write the byte slice to a file
|
|
|
|
err = ioutil.WriteFile("stock_in.json", data, 0644)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func gettimediff(start string, end string) time.Duration {
|
|
|
|
layout := "15:04:05"
|
|
|
|
closepm,_ := time.Parse(layout, "15:01:00")
|
|
|
|
openpm, _ := time.Parse(layout, "12:59:59")
|
|
|
|
closespan := -90 * time.Minute
|
|
|
|
|
|
|
|
if start =="" || end ==""{
|
|
|
|
return 0*time.Second
|
|
|
|
}
|
|
|
|
|
|
|
|
t1, err := time.Parse(layout, start)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return 0*time.Second
|
|
|
|
}
|
|
|
|
|
|
|
|
t2, err := time.Parse(layout, end)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return 0*time.Second
|
|
|
|
}
|
|
|
|
|
|
|
|
if t2.After(closepm) {
|
|
|
|
return 0*time.Second
|
|
|
|
}
|
|
|
|
|
|
|
|
if t1.After(openpm) {
|
|
|
|
t1 = t1.Add(closespan)
|
|
|
|
}
|
|
|
|
|
|
|
|
if t2.After(openpm) {
|
|
|
|
t2 = t2.Add(closespan)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 计算时间差
|
|
|
|
diff := t2.Sub(t1)
|
|
|
|
if diff > time.Hour {
|
|
|
|
//fmt.Printf("Time difference between %s and %s: %v\n", end, start, diff)
|
|
|
|
return diff
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0*time.Second
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ok returns two values: 1) boolean indicating whether the error has occurred,
|
|
|
|
// and 2) the error text itself.
|
|
|
|
func (quotes *Quotes) Ok() (bool, string) {
|
|
|
|
return quotes.errors == ``, quotes.errors
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddTickers saves the list of tickers and refreshes the stock data if new
|
|
|
|
// tickers have been added. The function gets called from the line editor
|
|
|
|
// when user adds new stock tickers.
|
|
|
|
func (quotes *Quotes) AddTickers(tickers []string) (added int, err error) {
|
|
|
|
if added, err = quotes.profile.AddTickers(tickers); err == nil && added > 0 {
|
|
|
|
quotes.stocks = nil // Force fetch.
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveTickers saves the list of tickers and refreshes the stock data if some
|
|
|
|
// tickers have been removed. The function gets called from the line editor
|
|
|
|
// when user removes existing stock tickers.
|
|
|
|
func (quotes *Quotes) RemoveTickers(tickers []string) (removed int, err error) {
|
|
|
|
if removed, err = quotes.profile.RemoveTickers(tickers); err == nil && removed > 0 {
|
|
|
|
quotes.stocks = nil // Force fetch.
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// isReady returns true if we haven't fetched the quotes yet *or* the stock
|
|
|
|
// market is still open and we might want to grab the latest quotes. In both
|
|
|
|
// cases we make sure the list of requested tickers is not empty.
|
|
|
|
func (quotes *Quotes) isReady() bool {
|
|
|
|
return (quotes.stocks == nil || !quotes.market.IsClosed) && len(quotes.profile.Tickers) > 0 && quotes.needrefresh == true
|
|
|
|
}
|
|
|
|
|
|
|
|
func inlist(slice []string, str string) bool {
|
|
|
|
for _, s := range slice {
|
|
|
|
if s == str {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
func contains(slice []stockinfo, str string) (bool, int) {
|
|
|
|
for i, s := range slice {
|
|
|
|
if s.Scode == str {
|
|
|
|
return true, i
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false, -1
|
|
|
|
}
|
|
|
|
|
|
|
|
func indexer(slice []stockinfo, str string) int {
|
|
|
|
for i, s := range slice {
|
|
|
|
if s.Scode == str {
|
|
|
|
return i+1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) getitembyscode(scode string) WatchlistItem {
|
|
|
|
watchlist := quotes.watchlist.Watchlist
|
|
|
|
for _, s := range watchlist {
|
|
|
|
if s.Scode == scode {
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return WatchlistItem{}
|
|
|
|
}
|
|
|
|
|
|
|
|
func padString(str string, length int) string {
|
|
|
|
if len(str) < length {
|
|
|
|
str = str + strings.Repeat(" ", length-len(str))
|
|
|
|
}
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) Sendtotalstocks() {
|
|
|
|
stockdata, err := json.Marshal(quotes.totalstocks)//json.MarshalIndent(quotes.totalstocks, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
topic := "stock/response/standby"
|
|
|
|
|
|
|
|
dataresp := map[string]interface{}{
|
|
|
|
"totalstocks": string(stockdata),
|
|
|
|
"date": quotes.watchlist.Baseon,
|
|
|
|
"time": time.Now().Format("15:04:05"),
|
|
|
|
}
|
|
|
|
|
|
|
|
jsonData, err := json.MarshalIndent(dataresp, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
message := string(jsonData)
|
|
|
|
token := quotes.client.Publish(topic, 0, false, message)
|
|
|
|
token.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) Sendstockgraphreq(index interface{}, istime bool) {
|
|
|
|
var selcode string
|
|
|
|
if intvalue, ok := index.(int); ok {
|
|
|
|
if intvalue > len(quotes.totalstocks){
|
|
|
|
return
|
|
|
|
}
|
|
|
|
selcode = quotes.totalstocks[intvalue-1].Scode
|
|
|
|
}
|
|
|
|
|
|
|
|
if stringValue, ok := index.(string); ok {
|
|
|
|
selcode = stringValue
|
|
|
|
}
|
|
|
|
|
|
|
|
topic := "my/topic"
|
|
|
|
itemsel := quotes.getitembyscode(selcode)
|
|
|
|
data := map[string]interface{}{
|
|
|
|
"scode": selcode,//quotes.totalstocks[index-1].Scode,
|
|
|
|
"tier": 0,
|
|
|
|
"daysback": itemsel.Daysback,
|
|
|
|
"stdprice": itemsel.Enterprice,
|
|
|
|
"name": quotes.res[selcode].Base.Name, //strings.TrimSpace(quotes.totalstocks[index-1].Sname),
|
|
|
|
"ed": itemsel.AnalyseDay,
|
|
|
|
}
|
|
|
|
if istime {
|
|
|
|
currentTime := time.Now()
|
|
|
|
data["date"] = currentTime.Format("2006-01-02")
|
|
|
|
}
|
|
|
|
jsonData, err := json.Marshal(data)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
message := string(jsonData)
|
|
|
|
token := quotes.client.Publish(topic, 0, false, message)
|
|
|
|
token.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) sendstockforevaluation(selcode string, datestring string, buyp string, pcp string) {
|
|
|
|
|
|
|
|
topic := "astock/candistock"
|
|
|
|
itemsel := quotes.getitembyscode(selcode)
|
|
|
|
data := map[string]interface{}{
|
|
|
|
"scode": selcode,//quotes.totalstocks[index-1].Scode,
|
|
|
|
"tier": 0,
|
|
|
|
"daysback": itemsel.Daysback,
|
|
|
|
"stdprice": itemsel.Enterprice,
|
|
|
|
"name": quotes.res[selcode].Base.Name, //strings.TrimSpace(quotes.totalstocks[index-1].Sname),
|
|
|
|
"ed": itemsel.AnalyseDay,
|
|
|
|
}
|
|
|
|
|
|
|
|
data["time"] = datestring
|
|
|
|
data["buyp"] = buyp
|
|
|
|
data["pcp"] = pcp
|
|
|
|
|
|
|
|
jsonData, err := json.Marshal(data)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
message := string(jsonData)
|
|
|
|
token := quotes.client.Publish(topic, 0, false, message)
|
|
|
|
token.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) Getselectedinfo(index int) *Stock {
|
|
|
|
if index > len(quotes.stocks) || index < 1 {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return "es.stocks[index-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) GetselectedinfobyTicker(ticker string) *Stock {
|
|
|
|
index := -1
|
|
|
|
if ticker == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
for i, s := range quotes.stocks {
|
|
|
|
realname := strings.TrimRight(s.Ticker, string(byte(32)))
|
|
|
|
if realname == ticker[6:] {
|
|
|
|
//log.Println(i)
|
|
|
|
index = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if index == -1 {
|
|
|
|
log.Println("Not found in quotes.stocks")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
log.Println(quotes.stocks[index])
|
|
|
|
return "es.stocks[index]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) parsereview(res map[string]*stock.Stock) (*Quotes, error) {
|
|
|
|
var scodes []string
|
|
|
|
fmt.Println("Start parsing review")
|
|
|
|
wamap := make(map[string]WatchlistItem, len(quotes.watchlist.Watchlist))
|
|
|
|
for _, item := range quotes.watchlist.Watchlist {
|
|
|
|
scodes = append(scodes, item.Scode)
|
|
|
|
wamap[item.Scode] = item
|
|
|
|
}
|
|
|
|
|
|
|
|
var snames []string
|
|
|
|
quotes.stocks = make([]Stock, len(scodes))
|
|
|
|
fmt.Println(scodes)
|
|
|
|
for i, scode := range scodes {
|
|
|
|
q := res[scode].Market
|
|
|
|
b := res[scode].Base
|
|
|
|
|
|
|
|
quotes.totalstocks = append(quotes.totalstocks, stockinfo{scode, b.Name, q.Time, q.Time})
|
|
|
|
quotes.upstocks[scode] = q.Time
|
|
|
|
|
|
|
|
open, close, high, low, ndays := getnextdaysHL(scode, wamap[scode].AnalyseFrom)
|
|
|
|
|
|
|
|
quotes.stocks[i].Ticker = fmt.Sprintf("%02d", indexer(quotes.totalstocks ,scode)) + padString(b.Name, 11)
|
|
|
|
snames = append(snames, b.Name)
|
|
|
|
quotes.stocks[i].LastTrade = wamap[scode].Enterprice
|
|
|
|
stdprice, _ := strconv.ParseFloat(wamap[scode].Enterprice, 64)
|
|
|
|
|
|
|
|
thelast := (close - stdprice) / stdprice * 100
|
|
|
|
quotes.stocks[i].ChangePct = float2Str(thelast)+"%"
|
|
|
|
|
|
|
|
quotes.stocks[i].Change = strconv.Itoa(ndays)+"day(s)"
|
|
|
|
|
|
|
|
theopen := (open - stdprice) / stdprice * 100
|
|
|
|
quotes.stocks[i].Open = float2Str(theopen)+"%"
|
|
|
|
quotes.stocks[i].Low = float2Str(low)
|
|
|
|
quotes.stocks[i].High = float2Str(high)
|
|
|
|
|
|
|
|
thehigh := (high - stdprice) / stdprice * 100
|
|
|
|
thelow := (low - stdprice) / stdprice * 100
|
|
|
|
quotes.stocks[i].Low52 = float2Str(thelow)
|
|
|
|
quotes.stocks[i].High52 = float2Str(thehigh)
|
|
|
|
|
|
|
|
quotes.stocks[i].Volume = ""
|
|
|
|
quotes.stocks[i].AvgVolume = wamap[scode].AnalyseFrom
|
|
|
|
|
|
|
|
adv, err := strconv.ParseFloat(quotes.stocks[i].High52, 64)
|
|
|
|
if err == nil {
|
|
|
|
if adv < 10.0 {
|
|
|
|
quotes.stocks[i].Direction = -1
|
|
|
|
} else if adv > 10.0 {
|
|
|
|
quotes.stocks[i].Direction = 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
quotes.stocks[i].Low52 = quotes.stocks[i].Low52 + "%"
|
|
|
|
quotes.stocks[i].High52 = quotes.stocks[i].High52 + "%"
|
|
|
|
quotes.stocks[i].MarketCap = quotes.upstocks[scode]
|
|
|
|
quotes.stocks[i].Dividend = b.Symbol
|
|
|
|
|
|
|
|
if inlist(quotes.profile.Tickers, scode) == true {
|
|
|
|
quotes.stocks[i].MarketCap = "M"
|
|
|
|
}
|
|
|
|
//fmt.Println(scode,"****")
|
|
|
|
}
|
|
|
|
quotes.needrefresh = false
|
|
|
|
|
|
|
|
return quotes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) Reload(){
|
|
|
|
filedata, err := ioutil.ReadFile("stocklist.json")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var stocks []stockinfo
|
|
|
|
err = json.Unmarshal(filedata, &stocks)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
quotes.totalstocks = quotes.totalstocks[:0]
|
|
|
|
for _, item := range quotes.profile.Tickers {//profile stock to trace
|
|
|
|
if _, ok := quotes.res[item]; ok {
|
|
|
|
quotes.totalstocks = append(quotes.totalstocks, stockinfo{item, quotes.res[item].Base.Name, "09:00:00", "09:00:00"})
|
|
|
|
quotes.upstocks["sh600000"] = "09:00:00"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
quotes.totalstocks = append(quotes.totalstocks, stocks...)
|
|
|
|
for _, item := range stocks {
|
|
|
|
quotes.upstocks[item.Scode] = item.Upt
|
|
|
|
}
|
|
|
|
quotes.stocks = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes *Quotes) Reloadbyjson(payload []byte){
|
|
|
|
/*filedata, err := ioutil.ReadFile("stocklist0616.json")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}*/
|
|
|
|
|
|
|
|
var records recordinfo
|
|
|
|
err := json.Unmarshal(payload, &records)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var stocks []stockinfo
|
|
|
|
err = json.Unmarshal([]byte(records.Totalstocks), &stocks)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Println(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
quotes.totalstocks = quotes.totalstocks[:0]
|
|
|
|
for _, item := range quotes.profile.Tickers {//profile stock to trace
|
|
|
|
if _, ok := quotes.res[item]; ok {
|
|
|
|
quotes.totalstocks = append(quotes.totalstocks, stockinfo{item, quotes.res[item].Base.Name, "09:00:00", "09:00:00"})
|
|
|
|
quotes.upstocks["sh600000"] = "09:00:00"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
quotes.totalstocks = append(quotes.totalstocks, stocks...)
|
|
|
|
for _, item := range stocks {
|
|
|
|
quotes.upstocks[item.Scode] = item.Upt
|
|
|
|
}
|
|
|
|
quotes.stocks = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes* Quotes) ResetforNewday(watchlist *Watchlist) {
|
|
|
|
quotes.totalstocks = quotes.totalstocks[:0]
|
|
|
|
quotes.watchlist = watchlist
|
|
|
|
quotes.upstocks = map[string]string{}
|
|
|
|
log.Println("here we retrieve data :", quotes.watchlist.Baseon)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (quotes* Quotes) getemotionindex(res map[string]*stock.Stock) (float64, int) {
|
|
|
|
avgpercent := 100.0
|
|
|
|
avgcount := 0
|
|
|
|
array_percent := []float64{}
|
|
|
|
|
|
|
|
wamap := make(map[string]WatchlistItem, len(quotes.watchlist.Watchlist))
|
|
|
|
for _, item := range quotes.watchlist.Watchlist {
|
|
|
|
if _, ok := res[item.Scode]; ok {
|
|
|
|
wamap[item.Scode] = item
|
|
|
|
}
|
|
|
|
//fmt.Println(scodes)
|
|
|
|
}
|
|
|
|
for _, sitem := range quotes.totalstocks {
|
|
|
|
if inlist(quotes.profile.Tickers, sitem.Scode) == false {
|
|
|
|
scode := sitem.Scode
|
|
|
|
q := res[scode].Market
|
|
|
|
|
|
|
|
if _, ok := wamap[scode]; ok {
|
|
|
|
strprice, _ := strconv.ParseFloat(wamap[scode].Enterprice, 64)
|
|
|
|
stdchangepercent := q.LastPrice*100 / strprice
|
|
|
|
avgcount++
|
|
|
|
avgpercent += stdchangepercent
|
|
|
|
array_percent = append(array_percent, stdchangepercent)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if avgcount > 0 {
|
|
|
|
log.Println(avgpercent, avgcount)
|
|
|
|
//log.Println(array_percent)
|
|
|
|
avgpercent = avgpercent / float64(avgcount)
|
|
|
|
//quotes.stocks[0].High52 = float2Str(avgpercent)
|
|
|
|
}
|
|
|
|
return avgpercent, avgcount
|
|
|
|
}
|
|
|
|
|
|
|
|
// this will parse the json objects
|
|
|
|
func (quotes *Quotes) parse2(body []byte, res map[string]*stock.Stock) (*Quotes, error) {
|
|
|
|
var scodes []string
|
|
|
|
wamap := make(map[string]WatchlistItem, len(quotes.watchlist.Watchlist))
|
|
|
|
for _, item := range quotes.watchlist.Watchlist {
|
|
|
|
enterPrice, err := strconv.ParseFloat(item.Enterprice, 64)
|
|
|
|
if err != nil {
|
|
|
|
// Handle error
|
|
|
|
fmt.Println(err)
|
|
|
|
}
|
|
|
|
if _, ok := res[item.Scode]; ok {
|
|
|
|
//fmt.Println(item.Scode)
|
|
|
|
q := res[item.Scode].Market
|
|
|
|
isin, _ := contains(quotes.totalstocks, item.Scode)
|
|
|
|
//fmt.Println(q.Name, q.PreClose , q.LastPrice ,q.LastPrice , enterPrice)
|
|
|
|
if ((q.PreClose < q.LastPrice && q.LastPrice >= enterPrice && q.PreClose < enterPrice )||
|
|
|
|
isin == true && quotes.Allflag == true){
|
|
|
|
//fmt.Println(enterPrice)
|
|
|
|
scodes = append(scodes, item.Scode)
|
|
|
|
//fmt.Println(scodes)
|
|
|
|
}
|
|
|
|
wamap[item.Scode] = item
|
|
|
|
}
|
|
|
|
//fmt.Println(scodes)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range quotes.profile.Tickers {//profile stock to trace
|
|
|
|
if _, ok := res[item]; ok {
|
|
|
|
scodes = append(scodes, item)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var snames []string
|
|
|
|
quotes.stocks = make([]Stock, len(scodes))
|
|
|
|
//fmt.Println(res["sh600000"])
|
|
|
|
for i, scode := range scodes {
|
|
|
|
newadd := 0
|
|
|
|
q := res[scode].Market
|
|
|
|
b := res[scode].Base
|
|
|
|
//fmt.Println(q)
|
|
|
|
|
|
|
|
isin, index := contains(quotes.totalstocks, scode)
|
|
|
|
if isin == false {
|
|
|
|
quotes.totalstocks = append(quotes.totalstocks, stockinfo{scode, q.Name, q.Time, q.Time})
|
|
|
|
quotes.upstocks[scode] = q.Time
|
|
|
|
newadd = 1
|
|
|
|
}else {
|
|
|
|
|
|
|
|
//compare uptime and a string as time, if the diff is bigger than 1 hour, then change quotes.upstocks[scode] to q.Time
|
|
|
|
//quotes.upstocks[scode] = uptime
|
|
|
|
diff := gettimediff(quotes.totalstocks[index].Ft, q.Time)
|
|
|
|
if diff > time.Hour {
|
|
|
|
quotes.upstocks[scode] = q.Time
|
|
|
|
quotes.totalstocks[index].Upt = q.Time
|
|
|
|
}
|
|
|
|
quotes.totalstocks[index].Ft = q.Time
|
|
|
|
}
|
|
|
|
|
|
|
|
quotes.stocks[i].Ticker = fmt.Sprintf("%02d", indexer(quotes.totalstocks ,scode)) + padString(q.Name, 11)
|
|
|
|
snames = append(snames, q.Name)
|
|
|
|
quotes.stocks[i].LastTrade = float2Str(q.LastPrice)
|
|
|
|
thechange := q.LastPrice - q.PreClose
|
|
|
|
thechangepercent := thechange / q.PreClose * 100
|
|
|
|
|
|
|
|
quotes.stocks[i].Change = float2Str(thechange)+"*"
|
|
|
|
quotes.stocks[i].ChangePct = float2Str(thechangepercent)
|
|
|
|
quotes.stocks[i].Open = float2Str(q.Open)
|
|
|
|
quotes.stocks[i].Low = float2Str(q.Low)
|
|
|
|
quotes.stocks[i].High = float2Str(q.High)
|
|
|
|
quotes.stocks[i].Low52 = float2Str(q.BidPice)
|
|
|
|
quotes.stocks[i].High52 = float2Str(q.OfferPice)
|
|
|
|
quotes.stocks[i].Volume = float2Str(q.Volumn)
|
|
|
|
quotes.stocks[i].MarketCap = q.Time
|
|
|
|
|
|
|
|
adv, err := strconv.ParseFloat(quotes.stocks[i].Change, 64)
|
|
|
|
//quotes.stocks[i].Direction = 0
|
|
|
|
//fmt.Println(q.LastPrice, q.High, q.Low)
|
|
|
|
/**/
|
|
|
|
if err == nil {
|
|
|
|
if adv < 0.0 {
|
|
|
|
quotes.stocks[i].Direction = -1
|
|
|
|
} else if adv > 0.0 {
|
|
|
|
quotes.stocks[i].Direction = 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if q.LastPrice == q.High {
|
|
|
|
quotes.stocks[i].Direction = -1
|
|
|
|
} else {
|
|
|
|
quotes.stocks[i].Direction = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if q.OfferPice == 0 {
|
|
|
|
quotes.stocks[i].Direction = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
if q.OfferPice >= q.High {
|
|
|
|
quotes.stocks[i].Direction = -1
|
|
|
|
}
|
|
|
|
|
|
|
|
quotes.stocks[i].AvgVolume = quotes.upstocks[scode]
|
|
|
|
quotes.stocks[i].Dividend = b.Symbol
|
|
|
|
quotes.stocks[i].PeRatio = quotes.watchlist.Baseon[5:]
|
|
|
|
if _, ok := wamap[scode]; ok {
|
|
|
|
strprice, _ := strconv.ParseFloat(wamap[scode].Enterprice, 64)
|
|
|
|
stdchange := q.LastPrice - strprice
|
|
|
|
quotes.stocks[i].Change = float2Str(stdchange)
|
|
|
|
|
|
|
|
}
|
|
|
|
if inlist(quotes.profile.Tickers, scode) == true {
|
|
|
|
quotes.stocks[i].AvgVolume = "M"
|
|
|
|
} else if newadd == 1 {
|
|
|
|
strprice, _ := strconv.ParseFloat(wamap[scode].Enterprice, 64)
|
|
|
|
pcp := ((strprice - q.PreClose) / q.PreClose) * 100
|
|
|
|
quotes.sendstockforevaluation(scode, quotes.upstocks[scode], float2Str(q.LastPrice), float2Str(pcp))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return quotes, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use reflection to parse and assign the quotes data fetched using the Yahoo
|
|
|
|
// market API.
|
|
|
|
func (quotes *Quotes) parse(body []byte) *Quotes {
|
|
|
|
lines := bytes.Split(body, []byte{'\n'})
|
|
|
|
quotes.stocks = make([]Stock, len(lines))
|
|
|
|
//
|
|
|
|
// Get the total number of fields in the Stock struct. Skip the last
|
|
|
|
// Advancing field which is not fetched.
|
|
|
|
//
|
|
|
|
fieldsCount := reflect.ValueOf(quotes.stocks[0]).NumField() - 1
|
|
|
|
//
|
|
|
|
// Split each line into columns, then iterate over the Stock struct
|
|
|
|
// fields to assign column values.
|
|
|
|
//
|
|
|
|
for i, line := range lines {
|
|
|
|
columns := bytes.Split(bytes.TrimSpace(line), []byte{','})
|
|
|
|
for j := 0; j < fieldsCount; j++ {
|
|
|
|
// ex. quotes.stocks[i].Ticker = string(columns[0])
|
|
|
|
reflect.ValueOf("es.stocks[i]).Elem().Field(j).SetString(string(columns[j]))
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// Try realtime value and revert to the last known if the
|
|
|
|
// realtime is not available.
|
|
|
|
//
|
|
|
|
if quotes.stocks[i].PeRatio == `N/A` && quotes.stocks[i].PeRatioX != `N/A` {
|
|
|
|
quotes.stocks[i].PeRatio = quotes.stocks[i].PeRatioX
|
|
|
|
}
|
|
|
|
if quotes.stocks[i].MarketCap == `N/A` && quotes.stocks[i].MarketCapX != `N/A` {
|
|
|
|
quotes.stocks[i].MarketCap = quotes.stocks[i].MarketCapX
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// Get the direction of the stock
|
|
|
|
//
|
|
|
|
adv, err := strconv.ParseFloat(quotes.stocks[i].Change, 64)
|
|
|
|
quotes.stocks[i].Direction = 0
|
|
|
|
if err == nil {
|
|
|
|
if adv < 0 {
|
|
|
|
quotes.stocks[i].Direction = -1
|
|
|
|
} else if (adv > 0) {
|
|
|
|
quotes.stocks[i].Direction = 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return quotes
|
|
|
|
}
|
|
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
func sanitize(body []byte) []byte {
|
|
|
|
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)
|
|
|
|
}
|