(原) 使用Go-ECharts制作股市趋势图

原创文章,请后转载,并注明出处。

代码尚未整理

package main

import (
	"bufio"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/PuerkitoBio/goquery"
	"github.com/go-echarts/go-echarts/v2/charts"
	"github.com/go-echarts/go-echarts/v2/components"
	"github.com/go-echarts/go-echarts/v2/opts"
)

var (
	MyStockCode = "300144"
	fileNameS   = "data_s.txt"
	fileNameH   = "data_h.txt"
	fileNameM   = fmt.Sprintf("data_%s.txt", MyStockCode)
)

type klineData struct {
	date string
	data [4]float32
}

// 当前为第几季度
func Season() (y, m, s int) {
	y = time.Now().Year()
	m = int(time.Now().Month())
	s = m/4 + 1
	return
}

// 获取历史数据
func WebSearch(url string) (ret []string) {
	var lineData string
	res, err := http.Get(url)
	if err != nil {
		fmt.Println(err)
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		fmt.Printf("status code error: %d %s", res.StatusCode, res.Status)
	}

	doc, err := goquery.NewDocumentFromReader(res.Body)
	if err != nil {
		fmt.Println(err)
	}

	doc.Find(".table_bg001 tbody tr").Each(func(i int, s *goquery.Selection) {
		lineData = ""
		s.Find("td").Each(func(n int, m *goquery.Selection) {
			lineData = fmt.Sprintf("%s %s", lineData, m.Text())
		})
		ret = append(ret, lineData)
	})
	return
}

// 保存文件
func writeFile(buf []byte, fileName string) error {
	tmpPath := "./"
	err := os.MkdirAll(tmpPath, 0700)
	if err != nil {
		return err
	}

	file := filepath.Join(tmpPath, fileName)
	err = ioutil.WriteFile(file, buf, 0600)
	if err != nil {
		return err
	}
	return nil
}

// 数组倒序函数
func Reverse(arr *[]string) {
	var temp string
	length := len(*arr)
	for i := 0; i < length/2; i++ {
		temp = (*arr)[i]
		(*arr)[i] = (*arr)[length-1-i]
		(*arr)[length-1-i] = temp
	}
}

// 文件存在判断
func isExist(path string) bool {
	_, err := os.Stat(path)
	if err != nil {
		if os.IsExist(err) {
			return true
		}
		if os.IsNotExist(err) {
			return false
		}
		return false
	}
	return true
}

// 读文件
func ReadFile(filename string) (ret []string) {
	fi, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	r := bufio.NewReader(fi) // 创建 Reader

	for {
		lineBytes, err := r.ReadBytes('\n')
		line := strings.TrimSpace(string(lineBytes))
		if err != nil && err != io.EOF {
			panic(err)
		}
		if err == io.EOF {
			break
		}
		ret = append(ret, line)
	}
	return
}

func GetData_KLine(fileName string) (kd []klineData) {
	var k klineData
	Stock := ReadFile(fileName)
	for _, n := range Stock {
		x := strings.Split(n, " ")
		n1, _ := strconv.ParseFloat(strings.ReplaceAll(x[4], ",", ""), 32) // 收盘价
		n2, _ := strconv.ParseFloat(strings.ReplaceAll(x[1], ",", ""), 32) // 开盘价
		n3, _ := strconv.ParseFloat(strings.ReplaceAll(x[3], ",", ""), 32) // 最低价
		n4, _ := strconv.ParseFloat(strings.ReplaceAll(x[2], ",", ""), 32) // 最高价
		k = klineData{date: x[0], data: [4]float32{float32(n1), float32(n2), float32(n3), float32(n4)}}
		kd = append(kd, k)
	}
	return
}

func klineDataZoomBoth(fileName, title string) *charts.Kline {
	kline := charts.NewKLine()
	kd := GetData_KLine(fileName)

	x := make([]string, 0)
	y := make([]opts.KlineData, 0)
	for i := 0; i < len(kd); i++ {
		x = append(x, kd[i].date)
		y = append(y, opts.KlineData{Value: kd[i].data})
	}

	kline.SetGlobalOptions(
		charts.WithTitleOpts(opts.Title{Title: title, Right: "40%", Top: "5%"}),
		charts.WithXAxisOpts(opts.XAxis{SplitNumber: 20}),
		charts.WithYAxisOpts(opts.YAxis{Scale: true}),
		charts.WithDataZoomOpts(opts.DataZoom{
			Type:       "inside",
			Start:      50,
			End:        100,
			XAxisIndex: []int{0},
		}),
		charts.WithDataZoomOpts(opts.DataZoom{
			Type:       "slider",
			Start:      50,
			End:        100,
			XAxisIndex: []int{0},
		}),
	)

	kline.SetXAxis(x).AddSeries("kline", y).
		SetSeriesOptions(
			charts.WithMarkPointNameTypeItemOpts(opts.MarkPointNameTypeItem{
				Name:     "highest value",
				Type:     "max",
				ValueDim: "highest",
			}),
			charts.WithMarkPointNameTypeItemOpts(opts.MarkPointNameTypeItem{
				Name:     "lowest value",
				Type:     "min",
				ValueDim: "lowest",
			}),
			charts.WithMarkPointStyleOpts(opts.MarkPointStyle{
				Label: &opts.Label{Show: true},
			}),
			charts.WithItemStyleOpts(opts.ItemStyle{
				Color: "#00da3c", Color0: "#ec0000",
				BorderColor: "#000099", BorderColor0: "#222222",
			}),
		)
	return kline
}

func GetData_Bar(fileName string) (title []string, bd1 []opts.BarData, bd2 []opts.BarData) {
	Stock := ReadFile(fileName)
	for _, n := range Stock {
		x := strings.Split(n, " ")
		n1, _ := strconv.ParseFloat(strings.ReplaceAll(x[8], ",", ""), 32) // 成交金额(万元)
		n2, _ := strconv.ParseFloat(strings.ReplaceAll(x[4], ",", ""), 32) // 收盘价
		title = append(title, strings.ReplaceAll(strings.ReplaceAll(x[0], "2022", ""), "-", ""))
		bd1 = append(bd1, opts.BarData{Value: float32(n1)})
		bd2 = append(bd2, opts.BarData{Value: float32(n2)})
	}
	return
}

func barDataZoomSlider() *charts.Bar {
	t, d1, _ := GetData_Bar(fileNameM)
	bar := charts.NewBar()
	bar.SetGlobalOptions(
		charts.WithTitleOpts(opts.Title{
			Title: MyStockCode + "成交量",
			Right: "40%",
			Top:   "5%",
		}),
		charts.WithDataZoomOpts(opts.DataZoom{
			Type:  "slider",
			Start: 10,
			End:   50,
		}),
		charts.WithTooltipOpts(opts.Tooltip{Show: true}),
		//charts.WithColorsOpts(opts.Colors{"blue", "pink"}),
	)

	bar.SetXAxis(t).
		AddSeries("成交金额", d1)
		//AddSeries("现价", d2)
		//SetSeriesOptions(charts.WithLabelOpts(opts.Label{Show: true, Position: "top"}))
	return bar
}

func init() {
	y, _, s := Season() // 获取年份及季度

	if isExist(fileNameS) == false || isExist(fileNameH) == false { // 数据文件存在
		log.Print("获取数据中...")
		fs, _ := os.OpenFile(fileNameS, os.O_APPEND|os.O_CREATE, 0666)
		fh, _ := os.OpenFile(fileNameH, os.O_APPEND|os.O_CREATE, 0666)
		defer fs.Close()
		defer fh.Close()
		for i := 1; i <= s; i++ {
			log.Printf("%d年%d季度上证", y, i)
			ShangZhen := WebSearch(fmt.Sprintf("http://quotes.money.163.com/trade/lsjysj_zhishu_000001.html?year=%d&season=%d", y, i))
			log.Printf("%d年%d季度沪证", y, i)
			HuZhen := WebSearch(fmt.Sprintf("http://quotes.money.163.com/trade/lsjysj_zhishu_399001.html?year=%d&season=%d", y, i))
			Reverse(&ShangZhen)
			Reverse(&HuZhen)
			for _, n := range ShangZhen {
				io.WriteString(fs, strings.TrimSpace(n)+"\n")
			}
			for _, n := range HuZhen {
				io.WriteString(fh, strings.TrimSpace(n)+"\n")
			}
		}
	}

	if isExist(fileNameM) == false {
		f, _ := os.OpenFile(fileNameM, os.O_APPEND|os.O_CREATE, 0666)
		defer f.Close()
		for i := 1; i <= s; i++ {
			log.Printf("%d年%d季度 %s", y, i, MyStockCode)
			Stock := WebSearch(fmt.Sprintf("http://quotes.money.163.com/trade/lsjysj_%s.html?year=%d&season=%d", MyStockCode, y, i))
			Reverse(&Stock)
			for _, n := range Stock {
				io.WriteString(f, strings.TrimSpace(n)+"\n")
			}
		}
	}
}

func main() {
	page := components.NewPage()
	page.AddCharts(
		klineDataZoomBoth(fileNameH, "沪证趋势图"),
		klineDataZoomBoth(fileNameS, "上证趋势图"),
		barDataZoomSlider(),
		klineDataZoomBoth(fileNameM, MyStockCode+"价格"),
	)

	f, _ := os.Create("line.html")
	page.Render(io.MultiWriter(f))
}

相关文章