feat(stock):优化股票分时图表展示

- 新增 GetStockMinutePriceLineData 函数获取股票分时数据
- 在前端实现分时数据图表展示
- 后端增加 GetStockMinutePriceData接口获取分时数据
- 更新数据库模型,添加 MinuteData 结构体
This commit is contained in:
ArvinLovegood 2025-05-07 16:17:33 +08:00
parent 11a1a47eca
commit cf537ca695
7 changed files with 201 additions and 6 deletions

11
app.go
View File

@ -1065,6 +1065,17 @@ func (a *App) RemoveGroup(groupId int) string {
func (a *App) GetStockKLine(stockCode, stockName string, days int64) *[]data.KLineData {
return data.NewStockDataApi().GetHK_KLineData(stockCode, "day", days)
}
func (a *App) GetStockMinutePriceLineData(stockCode, stockName string) map[string]any {
res := make(map[string]any, 4)
priceData, date := data.NewStockDataApi().GetStockMinutePriceData(stockCode)
res["priceData"] = priceData
res["date"] = date
res["stockName"] = stockName
res["stockCode"] = stockCode
return res
}
func (a *App) GetStockCommonKLine(stockCode, stockName string, days int64) *[]data.KLineData {
return data.NewStockDataApi().GetCommonKLineData(stockCode, "day", days)
}

View File

@ -1235,6 +1235,66 @@ func CheckBrowserOnWindows() (string, bool) {
return path + "\\msedge.exe", true
}
// 分时数据
func (receiver StockDataApi) GetStockMinutePriceData(stockCode string) (*[]MinuteData, string) {
url := fmt.Sprintf("https://web.ifzq.gtimg.cn/appstock/app/minute/query?code=%s", stockCode)
if strutil.HasPrefixAny(stockCode, []string{"gb_", "GB_"}) {
stockCode = strings.Replace(strings.ToUpper(stockCode), "GB_", "us", 1) + ".OQ"
}
if strutil.HasPrefixAny(stockCode, []string{"us", "US"}) {
url = fmt.Sprintf("https://web.ifzq.gtimg.cn/appstock/app/UsMinute/query?code=%s", stockCode)
}
logger.SugaredLogger.Infof("GetStockMinutePriceData url:%s", url)
res := make(map[string]interface{})
resp, err := receiver.client.SetTimeout(time.Duration(receiver.config.CrawlTimeOut)*time.Second).R().
SetHeader("Host", "web.ifzq.gtimg.cn").
SetHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0").
Get(url)
date := ""
minuteDatas := &[]MinuteData{}
if err != nil {
logger.SugaredLogger.Errorf("err:%s", err.Error())
return minuteDatas, date
}
//logger.SugaredLogger.Infof("resp:%s", resp.Body())
json.Unmarshal(resp.Body(), &res)
code, _ := convertor.ToInt(res["code"])
if res["data"] != nil && code == 0 {
data := res["data"].(map[string]interface{})
if stockData, ok := data[stockCode]; ok {
m := stockData.(map[string]interface{})
if d, ok := m["data"]; ok {
if m2, ok := d.(map[string]any); ok {
minutePriceData := m2["data"]
datas := minutePriceData.([]any)
for _, item := range datas {
minuteDataSplit := strutil.SplitEx(strutil.ReplaceWithMap(item.(string), map[string]string{
"\r\n": " ",
}), " ", true)
price, _ := convertor.ToFloat(minuteDataSplit[1])
volume, _ := convertor.ToFloat(minuteDataSplit[2])
amount := float64(0)
if len(minuteDataSplit) >= 4 {
amount, _ = convertor.ToFloat(minuteDataSplit[3])
}
minuteData := &MinuteData{
Time: minuteDataSplit[0][0:2] + ":" + minuteDataSplit[0][2:4],
Price: price,
Volume: volume,
Amount: amount,
}
*minuteDatas = append(*minuteDatas, *minuteData)
}
date = m2["date"].(string)
}
}
}
}
return minuteDatas, date
}
func (receiver StockDataApi) GetKLineData(stockCode string, kLineType string, days int64) *[]KLineData {
url := fmt.Sprintf("http://quotes.sina.cn/cn/api/json_v2.php/CN_MarketDataService.getKLineData?symbol=%s&scale=%s&ma=yes&datalen=%d", stockCode, kLineType, days)
K := &[]KLineData{}
@ -1592,3 +1652,10 @@ type KLineData struct {
Close string `json:"close"`
Volume string `json:"volume"`
}
type MinuteData struct {
Time string `json:"time"`
Price float64 `json:"price"`
Volume float64 `json:"volume"`
Amount float64 `json:"amount"`
}

View File

@ -70,7 +70,12 @@ func TestSearchStockPriceInfo(t *testing.T) {
//getZSInfo("沪深300指数", "sh000300", 30)
}
func TestGetStockMinutePriceData(t *testing.T) {
db.Init("../../data/stock.db")
data, date := NewStockDataApi().GetStockMinutePriceData("usTSLA.OQ")
logger.SugaredLogger.Infof("date:%s", date)
logger.SugaredLogger.Infof("%+#v", *data)
}
func TestGetKLineData(t *testing.T) {
db.Init("../../data/stock.db")
k := NewStockDataApi().GetKLineData("sh600171", "240", 30)

View File

@ -96,7 +96,7 @@ function handleKLine(code,name){
color: darkTheme?'#ccc':'#456'
},
formatter: function (params) {//
console.log("params",params)
//console.log("params",params)
let currentItemData = _.filter(params, (param) => param.seriesIndex === 0)[0].data;
let ma5=_.filter(params, (param) => param.seriesIndex === 1)[0].data;//ma5
let ma10=_.filter(params, (param) => param.seriesIndex === 2)[0].data;//ma10
@ -354,6 +354,10 @@ function handleKLine(code,name){
]
};
chart.setOption(option);
chart.on('click',{seriesName:'日K'}, function(params) {
console.log("click:",params);
});
})
}
function calculateMA(dayCount,values) {

View File

@ -23,7 +23,7 @@ import {
AddGroup,
GetGroupList,
AddStockGroup,
RemoveStockGroup, RemoveGroup,GetStockKLine
RemoveStockGroup, RemoveGroup, GetStockKLine, GetStockMinutePriceLineData
} from '../../wailsjs/go/main/App'
import {
NAvatar,
@ -66,6 +66,7 @@ const upBorderColor = '';
const downColor = '#00da3c';
const downBorderColor = '';
const kLineChartRef = ref(null);
const kLineChartRef2 = ref(null);
const handleProgress = (progress) => {
@ -600,6 +601,103 @@ function showFenshi(code,name){
}
modalShow2.value=true
GetStockMinutePriceLineData(code,name).then(result => {
console.log("GetStockMinutePriceLineData",result)
const priceData=result.priceData
const chart = echarts.init(kLineChartRef2.value);
let category=[]
let price=[]
let volume=[]
let min=0
let max=0
for (let i = 0; i < priceData.length; i++) {
category.push(priceData[i].time)
price.push(priceData[i].price)
if(min===0||min>priceData[i].price){
min=priceData[i].price
}
if(max<priceData[i].price){
max=priceData[i].price
}
if(i>0){
volume.push(priceData[i].volume-priceData[i-1].volume)
}else{
volume.push(priceData[i].volume)
}
}
let option = {
title: {
text: result.date,
left: 'center',
textStyle: {
color: data.darkTheme?'#ccc':'#456'
}
},
legend: {
data: ['股价', '成交量'],
textStyle: {
color: data.darkTheme?'#ccc':'#456'
},
right: 20,
},
darkMode: data.darkTheme,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
animation: false,
label: {
backgroundColor: '#505765'
}
}
},
xAxis: {
type: 'category',
data: category
},
yAxis: [
{
name:"股价",
min: min-1,
max: max+1,
minInterval:0.01,
type: 'value'
},
{
name:"成交量",
type: 'value',
}
],
series: [
{
name:"股价",
data: price,
type: 'line',
smooth: false,
showSymbol: false,
markPoint: {
data: [
{ type: 'max', name: 'Max' },
{ type: 'min', name: 'Min' }
]
},
markLine: {
data: [{ type: 'average', name: 'Avg' }]
}
},
{
yAxisIndex: 1,
name:"成交量",
data: volume,
type: 'bar',
}
]
};
chart.setOption(option);
})
}
function calculateMA(dayCount,values) {
@ -670,7 +768,7 @@ function handleKLine(){
color: data.darkTheme?'#ccc':'#456'
},
formatter: function (params) {//
console.log("params",params)
//console.log("params",params)
let volum=params[5].data;//ma5
let ma5=params[1].data;//ma5
let ma10=params[2].data;//ma10
@ -937,6 +1035,9 @@ function handleKLine(){
]
};
chart.setOption(option);
chart.on('click',{seriesName:'日K'}, function(params) {
console.log("click:",params);
});
})
}
function showK(code,name){
@ -1534,8 +1635,9 @@ function delStockGroup(code,name,groupId){
</n-flex>
</template>
</n-modal>
<n-modal v-model:show="modalShow2" :title="data.name" style="width: 600px" :preset="'card'">
<n-image :src="data.fenshiURL" />
<n-modal v-model:show="modalShow2" :title="data.name" style="width: 900px" :preset="'card'">
<!-- <n-image :src="data.fenshiURL" />-->
<div ref="kLineChartRef2" style="width: 850px; height: 500px;"></div>
</n-modal>
<n-modal v-model:show="modalShow3" :title="data.name" style="width: 900px" :preset="'card'" @after-enter="handleKLine">
<!-- <n-image :src="data.kURL" />-->

View File

@ -41,6 +41,8 @@ export function GetStockKLine(arg1:string,arg2:string,arg3:number):Promise<any>;
export function GetStockList(arg1:string):Promise<Array<data.StockBasic>>;
export function GetStockMinutePriceLineData(arg1:string,arg2:string):Promise<Record<string, any>>;
export function GetTelegraphList(arg1:string):Promise<any>;
export function GetVersionInfo():Promise<models.VersionInfo>;

View File

@ -78,6 +78,10 @@ export function GetStockList(arg1) {
return window['go']['main']['App']['GetStockList'](arg1);
}
export function GetStockMinutePriceLineData(arg1, arg2) {
return window['go']['main']['App']['GetStockMinutePriceLineData'](arg1, arg2);
}
export function GetTelegraphList(arg1) {
return window['go']['main']['App']['GetTelegraphList'](arg1);
}