mop本地适配提交

master
yg070520@sina.com 2 years ago
parent 192ef5303b
commit a9f4de28d0
  1. 353
      cmd/mop/main.go
  2. 17
      layout.go
  3. 57
      line_editor.go
  4. 14
      profile.go
  5. 60
      screen.go
  6. 7857
      stock_codes
  7. 61
      yahoo_market.go
  8. 535
      yahoo_quotes.go

@ -12,10 +12,23 @@ import (
"path" "path"
"strings" "strings"
"time" "time"
"net/http"
"bufio"
"encoding/json"
"strconv"
//"io/ioutil"
"easyquotation"
"easyquotation/sina"
"easyquotation/stock"
"github.com/gocolly/colly"
"github.com/eiannone/keyboard" "github.com/eiannone/keyboard"
"github.com/mop-tracker/mop" "github.com/mop-tracker/mop"
"github.com/nsf/termbox-go" "github.com/nsf/termbox-go"
"github.com/olekukonko/tablewriter"
mqtt "github.com/eclipse/paho.mqtt.golang"
) )
// File name in user's home directory where we store the settings. // File name in user's home directory where we store the settings.
@ -42,9 +55,58 @@ Enter comma-delimited list of stock tickers when prompted.
<r> Press any key to continue </r> <r> Press any key to continue </r>
` `
func getuserinput() string{
scanner := bufio.NewScanner(os.Stdin)
// Prompt the user to enter the first date
fmt.Print("Enter Command: ")
scanner.Scan()
cmdstr := scanner.Text()
preset := Preset{}
inputcmd := strings.Split(cmdstr, " ")
if len(inputcmd) == 2 {
if inputcmd[0] == "buy" {
preset.Direction = 23
}else if inputcmd[0] == "sell" {
preset.Direction = 24
}else{
return ""
}
preset.Scode = inputcmd[1]
fmt.Print("Enter Condition: ")
scanner.Scan()
condition := scanner.Text()
if strings.Contains(condition, ">") {
preset.Ifbelow = 0
preset.Ifabove, _ = strconv.ParseFloat(strings.Replace(condition, ">", "", -1), 64)
}else if strings.Contains(condition, "<") {
preset.Ifabove = 0
preset.Ifbelow, _ = strconv.ParseFloat(strings.Replace(condition, "<", "", -1), 64)
}
fmt.Print("Enter Vol: ")
scanner.Scan()
vol := scanner.Text()
preset.Vol, _ = strconv.ParseFloat(vol, 64)
//fmt.Print(preset)
jsonData, err := json.Marshal(preset)
if err != nil {
fmt.Println(err)
}
extraField := `,"cmd": "preset"}`
jsonData = append(jsonData[:len(jsonData)-1], extraField...)
message := string(jsonData)
fmt.Println(message)
return message
}
return ""
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func mainLoop(screen *mop.Screen, profile *mop.Profile) { func mainLoop(screen *mop.Screen, profile *mop.Profile, mode string) {
var lineEditor *mop.LineEditor var lineEditor *mop.LineEditor
var columnEditor *mop.ColumnEditor var columnEditor *mop.ColumnEditor
@ -54,7 +116,7 @@ func mainLoop(screen *mop.Screen, profile *mop.Profile) {
keyboardQueue := make(chan termbox.Event, 128) keyboardQueue := make(chan termbox.Event, 128)
timestampQueue := time.NewTicker(1 * time.Second) timestampQueue := time.NewTicker(1 * time.Second)
quotesQueue := time.NewTicker(5 * time.Second) quotesQueue := time.NewTicker(2 * time.Second)
marketQueue := time.NewTicker(12 * time.Second) marketQueue := time.NewTicker(12 * time.Second)
showingHelp := false showingHelp := false
paused := false paused := false
@ -62,14 +124,64 @@ func mainLoop(screen *mop.Screen, profile *mop.Profile) {
redrawQuotesFlag := false redrawQuotesFlag := false
redrawMarketFlag := false redrawMarketFlag := false
// 创建一个用于存储用户输入的缓冲区
input := ""
easyquotation.Init()
c := colly.NewCollector()
SinaStock_spider := sina.NewSinaStock(c)
si := stock.G_STOCK_MANAGER.StockList
if mode == "review" {
SinaStock_spider.Start("")
}else{
SinaStock_spider.Start("stock_in.json")
}
go func() { go func() {
for { for {
keyboardQueue <- termbox.PollEvent() keyboardQueue <- termbox.PollEvent()
} }
}() }()
market := mop.NewMarket() // create a new MQTT client
quotes := mop.NewQuotes(market, profile) opts := mqtt.NewClientOptions()
opts.AddBroker("tcp://119.29.166.226:1883")
client := mqtt.NewClient(opts)
// connect to the MQTT broker
if token := client.Connect(); token.Wait() && token.Error() != nil {
panic(token.Error())
}
// create a channel for receiving MQTT messages
messages := make(chan mqtt.Message)
// subscribe to the MQTT topic of interest
//topic := "stock/response"
topic := "stock/#"
if token := client.Subscribe(topic, 0, func(client mqtt.Client, message mqtt.Message) {
//fmt.Printf("Received message: %s from topic: %s\n", message.Payload(), message.Topic())
// send the message to the messages channel
messages <- message
}); token.Wait() && token.Error() != nil {
panic(token.Error())
}
response, err := http.Get("http://119.29.166.226/q/dayjson/ml.json")//("http://119.29.166.226/q/dayjson/ml.json")
if err != nil {
// Handle error
fmt.Println(err)
}
defer response.Body.Close()
var watchlist mop.Watchlist
err = json.NewDecoder(response.Body).Decode(&watchlist)
if err != nil {
fmt.Println(err)
}
market := mop.NewMarket(stock.G_STOCK_MANAGER.StockList, &watchlist)
quotes := mop.NewQuotes(market, profile, stock.G_STOCK_MANAGER.StockList, &watchlist, client)
codestoadd := []string{"sh000001", "sz399001", "sz399006"}
quotes.Addstockcodetofile(codestoadd)
screen.Draw(market) screen.Draw(market)
screen.Draw(quotes) screen.Draw(quotes)
@ -81,6 +193,12 @@ loop:
case termbox.EventKey: case termbox.EventKey:
if lineEditor == nil && columnEditor == nil && !showingHelp { if lineEditor == nil && columnEditor == nil && !showingHelp {
if event.Key == termbox.KeyEsc || event.Ch == 'q' || event.Ch == 'Q' { if event.Key == termbox.KeyEsc || event.Ch == 'q' || event.Ch == 'Q' {
// 取消订阅并断开连接
if token := client.Unsubscribe(topic); token.Wait() && token.Error() != nil {
panic(token.Error())
}
client.Disconnect(250)
quotes.SaveStocks()
break loop break loop
} else if event.Ch == '+' || event.Ch == '-' { } else if event.Ch == '+' || event.Ch == '-' {
lineEditor = mop.NewLineEditor(screen, quotes) lineEditor = mop.NewLineEditor(screen, quotes)
@ -112,9 +230,11 @@ loop:
redrawQuotesFlag = true redrawQuotesFlag = true
} else if event.Key == termbox.KeyArrowUp || event.Ch == 'k' { } else if event.Key == termbox.KeyArrowUp || event.Ch == 'k' {
screen.DecreaseOffset(1) screen.DecreaseOffset(1)
screen.Selectmoveup()
redrawQuotesFlag = true redrawQuotesFlag = true
} else if event.Key == termbox.KeyArrowDown || event.Ch == 'j' { } else if event.Key == termbox.KeyArrowDown || event.Ch == 'j' {
screen.IncreaseOffset(1) screen.IncreaseOffset(1)
screen.Selectmovedown(quotes)
redrawQuotesFlag = true redrawQuotesFlag = true
} else if event.Key == termbox.KeyHome { } else if event.Key == termbox.KeyHome {
screen.ScrollTop() screen.ScrollTop()
@ -122,6 +242,33 @@ loop:
} else if event.Key == termbox.KeyEnd { } else if event.Key == termbox.KeyEnd {
screen.ScrollBottom() screen.ScrollBottom()
redrawQuotesFlag = true redrawQuotesFlag = true
} else if event.Key == termbox.KeySpace {
data := map[string]string{
"cmd": "getaccount",
}
jsonData, err := json.Marshal(data)
if err != nil {
fmt.Println(err)
}
message := string(jsonData)
token := client.Publish("stock/request", 0, false, message)
token.Wait()
} else if event.Ch >= '0' && event.Ch <= '9' {
input += string(event.Ch)
} else if event.Key == termbox.KeyEnter {
indexnum, err := strconv.Atoi(input)
selectindex := screen.Selectindex()
if selectindex > 0 {
indexnum = selectindex
err = nil
}
//fmt.Println("indexnum:", indexnum)
if err == nil && indexnum > 0 {
quotes.Sendstockgraphreq(indexnum, false)
quotes.Sendstockgraphreq(indexnum, true)
}
// 清空输入缓冲区
input = ""
} }
} else if lineEditor != nil { } else if lineEditor != nil {
if done := lineEditor.Handle(event); done { if done := lineEditor.Handle(event); done {
@ -161,20 +308,74 @@ loop:
} }
case <-timestampQueue.C: case <-timestampQueue.C:
now := time.Now()
//add for save stock records
hour := now.Hour()
minute := now.Minute()
second := now.Second()
if hour > 9 && hour < 16 {
// Check if the current time is 1 minute and 0 seconds past every hour
if minute == 1 && second == 0 {
//fmt.Println("It's 1 minute and 0 seconds past the hour")
quotes.SaveStocks()
}
}
if !showingHelp && !paused { if !showingHelp && !paused {
screen.Draw(time.Now()) screen.Draw(now)
} }
case <-quotesQueue.C: case <-quotesQueue.C:
if !showingHelp && !paused && len(keyboardQueue) == 0 { if !showingHelp && !paused && len(keyboardQueue) == 0 {
//res := stock.G_STOCK_MANAGER.StockList
//fmt.Println(res)
//if res["sh600000"].Market.Open != 0 {
// fmt.Println("got quotes")
go quotes.Fetch() go quotes.Fetch()
redrawQuotesFlag = true redrawQuotesFlag = true
//}
} }
case <-marketQueue.C: case <-marketQueue.C:
if !showingHelp && !paused { if !showingHelp && !paused {
screen.Draw(market) screen.Draw(market)
} }
case msg := <-messages:
//fmt.Printf("Received message: %s\n", msg.Payload())
screen.Close()
// 清除屏幕
if strings.HasPrefix(msg.Topic(), "stock/image/"){
if strings.HasPrefix(msg.Topic(), "stock/image/day"){
fmt.Print("\033[2J")
os.Stdout.Write(msg.Payload())
break
}else if strings.HasPrefix(msg.Topic(), "stock/image/time"){
os.Stdout.Write(msg.Payload())
jsonstr := getuserinput()
if jsonstr != "" {
token := client.Publish("stock/request", 0, false, jsonstr)
token.Wait()
}
}
}else if strings.HasPrefix(msg.Topic(), "stock/response"){
fmt.Print("\033[2J")
showposition(string(msg.Payload()) ,si)
jsonstr := getuserinput()
if jsonstr != "" {
token := client.Publish("stock/request", 0, false, jsonstr)
token.Wait()
}
}
//time.Sleep(1 * time.Second)
screen := mop.NewScreen(profile)
defer screen.Close()
} }
if redrawQuotesFlag && len(keyboardQueue) == 0 { if redrawQuotesFlag && len(keyboardQueue) == 0 {
@ -188,19 +389,111 @@ loop:
} }
} }
type Position struct {
Scode string `json:"scode"`
Sname string `json:"sname"`
Openprice float64 `json:"openprice"`
Floatprofit float64 `json:"floatprofit"`
Marketvalue float64 `json:"marketvalue"`
}
type Preset struct {
Direction int64 `json:"direction"`
Scode string `json:"scode"`
Sname string `json:"sname"`
Ifabove float64 `json:"ifabove"`
Ifbelow float64 `json:"ifbelow"`
Vol float64 `json:"vol"`
}
type Positions struct {
Position []Position `json:"position"`
Preset []Preset `json:"preset"`
Latestinfo string `json:"lastinfo"`
}
func showposition(payload string,si map[string]*stock.Stock) string{
var positions Positions
//fmt.Println(payload)
if err := json.Unmarshal([]byte(payload), &positions); err != nil {
fmt.Println("Error parsing JSON:", err)
return ""
}
var data [][]string
//fmt.Println(positions.Position)
for _, pos := range positions.Position {
row := []string{pos.Scode + " " +pos.Sname, fmt.Sprintf("%.2f", pos.Openprice), fmt.Sprintf("%.2f", pos.Floatprofit), fmt.Sprintf("%.2f", pos.Marketvalue)}
data = append(data, row)
}
for _, pos := range positions.Preset {
condition := ""
newscode := pos.Scode
row := []string{}
if pos.Ifabove != 0 {
condition = "> " + fmt.Sprintf("%.2f", pos.Ifabove)
}else{
condition = "< " + fmt.Sprintf("%.2f", pos.Ifbelow)
}
//if pos.Scode starts with 's'
if !strings.HasPrefix(pos.Scode, "s"){
if strings.HasPrefix(pos.Scode, "6"){
newscode = "sh" + pos.Scode
}else{
newscode = "sz" + pos.Scode
}
}
if pos.Direction == 23 {
row = []string{pos.Scode+" "+si[newscode].Base.Name , "buy", condition, fmt.Sprintf("%.2f", pos.Vol)}
}else{
row = []string{pos.Scode+" "+si[newscode].Base.Name , "sell", condition, fmt.Sprintf("%.2f", pos.Vol)}
}
data = append(data, row)
}
tableString := &strings.Builder{}
table := tablewriter.NewWriter(tableString)
table.SetHeader([]string{"Date", "Description", "CV2", "Amount"})
table.SetFooter([]string{"", "", "Total", "$146.93"}) // Add Footer
//table.EnableBorder(false) // Set Border to false
table.SetHeaderColor(tablewriter.Colors{tablewriter.Bold, tablewriter.BgGreenColor},
tablewriter.Colors{tablewriter.FgHiRedColor, tablewriter.Bold, tablewriter.BgBlackColor},
tablewriter.Colors{tablewriter.BgRedColor, tablewriter.FgWhiteColor},
tablewriter.Colors{tablewriter.BgCyanColor, tablewriter.FgWhiteColor})
table.SetColumnColor(tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiRedColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgHiBlackColor},
tablewriter.Colors{tablewriter.Bold, tablewriter.FgBlackColor})
table.SetFooterColor(tablewriter.Colors{}, tablewriter.Colors{},
tablewriter.Colors{tablewriter.Bold},
tablewriter.Colors{tablewriter.FgHiRedColor})
table.AppendBulk(data)
table.Render()
fmt.Println(tableString.String())//()
fmt.Println(positions.Latestinfo)
return tableString.String()
}
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func main() { func main() {
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
panic(err) panic(err)
} }
var profileName string
profileName := flag.String("profile", path.Join(usr.HomeDir, defaultProfile), "path to profile") flag.StringVar(&profileName, "profile", path.Join(usr.HomeDir, defaultProfile), "path to profile")
var mode string
flag.StringVar(&mode, "mode", "normal", "execution mode (normal/review)")
flag.Parse() flag.Parse()
profile, err := mop.NewProfile(*profileName) profile, err := mop.NewProfile(profileName)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "The profile read from `%s` is corrupted.\n\tError: %s\n\n", *profileName, err) fmt.Fprintf(os.Stderr, "The profile read from `%s` is corrupted.\n\tError: %s\n\n", profileName, err)
// Loop until we get a "y" or "n" answer. // Loop until we get a "y" or "n" answer.
// Note: This is only for the interactive mode. Once we have the "one-shot", this should be skipped // Note: This is only for the interactive mode. Once we have the "one-shot", this should be skipped
@ -221,9 +514,49 @@ func main() {
} }
} }
} }
profile.SetMode(mode)
fmt.Println("mode:", mode)
if mode == "review" {
scanner := bufio.NewScanner(os.Stdin)
// Prompt the user to enter the first date
fmt.Print("Enter the first date (YYYY-MM-DD): ")
scanner.Scan()
firstDateStr := scanner.Text()
// Parse the first date
firstDate, err := time.Parse("2006-01-02", firstDateStr)
if err != nil {
fmt.Println("Error parsing start date:", err)
return
}
// Prompt the user to enter the second date
fmt.Print("Enter the end date (YYYY-MM-DD): ")
scanner.Scan()
secondDateStr := scanner.Text()
// Parse the second date
secondDate, err := time.Parse("2006-01-02", secondDateStr)
if err != nil {
fmt.Println("Error parsing end date:", err)
return
}
// Print the two dates
fmt.Println("Start date:", firstDate)
fmt.Println("End date:", secondDate)
for d := firstDate; d.Before(secondDate); d = d.AddDate(0, 0, 1) {
profile.AddDate(d.Format("2006-01-02"))
}
//fmt.Println("Dates:", profile.date_json)
}
screen := mop.NewScreen(profile) screen := mop.NewScreen(profile)
defer screen.Close() defer screen.Close()
mainLoop(screen, profile) mainLoop(screen, profile, mode)
profile.Save() profile.Save()
} }

@ -14,6 +14,7 @@ import (
"text/template" "text/template"
"time" "time"
"unicode" "unicode"
//"unicode/utf8"
) )
var currencies = map[string]string{ var currencies = map[string]string{
@ -49,7 +50,7 @@ type Layout struct {
func NewLayout() *Layout { func NewLayout() *Layout {
layout := &Layout{} layout := &Layout{}
layout.columns = []Column{ layout.columns = []Column{
{-10, `Ticker`, `Ticker`, nil}, {-5, `Ticker`, `Ticker `, nil},
{10, `LastTrade`, `Last`, currency}, {10, `LastTrade`, `Last`, currency},
{10, `Change`, `Change`, currency}, {10, `Change`, `Change`, currency},
{10, `ChangePct`, `Change%`, last}, {10, `ChangePct`, `Change%`, last},
@ -108,10 +109,10 @@ func (layout *Layout) Quotes(quotes *Quotes) string {
layout.Header(quotes.profile), layout.Header(quotes.profile),
layout.prettify(quotes), layout.prettify(quotes),
} }
//fmt.Println(vars.Stocks)
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
layout.quotesTemplate.Execute(buffer, vars) layout.quotesTemplate.Execute(buffer, vars)
//fmt.Println(buffer.String())
return buffer.String() return buffer.String()
} }
@ -204,14 +205,16 @@ func (layout *Layout) pad(str string, width int) string {
} }
} }
return fmt.Sprintf(`%*s`, width, str) newstr := fmt.Sprintf(`%*s`, width, str)
//fmt.Println(newstr)
return newstr
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
func buildMarketTemplate() *template.Template { func buildMarketTemplate() *template.Template {
markup := `<tag>Dow</> {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}} <tag>S&P 500</> {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}} <tag>NASDAQ</> {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}} markup := `<tag>Dow</> {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}} <tag>S&P 500</> {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}} <tag>NASDAQ</> {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}}
<tag>Tokyo</> {{.Tokyo.change}} ({{.Tokyo.percent}}) at {{.Tokyo.latest}} <tag>HK</> {{.HongKong.change}} ({{.HongKong.percent}}) at {{.HongKong.latest}} <tag>London</> {{.London.change}} ({{.London.percent}}) at {{.London.latest}} <tag>Frankfurt</> {{.Frankfurt.change}} ({{.Frankfurt.percent}}) at {{.Frankfurt.latest}} {{if .IsClosed}}<right>U.S. markets closed</right>{{end}} <tag>{{.Tokyo.name}}</> {{.Tokyo.change}} ({{.Tokyo.percent}}) at {{.Tokyo.latest}} <tag>{{.London.name}}</> {{.London.change}} ({{.London.percent}}) at {{.London.latest}} <tag>{{.Frankfurt.name}}</> {{.Frankfurt.change}} ({{.Frankfurt.percent}}) at {{.Frankfurt.latest}} <tag>HK</> {{.HongKong.change}} ({{.HongKong.percent}}) at {{.HongKong.latest}} {{if .IsClosed}}<right>U.S. markets closed</right>{{end}}
<tag>10-Year Yield</> {{.Yield.latest}} ({{.Yield.change}}) <tag>Euro</> ${{.Euro.latest}} ({{.Euro.change}}) <tag>Yen</> ¥{{.Yen.latest}} ({{.Yen.change}}) <tag>Oil</> ${{.Oil.latest}} ({{.Oil.change}}) <tag>Gold</> ${{.Gold.latest}} ({{.Gold.change}})` <tag>{{.Yield.name}}</> {{.Yield.latest}} ({{.Yield.change}}) <tag>Euro</> ${{.Euro.latest}} ({{.Euro.change}}) <tag>Yen</> ¥{{.Yen.latest}} ({{.Yen.change}}) <tag>Oil</> ${{.Oil.latest}} ({{.Oil.change}}) <tag>Gold</> ${{.Gold.latest}} ({{.Gold.change}})`
return template.Must(template.New(`market`).Parse(markup)) return template.Must(template.New(`market`).Parse(markup))
} }
@ -321,7 +324,7 @@ func currency(str ...string) string {
return "ERR" return "ERR"
} }
//default to $ //default to $
symbol := "$" symbol := ""
c, ok := currencies[str[1]] c, ok := currencies[str[1]]
if ok { if ok {
symbol = c symbol = c

@ -7,6 +7,7 @@ package mop
import ( import (
"regexp" "regexp"
"strings" "strings"
"strconv"
"github.com/nsf/termbox-go" "github.com/nsf/termbox-go"
) )
@ -23,6 +24,7 @@ type LineEditor struct {
screen *Screen // Pointer to Screen. screen *Screen // Pointer to Screen.
quotes *Quotes // Pointer to Quotes. quotes *Quotes // Pointer to Quotes.
regex *regexp.Regexp // Regex to split comma-delimited input string. regex *regexp.Regexp // Regex to split comma-delimited input string.
currentTextIndex int
} }
// Returns new initialized LineEditor struct. // Returns new initialized LineEditor struct.
@ -31,6 +33,7 @@ func NewLineEditor(screen *Screen, quotes *Quotes) *LineEditor {
screen: screen, screen: screen,
quotes: quotes, quotes: quotes,
regex: regexp.MustCompile(`[,\s]+`), regex: regexp.MustCompile(`[,\s]+`),
currentTextIndex: -1,
} }
} }
@ -55,11 +58,18 @@ func (editor *LineEditor) Prompt(command rune) *LineEditor {
editor.screen.DrawLine(0, 3, `<white>`+editor.prompt+`</>`) editor.screen.DrawLine(0, 3, `<white>`+editor.prompt+`</>`)
termbox.SetCursor(len(editor.prompt), 3) termbox.SetCursor(len(editor.prompt), 3)
termbox.Flush() termbox.Flush()
} }
return editor return editor
} }
func (editor *LineEditor) insertString(str string) {
for _, ch := range str {
editor.insertCharacter(ch)
}
}
// Handle takes over the keyboard events and dispatches them to appropriate // Handle takes over the keyboard events and dispatches them to appropriate
// line editor handlers. As user types or edits the text cursor movements // line editor handlers. As user types or edits the text cursor movements
// are tracked in `editor.cursor` while the text itself is stored in // are tracked in `editor.cursor` while the text itself is stored in
@ -93,6 +103,45 @@ func (editor *LineEditor) Handle(ev termbox.Event) bool {
case termbox.KeySpace: case termbox.KeySpace:
editor.insertCharacter(' ') editor.insertCharacter(' ')
case termbox.KeyArrowUp:
// Add some text when the up arrow is pressed
editor.currentTextIndex++
if(editor.currentTextIndex >= len(editor.quotes.stocks)) {
editor.currentTextIndex = len(editor.quotes.stocks) - 1
}/*
fullstr := editor.quotes.stocks[editor.currentTextIndex].Ticker
indexnum, err := strconv.Atoi(fullstr[:2])
editor.input = strconv.Itoa(indexnum)
//editor.insertString(showstr)
editor.screen.DrawLine(len(editor.prompt), 3, editor.input+` `)
topic := "my/topic"
itemsel := editor.quotes.getitembyscode(editor.quotes.stocks[editor.currentTextIndex].Dividend)
data := map[string]interface{}{
"scode": itemsel.Scode,
"tier": 0,
"daysback": itemsel.Daysback,
"stdprice": itemsel.Enterprice,
"name": editor.quotes.stocks[editor.currentTextIndex].Ticker,
"ed": itemsel.AnalyseDay,
}
jsonData, err := json.Marshal(data)
if err != nil {
fmt.Println(err)
}
message := string(jsonData)
token := editor.quotes.client.Publish(topic, 0, false, message)
token.Wait()
*/
case termbox.KeyArrowDown:
if 0 != len(editor.input) {
indexnum, err := strconv.Atoi(editor.input)
if err == nil && indexnum > 0 && indexnum <= len(editor.quotes.totalstocks) {
editor.quotes.Sendstockgraphreq(indexnum ,true)
}
}
default: default:
if ev.Ch != 0 { if ev.Ch != 0 {
editor.insertCharacter(ev.Ch) editor.insertCharacter(ev.Ch)
@ -149,6 +198,11 @@ func (editor *LineEditor) moveRight() *LineEditor {
if editor.cursor < len(editor.input) { if editor.cursor < len(editor.input) {
editor.cursor++ editor.cursor++
termbox.SetCursor(len(editor.prompt)+editor.cursor, 3) termbox.SetCursor(len(editor.prompt)+editor.cursor, 3)
} else if 0 != len(editor.input) {
indexnum, err := strconv.Atoi(editor.input)
if err == nil && indexnum > 0 && indexnum <= len(editor.quotes.totalstocks) {
editor.quotes.Sendstockgraphreq(indexnum, false)
}
} }
return editor return editor
@ -177,6 +231,7 @@ func (editor *LineEditor) execute() *LineEditor {
tickers := editor.tokenize() tickers := editor.tokenize()
if len(tickers) > 0 { if len(tickers) > 0 {
if added, _ := editor.quotes.AddTickers(tickers); added > 0 { if added, _ := editor.quotes.AddTickers(tickers); added > 0 {
editor.quotes.Addstockcodetofile([]string{})
editor.screen.Draw(editor.quotes) editor.screen.Draw(editor.quotes)
} }
} }
@ -218,6 +273,6 @@ func (editor *LineEditor) done() bool {
// Split by whitespace/comma to convert a string to array of tickers. Make sure // Split by whitespace/comma to convert a string to array of tickers. Make sure
// the string is trimmed to avoid empty tickers in the array. // the string is trimmed to avoid empty tickers in the array.
func (editor *LineEditor) tokenize() []string { func (editor *LineEditor) tokenize() []string {
input := strings.ToUpper(strings.Trim(editor.input, `, `)) input := strings.Trim(editor.input, `, `)//strings.ToUpper(
return editor.regex.Split(input, -1) return editor.regex.Split(input, -1)
} }

@ -43,6 +43,9 @@ type Profile struct {
filterExpression *govaluate.EvaluableExpression // The filter as a govaluate expression filterExpression *govaluate.EvaluableExpression // The filter as a govaluate expression
selectedColumn int // Stores selected column number when the column editor is active. selectedColumn int // Stores selected column number when the column editor is active.
filename string // Path to the file in which the configuration is stored filename string // Path to the file in which the configuration is stored
mode string
date_json []string
} }
// Checks if a string represents a supported color or not. // Checks if a string represents a supported color or not.
@ -104,7 +107,7 @@ func NewProfile(filename string) (*Profile, error) {
// Initializes a profile with the default values // Initializes a profile with the default values
func (profile *Profile) InitDefaultProfile() { func (profile *Profile) InitDefaultProfile() {
profile.MarketRefresh = 12 // Market data gets fetched every 12s (5 times per minute). profile.MarketRefresh = 12 // Market data gets fetched every 12s (5 times per minute).
profile.QuotesRefresh = 5 // Stock quotes get updated every 5s (12 times per minute). profile.QuotesRefresh = 3 // Stock quotes get updated every 5s (12 times per minute).
profile.Grouped = false // Stock quotes are *not* grouped by advancing/declining. profile.Grouped = false // Stock quotes are *not* grouped by advancing/declining.
profile.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`} profile.Tickers = []string{`AAPL`, `C`, `GOOG`, `IBM`, `KO`, `ORCL`, `V`}
profile.SortColumn = 0 // Stock quotes are sorted by ticker name. profile.SortColumn = 0 // Stock quotes are sorted by ticker name.
@ -120,6 +123,15 @@ func (profile *Profile) InitDefaultProfile() {
profile.Save() profile.Save()
} }
//add mode for profile
func (profile *Profile) SetMode(mode string) {
profile.mode = mode
}
func (profile *Profile) AddDate(date string) {
profile.date_json = append(profile.date_json, date)
}
// Initializes a color to the given string, or to the default value if the given // Initializes a color to the given string, or to the default value if the given
// string does not represent a supported color. // string does not represent a supported color.
func InitColor(color *string, defaultValue string) { func InitColor(color *string, defaultValue string) {

@ -9,8 +9,10 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode"
"github.com/nsf/termbox-go" "github.com/nsf/termbox-go"
//"github.com/olekukonko/tablewriter"
) )
// Screen is thin wrapper around Termbox library to provide basic display // Screen is thin wrapper around Termbox library to provide basic display
@ -25,6 +27,11 @@ type Screen struct {
offset int // Offset for scolling offset int // Offset for scolling
headerLine int // Line number of header for scroll feature headerLine int // Line number of header for scroll feature
max int // highest offset max int // highest offset
selectindex int // selected index of the list
}
func isChineseChar(r rune) bool {
return unicode.Is(unicode.Han, r)
} }
// Initializes Termbox, creates screen along with layout and markup, and // Initializes Termbox, creates screen along with layout and markup, and
@ -38,6 +45,7 @@ func NewScreen(profile *Profile) *Screen {
screen.layout = NewLayout() screen.layout = NewLayout()
screen.markup = NewMarkup(profile) screen.markup = NewMarkup(profile)
screen.offset = 0 screen.offset = 0
screen.selectindex = 4
return screen.Resize() return screen.Resize()
} }
@ -109,6 +117,22 @@ func (screen *Screen) DecreaseOffset(n int) {
} }
} }
func (screen *Screen) Selectmoveup() {
if screen.selectindex >= 5 {
screen.selectindex -= 1
}
}
func (screen *Screen) Selectmovedown(quotes *Quotes) {
if screen.selectindex < len(quotes.stocks) + 4 {
screen.selectindex += 1
}
}
func (screen *Screen) Selectindex() int {
return screen.selectindex - 4
}
func (screen *Screen) ScrollTop() { func (screen *Screen) ScrollTop() {
screen.offset = 0 screen.offset = 0
} }
@ -129,6 +153,8 @@ func (screen *Screen) DrawOldMarket(market *Market) {
termbox.Flush() termbox.Flush()
} }
// Draw accepts variable number of arguments and knows how to display the // Draw accepts variable number of arguments and knows how to display the
// market data, stock quotes, current time, and an arbitrary string. // market data, stock quotes, current time, and an arbitrary string.
func (screen *Screen) Draw(objects ...interface{}) *Screen { func (screen *Screen) Draw(objects ...interface{}) *Screen {
@ -164,12 +190,12 @@ func (screen *Screen) Draw(objects ...interface{}) *Screen {
// wrapper for DrawLineFlush // wrapper for DrawLineFlush
func (screen *Screen) DrawLine(x int, y int, str string) { func (screen *Screen) DrawLine(x int, y int, str string) {
screen.DrawLineFlush(x, y, str, true) screen.DrawLineFlush(x, y, str, true, 0)
} }
func (screen *Screen) DrawLineFlush(x int, y int, str string, flush bool) { func (screen *Screen) DrawLineFlush(x int, y int, str string, flush bool, swap int) {
start, column := 0, 0 start, column := 0, 0
//fmt.Println(str)
for _, token := range screen.markup.Tokenize(str) { for _, token := range screen.markup.Tokenize(str) {
// First check if it's a tag. Tags are eaten up and not displayed. // First check if it's a tag. Tags are eaten up and not displayed.
if screen.markup.IsTag(token) { if screen.markup.IsTag(token) {
@ -177,14 +203,30 @@ func (screen *Screen) DrawLineFlush(x int, y int, str string, flush bool) {
} }
// Here comes the actual text: display it one character at a time. // Here comes the actual text: display it one character at a time.
//fmt.Println(token)
for i, char := range token { for i, char := range token {
if !screen.markup.RightAligned { if !screen.markup.RightAligned {
start = x + column start = x + column
column++ column++
if char == '|'{
//bg = screen.markup.Background //termbox.ColorLightRed
}
if isChineseChar(char) {
column ++
//bg, fg = fg, bg
}
} else { } else {
start = screen.width - len(token) + i start = screen.width - len(token) + i //- 15
} }
termbox.SetCell(start, y, char, screen.markup.Foreground, screen.markup.Background) //fmt.Println(string(char))
bg := screen.markup.Background
fg := screen.markup.Foreground
if swap == 1 {
termbox.SetCell(start, y, char, bg, fg)
} else{
termbox.SetCell(start, y, char, fg, bg)
}
//bg, fg = fg, bg
} }
} }
if flush { if flush {
@ -231,13 +273,17 @@ func (screen *Screen) draw(str string, offset bool) {
// only write the necessary lines // only write the necessary lines
if row <= len(allLines) && if row <= len(allLines) &&
row > screen.headerLine { row > screen.headerLine {
screen.DrawLineFlush(0, row-screen.offset, allLines[row], false) swap := 0
if row == screen.selectindex{
swap = 1
}
screen.DrawLineFlush(0, row-screen.offset, allLines[row], false, swap)
} else if row > len(allLines) + 1 { } else if row > len(allLines) + 1 {
row = len(allLines) row = len(allLines)
} }
} }
} else { } else {
screen.DrawLineFlush(0, row, allLines[row], false) screen.DrawLineFlush(0, row, allLines[row], false, 0)
} }
} }
// If the quotes lines in this cycle are shorter than in the previous // If the quotes lines in this cycle are shorter than in the previous

File diff suppressed because it is too large Load Diff

@ -9,9 +9,12 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"encoding/json" "encoding/json"
//"net/url"
"easyquotation/stock"
) )
const marketURL = `https://query1.finance.yahoo.com/v7/finance/quote?symbols=%s` const marketURL = `https://query1.finance.yahoo.com/v6/finance/quote?symbols=%s`
const marketURLQueryParts = `&range=1d&interval=5m&indicators=close&includeTimestamps=false&includePrePost=false&corsDomain=finance.yahoo.com&.tsrc=finance` const marketURLQueryParts = `&range=1d&interval=5m&indicators=close&includeTimestamps=false&includePrePost=false&corsDomain=finance.yahoo.com&.tsrc=finance`
// Market stores current market information displayed in the top three lines of // Market stores current market information displayed in the top three lines of
@ -32,10 +35,13 @@ type Market struct {
Gold map[string]string Gold map[string]string
errors string // Error(s), if any. errors string // Error(s), if any.
url string // URL with symbols to fetch data url string // URL with symbols to fetch data
res map[string]*stock.Stock
watchlist *Watchlist
} }
// Returns new initialized Market struct. // Returns new initialized Market struct.
func NewMarket() *Market { func NewMarket(res map[string]*stock.Stock, watchlist *Watchlist) *Market {
market := &Market{} market := &Market{}
market.IsClosed = false market.IsClosed = false
market.Dow = make(map[string]string) market.Dow = make(map[string]string)
@ -53,10 +59,11 @@ func NewMarket() *Market {
market.Euro = make(map[string]string) market.Euro = make(map[string]string)
market.Gold = make(map[string]string) market.Gold = make(map[string]string)
market.url = fmt.Sprintf(marketURL, `^DJI,^IXIC,^GSPC,^N225,^HSI,^FTSE,^GDAXI,^TNX,CL=F,JPY=X,EUR=X,GC=F`) + marketURLQueryParts market.url = fmt.Sprintf(marketURL, `^DJI,^IXIC,^GSPC,^N225,^HSI,^FTSE,^GDAXI,^TNX,CL=F,CNH=X,EUR=X,GC=F`) + marketURLQueryParts
market.errors = `` market.errors = ``
market.res = res
market.watchlist = watchlist
return market return market
} }
@ -71,8 +78,22 @@ func (market *Market) Fetch() (self *Market) {
market.errors = "" market.errors = ""
} }
}() }()
// Define the proxy URL
//proxyUrl, err := url.Parse("http://172.29.100.107:28888")
//if err != nil {
// fmt.Println(err)
//}
// Create a new transport with the proxy URL
transport := &http.Transport{
//Proxy: http.ProxyURL(proxyUrl),
}
response, err := http.Get(market.url) // Create a new HTTP client with the transport
client := &http.Client{
Transport: transport,
}
response, err := client.Get(market.url)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -82,7 +103,7 @@ func (market *Market) Fetch() (self *Market) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
//fmt.Println(string(body))
body = market.isMarketOpen(body) body = market.isMarketOpen(body)
return market.extract(body) return market.extract(body)
} }
@ -130,6 +151,34 @@ func (market *Market) extract(body []byte) *Market {
market.Yield[`name`] = `10-year Yield` market.Yield[`name`] = `10-year Yield`
market.Yield = assign(results, 7, false) market.Yield = assign(results, 7, false)
market.Tokyo[`name`] = `SH`
q := market.res["sh000001"].Market
thechange := q.LastPrice - q.PreClose
thechangepercent := thechange / q.PreClose * 100
market.Tokyo[`change`] = float2Str(thechange)
market.Tokyo[`latest`] = float2Str(q.LastPrice)
market.Tokyo[`percent`] = float2Str(thechangepercent)
market.London[`name`] = `SZ`
q = market.res["sz399001"].Market
thechange = q.LastPrice - q.PreClose
thechangepercent = thechange / q.PreClose * 100
market.London[`change`] = float2Str(thechange)
market.London[`latest`] = float2Str(q.LastPrice)
market.London[`percent`] = float2Str(thechangepercent)
market.Frankfurt[`name`] = `CYB`
q = market.res["sz399006"].Market
thechange = q.LastPrice - q.PreClose
thechangepercent = thechange / q.PreClose * 100
market.Frankfurt[`change`] = float2Str(thechange)
market.Frankfurt[`latest`] = float2Str(q.LastPrice)
market.Frankfurt[`percent`] = float2Str(thechangepercent)
market.Yield[`name`] = `Date`
market.Yield[`change`] = market.watchlist.Baseon
market.Yield[`latest`] = ""
market.Oil = assign(results, 8, true) market.Oil = assign(results, 8, true)
market.Yen = assign(results, 9, true) market.Yen = assign(results, 9, true)
market.Euro = assign(results, 10, true) market.Euro = assign(results, 10, true)

@ -13,6 +13,11 @@ import (
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
//"io"
"time"
"easyquotation/stock"
mqtt "github.com/eclipse/paho.mqtt.golang"
) )
// const quotesURL = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=sl1c1p2oghjkva2r2rdyj3j1` // const quotesURL = `http://download.finance.yahoo.com/d/quotes.csv?s=%s&f=sl1c1p2oghjkva2r2rdyj3j1`
@ -47,6 +52,12 @@ type Stock struct {
AfterHours string `json:"postMarketChangePercent,omitempty"` AfterHours string `json:"postMarketChangePercent,omitempty"`
} }
type stockinfo struct {
Scode string
Sname string
Ft string
}
// Quotes stores relevant pointers as well as the array of stock quotes for // Quotes stores relevant pointers as well as the array of stock quotes for
// the tickers we are tracking. // the tickers we are tracking.
type Quotes struct { type Quotes struct {
@ -54,17 +65,59 @@ type Quotes struct {
profile *Profile // Pointer to Profile. profile *Profile // Pointer to Profile.
stocks []Stock // Array of stock quote data. stocks []Stock // Array of stock quote data.
errors string // Error string if any. errors string // Error string if any.
res map[string]*stock.Stock
watchlist *Watchlist
client mqtt.Client
upstocks map[string]string
totalstocks []stockinfo
needrefresh bool
} }
// Sets the initial values and returns new Quotes struct. // Sets the initial values and returns new Quotes struct.
func NewQuotes(market *Market, profile *Profile) *Quotes { 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{ return &Quotes{
market: market, market: market,
profile: profile, profile: profile,
errors: ``, errors: ``,
res: res,
watchlist: watchlist,
client: client,
totalstocks: []stockinfo{},
upstocks: map[string]string{},
needrefresh: true,
} }
} }
// 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"`
}
// Fetch the latest stock quotes and parse raw fetched data into array of // Fetch the latest stock quotes and parse raw fetched data into array of
// []Stock structs. // []Stock structs.
func (quotes *Quotes) Fetch() (self *Quotes) { func (quotes *Quotes) Fetch() (self *Quotes) {
@ -78,6 +131,53 @@ func (quotes *Quotes) Fetch() (self *Quotes) {
} }
}() }()
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, `,`)) url := fmt.Sprintf(quotesURLv7, strings.Join(quotes.profile.Tickers, `,`))
response, err := http.Get(url + quotesURLv7QueryParts) response, err := http.Get(url + quotesURLv7QueryParts)
if err != nil { if err != nil {
@ -89,13 +189,123 @@ func (quotes *Quotes) Fetch() (self *Quotes) {
if err != nil { if err != nil {
panic(err) panic(err)
} }
res := quotes.res
quotes.parse2(body, res)
//fmt.Println(res["sh600111"])
//fmt.Println("mode:", quotes.profile.mode)
quotes.parse2(body) }
}else{
fmt.Println("***not ready***")
} }
return quotes return quotes
} }
//write a function to save the slice of quotes.stocks to file
func (quotes *Quotes) SaveStocks() {
//fmt.Println("save")
//fmt.Println(quotes.stocks)
//fmt.Println(quotes.profile.mode)
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...)
}
// 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, // Ok returns two values: 1) boolean indicating whether the error has occurred,
// and 2) the error text itself. // and 2) the error text itself.
func (quotes *Quotes) Ok() (bool, string) { func (quotes *Quotes) Ok() (bool, string) {
@ -126,23 +336,164 @@ func (quotes *Quotes) RemoveTickers(tickers []string) (removed int, err error) {
// market is still open and we might want to grab the latest quotes. In both // 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. // cases we make sure the list of requested tickers is not empty.
func (quotes *Quotes) isReady() bool { 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 && 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) Sendstockgraphreq(index int, istime bool) {
if index > len(quotes.totalstocks){
return
}
topic := "my/topic"
itemsel := quotes.getitembyscode(quotes.totalstocks[index-1].Scode)
data := map[string]interface{}{
"scode": quotes.totalstocks[index-1].Scode,
"tier": 0,
"daysback": itemsel.Daysback,
"stdprice": itemsel.Enterprice,
"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) 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})
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
} }
// this will parse the json objects // this will parse the json objects
func (quotes *Quotes) parse2(body []byte) (*Quotes, error) { func (quotes *Quotes) parse2(body []byte, res map[string]*stock.Stock) (*Quotes, error) {
// response -> quoteResponse -> result|error (array) -> map[string]interface{} // response -> quoteResponse -> result|error (array) -> map[string]interface{}
// Stocks has non-int things // Stocks has non-int things
// d := map[string]map[string][]Stock{} // d := map[string]map[string][]Stock{}
// some of these are numbers vs strings // some of these are numbers vs strings
// d := map[string]map[string][]map[string]string{} // d := map[string]map[string][]map[string]string{}
d := map[string]map[string][]map[string]interface{}{} //d := map[string]map[string][]map[string]interface{}{}
err := json.Unmarshal(body, &d) //err := json.Unmarshal(body, &d)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
results := d["quoteResponse"]["result"] //results := d["quoteResponse"]["result"]
/*
quotes.stocks = make([]Stock, len(results)) quotes.stocks = make([]Stock, len(results))
for i, raw := range results { for i, raw := range results {
result := map[string]string{} result := map[string]string{}
@ -179,14 +530,14 @@ func (quotes *Quotes) parse2(body []byte) (*Quotes, error) {
quotes.stocks[i].Currency = result["currency"] quotes.stocks[i].Currency = result["currency"]
quotes.stocks[i].PreOpen = result["preMarketChangePercent"] quotes.stocks[i].PreOpen = result["preMarketChangePercent"]
quotes.stocks[i].AfterHours = result["postMarketChangePercent"] quotes.stocks[i].AfterHours = result["postMarketChangePercent"]
/*
fmt.Println(i) fmt.Println(i)
fmt.Println("-------------------") fmt.Println("-------------------")
for k, v := range result { for k, v := range result {
fmt.Println(k, v) fmt.Println(k, v)
} }
fmt.Println("-------------------") fmt.Println("-------------------")
*/
adv, err := strconv.ParseFloat(quotes.stocks[i].Change, 64) adv, err := strconv.ParseFloat(quotes.stocks[i].Change, 64)
quotes.stocks[i].Direction = 0 quotes.stocks[i].Direction = 0
if err == nil { if err == nil {
@ -197,6 +548,164 @@ func (quotes *Quotes) parse2(body []byte) (*Quotes, error) {
} }
} }
} }
// Use the "http.Get" function to fetch the URL and get the response
response, err := http.Get("http://119.29.166.226/q/dayjson/2023-04-20ml.json")//("http://119.29.166.226/q/dayjson/ml.json")
if err != nil {
// Handle error
fmt.Println(err)
}
defer response.Body.Close()
//respbody, err := ioutil.ReadAll(response.Body)
//if err != nil {
// handle error
//}
// Use the "json" package to decode the response body into a JSON object
var watchlist Watchlist
var scodes []string
err = json.NewDecoder(response.Body).Decode(&watchlist)
if err != nil {
// Handle error
fmt.Println(err)
}
//var data Data
//err = json.Unmarshal(respbody, &data)
if err != nil {
// Handle error
fmt.Println(err)
}
*/
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
//fmt.Println(q.Name, q.PreClose , q.LastPrice ,q.LastPrice , enterPrice)
if q.PreClose < q.LastPrice && q.LastPrice >= enterPrice && q.PreClose < enterPrice{
//fmt.Println(enterPrice)
scodes = append(scodes, item.Scode)
//fmt.Println(scodes)
}
wamap[item.Scode] = item
}
//fmt.Println(scodes)
}
//fmt.Println(scodes)
//canstocklen := len(scodes)
for _, item := range quotes.profile.Tickers {
if _, ok := res[item]; ok {
scodes = append(scodes, item)
}
}
//scodes = append(scodes, quotes.profile.Tickers...)
//fmt.Println(scodes)
//
var snames []string
quotes.stocks = make([]Stock, len(scodes))
//fmt.Println(res["sh600000"])
for i, scode := range scodes {
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})
quotes.upstocks[scode] = q.Time
}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].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
if _, ok := wamap[scode]; ok {
strprice, _ := strconv.ParseFloat(wamap[scode].Enterprice, 64)
stdchange := q.LastPrice - strprice
quotes.stocks[i].Change = float2Str(stdchange)
//quotes.stocks[i].PreOpen = wamap[scode].Enterprice
//quotes.stocks[i].AfterHours = strconv.Itoa(wamap[scode].Daysback)
//quotes.stocks[i].Yield = wamap[scode].AnalyseDay
}
if inlist(quotes.profile.Tickers, scode) == true {
quotes.stocks[i].AvgVolume = "M"
}
}
//quotes.stocks[0].PeRatio = result["trailingPE"]
/*
// TODO calculate rt
quotes.stocks[0].PeRatioX = result["trailingPE"]
quotes.stocks[0].Dividend = result["trailingAnnualDividendRate"]
quotes.stocks[0].Yield = result["trailingAnnualDividendYield"]
quotes.stocks[0].MarketCap = result["marketCap"]
// TODO calculate rt?
quotes.stocks[0].MarketCapX = result["marketCap"]
quotes.stocks[0].Currency = result["currency"]
quotes.stocks[0].PreOpen = result["preMarketChangePercent"]
quotes.stocks[0].AfterHours = result["postMarketChangePercent"]
*/
//fmt.Println(quotes)
return quotes, nil return quotes, nil
} }

Loading…
Cancel
Save