core/main.go
2024-12-02 14:03:35 +08:00

640 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"encoding/json"
"fmt"
"log"
"math/rand"
"os"
"strings"
"time"
"v5sdk_go/rest"
"v5sdk_go/ws"
"v5sdk_go/ws/wImpl"
simple "github.com/bitly/go-simplejson"
"github.com/go-redis/redis"
"phyer.click/tunas/core"
"phyer.click/tunas/private"
"phyer.click/tunas/utils"
)
func init() {
// //fmt.Println("inited")
}
// 通过rest接口获取所有ticker信息存入redis的stream和成交量排行榜
func RestTicker(cr *core.Core, dura time.Duration) {
rsp := rest.RESTAPIResult{}
js := simple.Json{}
itemList := []interface{}{}
fmt.Println("getAllTickerInfo err: ")
rsp1, err := cr.GetAllTickerInfo()
rsp = *rsp1
js1, err := simple.NewJson([]byte(rsp.Body))
js = *js1
if err != nil {
fmt.Println("restTicker err: ", err)
return
}
if len(rsp.Body) == 0 {
fmt.Println("rsp body is null")
return
}
itemList = js.Get("data").MustArray()
// maxTickers是重点关注的topScore的coins的数量
length, _ := cr.Cfg.Config.Get("threads").Get("maxTickers").Int()
fmt.Println("itemList length:", len(itemList))
if len(itemList) < length {
return
}
// 关注多少个币,在这里设置, 只需要5个币
allTicker := cr.GetScoreList(5)
redisCli := cr.RedisCli
// 全部币种列表跟特定币种列表进行比对匹配后push到redis
for _, v := range itemList {
tir := core.TickerInfoResp{}
bs, err := json.Marshal(v)
if err != nil {
fmt.Println("restTicker marshal err: ", err)
return
}
err = json.Unmarshal(bs, &tir)
if err != nil {
fmt.Println("restTicker unmarshal err: ", err)
return
}
ti := tir.Convert()
isUsdt := strings.Contains(ti.InstId, "-USDT")
if !isUsdt {
continue
}
if ti.InstType != "SPOT" {
continue
}
// 把单个ticker信息小时交易量存到交易量排行榜
// fmt.Println("ticker item: ", item)
// 不需要排行榜了
// _, err = redisCli.ZAdd("tickersVol|sortedSet", redis.Z{ti.VolCcy24h, ti.InstId}).Result()
// if err != nil {
// fmt.Println("restTicker redis err: ", err)
// }
ab, _ := json.Marshal(ti)
suffix := ""
env := os.Getenv("GO_ENV")
if env == "demoEnv" {
suffix = "-demoEnv"
}
for _, v := range allTicker {
if v == ti.InstId {
redisCli.Publish(core.TICKERINFO_PUBLISH+suffix, string(ab)).Result()
}
}
}
}
// 私有订阅: 订阅订单频道
func wsPriv(core *core.Core) error {
cfg := core.Cfg
url, _ := cfg.Config.Get("connect").Get("wsPrivateBaseUrl").String()
priCli, err := ws.NewWsClient("wss://" + url)
err = priCli.Start()
if err != nil {
log.Println(err)
return err
}
priCli.SetDailTimeout(time.Second * 10)
defer func() {
priCli.Stop()
}()
var res bool
key, _ := core.Cfg.Config.Get("credentialReadOnly").Get("okAccessKey").String()
secure, _ := core.Cfg.Config.Get("credentialReadOnly").Get("secretKey").String()
pass, _ := core.Cfg.Config.Get("credentialReadOnly").Get("okAccessPassphrase").String()
// TODO 这里订阅收听订单频道结果但是ws请求不可靠这个所谓冗余机制存在有比没有强restPost函数 是保底方案
priCli.AddBookMsgHook(func(ts time.Time, data wImpl.MsgData) error {
// 添加你的方法
fmt.Println("这是自定义AddBookMsgHook")
bj, _ := json.Marshal(data)
fmt.Println("当前数据是", string(bj))
resp := private.OrderResp{}
// TODO FIXME 这里默认所有的数据都是OrderResp类型但是ws请求有其他类型的这个地方将来肯定得改
err := json.Unmarshal(bj, &resp)
if err != nil {
fmt.Println("Canvert MsgData to OrderResp err:", err)
return nil
}
fmt.Println("order resp: ", resp)
list, _ := resp.Convert()
fmt.Println("order list: ", list)
go func() {
for _, v := range list {
fmt.Println("order orderV:", v)
core.OrderChan <- v
}
}()
return nil
})
fmt.Println("key, secure, pass:", key, secure, pass)
res, _, err = priCli.Login(key, secure, pass)
if res {
fmt.Println("私有订阅登录成功!")
} else {
fmt.Println("私有订阅登录失败!", err)
return err
}
var args []map[string]string
arg := make(map[string]string)
arg["instType"] = ws.ANY
args = append(args, arg)
// 订阅订单频道动作执行
res, msg, err := priCli.PrivBookOrder("subscribe", args)
bj, _ := json.Marshal(msg)
fmt.Println("PrivBookOrder res:", res, " msg:", string(bj), " err:", err)
for {
fmt.Println(utils.GetFuncName(), url+" is in subscribing")
time.Sleep(10 * time.Minute)
priCli.Stop()
wsPriv(core)
}
return err
}
// 统一受理发起rest请求的请求
func LoopSaveCandle(cr *core.Core) {
for {
ary, err := cr.RedisCli.BRPop(0, "restQueue").Result()
if err != nil {
fmt.Println("brpop err:", err)
continue
}
restQ := core.RestQueue{}
json.Unmarshal([]byte(ary[1]), &restQ)
// fmt.Println("before: ", restQ.InstId)
// before: USDT|position|key
ary1 := strings.Split(restQ.InstId, "|")
if ary1[0] == "USDT" {
// "USDT-USDT" 这个没有意义,忽略
continue
}
if len(ary1) > 1 && ary1[1] == "position" {
restQ.InstId = ary1[0] + "-USDT"
}
// fmt.Println("after: ", restQ.InstId)
// after: restQueue-USDT
go func() {
restQ.Show(cr)
restQ.Save(cr)
}()
}
}
// 这个函数被废弃了, 根据现有逻辑redisCli.Scan(cursor, "*"+pattern+"*", 2000) 得不到任何内容
func LoopRestQ(cr *core.Core) {
redisCli := cr.RedisCli
cursor := uint64(0)
n := 0
// allTs := []int64{}
rstr := []string{}
pattern := "restQueue"
fmt.Println("LoopRestQ start")
for {
var err error
rstr, cursor, err = redisCli.Scan(cursor, "*"+pattern+"*", 2000).Result()
if err != nil {
panic(err)
}
n += len(rstr)
if n == 0 {
break
}
if n > 200000 {
break
}
if n > 0 {
fmt.Println("LoopRestQ rstr: ", rstr)
}
}
// fmt.Println("LoopRestQ rstr: ", rstr)
fmt.Println("LoopRestQ rstr len: ", len(rstr))
for _, keyN := range rstr {
val, err := redisCli.Get(keyN).Result()
fmt.Println("LoopRestQ val:", val)
restQ := core.RestQueue{}
if err != nil || len(val) == 0 {
continue
}
err = json.Unmarshal([]byte(val), &restQ)
if err != nil {
fmt.Println("RestQueue Unmarshal err: ", err)
}
// res, err := redisCli.LPush("restQueue", val).Result()
fmt.Println("restQueue will LPush: ", val)
if err != nil {
redisCli.Del(keyN)
}
}
time.Sleep(10 * time.Second)
LoopRestQ(cr)
}
func LoopSubscribe(cr *core.Core) {
redisCli := cr.RedisCli
suffix := ""
if cr.Env == "demoEnv" {
suffix = "-demoEnv"
}
pubsub := redisCli.Subscribe(core.ALLCANDLES_INNER_PUBLISH + suffix)
_, err := pubsub.Receive()
if err != nil {
log.Fatal(err)
}
// 用管道来接收消息
ch := pubsub.Channel()
// 处理消息
for msg := range ch {
cd := core.Candle{}
json.Unmarshal([]byte(msg.Payload), &cd)
fmt.Println("msg.PayLoad:", msg.Payload, "candle:", cd)
go func() {
cr.WsSubscribe(&cd)
}()
}
}
// 订阅并执行sardine端传来的订单相关的动作
func LoopSubscribeSubAction(cr *core.Core) {
redisCli := cr.RedisCli
suffix := ""
if cr.Env == "demoEnv" {
suffix = "-demoEnv"
}
// TODO FIXME cli2
prisub := redisCli.Subscribe(core.SUBACTIONS_PUBLISH + suffix)
_, err := prisub.Receive()
if err != nil {
log.Fatal(err)
}
// 用管道来接收消息
ch := prisub.Channel()
// 处理消息
for msg := range ch {
action := core.SubAction{}
json.Unmarshal([]byte(msg.Payload), &action)
fmt.Println("actionMsg.PayLoad:", msg.Payload, "action:", action)
go func() {
cr.DispatchSubAction(&action)
}()
}
}
// period: 每个循环开始的时间点,单位:秒
// delay延时多少秒后去取此值, 单位:秒
// mdura多少个分钟之内遍历完获取到的goins列表, 单位:秒
// onceCount每次获取这个coin几个当前周期的candle数据
// range: 随机的范围从0开始到range个周期作为查询的after值也就是随机n个周期去取之前的记录,对于2D5D等数据可以用来补全数据, range值越大随机散点的范围越大, 越失焦
func LoopAllCoinsList(period int64, delay int64, mdura int, barPeriod string, onceCount int, rge int) {
cr := core.Core{}
cr.Init()
allScoreChan := make(chan []string)
// fmt.Println("allCoins1")
per1 := 1 * time.Minute
ticker := time.NewTicker(per1)
go func() {
for {
tsi := time.Now().Unix()
// fmt.Println("tsi, period, delay, tsi%(period): ", tsi, period, delay, tsi%(period))
if tsi%(period) != delay {
time.Sleep(1 * time.Second)
continue
}
select {
case <-ticker.C:
go func() {
// -1 是获取全部coin列表
list := cr.GetScoreList(5)
// fmt.Println("allCoins3", list)
allScoreChan <- list
}()
}
}
}()
for {
allScore, _ := <-allScoreChan
// fmt.Println("allCoins allScore", allScore)
if len(allScore) == 0 {
continue
}
utils.TickerWrapper(time.Duration(mdura)*time.Second, allScore, func(i int, ary []string) error {
nw := time.Now()
rand.Seed(nw.UnixNano())
ct := rand.Intn(rge)
minutes := cr.PeriodToMinutes(barPeriod)
tmi := nw.UnixMilli()
tmi = tmi - tmi%60000
tmi = tmi - (int64(ct) * minutes * 60000)
fmt.Println("instId: ", ary[i])
restQ := core.RestQueue{
InstId: ary[i],
Bar: barPeriod,
WithWs: false,
After: tmi,
}
js, err := json.Marshal(restQ)
// fmt.Println("allCoins lpush js:", string(js))
cr.RedisCli.LPush("restQueue", js)
return err
})
}
}
func LoopCheckMyFavorList(core *core.Core) {
maxTickers, _ := core.Cfg.Config.Get("threads").Get("maxTickers").Int()
myFavorChan := make(chan []string)
//协程动态维护topScore
go func(mx int) {
myFavor := []string{}
for {
myFavor = core.GetMyFavorList()
if len(myFavor) < mx {
fmt.Println("topScore 长度不符合预期")
break
} else {
fmt.Println("topScore 长度符合预期")
}
myFavorChan <- myFavor
time.Sleep(12 * time.Minute)
}
}(maxTickers)
//协程循环执行rest请求candle
for {
myFavor, _ := <-myFavorChan
go func() {
loop2(core, myFavor, maxTickers)
}()
}
}
func loop2(core *core.Core, topScore []string, maxTickers int) {
restPeriod, _ := core.Cfg.Config.Get("threads").Get("restPeriod").Int()
maxCandles, _ := core.Cfg.Config.Get("threads").Get("maxCandles").Int()
if len(topScore) < maxTickers {
return
}
// fmt.Println("loop1 ", 12*time.Minute, " topScore2: ", topScore)
// fmt.Println("topScore: ", topScore, len(topScore), mx, ok)
//每隔Period1重新发起一次rest请求的大循环。里面还有60秒一次的小循环. 作为ws请求的备份(保底)机制,实效性差了一点但是稳定性高于ws
dura := time.Duration(restPeriod) * time.Second
mdura := dura/time.Duration(len(topScore)) - 20*time.Millisecond
ticker := time.NewTicker(mdura)
done := make(chan bool)
idx := 0
go func(i int) {
for {
select {
case <-ticker.C:
if i >= 4 { //: 12分钟 / 3分钟 = 4
done <- true
break
}
for {
//内层循环3分钟一圈3分钟内遍历完topScore限定的candle列表, 12分钟能够跑4圈
if i >= 4 { //: 12分钟 / 3分钟 = 4
done <- true
break
}
go func() {
// fmt.Println("loop2 :", "dura:", dura, "i:", i)
// core.InVokeCandle(topScore, per1, maxCandles)
mdura := dura / time.Duration(len(topScore)+1)
for k, v := range topScore {
go func(k int, v string) {
time.Sleep(mdura*time.Duration(k) - 10*time.Millisecond)
core.GetCandlesWithRest(v, k, mdura, maxCandles)
}(k, v)
}
i++
}()
time.Sleep(dura)
continue
}
}
}
}(idx)
time.Sleep(dura - 100*time.Millisecond)
done <- true
ticker.Stop()
}
// 订阅公共频道
func wsPub(core *core.Core) {
wsCli, _ := core.GetWsCli()
err := wsCli.Start()
// 创建ws客户端
if err != nil {
//fmt.Println("ws client err", err)
return
}
// 设置连接超时
wsCli.SetDailTimeout(time.Second * 20)
defer wsCli.Stop()
// 订阅产品频道
// 在这里初始化instrument列表
var args []map[string]string
arg := make(map[string]string)
arg["instType"] = ws.SPOT
//arg["instType"] = OPTION
args = append(args, arg)
// start := time.Now()
//订阅
//设置订阅事件的event handler
wsCli.AddBookMsgHook(core.PubMsgDispatcher)
res, _, err := wsCli.PubInstruemnts(ws.OP_SUBSCRIBE, args)
//fmt.Println("args:", args)
if res {
// usedTime := time.Since(start)
//fmt.Println("订阅成功!", usedTime.String())
} else {
//fmt.Println("订阅失败!", err)
}
core.LoopInstrumentList()
// start = time.Now()
// res, _, err = r.PubInstruemnts(OP_UNSUBSCRIBE, args)
// if res {
// usedTime := time.Since(start)
// //fmt.Println("取消订阅成功!", usedTime.String())
// } else {
// //fmt.Println("取消订阅失败!", err)
// }
}
// 处理 ws 订单请求相关订阅回调
func subscribeOrder(cr *core.Core) {
fmt.Println("subscribeOrder order:")
for {
order := <-cr.OrderChan
go func() {
bj, _ := json.Marshal(order)
fmt.Println("process order:", string(bj))
cr.ProcessOrder(order)
}()
}
}
func main() {
cr := core.Core{}
cr.Init()
cr.ShowSysTime()
// 从rest接口获取的ticker记录种的交量计入排行榜指定周期刷新一次
go func() {
per1 := 1 * time.Minute
RestTicker(&cr, per1)
limiter := time.Tick(per1)
for {
<-limiter
go func() {
RestTicker(&cr, per1)
}()
}
}()
//全员1分钟candle & maX
// period: 每个循环开始的时间点,单位:秒
// delay延时多少秒后去取此值
// mdura多少秒之内遍历完获取到的goins列表
// onceCount每次获取这个coin几个当前周期的candle数据
// 全员3m
// go func() {
// fmt.Println("LoopAllCoinsList1")
// LoopAllCoinsList(180, 0, 180, "3m", 5, 6)
// }()
// 全员15m candle
// go func() {
// fmt.Println("LoopAllCoinsList2")
// LoopAllCoinsList(380, 90, 380, "15m", 4, 7)
// }()
// 全员30m candle
// go func() {
// fmt.Println("LoopAllCoinsList2")
// LoopAllCoinsList(510, 90, 500, "30m", 5, 8)
// }()
// 全员1H candle
// go func() {
// fmt.Println("LoopAllCoinsList2")
// LoopAllCoinsList(770, 0, 760, "1H", 9, 12)
// }()
// 全员2H candle
go func() {
fmt.Println("LoopAllCoinsList2")
LoopAllCoinsList(820, 0, 820, "2H", 12, 15)
}()
// 全员4小时candle
// go func() {
// fmt.Println("LoopAllCoinsList1")
// LoopAllCoinsList(1280, 150, 1280, "4H", 15, 19)
// }()
// 全员6小时candle
//go func() {
// fmt.Println("LoopAllCoinsList1")
// LoopAllCoinsList(1440, 180, 1440, "6H", 17, 21)
//}()
// 全员12小时candle
go func() {
fmt.Println("LoopAllCoinsList1")
LoopAllCoinsList(1680, 180, 1680, "12H", 19, 23)
}()
// 全员1Day candle & maX
//go func() {
// fmt.Println("LoopAllCoinsList1")
// LoopAllCoinsList(1920, 4, 1920, "1D", 25, 30)
// }()
// 全员2Day candle & maX
go func() {
fmt.Println("LoopAllCoinsList1")
LoopAllCoinsList(3840, 220, 3840, "2D", 26, 31)
}()
// 全员5Day candle & maX
go func() {
fmt.Println("LoopAllCoinsList1")
LoopAllCoinsList(6400, 4, 6400, "5D", 28, 35)
}()
// 循环检查tickersVol|sortedSet,并执行订阅candles
go func() {
LoopCheckMyFavorList(&cr)
}()
go func() {
LoopSaveCandle(&cr)
}()
go func() {
LoopSubscribe(&cr)
}()
// 订阅下游sardine发过来的要执行的动作
go func() {
LoopSubscribeSubAction(&cr)
}()
// 废弃
// go func() {
// LoopRestQ(&cr)
// }()
//-----------
//私有部分
go func() {
core.LoopLivingOrders(&cr, 1*time.Minute)
}()
go func() {
core.LoopBalances(&cr, 1*time.Minute)
}()
// 公共订阅
// wsPub(&cr)
// 停止私有订阅
// go func() {
// //正常情况下 wsPrive不会主动退出如果不慎退出了自动重新运行
// for {
// wsPriv(&cr)
// }
//}()
go func() {
subscribeOrder(&cr)
}()
// gcl := map[string]models.GlobalCoin{}
// msgr := cr.Messager{}
// msgr.Init()
// msgr.Login()
// msgr.Alive()
// msgr.GlobalSubscribe() // 订阅全局该订阅的公共和私有内容
// msgr.Dispatcher(gcl)
// msgr.Pop()
// //fmt.Println("listenning ... ")
// 这个地方为了让main不退出, 将来可以改成一个http的listener
// ip := "0.0.0.0:6066"
// if err := http.ListenAndServe(ip, nil); err != nil {
// fmt.Printf("start pprof failed on %s\n", ip)
// }
// 永久阻塞
select {}
}