From 512f9a0757af8cff92b87de142f6ce4424296bd7 Mon Sep 17 00:00:00 2001 From: ArvinLovegood Date: Thu, 3 Apr 2025 17:21:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(stock):=E6=B7=BB=E5=8A=A0=E8=82=A1?= =?UTF-8?q?=E7=A5=A8=E5=88=86=E7=BB=84=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增股票分组相关接口和页面 - 实现分组添加、删除和股票移除功能 - 优化股票列表展示,支持按分组筛选 - 添加分组相关数据结构和 API --- app.go | 78 ++++++++-- backend/data/fund_data_api.go | 2 +- backend/data/stock_data_api.go | 21 ++- backend/data/stock_group_api.go | 80 ++++++++++ backend/db/db.go | 2 +- frontend/src/components/settings.vue | 20 +-- frontend/src/components/stock.vue | 218 +++++++++++++++++++++++++-- frontend/src/router/router.js | 4 +- frontend/wailsjs/go/main/App.d.ts | 14 +- frontend/wailsjs/go/main/App.js | 28 +++- frontend/wailsjs/go/models.ts | 150 ++++++++++++++---- go.mod | 2 +- main.go | 7 + 13 files changed, 548 insertions(+), 78 deletions(-) create mode 100644 backend/data/stock_group_api.go diff --git a/app.go b/app.go index bf4af75..0496d25 100644 --- a/app.go +++ b/app.go @@ -101,6 +101,7 @@ func (a *App) startup(ctx context.Context) { onExit(a) }) + logger.SugaredLogger.Infof(" application startup Version:%s", Version) } func (a *App) CheckUpdate() { @@ -237,18 +238,19 @@ func (a *App) domReady(ctx context.Context) { // logger.SugaredLogger.Infof("Edge浏览器已安装,路径为: %s", path) // } //}() - followList := data.NewStockDataApi().GetFollowList() + followList := data.NewStockDataApi().GetFollowList(0) for _, follow := range *followList { - if follow.Cron == "" { + if follow.Cron == nil || *follow.Cron == "" { continue } - entryID, err := a.cron.AddFunc(follow.Cron, a.AddCronTask(follow)) - logger.SugaredLogger.Errorf("添加自动分析任务:%s cron=%s entryID:%v", follow.Name, follow.Cron, entryID) - a.cronEntrys[follow.StockCode] = entryID + entryID, err := a.cron.AddFunc(*follow.Cron, a.AddCronTask(follow)) if err != nil { - return + logger.SugaredLogger.Errorf("添加自动分析任务失败:%s cron=%s entryID:%v", follow.Name, *follow.Cron, entryID) + continue } + a.cronEntrys[follow.StockCode] = entryID } + logger.SugaredLogger.Infof("domReady-cronEntrys:%+v", a.cronEntrys) } @@ -504,6 +506,7 @@ func addStockFollowData(follow data.FollowedStock, stockData *data.StockInfo) { stockData.CostVolume = follow.Volume //成本量 stockData.AlarmChangePercent = follow.AlarmChangePercent stockData.AlarmPrice = follow.AlarmPrice + stockData.Groups = follow.Groups //当前价格 price, _ := convertor.ToFloat(stockData.Price) @@ -592,17 +595,19 @@ func (a *App) beforeClose(ctx context.Context) (prevent bool) { logger.SugaredLogger.Debugf("dialog:%s", dialog) if dialog == "No" { return true + } else { + systray.Quit() + a.cron.Stop() + return false } - return false } // shutdown is called at application termination func (a *App) shutdown(ctx context.Context) { defer PanicHandler() // Perform your teardown here - systray.Quit() - a.cron.Stop() - os.Exit(0) + //os.Exit(0) + logger.SugaredLogger.Infof("application shutdown Version:%s", Version) } // Greet returns a greeting for the given name @@ -612,7 +617,7 @@ func (a *App) Greet(stockCode string) *data.StockInfo { follow := &data.FollowedStock{ StockCode: stockCode, } - db.Dao.Model(follow).Where("stock_code = ?", stockCode).First(follow) + db.Dao.Model(follow).Where("stock_code = ?", stockCode).Preload("Groups").Preload("Groups.GroupInfo").First(follow) stockInfo := getStockInfo(*follow) return stockInfo } @@ -625,8 +630,8 @@ func (a *App) UnFollow(stockCode string) string { return data.NewStockDataApi().UnFollow(stockCode) } -func (a *App) GetFollowList() *[]data.FollowedStock { - return data.NewStockDataApi().GetFollowList() +func (a *App) GetFollowList(groupId int) *[]data.FollowedStock { + return data.NewStockDataApi().GetFollowList(groupId) } func (a *App) GetStockList(key string) []data.StockBasic { @@ -796,7 +801,7 @@ func getMsgTypeName(msgType int) string { func onExit(a *App) { // 清理操作 - logger.SugaredLogger.Infof("onExit") + logger.SugaredLogger.Infof("systray onExit") //systray.Quit() //runtime.Quit(a.ctx) } @@ -804,7 +809,7 @@ func onExit(a *App) { func onReady(a *App) { // 初始化操作 - logger.SugaredLogger.Infof("onReady") + logger.SugaredLogger.Infof("systray onReady") systray.SetIcon(icon2) systray.SetTitle("go-stock") systray.SetTooltip("go-stock 股票行情实时获取") @@ -988,3 +993,46 @@ func OnSecondInstanceLaunch(secondInstanceData options.SecondInstanceData) { } time.Sleep(time.Second * 3) } + +func (a *App) AddGroup(group data.Group) string { + ok := data.NewStockGroupApi(db.Dao).AddGroup(group) + if ok { + return "添加成功" + } else { + return "添加失败" + } +} +func (a *App) GetGroupList() []data.Group { + return data.NewStockGroupApi(db.Dao).GetGroupList() +} + +func (a *App) GetGroupStockList(groupId int) []data.GroupStock { + return data.NewStockGroupApi(db.Dao).GetGroupStockByGroupId(groupId) +} + +func (a *App) AddStockGroup(groupId int, stockCode string) string { + ok := data.NewStockGroupApi(db.Dao).AddStockGroup(groupId, stockCode) + if ok { + return "添加成功" + } else { + return "添加失败" + } +} + +func (a *App) RemoveStockGroup(code, name string, groupId int) string { + ok := data.NewStockGroupApi(db.Dao).RemoveStockGroup(code, name, groupId) + if ok { + return "移除成功" + } else { + return "移除失败" + } +} + +func (a *App) RemoveGroup(groupId int) string { + ok := data.NewStockGroupApi(db.Dao).RemoveGroup(groupId) + if ok { + return "移除成功" + } else { + return "移除失败" + } +} diff --git a/backend/data/fund_data_api.go b/backend/data/fund_data_api.go index dc652ab..3289fd6 100644 --- a/backend/data/fund_data_api.go +++ b/backend/data/fund_data_api.go @@ -344,7 +344,7 @@ func (f *FundApi) CrawlFundNetEstimatedUnit(code string) { //logger.SugaredLogger.Infof("基金净值信息:%s", htmlContent) err := json.Unmarshal([]byte(htmlContent), &fundNetUnitValue) if err != nil { - logger.SugaredLogger.Errorf("json.Unmarshal error:%s", err.Error()) + //logger.SugaredLogger.Errorf("json.Unmarshal error:%s", err.Error()) return } fund := &FollowedFund{ diff --git a/backend/data/stock_data_api.go b/backend/data/stock_data_api.go index 062d65a..b6ead3a 100644 --- a/backend/data/stock_data_api.go +++ b/backend/data/stock_data_api.go @@ -16,6 +16,7 @@ import ( "github.com/duke-git/lancet/v2/slice" "github.com/duke-git/lancet/v2/strutil" "github.com/go-resty/resty/v2" + "github.com/samber/lo" "go-stock/backend/db" "go-stock/backend/logger" "go-stock/backend/models" @@ -90,6 +91,8 @@ type StockInfo struct { Sort int64 `json:"sort"` //排序 AlarmChangePercent float64 `json:"alarmChangePercent"` AlarmPrice float64 `json:"alarmPrice"` + + Groups []GroupStock `gorm:"-:all"` } func (receiver StockInfo) TableName() string { @@ -162,8 +165,9 @@ type FollowedStock struct { AlarmPrice float64 Time time.Time Sort int64 - Cron string + Cron *string IsDel soft_delete.DeletedAt `gorm:"softDelete:flag"` + Groups []GroupStock `gorm:"foreignKey:StockCode;references:StockCode"` } func (receiver FollowedStock) TableName() string { @@ -429,9 +433,20 @@ func (receiver StockDataApi) SetStockAICron(cron string, stockCode string) { db.Dao.Model(&FollowedStock{}).Where("stock_code = ?", strings.ToLower(stockCode)).Update("cron", cron) } -func (receiver StockDataApi) GetFollowList() *[]FollowedStock { +func (receiver StockDataApi) GetFollowList(groupId int) *[]FollowedStock { + logger.SugaredLogger.Infof("GetFollowList %d", groupId) + var result *[]FollowedStock - db.Dao.Model(&FollowedStock{}).Order("sort asc,time desc").Find(&result) + if groupId == 0 { + db.Dao.Model(&FollowedStock{}).Order("sort asc,time desc").Find(&result) + } else { + infos := NewStockGroupApi(db.Dao).GetGroupStockByGroupId(groupId) + codes := lo.FlatMap(infos, func(info GroupStock, idx int) []string { + return []string{info.StockCode} + }) + db.Dao.Model(&FollowedStock{}).Where("stock_code in ?", codes).Order("sort asc,time desc").Find(&result) + logger.SugaredLogger.Infof("GetFollowList %+v", result) + } return result } diff --git a/backend/data/stock_group_api.go b/backend/data/stock_group_api.go new file mode 100644 index 0000000..7d55c9c --- /dev/null +++ b/backend/data/stock_group_api.go @@ -0,0 +1,80 @@ +package data + +import ( + "go-stock/backend/db" + "gorm.io/gorm" +) + +// @Author spark +// @Date 2025/4/3 11:18 +// @Desc +// ----------------------------------------------------------------------------------- +type Group struct { + gorm.Model + Name string `json:"name" gorm:"index"` + Sort int `json:"sort"` +} + +func (Group) TableName() string { + return "stock_groups" +} + +type GroupStock struct { + gorm.Model + StockCode string `json:"stockCode" gorm:"index"` + GroupId int `json:"groupId" gorm:"index"` + GroupInfo Group `json:"groupInfo" gorm:"foreignKey:GroupId;references:ID"` +} + +func (GroupStock) TableName() string { + return "group_stock_info" +} + +type StockGroupApi struct { + dao *gorm.DB +} + +func NewStockGroupApi(dao *gorm.DB) *StockGroupApi { + return &StockGroupApi{dao: db.Dao} +} + +func (receiver StockGroupApi) AddGroup(group Group) bool { + err := receiver.dao.Where("name = ?", group.Name).FirstOrCreate(&group).Updates(&Group{ + Name: group.Name, + Sort: group.Sort, + }).Error + return err == nil +} +func (receiver StockGroupApi) GetGroupList() []Group { + var groups []Group + receiver.dao.Find(&groups) + return groups +} +func (receiver StockGroupApi) GetGroupStockByGroupId(groupId int) []GroupStock { + var stockGroup []GroupStock + receiver.dao.Preload("GroupInfo").Where("group_id = ?", groupId).Find(&stockGroup) + return stockGroup +} + +func (receiver StockGroupApi) AddStockGroup(groupId int, stockCode string) bool { + err := receiver.dao.Where("group_id = ? and stock_code = ?", groupId, stockCode).FirstOrCreate(&GroupStock{ + GroupId: groupId, + StockCode: stockCode, + }).Updates(&GroupStock{ + GroupId: groupId, + StockCode: stockCode, + }).Error + return err == nil +} + +func (receiver StockGroupApi) RemoveStockGroup(code string, name string, id int) bool { + err := receiver.dao.Where("group_id = ? and stock_code = ?", id, code).Delete(&GroupStock{}).Error + return err == nil +} + +func (receiver StockGroupApi) RemoveGroup(id int) bool { + err := receiver.dao.Where("id = ?", id).Delete(&Group{}).Error + err = receiver.dao.Where("group_id = ?", id).Delete(&GroupStock{}).Error + return err == nil + +} diff --git a/backend/db/db.go b/backend/db/db.go index ca8010d..260c3af 100644 --- a/backend/db/db.go +++ b/backend/db/db.go @@ -20,7 +20,7 @@ func Init(sqlitePath string) { Colorful: false, IgnoreRecordNotFoundError: true, ParameterizedQueries: false, - LogLevel: logger.Warn, + LogLevel: logger.Info, }, ) var openDb *gorm.DB diff --git a/frontend/src/components/settings.vue b/frontend/src/components/settings.vue index 90d7c57..2492c8a 100644 --- a/frontend/src/components/settings.vue +++ b/frontend/src/components/settings.vue @@ -259,36 +259,36 @@ function deletePrompt(ID){ - - - + + + + + + + + + + + + + ({{result['盘前盘后']}} {{result['盘前盘后涨跌幅']}}%) + + + % +   + + + + + + + + {{"最高 "+result["今日最高价"]+" "+result.highRate }}% + + + {{"最低 "+result["今日最低价"]+" "+result.lowRate }}% + + + {{"昨收 "+result["昨日收盘价"]}} + + + {{"今开 "+result["今日开盘价"]}} + + + + + + + + + +
@@ -991,6 +1156,31 @@ function share(code,name){ + + + + + + + + + + + + diff --git a/frontend/src/router/router.js b/frontend/src/router/router.js index 1cb9834..8eba737 100644 --- a/frontend/src/router/router.js +++ b/frontend/src/router/router.js @@ -1,4 +1,4 @@ -import { createMemoryHistory, createRouter } from 'vue-router' +import {createMemoryHistory, createRouter, createWebHashHistory} from 'vue-router' import stockView from '../components/stock.vue' import settingsView from '../components/settings.vue' @@ -13,7 +13,7 @@ const routes = [ ] const router = createRouter({ - history: createMemoryHistory(), + history: createWebHashHistory(), routes, }) diff --git a/frontend/wailsjs/go/main/App.d.ts b/frontend/wailsjs/go/main/App.d.ts index 2420d60..a8d8d94 100644 --- a/frontend/wailsjs/go/main/App.d.ts +++ b/frontend/wailsjs/go/main/App.d.ts @@ -5,8 +5,12 @@ import {models} from '../models'; export function AddCronTask(arg1:data.FollowedStock):Promise; +export function AddGroup(arg1:data.Group):Promise; + export function AddPrompt(arg1:models.Prompt):Promise; +export function AddStockGroup(arg1:number,arg2:string):Promise; + export function CheckUpdate():Promise; export function DelPrompt(arg1:number):Promise; @@ -21,10 +25,14 @@ export function GetAIResponseResult(arg1:string):Promise; -export function GetFollowList():Promise; +export function GetFollowList(arg1:number):Promise; export function GetFollowedFund():Promise>; +export function GetGroupList():Promise>; + +export function GetGroupStockList(arg1:number):Promise>; + export function GetPromptTemplates(arg1:string,arg2:string):Promise; export function GetStockList(arg1:string):Promise>; @@ -37,6 +45,10 @@ export function Greet(arg1:string):Promise; export function NewChatStream(arg1:string,arg2:string,arg3:string,arg4:any):Promise; +export function RemoveGroup(arg1:number):Promise; + +export function RemoveStockGroup(arg1:string,arg2:string,arg3:number):Promise; + export function SaveAIResponseResult(arg1:string,arg2:string,arg3:string,arg4:string,arg5:string):Promise; export function SaveAsMarkdown(arg1:string,arg2:string):Promise; diff --git a/frontend/wailsjs/go/main/App.js b/frontend/wailsjs/go/main/App.js index f26cb44..2c2f8e8 100644 --- a/frontend/wailsjs/go/main/App.js +++ b/frontend/wailsjs/go/main/App.js @@ -6,10 +6,18 @@ export function AddCronTask(arg1) { return window['go']['main']['App']['AddCronTask'](arg1); } +export function AddGroup(arg1) { + return window['go']['main']['App']['AddGroup'](arg1); +} + export function AddPrompt(arg1) { return window['go']['main']['App']['AddPrompt'](arg1); } +export function AddStockGroup(arg1, arg2) { + return window['go']['main']['App']['AddStockGroup'](arg1, arg2); +} + export function CheckUpdate() { return window['go']['main']['App']['CheckUpdate'](); } @@ -38,14 +46,22 @@ export function GetConfig() { return window['go']['main']['App']['GetConfig'](); } -export function GetFollowList() { - return window['go']['main']['App']['GetFollowList'](); +export function GetFollowList(arg1) { + return window['go']['main']['App']['GetFollowList'](arg1); } export function GetFollowedFund() { return window['go']['main']['App']['GetFollowedFund'](); } +export function GetGroupList() { + return window['go']['main']['App']['GetGroupList'](); +} + +export function GetGroupStockList(arg1) { + return window['go']['main']['App']['GetGroupStockList'](arg1); +} + export function GetPromptTemplates(arg1, arg2) { return window['go']['main']['App']['GetPromptTemplates'](arg1, arg2); } @@ -70,6 +86,14 @@ export function NewChatStream(arg1, arg2, arg3, arg4) { return window['go']['main']['App']['NewChatStream'](arg1, arg2, arg3, arg4); } +export function RemoveGroup(arg1) { + return window['go']['main']['App']['RemoveGroup'](arg1); +} + +export function RemoveStockGroup(arg1, arg2, arg3) { + return window['go']['main']['App']['RemoveStockGroup'](arg1, arg2, arg3); +} + export function SaveAIResponseResult(arg1, arg2, arg3, arg4, arg5) { return window['go']['main']['App']['SaveAIResponseResult'](arg1, arg2, arg3, arg4, arg5); } diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index cf15b91..98bfbbd 100644 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -142,41 +142,29 @@ export namespace data { return a; } } - export class FollowedStock { - StockCode: string; - Name: string; - Volume: number; - CostPrice: number; - Price: number; - PriceChange: number; - ChangePercent: number; - AlarmChangePercent: number; - AlarmPrice: number; + export class Group { + ID: number; // Go type: time - Time: any; - Sort: number; - Cron: string; - IsDel: number; + CreatedAt: any; + // Go type: time + UpdatedAt: any; + // Go type: gorm + DeletedAt: any; + name: string; + sort: number; static createFrom(source: any = {}) { - return new FollowedStock(source); + return new Group(source); } constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); - this.StockCode = source["StockCode"]; - this.Name = source["Name"]; - this.Volume = source["Volume"]; - this.CostPrice = source["CostPrice"]; - this.Price = source["Price"]; - this.PriceChange = source["PriceChange"]; - this.ChangePercent = source["ChangePercent"]; - this.AlarmChangePercent = source["AlarmChangePercent"]; - this.AlarmPrice = source["AlarmPrice"]; - this.Time = this.convertValues(source["Time"], null); - this.Sort = source["Sort"]; - this.Cron = source["Cron"]; - this.IsDel = source["IsDel"]; + this.ID = source["ID"]; + this.CreatedAt = this.convertValues(source["CreatedAt"], null); + this.UpdatedAt = this.convertValues(source["UpdatedAt"], null); + this.DeletedAt = this.convertValues(source["DeletedAt"], null); + this.name = source["name"]; + this.sort = source["sort"]; } convertValues(a: any, classs: any, asMap: boolean = false): any { @@ -197,6 +185,110 @@ export namespace data { return a; } } + export class GroupStock { + ID: number; + // Go type: time + CreatedAt: any; + // Go type: time + UpdatedAt: any; + // Go type: gorm + DeletedAt: any; + stockCode: string; + groupId: number; + groupInfo: Group; + + static createFrom(source: any = {}) { + return new GroupStock(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.ID = source["ID"]; + this.CreatedAt = this.convertValues(source["CreatedAt"], null); + this.UpdatedAt = this.convertValues(source["UpdatedAt"], null); + this.DeletedAt = this.convertValues(source["DeletedAt"], null); + this.stockCode = source["stockCode"]; + this.groupId = source["groupId"]; + this.groupInfo = this.convertValues(source["groupInfo"], Group); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + export class FollowedStock { + StockCode: string; + Name: string; + Volume: number; + CostPrice: number; + Price: number; + PriceChange: number; + ChangePercent: number; + AlarmChangePercent: number; + AlarmPrice: number; + // Go type: time + Time: any; + Sort: number; + Cron?: string; + IsDel: number; + Groups: GroupStock[]; + + static createFrom(source: any = {}) { + return new FollowedStock(source); + } + + constructor(source: any = {}) { + if ('string' === typeof source) source = JSON.parse(source); + this.StockCode = source["StockCode"]; + this.Name = source["Name"]; + this.Volume = source["Volume"]; + this.CostPrice = source["CostPrice"]; + this.Price = source["Price"]; + this.PriceChange = source["PriceChange"]; + this.ChangePercent = source["ChangePercent"]; + this.AlarmChangePercent = source["AlarmChangePercent"]; + this.AlarmPrice = source["AlarmPrice"]; + this.Time = this.convertValues(source["Time"], null); + this.Sort = source["Sort"]; + this.Cron = source["Cron"]; + this.IsDel = source["IsDel"]; + this.Groups = this.convertValues(source["Groups"], GroupStock); + } + + convertValues(a: any, classs: any, asMap: boolean = false): any { + if (!a) { + return a; + } + if (a.slice && a.map) { + return (a as any[]).map(elem => this.convertValues(elem, classs)); + } else if ("object" === typeof a) { + if (asMap) { + for (const key of Object.keys(a)) { + a[key] = new classs(a[key]); + } + return a; + } + return new classs(a); + } + return a; + } + } + + export class Settings { ID: number; @@ -413,6 +505,7 @@ export namespace data { sort: number; alarmChangePercent: number; alarmPrice: number; + Groups: GroupStock[]; static createFrom(source: any = {}) { return new StockInfo(source); @@ -473,6 +566,7 @@ export namespace data { this.sort = source["sort"]; this.alarmChangePercent = source["alarmChangePercent"]; this.alarmPrice = source["alarmPrice"]; + this.Groups = this.convertValues(source["Groups"], GroupStock); } convertValues(a: any, classs: any, asMap: boolean = false): any { diff --git a/go.mod b/go.mod index 53fd3bb..57473bb 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/go-resty/resty/v2 v2.16.2 github.com/go-toast/toast v0.0.0-20190211030409-01e6764cf0a4 github.com/robfig/cron/v3 v3.0.1 + github.com/samber/lo v1.49.1 github.com/stretchr/testify v1.10.0 github.com/wailsapp/wails/v2 v2.10.1 go.uber.org/zap v1.27.0 @@ -59,7 +60,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/samber/lo v1.49.1 // indirect github.com/tevino/abool v0.0.0-20220530134649-2bfc934cb23c // indirect github.com/tkrajina/go-reflector v0.5.8 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect diff --git a/main.go b/main.go index 2a11de4..485d06f 100644 --- a/main.go +++ b/main.go @@ -66,6 +66,13 @@ func main() { db.Dao.AutoMigrate(&data.FollowedFund{}) db.Dao.AutoMigrate(&data.FundBasic{}) db.Dao.AutoMigrate(&models.PromptTemplate{}) + db.Dao.AutoMigrate(&data.Group{}) + db.Dao.AutoMigrate(&data.GroupStock{}) + + //db.Dao.Model(&data.Group{}).Where("id = ?", 0).FirstOrCreate(&data.Group{ + // Name: "默认分组", + // Sort: 0, + //}) if stocksBin != nil && len(stocksBin) > 0 { go initStockData()