This commit is contained in:
zhangkun9038@dingtalk.com 2025-02-20 14:19:47 +08:00
parent 3b667443c2
commit 3f2c113e88
21 changed files with 1627 additions and 153 deletions

3
build.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/sh
go build -ldflags "-s -w" -trimpath -o ./bin/core ./cmd/core
rm bin/core

20
cmd/core/main.go Normal file
View File

@ -0,0 +1,20 @@
package main
import (
"fmt"
"os"
)
const (
Name = "core"
Version = "1.0.0"
Author = "phyer"
)
func main() {
if len(os.Args) > 1 && os.Args[1] == "--version" {
fmt.Printf("%s %s (by %s)\n", Name, Version, Author)
return
}
fmt.Println("This is a library package, not an executable")
}

23
ini.sh Executable file
View File

@ -0,0 +1,23 @@
# Create standard Go project directories
mkdir -p api/v1 # For API contracts/protos
mkdir -p configs # For configuration files
mkdir -p scripts # For deployment/build scripts
mkdir -p test/e2e # For different test types
mkdir -p third_party # For third party dependencies
# Reorganize existing directories
mv pkg/config internal/ # Move config to internal implementation
mv pkg/log internal/logger # Move logging to internal
mv pkg/utils internal/ # Move utilities to internal
# Create new service layer
mkdir -p internal/service # For business logic services
mkdir -p internal/repository # For data access layer
mkdir -p internal/transport # For HTTP/gRPC handlers
# Cleanup empty directories
rmdir pkg 2>/dev/null || true
# Update build script
echo '#!/bin/sh
go build -ldflags "-s -w" -trimpath -o ./bin/core ./cmd/core' > build.sh

View File

@ -1,11 +1,12 @@
package core
package analysis
import (
"encoding/json"
"errors"
"fmt"
logrus "github.com/sirupsen/logrus"
// "os"
"github.com/phyer/core/internal/core"
"strconv"
"time"
)
@ -36,7 +37,7 @@ type WillMX struct {
Count int
}
func (mx MaX) SetToKey(cr *Core) ([]interface{}, error) {
func (mx MaX) SetToKey(cr *core.Core) ([]interface{}, error) {
// fmt.Println(utils.GetFuncName(), " step1 ", mx.InstID, " ", mx.Period)
// mx.Timestamp, _ = Int64ToTime(mx.Ts)
cstr := strconv.Itoa(mx.Count)
@ -100,7 +101,7 @@ func (mx *MaX) PushToWriteLogChan(cr *Core) error {
hs := HashString(did)
mx.Id = hs
md, _ := json.Marshal(mx)
wg := WriteLog{
wg := logger.WriteLog{
Content: md,
Tag: "sardine.log.maX." + mx.Period,
Id: hs,
@ -168,3 +169,38 @@ func (mxl *MaXList) RecursiveBubbleS(length int, ctype string) error {
err := mxl.RecursiveBubbleS(length, ctype)
return err
}
// TODO pixel
func (mxl *MaXList) MakePixelList(cr *Core, mx *MaX, score float64) (*PixelList, error) {
if len(mx.Data) == 2 {
err := errors.New("ma30 原始数据不足30条")
return nil, err
}
if mx.Data[2] != float64(30) {
err := errors.New("ma30 原始数据不足30条")
return nil, err
}
pxl := PixelList{
Count: mxl.Count,
UpdateNickName: mxl.UpdateNickName,
LastUpdateTime: mxl.LastUpdateTime,
List: []*Pixel{},
}
for i := 0; i < mxl.Count; i++ {
pix := Pixel{}
pxl.List = append(pxl.List, &pix)
}
ma30Val := (mx.Data[1]).(float64)
realLens := len(mxl.List)
cha := mxl.Count - realLens
// fmt.Println("mxl.Count: ", mxl.Count, "realLens: ", realLens)
for h := mxl.Count - 1; h-cha >= 0; h-- {
// Count 是希望值,比如24realLens是实际值, 如果希望值和实际值相等cha就是0
cdLast := mxl.List[h-cha].Data[1]
pxl.List[h].Y = (cdLast.(float64) - ma30Val) / ma30Val / score
pxl.List[h].X = float64(h)
pxl.List[h].Score = cdLast.(float64)
pxl.List[h].TimeStamp = int64(mxl.List[h-cha].Data[0].(float64))
}
return &pxl, nil
}

View File

@ -1,4 +1,4 @@
package core
package analysis
import (
// "crypto/sha256"
@ -15,12 +15,15 @@ import (
// simple "github.com/bitly/go-simplejson"
// "github.com/go-redis/redis"
// "github.com/phyer/texus/utils"
"github.com/phyer/core/internal/core"
"github.com/phyer/core/internal/utils"
logrus "github.com/sirupsen/logrus"
)
type Rsi struct {
Id string `json:"_id"`
core *Core
core *core.Core
InstID string `json:"instID"`
Period string `json:"period"`
Timestamp time.Time `json:"timeStamp"`
@ -38,7 +41,7 @@ type RsiList struct {
}
type StockRsi struct {
Id string `json:"_id"`
core *Core
core *core.Core
InstID string `json:"instID"`
Period string `json:"period"`
Timestamp time.Time `json:"timeStamp"`
@ -56,7 +59,7 @@ type StockRsiList struct {
List []*StockRsi `json:"list"`
}
func (rsi *Rsi) PushToWriteLogChan(cr *Core) error {
func (rsi *Rsi) PushToWriteLogChan(cr *core.Core) error {
did := rsi.InstID + rsi.Period + ToString(rsi.Ts)
rsi.Id = HashString(did)
cd, err := json.Marshal(rsi)
@ -71,9 +74,9 @@ func (rsi *Rsi) PushToWriteLogChan(cr *Core) error {
cr.WriteLogChan <- &wg
return nil
}
func (srsi *StockRsi) PushToWriteLogChan(cr *Core) error {
func (srsi *StockRsi) PushToWriteLogChan(cr *core.Core) error {
did := srsi.InstID + srsi.Period + ToString(srsi.Ts)
srsi.Id = HashString(did)
srsi.Id = util.HashString(did)
cd, err := json.Marshal(srsi)
if err != nil {
logrus.Error("PushToWriteLog json marshal rsi err: ", err)

View File

@ -0,0 +1,317 @@
package analysis
import (
"encoding/json"
"fmt"
"os"
"time"
logrus "github.com/sirupsen/logrus"
)
// 段对象是对某个线段的表现进行评估的一个手段, 整个段会被分成3个小段, 整个段,计算整体的,字段,各自计算。包含仰角,段内极值等。
// SegmentItem 属于一阶分析结果
// {DMD-USDT 5D ma30 1643240793839 0 1642867200000 0xc00835fd80 0xc001687600 NaN 23 23 []}]
type SegmentItem struct {
InstID string
Period string //通过InstID,Periods可以定位到Series对象, 里面有一手数据
Ctype string //candle|ma7|ma30
ReportTime int64
ReportTimeStr string
PolarQuadrant string // shangxianmanyuexiaxian,xinyue 分别对应圆周的四个阶段。
LastUpdate int64
ExtremumPixels *Extremum // 极值 是两个pixel对象
FirstPixel *Pixel // 起始值最后的pixel对象
LastPixel *Pixel // 最后值最后的maX pixel对象
LastCandle *Pixel // 最后值最后的Candle的pixel对象
LastMa7 *Pixel // 最后值最后的Ma7的pixel对象
LastMa30 *Pixel // 最后值最后的Ma30的pixel对象
VerticalElevation float64 // 仰角, Interval范围内线段的仰角
StartIdx int // 开始的坐标
EndIdx int // 结束的坐标
SubItemList []SegmentItem //往下一级微分
}
const DAMANYUE = "damanyue"
const DAMANYUE_POST = "damanyue_post"
const DAMANYUE_PRE = "damanyue_pre"
const XIAOMANYUE = "xiaomanyue"
const XIAOMANYUE_POST = "xiaomanyue_post"
const XIAOMANYUE_PRE = "xiaomanyue_pre"
const DAXINYUE = "daxinyue"
const DAXINYUE_POST = "daxinyue_post"
const DAXINYUE_PRE = "daxinyue_pre"
const XIAOXINYUE = "xiaoxinyue"
const XIAOXINYUE_POST = "xiaoxinyue_post"
const XIAOXINYUE_PRE = "xiaoxinyue_pre"
const DASHANGXIANYUE = "dashangxianyue"
const XIAOSHANGXIANYUE = "xiaoshangxianyue"
const DAXIAXIANYUE = "daxiaxianyue"
const XIAOXIAXIANYUE = "xiaoxiaxianyue"
const tinySeg = 0.1
type Extremum struct {
Max *Pixel
Min *Pixel
}
func CalPolar(e0 float64, e1 float64, e2 float64) string {
polarQuadrant := "default"
//
// ## 上弦月
//
// e0,e1,e2: -3.5315694477826525 -0.5773082714100172 1.0558744145515746
if e2 >= e1 && e2 >= 0 {
polarQuadrant = XIAOSHANGXIANYUE
}
// e2 > e1 > 0: 小上弦月
// -> e1 > e0 > 0 : 大上弦月
if e2 >= e1 && e1 >= 0 {
polarQuadrant = XIAOSHANGXIANYUE
if e1 >= e0 && e0 >= 0 {
polarQuadrant = DASHANGXIANYUE
}
}
//
// ## 下弦月
// 0 > e1 > e2小下弦月
// -> 0 > e0 > e1: 大下弦月
//
if 0 >= e1 && e1 >= e2 {
polarQuadrant = XIAOXIAXIANYUE
if 0 >= e0 && e0 >= e1 {
polarQuadrant = DAXIAXIANYUE
}
}
// ## 同上
if (0 >= e2 && 0 >= e1) && e2 >= e1 {
polarQuadrant = XIAOXIAXIANYUE
}
// ##
// ## 满月
//
// e1 > e2 > 0 : 小满月 pre
// -> e0 > e1 : 大满月pre
//
if e1 >= e2 && e2 >= 0 {
polarQuadrant = XIAOMANYUE_PRE
if e0 > e1 {
polarQuadrant = DAMANYUE_PRE
}
}
// e1 > 0.1 > e2 > 0 : 小满月
// -> e0 > e1 : 大满月
//
if e1 >= tinySeg && tinySeg >= e2 && e2 >= 0 {
polarQuadrant = XIAOMANYUE
if e0 > e1 {
polarQuadrant = DAMANYUE
}
}
// e0,e1,e2: 0.9699903789854316 0.1802190672652184 -1.7888783234326784
// e1 > 0 > e2 > -0.1 : 小满月post
// -> e0 > e1 > 0 : 大满月post
//
if e1 >= 0 && 0 >= e2 && e2 >= -100000 {
polarQuadrant = XIAOMANYUE_POST
if e0 > e1 {
polarQuadrant = DAMANYUE_POST
}
}
// e0,e1,e2: -0.049579775302532776 0 -0.018291567587323976
// ## 新月
// e1 < e2 <0: 小新月pre
// -> e1 > e0 : 大新月pre
//
if e1 <= e2 && e2 <= 0 && e2 >= -1*tinySeg {
polarQuadrant = XIAOXINYUE_PRE
if e1 > e0 {
polarQuadrant = DAXINYUE_PRE
}
}
// e1 < -0.1 < e2 <0 小新月
// -> e1 > e0 : 大新月
if e1 <= -1*tinySeg && -1*tinySeg <= e2 && e2 <= 0 {
polarQuadrant = XIAOXINYUE
if e1 > e0 {
polarQuadrant = DAXINYUE
}
}
//
// e1 < 0 < e2 < 0.1 小新月post
// -> e1 > e0 : 大新月post
//e0,e1,e2: -0.03902244287114438 -0.13929829606729519 0.14828528291036536
if e1 <= 0 && 0 <= e2 && e2 <= 1000000 {
polarQuadrant = XIAOXINYUE_POST
if e1 > e0 {
polarQuadrant = DAXINYUE_POST
}
}
return polarQuadrant
}
// 计算当前某段的曲线正弦所处极坐标象限
func CalPolarQuadrant(maXSeg *SegmentItem) string {
if len(maXSeg.SubItemList) == 0 {
return "subItem no polarQuadrant"
}
m0 := maXSeg.SubItemList[0]
m1 := maXSeg.SubItemList[1]
m2 := maXSeg.SubItemList[2]
e0 := m0.VerticalElevation
e1 := m1.VerticalElevation
e2 := m2.VerticalElevation
polarQuadrant := CalPolar(e0, e1, e2)
if polarQuadrant == "default" {
env := os.Getenv("GO_ENV")
if env != "production" {
fmt.Println(GetFuncName(), " instId:", maXSeg.InstID, " period:", maXSeg.Period, " ctype", maXSeg.Ctype, " e0,e1,e2:", e0, e1, e2)
}
}
return polarQuadrant
}
func (seg *SegmentItem) SetToKey(cr *Core) error {
if seg.InstID == "USDC-USDT" {
return nil
}
keyName := seg.InstID + "|" + seg.Period + "|" + seg.Ctype + "|segmentItem"
bj, err := json.Marshal(seg)
if err != nil {
logrus.Warn("se.MakeSegment: ", err, seg)
}
cr.RedisLocalCli.Set(keyName, string(bj), 0)
sf7 := float64(0)
sf7 = seg.LastCandle.Y - seg.LastMa7.Y
sf30 := float64(0)
sf30 = seg.LastCandle.Y
tms := time.Now().Format("2006-01-02 15:04:05.000")
// fmt.Println("tms: ", seg.InstID, seg.Period, tms, seg.LastUpdate)
she := ShearItem{
LastUpdate: time.Now().UnixMilli(),
LastUpdateTime: tms,
VerticalElevation: seg.SubItemList[2].VerticalElevation,
Ratio: seg.LastCandle.Y / seg.SubItemList[2].VerticalElevation,
Score: seg.LastCandle.Score,
PolarQuadrant: seg.PolarQuadrant,
}
if seg.Ctype == "ma7" {
she.ShearForce = sf7
}
if seg.Ctype == "ma30" {
she.ShearForce = sf30
}
sbj, _ := json.Marshal(she)
keyName = seg.InstID + "|" + seg.Period + "|" + seg.Ctype + "|shearItem"
cr.RedisLocalCli.Set(keyName, string(sbj), 3*time.Minute)
cr.RedisLocal2Cli.Set(keyName, string(sbj), 3*time.Minute)
return nil
}
func (seg *SegmentItem) Show() error {
if seg.InstID == "USDC-USDT" {
return nil
}
bj, _ := json.Marshal(*seg)
logrus.Warn("SegmentItem Show:", string(bj))
return nil
}
func (jgm *SegmentItem) Report(cr *Core) error {
return nil
}
func (seg *SegmentItem) Process(cr *Core) {
go func() {
if seg == nil {
return
}
seg.Show()
seg.SetToKey(cr)
// sheGrp, err := seg.MakeShearForceGrp(cr)
// if err != nil {
// log.Panic(err)
// }
// 当最后一个维度数据更新后,触发显示和备份
// 空的就可以
shg := ShearForceGrp{
InstID: seg.InstID,
Ma30PeriodGroup: map[string]ShearItem{},
Ma7PeriodGroup: map[string]ShearItem{},
}
if seg.Period == "4H" {
time.Sleep(50 * time.Millisecond) //等可能存在的5D也ready
go func() {
cr.ShearForceGrpChan <- &shg
}()
}
}()
}
func (srs *Series) MakeSegment(cr *Core, start int, end int, subArys [][]int, ctype string) *SegmentItem {
list := []*Pixel{}
if ctype == "ma7" {
list = srs.Ma7Series.List
}
if ctype == "ma30" {
list = srs.Ma30Series.List
}
st := start
if len(list) == 0 {
return nil
}
for i := start; i <= end; i++ {
if list[i].X == 0 && list[i].Y == 0 {
if i+1 < len(list) {
st = i + 1
} else {
logrus.Panic(GetFuncName(), "没有符合的记录")
}
}
}
extra, _ := srs.GetExtremum(cr, st, end, ctype)
yj, err := srs.GetElevation(cr, ctype, st, end)
if err != nil {
fmt.Println("MakeSegment GetElevation err : ", err)
}
tm := time.Now()
seg := SegmentItem{
InstID: srs.InstID,
Period: srs.Period,
ReportTime: tm.UnixMilli(),
ReportTimeStr: tm.Format("2006-01-02 15:04:05.000"),
LastUpdate: srs.LastUpdateTime,
FirstPixel: list[st],
LastPixel: list[end],
ExtremumPixels: extra,
Ctype: ctype,
VerticalElevation: yj,
StartIdx: st,
EndIdx: end,
LastCandle: srs.CandleSeries.List[end],
LastMa7: srs.Ma7Series.List[end],
SubItemList: []SegmentItem{},
PolarQuadrant: "none",
}
if len(subArys) > 0 {
for _, pair := range subArys {
sub := [][]int{}
curSeg := srs.MakeSegment(cr, pair[0], pair[1], sub, ctype)
seg.SubItemList = append(seg.SubItemList, *curSeg)
}
}
polar := CalPolarQuadrant(&seg)
seg.PolarQuadrant = polar
return &seg
}

565
internal/analysis/series.go Normal file
View File

@ -0,0 +1,565 @@
package analysis
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"os"
"strconv"
"time"
"github.com/go-redis/redis"
logrus "github.com/sirupsen/logrus"
)
type Series struct {
InstID string `json:"instID"`
Period string `json:"Period"`
Count int `json:"count,number"`
Scale float64 `json:"scale,number"`
LastUpdateTime int64 `json:"lastUpdateTime,number"`
UpdateNickName string
LastCandle1m Candle `json:"lastCandle1m"`
CandleSeries *PixelList `json:"candleSerie"`
Ma7Series *PixelList `json:"ma7Serie"`
Ma30Series *PixelList `json:"ma30Serie"`
}
type SeriesInfo struct {
InstID string `json:"instID"`
Period string `json:"period"`
InsertedNew bool `json:"insertedNew,bool"`
Score float64 `json:"score,number"`
}
type SeriesInfoScore struct {
InstID string `json:"instID"`
Score float64 `json:"score,number"`
}
// TODO
// redis key: verticalReportItem|BTC-USDT|4H-15m|ts:1643002300000
// sortedSet: verticalLimit|2D-4H|rank|sortedSet
type VerticalReportItem struct {
InstID string
Period string
ReportTime int64
LastUpdate int64
LastUpdateTime string
Interval int
TrigerValue float64
AdvUpSellPrice float64
AdvDownSellPrice float64
Rank float64
ShearForce float64
VerticalElevation float64
SecondPeriod string
}
// type Segment struct {
// IndextStart int
// IndexEnd int
//
// }
// 根据instId 和period 从 PlateMap里拿到coaster创建对应的 series,
func (sr *Series) Refresh(cr *Core) error {
curCo, err := cr.GetCoasterFromPlate(sr.InstID, sr.Period)
if err != nil {
return err
}
ma30List := curCo.Ma30List.List
ma30len := len(ma30List)
if ma30len == 0 {
err = errors.New("ma30List is empty:" + sr.InstID + "," + sr.Period)
return err
}
baseMaX := ma30List[ma30len-1]
ma30Pxl, err := curCo.Ma30List.MakePixelList(cr, baseMaX, sr.Scale)
if err != nil {
return err
}
sr.Ma30Series = ma30Pxl
ma7Pxl, err := curCo.Ma7List.MakePixelList(cr, baseMaX, sr.Scale)
if err != nil {
return err
}
sr.Ma7Series = ma7Pxl
curCo.CandleList.RecursiveBubbleS(len(curCo.CandleList.List), "asc")
candlePxl, err := curCo.CandleList.MakePixelList(cr, baseMaX, sr.Scale)
if err != nil {
return err
}
sr.CandleSeries = candlePxl
// bj, _ := json.Marshal(sr.Ma30Series)
// fmt.Println("sr.Ma30Series:", sr.Period, sr.InstID, string(bj))
sr.LastUpdateTime = sr.Ma30Series.LastUpdateTime
// fmt.Println("candlePxl: ", candlePxl)
return nil
}
func (sr *Series) SetToKey(cr *Core) (string, error) {
if sr == nil || sr.CandleSeries == nil {
return "", errors.New("sr.CandlesSeries == nil")
}
sr.CandleSeries.RecursiveBubbleS(sr.CandleSeries.Count, "asc")
sr.CandleSeries.ReIndex()
sr.CandleSeries.RecursiveBubbleS(sr.CandleSeries.Count, "asc")
// sr.CandleSeries.RecursiveBubbleX(sr.CandleSeries.Count, "asc")
sr.Ma7Series.RecursiveBubbleS(sr.CandleSeries.Count, "asc")
// sr.Ma7Series.RecursiveBubbleX(sr.CandleSeries.Count, "asc")
sr.Ma30Series.RecursiveBubbleS(sr.CandleSeries.Count, "asc")
// sr.Ma30Series.RecursiveBubbleX(sr.CandleSeries.Count, "asc")
now := time.Now().UnixMilli()
sr.LastUpdateTime = now
sr.CandleSeries.LastUpdateTime = now
sr.CandleSeries.UpdateNickName = GetRandomString(12)
sr.UpdateNickName = GetRandomString(12)
js, _ := json.Marshal(*sr)
seriesName := sr.InstID + "|" + sr.Period + "|series"
res, err := cr.RedisLocalCli.Set(seriesName, string(js), 0).Result()
if err != nil {
logrus.Panic(GetFuncName(), err, " seriesSetToKey1: instId:", sr.InstID, " period: ", sr.Period, " lastUpdate:", sr.LastUpdateTime, " md5:", Md5V(string(js)))
}
res, err = cr.RedisLocal2Cli.Set(seriesName, string(js), 0).Result()
return res, err
}
func PrintSerieY(cr *Core, list []redis.Z, period string, count int) {
// fmt.Println("PrintSerieY start")
env := os.Getenv("GO_ENV")
isProduction := env == "production"
//TODO 只有非产线环境,才会显示此列表
if !isProduction {
fmt.Println("seriesYTop count:", count, "period:", period, "sort start")
}
seiScrList := []*SeriesInfoScore{}
for _, v := range list {
sei := SeriesInfo{}
seiScr := SeriesInfoScore{}
json.Unmarshal([]byte(v.Member.(string)), &sei)
seiScr.InstID = sei.InstID
seiScr.Score = v.Score
seiScrList = append(seiScrList, &seiScr)
// if k < count {
// if !isProduction {
// fmt.Println("seriesYTop", count, "No.", k+1, "period"+period, "InstID:", sei.InstID, "score:", v.Score)
// }
// 拉扯极限报告
// }
// if k == count+1 {
// if !isProduction {
// fmt.Println("seriesYTop end -------" + "period" + period + "-------------------------------------")
// fmt.Println("seriesYLast start -------" + "period" + period + "-------------------------------------")
// }
// }
// if k > len(list)-count-1 {
// if !isProduction {
// fmt.Println("seriesYLast", count, "No.", k+1, "period"+period, "InstID:", sei.InstID, "score:", v.Score)
// }
// }
}
bj, _ := json.Marshal(seiScrList)
reqBody := bytes.NewBuffer(bj)
cr.Env = os.Getenv("GO_ENV")
cr.FluentBitUrl = os.Getenv("SARDINE_FluentBitUrl")
fullUrl := "http://" + cr.FluentBitUrl + "/seriesY." + period
res, err := http.Post(fullUrl, "application/json", reqBody)
fmt.Println("requested, response:", fullUrl, reqBody, res)
if err != nil {
logrus.Error(err)
}
if !isProduction {
fmt.Println("seriesYLast count:", count, "period:", period, "sort end")
}
}
func (sei *SeriesInfo) Process(cr *Core) {
curSe, err := cr.GetPixelSeries(sei.InstID, sei.Period)
if err != nil {
logrus.Warn("GetPixelSeries: ", err)
return
}
// TODO 金拱门
// list := cr.GetMyCcyBalanceName()
go func(se Series) {
threeSeg := [][]int{[]int{0, 19}, []int{19, 22}, []int{22, 23}}
ma7Seg := se.MakeSegment(cr, 0, 23, threeSeg, "ma7")
go func() {
cr.SegmentItemChan <- ma7Seg
}()
ma30Seg := se.MakeSegment(cr, 0, 23, threeSeg, "ma30")
go func() {
cr.SegmentItemChan <- ma30Seg
}()
}(curSe)
cli := cr.RedisLocalCli
go func(se Series) {
// 拉扯极限报告
willReport := os.Getenv("SARDINE_SERIESTOREPORT") == "true"
logrus.Info("willReport:", willReport)
// fmt.Println("willReport:", willReport)
if !willReport {
return
}
err = curSe.AddToYSorted(cr)
if err != nil {
logrus.Warn("sei addToYSorted err: ", err)
return
}
// 所有维度拉扯极限
go func(se Series) {
if se.InstID != "BTC-USDT" {
return
}
list, err := cli.ZRevRangeWithScores("series|YValue|sortedSet|period"+se.Period, 0, -1).Result()
if err != nil {
fmt.Println("series sorted err", err)
}
PrintSerieY(cr, list, se.Period, 20)
}(se)
}(curSe)
// TODO 刘海儿检测, 监测金拱门中的刘海儿,预警下跌趋势, 其实有没有金拱门并不重要,刘海儿比金拱门更有说服力
go func(se Series) {
// 如何定义刘海目前定义如下3m以上的周期时当7个或小于7个周期内的时间内发生了一次下坠和一次上升下坠幅度达到2%以上并随后的上升中收复了下坠的幅度那么疑似刘海儿发生。用的周期越少越强烈探底和抬升的幅度越大越强烈所处的维度越高越强烈比如15m的没有1H的强烈
// 如果发生在BTC身上那么将影响所有
// se.CheckLiuhai() {
//
// }
}(curSe)
go func(se Series) {
allow := os.Getenv("SARDINE_SERIESINFOTOCHNL") == "true"
if !allow {
return
}
time.Sleep(0 * time.Second)
sei := SeriesInfo{
InstID: curSe.InstID,
Period: curSe.Period,
}
cr.AddToGeneralSeriesInfoChnl(&sei)
}(curSe)
}
//-------------------------------------------------------------------------------
// 拉扯极限相关: 加入seriesY值排行榜, 用于生成拉扯极限
func (srs *Series) AddToYSorted(cr *Core) error {
setName := "series|YValue|sortedSet|period" + srs.Period
srs.CandleSeries.RecursiveBubbleS(srs.CandleSeries.Count, "asc")
length := len(srs.CandleSeries.List)
if length != srs.Count {
err := errors.New("AddToYSorted err: 数据量不够")
return err
}
lastCandlePixel1 := srs.CandleSeries.List[srs.Count-1]
sei := SeriesInfo{
InstID: srs.InstID,
Period: srs.Period,
}
bj, _ := json.Marshal(sei)
// TODO -200 是个无效的值,如果遇到-200就赋予0值这个办法不好后面考虑不用sortedSet而用自定义对象更好些。
if lastCandlePixel1.Y == -200 {
lastCandlePixel1.Y = 0
}
z := redis.Z{
Score: float64(lastCandlePixel1.Y),
Member: string(bj),
}
// TODO ZAdd 有可能由于bug或者key不一样的原因让列表变长需要想办法怎么定期请空
if lastCandlePixel1.Score != 0 {
cr.RedisLocalCli.ZAdd(setName, z).Result()
}
return nil
}
// 垂直极限排名有一定片面性。暂时先不开放。垂直极限推荐最高的,可能是个不太容易📈上来的股票,甚至垃圾股,而且过一会儿可能跌的更多,所以就算使用这个功能,也仅供参考,
func (vir *VerticalReportItem) AddToVeriticalLimitSorted(cr *Core, srs *Series, period2 string) error {
// redis key: verticalReportItem|BTC-USDT|4H-15m|ts:1643002300000
// sortedSet: verticalLimit|2D-4H|rank|sortedSet
setName := "verticalLimit|" + srs.Period + "-" + period2 + "|rank|sortedSet"
tms := strconv.FormatInt(srs.LastUpdateTime, 10)
keyName := "verticalLimit|" + srs.InstID + "|" + srs.Period + "-" + period2 + "|ts:" + tms
z := redis.Z{
Score: float64(srs.LastUpdateTime),
Member: keyName,
}
if vir.Rank != -1 && vir.Rank != 0 {
extt := 48 * time.Hour
ot := time.Now().Add(extt * -1)
oti := ot.UnixMilli()
count, _ := cr.RedisLocalCli.ZRemRangeByScore(setName, "0", strconv.FormatInt(oti, 10)).Result()
if count > 0 {
logrus.Warning("移出过期的引用数量:", setName, count, "ZRemRangeByScore ", setName, 0, strconv.FormatInt(oti, 10))
}
cr.RedisLocalCli.ZAdd(setName, z).Result()
bj, _ := json.Marshal(vir)
cr.RedisLocalCli.Set(keyName, bj, 48*time.Hour).Result()
}
return nil
}
func (vri *VerticalReportItem) Report(cr *Core) error {
dd := DingdingMsg{
Topic: "垂直极限触发",
RobotName: "pengpeng",
AtAll: true,
Ctype: "markdown",
Content: "",
}
ary1 := []string{}
str := "``币名: ``" + vri.InstID + "\n"
str1 := fmt.Sprintln("``基础维度:``", vri.Period)
str2 := fmt.Sprintln("``剪切维度:``", vri.SecondPeriod)
str21 := fmt.Sprintln("``观察周期:``", vri.Interval)
str3 := fmt.Sprintln("``平均仰角:``", vri.VerticalElevation)
str4 := fmt.Sprintln("``剪切力:``", vri.ShearForce)
str5 := fmt.Sprintln("``Rank``", vri.Rank)
score := vri.TrigerValue
str6 := fmt.Sprintln("``触发买入价位:``", score)
str7 := fmt.Sprintln("``建议止盈价位:``", vri.AdvUpSellPrice)
str8 := fmt.Sprintln("``建议止损价位:``", vri.AdvDownSellPrice)
str9 := "----------------------\n"
ary1 = append(ary1, str, str1, str2, str21, str3, str4, str5, str6, str7, str8, str9)
dd.AddItemListGrp("垂直极限", 2, ary1)
ary2 := []string{}
tm := time.Now().Format("01-02:15:04")
rtime := fmt.Sprintln("``报告时间:``", tm)
ctype := fmt.Sprintln("``类型:``", "极限触发,已弃用")
from := "来自: " + os.Getenv("HOSTNAME")
ary2 = append(ary2, rtime, ctype, from)
dd.AddItemListGrp("", 2, ary2)
dd.PostToRobot("pengpeng", cr)
return nil
}
func (vri *VerticalReportItem) Show(cr *Core) error {
ary1 := []string{}
str := "``币名: ``" + vri.InstID + "\n"
str1 := fmt.Sprintln("``基础维度:``", vri.Period)
str2 := fmt.Sprintln("``剪切维度:``", vri.SecondPeriod)
str21 := fmt.Sprintln("``观察周期:``", vri.Interval)
str3 := fmt.Sprintln("``平均仰角:``", vri.VerticalElevation)
str4 := fmt.Sprintln("``剪切力:``", vri.ShearForce)
str5 := fmt.Sprintln("``Rank``", vri.Rank)
score := vri.TrigerValue
str6 := fmt.Sprintln("``触发买入价位:``", score)
str7 := fmt.Sprintln("``建议止盈价位:``", vri.AdvUpSellPrice)
str8 := fmt.Sprintln("``建议止损价位:``", vri.AdvDownSellPrice)
str9 := "----------------------\n"
ary1 = append(ary1, str, str1, str2, str21, str3, str4, str5, str6, str7, str8, str9)
for _, v := range ary1 {
fmt.Println("verticalReportItem: ", v)
}
return nil
}
// TODO 求某个PixelList里两个点之间的仰角从ridx开始往lidx的元素画一条直线的仰角
func (srs *Series) GetElevation(cr *Core, ctype string, lIdx int, rIdx int) (float64, error) {
yj := float64(0)
switch ctype {
case "candle":
{
yj = (srs.CandleSeries.List[rIdx].Y - srs.CandleSeries.List[lIdx].Y) / float64(rIdx-lIdx)
}
case "ma7":
{
yj = (srs.Ma7Series.List[rIdx].Y - srs.Ma7Series.List[lIdx].Y) / float64(rIdx-lIdx)
}
case "ma30":
{
yj = (srs.Ma30Series.List[rIdx].Y - srs.Ma30Series.List[lIdx].Y) / float64(rIdx-lIdx)
}
}
return yj, nil
}
// TODO 求极值,在某个线段上。一个最大值,一个最小值
func (srs *Series) GetExtremum(cr *Core, lIdx int, rIdx int, ctype string) (*Extremum, error) {
ext := Extremum{
Max: &Pixel{},
Min: &Pixel{},
}
switch ctype {
case "candle":
{
done := false
for k, v := range srs.CandleSeries.List {
if k < lIdx {
continue
}
if v.X == 0 && v.Y == 0 {
continue
}
if k > rIdx {
continue
}
if !done {
ext.Max = srs.CandleSeries.List[k]
ext.Min = srs.CandleSeries.List[k]
done = true
}
if v.Y > ext.Max.Y {
ext.Max = v
}
if v.Y < ext.Min.Y {
ext.Min = v
}
}
// ext = nil
}
case "ma7":
{
done := false
for k, v := range srs.Ma7Series.List {
if k < lIdx {
continue
}
if v.X == 0 && v.Y == 0 {
continue
}
if k > rIdx {
continue
}
if !done {
ext.Max = srs.Ma7Series.List[k]
ext.Min = srs.Ma7Series.List[k]
done = true
}
if v.Y > ext.Max.Y {
ext.Max = v
}
if v.Y < ext.Min.Y {
ext.Min = v
}
}
// ext = nil
}
case "ma30":
{
done := false
for k, v := range srs.Ma30Series.List {
if k < lIdx {
continue
}
if v.X == 0 && v.Y == 0 {
continue
}
if k > rIdx {
continue
}
if !done {
ext.Max = srs.Ma30Series.List[k]
ext.Min = srs.Ma30Series.List[k]
done = true
}
if v.Y > ext.Max.Y {
ext.Max = v
}
if v.Y < ext.Min.Y {
ext.Min = v
}
}
// ext = nil
}
}
return &ext, nil
}
// TODO 获取垂直极限列表
// 筛选条件:
//
// 1. 极大值未发生在最后周期的,排除
// 2. n周期内有仰角小于0的排除
// 注意: 仰角极值未必发生在最后一个周期
//
// 对剩下的币种结果,计算:
//
// 1. n周期平均仰角: s
// 2. 最后周期仰角: p
//
// 筛选出最后仰角高于n周期平均仰角的币列表
// 以最后仰角为结果,得到一个值 p
// 对此列表集合得到每个的15分钟维度拉扯极限每个计算后得到一个结果 f,
//
// f值权重更高p值权重降一个量级求出分值用于排名,
//
// rank = 2 * (lcjx * -1) * (1 + avgElevation) * (1 + lastElevation) * (1 + lastElevation)
//
// 存储在sortedSet里命名
// verticalLimit|15m~4H|rank|sortedSet
// return rank, err
func (vir *VerticalReportItem) MakeVerticalLimit(cr *Core, srs *Series, startIdx int, endIdx int, period2 string) (err error) {
count := len(srs.CandleSeries.List) - 1
lastMa30Pixel := srs.Ma30Series.List[count]
// func (srs *Series) GetExtremum(cr *Core, lIdx int, rIdx int, ctype string) (*Extremum, error) {
ext, err := srs.GetExtremum(cr, startIdx, endIdx, "ma30")
if err != nil {
logrus.Warn(GetFuncName(), ":", err)
}
if ext.Max.Score < 1.05*lastMa30Pixel.Score {
lbj, _ := json.Marshal(lastMa30Pixel)
lext, _ := json.Marshal(ext)
err = errors.New(fmt.Sprintln("当前pixel不是极值", " lastMa30Pixel: ", string(lbj), " ext: ", string(lext)))
return err
} else {
err = errors.New(fmt.Sprintln("当前pixel满足极值", lastMa30Pixel))
}
yj, err := srs.GetElevation(cr, "ma30", startIdx, endIdx)
if err != nil {
logrus.Warn(GetFuncName(), ":", err)
}
vir.VerticalElevation = yj
lcjx, _ := LacheJixian(cr, srs, period2)
vir.ShearForce = lcjx
vir.TrigerValue = srs.CandleSeries.List[len(srs.CandleSeries.List)-1].Score
vir.AdvUpSellPrice = vir.TrigerValue * 1.04
vir.AdvDownSellPrice = vir.TrigerValue * 0.98
// 计算rank的公式如下
// rank := 2 * (lcjx * -1) * (1 + avgElevation) * (1 + lastElevation) * (1 + lastElevation)
// vir.Rank = rank
return nil
}
// 计算剪切力
func LacheJixian(cr *Core, srs *Series, period string) (float64, error) {
curSe, _ := cr.GetPixelSeries(srs.InstID, period)
return curSe.CandleSeries.List[len(srs.CandleSeries.List)-1].Y, nil
}
// type SegmentItem struct {
// InstID string
// Period string
// ReportTime int64
// lastUpdate int64
// Interval int
// Direct string // up, down
// VerticalElevation float64
// }

View File

@ -0,0 +1,267 @@
package analysis
import (
"encoding/json"
"fmt"
"os"
"strconv"
"time"
"github.com/go-redis/redis"
logrus "github.com/sirupsen/logrus"
)
type ShearItem struct {
ShearForce float64 // ma30-candle剪切力
VerticalElevation float64 // 仰角, Interval范围内线段的仰角
Ratio float64 // 剪切力除以仰角的比值
Score float64 // 当前LastCandleY点本值
PolarQuadrant string // shangxianmanyuexiaxian,xinyue 分别对应圆周的四个阶段。
LastUpdate int64
LastUpdateTime string
}
type ShearForceGrp struct {
InstID string
LastUpdate int64
LastUpdateTime string
Ma30PeriodGroup map[string]ShearItem
Ma7PeriodGroup map[string]ShearItem
From string
}
// TODO 弃用
// func (seg *SegmentItem) MakeShearForceGrp(cr *Core) (*ShearForceGrp, error) {
// shg := ShearForceGrp{
// InstID: seg.InstID,
// Ma30PeriodGroup: map[string]ShearItem{},
// Ma7PeriodGroup: map[string]ShearItem{},
// }
// err := shg.ForceUpdate(cr)
// sf1 := float64(0)
// sf1 = seg.LastCandle.Y - seg.LastMa7.Y
// she := ShearItem{
// LastUpdate: time.Now().UnixMilli(),
// VerticalElevation: seg.SubItemList[2].VerticalElevation,
// Ratio: seg.LastCandle.Y / seg.SubItemList[2].VerticalElevation,
// Score: seg.LastCandle.Score,
// PolarQuadrant: seg.PolarQuadrant,
// }
// if seg.Ctype == "ma7" {
// she.ShearForce = seg.LastCandle.Y
// shg.Ma7PeriodGroup[seg.Period] = she
// }
// if seg.Ctype == "ma30" {
// she.ShearForce = sf1
// shg.Ma30PeriodGroup[seg.Period] = she
// }
// return &shg, err
// }
// TODO 弃用
// func (shg *ShearForceGrp) ForceUpdate(cr *Core) error {
// ctype := "ma7"
// hmName := shg.InstID + "|" + ctype + "|shearForceGrp"
// res, err := cr.RedisLocalCli.HGetAll(hmName).Result()
//
// for k, v := range res {
// si := ShearItem{}
// json.Unmarshal([]byte(v), &si)
// shg.Ma7PeriodGroup[k] = si
// }
//
// ctype = "ma30"
// hmName = shg.InstID + "|" + ctype + "|shearForceGrp"
// res, err = cr.RedisLocalCli.HGetAll(hmName).Result()
//
// for k, v := range res {
// si := ShearItem{}
// json.Unmarshal([]byte(v), &si)
// shg.Ma30PeriodGroup[k] = si
// }
// shg.SetToKey(cr)
// return err
// }
func (she *ShearForceGrp) Show(cr *Core) error {
js, err := json.Marshal(she)
logrus.Info(GetFuncName(), ": ", string(js))
return err
}
// TODO 需要重构: 已经重构
// 对象数据库落盘
func (she *ShearForceGrp) SetToKey(cr *Core) error {
keyName := she.InstID + "|shearForce"
she.From = os.Getenv("HOSTNAME")
she.LastUpdateTime = time.Now().Format("2006-01-02 15:04:05.000")
js, err := json.Marshal(she)
if err != nil {
logrus.Panic(GetFuncName(), " err: ", err)
} else {
cr.RedisLocalCli.Set(keyName, string(js), 0).Result()
cr.RedisLocal2Cli.Set(keyName, string(js), 0).Result()
}
return err
}
func (she *ShearForceGrp) maXPrd(cr *Core, ctype string) {
// 先把对象克隆,防止在处理的过程中对象发生变更
she2 := *she
she3 := &she2
// 查了一下json marshal 有线程安全问题,需要用户自己加锁,先不用了
// bj, _ := json.Marshal(she3)
// bytes := []byte(bj)
// var she4 ShearForceGrp
// json.Unmarshal(bytes, she4)
// 先声明map
var grp map[string]ShearItem
// 再使用make函数创建一个非nil的mapnil map不能赋值
grp = make(map[string]ShearItem)
if ctype == "ma7" {
//fmt.Println("len of ma7 she.Ma7PeriodGroup: ", len(she3.Ma7PeriodGroup))
bj, err := json.Marshal(she3.Ma7PeriodGroup)
if err != nil {
logrus.Panic(GetFuncName(), " err:", err)
}
json.Unmarshal(bj, &grp)
//fmt.Println("len of ma30 she.Ma7PeriodGroup: ", len(she3.Ma7PeriodGroup))
} else if ctype == "ma30" {
bj, err := json.Marshal(she3.Ma30PeriodGroup)
if err != nil {
logrus.Panic(GetFuncName(), " err: ", err)
}
json.Unmarshal(bj, &grp)
}
for period, shearItem := range grp {
setName := "shearForce|ratio|" + ctype + "|" + period + "|sortedSet"
// TODO这个key用于判定当前instID|maX|period|的ratio排名是否已经过期
timelinessKey := "shearForce|ratio|" + she.InstID + "|" + ctype + "|" + period + "|lastUpdate"
sei := SeriesInfo{
InstID: she3.InstID,
Period: period,
}
// 阈值先暂且设置为 -100
// SHEARFORCE_VERTICAL_RATE
threahold := float64(SHEARFORCE_VERTICAL_RATE)
bj, _ := json.Marshal(sei)
z := redis.Z{
Score: float64(shearItem.Ratio),
Member: string(bj),
}
//无论超过阈值,还是低于阈值的负数,都是达标
if shearItem.Ratio < -1*threahold {
cr.RedisLocalCli.ZAdd(setName, z).Result()
cr.RedisLocalCli.Set(timelinessKey, shearItem.LastUpdate, 3*time.Minute)
} else if shearItem.Ratio > threahold {
cr.RedisLocalCli.ZAdd(setName, z).Result()
cr.RedisLocalCli.Set(timelinessKey, shearItem.LastUpdate, 3*time.Minute)
} else {
cr.RedisLocalCli.ZRem(setName, string(bj)).Result()
}
}
}
// 把所有引用调用都改成传值调用试试看能不能解决那个陈年bug
func (she *ShearForceGrp) AddToRatioSorted(cr *Core) error {
she.maXPrd(cr, "ma7")
she.maXPrd(cr, "ma30")
return nil
}
// TODO 需要重构: 看了一下,不用重构
func (she *ShearForceGrp) MakeSnapShot(cr *Core) error {
nw := time.Now()
tm := nw.UnixMilli()
tm = tm - tm%60000
tms := strconv.FormatInt(tm, 10)
js, err := json.Marshal(she)
keyName1 := fmt.Sprint(she.InstID + "|shearForce|snapShot|ts:" + tms)
keyName2 := fmt.Sprint(she.InstID + "|shearForce|snapShot|last")
_, err = cr.RedisLocalCli.Set(keyName1, string(js), time.Duration(24)*time.Hour).Result()
_, err = cr.RedisLocalCli.Set(keyName2, string(js), time.Duration(24)*time.Hour).Result()
_, err = cr.RedisLocal2Cli.Set(keyName1, string(js), time.Duration(24)*time.Hour).Result()
_, err = cr.RedisLocal2Cli.Set(keyName2, string(js), time.Duration(24)*time.Hour).Result()
writeLog := os.Getenv("SARDINE_WRITELOG") == "true"
if !writeLog {
return err
}
wg := WriteLog{
Content: js,
Tag: she.InstID + ".shearForce",
}
go func() {
cr.WriteLogChan <- &wg
}()
return nil
}
func (sheGrp *ShearForceGrp) Refresh(cr *Core) error {
segments := cr.Cfg.Config.Get("softCandleSegmentList").MustArray()
ma7Grp := map[string]ShearItem{}
ma30Grp := map[string]ShearItem{}
//搜集各个维度未过期的shearItem数据,组合成shearForceGrp对象
for _, v := range segments {
cs := CandleSegment{}
sv, _ := json.Marshal(v)
json.Unmarshal(sv, &cs)
shi30, err := MakeShearItem(cr, sheGrp.InstID, cs.Seg, "ma30")
if err != nil {
logrus.Warn(GetFuncName(), err)
} else {
ma30Grp[cs.Seg] = *shi30
}
shi7, err := MakeShearItem(cr, sheGrp.InstID, cs.Seg, "ma7")
if err != nil {
logrus.Warn(GetFuncName(), err)
} else {
ma7Grp[cs.Seg] = *shi7
}
sheGrp.Ma7PeriodGroup = ma7Grp
sheGrp.Ma30PeriodGroup = ma30Grp
}
return nil
}
func MakeShearItem(cr *Core, instId string, period string, ctype string) (*ShearItem, error) {
shi := ShearItem{}
keyn := instId + "|" + period + "|" + ctype + "|shearItem"
res, err := cr.RedisLocalCli.Get(keyn).Result()
if err != nil && len(res) == 0 {
return &shi, err
}
json.Unmarshal([]byte(res), &shi)
return &shi, err
}
func (sheGrp *ShearForceGrp) Process(cr *Core) error {
go func() {
sheGrp.Show(cr)
// 传递过来的shg对象是空的需要从segmentItem对象创建的shearItem对象组合中来重建
sheGrp.Refresh(cr)
err := sheGrp.SetToKey(cr)
if err != nil {
logrus.Panic("srs SetToKey err: ", err)
}
// sheGrp.MakeSnapShot(cr)
// 下一个阶段计算
allow := os.Getenv("SARDINE_MAKEANALYTICS") == "true"
if !allow {
return
}
periodList := []string{}
for k := range sheGrp.Ma30PeriodGroup {
periodList = append(periodList, k)
}
}()
go func() {
sheGrp.AddToRatioSorted(cr)
}()
go func() {
// 另一个携程中Analytics对象要读这里snapShot我希望它读到的是老的而不是新的所以等待2秒钟
time.Sleep(2 * time.Second)
sheGrp.MakeSnapShot(cr)
}()
return nil
}

View File

@ -1,4 +1,4 @@
package core
package config
import (
// "fmt"

View File

@ -1,4 +1,4 @@
package core
package config
const MAIN_ALLCOINS_PERIOD_MINUTES = 1
const MAIN_ALLCOINS_ONCE_COUNTS = 3

View File

@ -5,8 +5,6 @@ import (
"encoding/json"
"errors"
"fmt"
// "math/rand"
"io/ioutil"
"net/http"
"os"
@ -15,8 +13,6 @@ import (
"sync"
"time"
// simple "github.com/bitly/go-simplejson"
// "v5sdk_go/ws/wImpl"
"github.com/go-redis/redis"
"github.com/phyer/texus/private"
"github.com/phyer/v5sdkgo/rest"
@ -41,10 +37,10 @@ type Core struct {
StockRsiProcessChan chan *StockRsi
TickerInforocessChan chan *TickerInfo
CoasterChan chan *CoasterInfo
// SeriesChan chan *SeriesInfo
// SegmentItemChan chan *SegmentItem
SeriesChan chan *SeriesInfo // to be init
SegmentItemChan chan *SegmentItem // to be init
MakeMaXsChan chan *Candle
// ShearForceGrpChan chan *ShearForceGrp
ShearForceGrpChan chan *ShearForceGrp // to be init
InvokeRestQueueChan chan *RestQueue
RedisLocal2Cli *redis.Client
RestQueueChan chan *RestQueue
@ -871,3 +867,43 @@ func (cr *Core) GetCoasterFromPlate(instID string, period string) (Coaster, erro
return co, nil
}
func (core *Core) GetPixelSeries(instId string, period string) (Series, error) {
srs := Series{}
srName := instId + "|" + period + "|series"
cli := core.RedisLocalCli
srsStr, err := cli.Get(srName).Result()
if err != nil {
return *new(Series), err
}
err = json.Unmarshal([]byte(srsStr), &srs)
if err != nil {
return *new(Series), err
}
logrus.Info("sei:", srsStr)
err = srs.CandleSeries.RecursiveBubbleS(srs.CandleSeries.Count, "asc")
if err != nil {
return *new(Series), err
}
// err = srs.CandleSeries.RecursiveBubbleX(srs.CandleSeries.Count, "asc")
// if err != nil {
// return nil, err
// }
err = srs.Ma7Series.RecursiveBubbleS(srs.CandleSeries.Count, "asc")
if err != nil {
return *new(Series), err
}
// err = srs.Ma7Series.RecursiveBubbleX(srs.CandleSeries.Count, "asc")
// if err != nil {
// return nil, err
// }
err = srs.Ma30Series.RecursiveBubbleS(srs.CandleSeries.Count, "asc")
if err != nil {
return *new(Series), err
}
// err = srs.Ma30Series.RecursiveBubbleX(srs.CandleSeries.Count, "asc")
// if err != nil {
// return nil, err
// }
return srs, nil
}

View File

@ -1,4 +1,4 @@
package core
package logger
import (
"bytes"
@ -6,6 +6,7 @@ import (
"net/http"
"os"
// "github.com/phyer/core/internal/core"
logrus "github.com/sirupsen/logrus"
)
@ -15,7 +16,7 @@ type WriteLog struct {
Id string
}
func (wg *WriteLog) Process(cr *Core) error {
func (wg *WriteLog) Process(cr *core.Core) error {
go func() {
reqBody := bytes.NewBuffer(wg.Content)
cr.Env = os.Getenv("GO_ENV")

View File

@ -1,4 +1,4 @@
package core
package market
import (
"encoding/json"
@ -6,6 +6,8 @@ import (
"reflect"
"strconv"
"time"
"github.com/phyer/core/internal/utils" // 新增
)
type TickerInfo struct {
@ -28,7 +30,7 @@ type TickerInfoResp struct {
func (tir *TickerInfoResp) Convert() TickerInfo {
ti := TickerInfo{
Id: HashString(tir.InstID + tir.Ts),
Id: utils.HashString(tir.InstID + tir.Ts),
InstID: tir.InstID,
InstType: tir.InstType,
Last: ToFloat64(tir.Last),

View File

@ -1,4 +1,4 @@
package core
package model
import (
"crypto/sha256"
@ -15,13 +15,14 @@ import (
simple "github.com/bitly/go-simplejson"
"github.com/go-redis/redis"
"github.com/phyer/texus/utils"
logrus "github.com/sirupsen/logrus"
"github.com/phyer/core/internal/core" // 新增
)
type Candle struct {
Id string `json:"_id"`
core *Core
core *core.Core
InstID string `json:"instID"`
Period string `json:"period"`
Data []interface{}
@ -36,7 +37,7 @@ type Candle struct {
Confirm bool `json:"confirm"`
}
type Sample interface {
SetToKey(cr *Core) ([]interface{}, error)
SetToKey(cr *core.Core) ([]interface{}, error)
}
type SampleList interface {
@ -62,7 +63,7 @@ type MatchCheck struct {
Matched bool
}
func (cd *Candle) Filter(cr *Core) bool {
func (cd *Candle) Filter(cr *core.Core) bool {
myFocusList := cr.Cfg.Config.Get("focusList").MustArray()
founded := false
for _, v := range myFocusList {
@ -77,7 +78,7 @@ func (mc *MatchCheck) SetMatched(value bool) {
mc.Matched = value
}
func (core *Core) GetCandlesWithRest(instId string, kidx int, dura time.Duration, maxCandles int) error {
func (core *core.Core) GetCandlesWithRest(instId string, kidx int, dura time.Duration, maxCandles int) error {
ary := []string{}
wsary := core.Cfg.CandleDimentions
@ -177,7 +178,7 @@ func IsModOf(curInt int64, duration time.Duration) bool {
return false
}
func (core *Core) SaveCandle(instId string, period string, rsp *CandleData, dura time.Duration, withWs bool) {
func (core *core.Core) SaveCandle(instId string, period string, rsp *CandleData, dura time.Duration, withWs bool) {
leng := len(rsp.Data)
// fmt.Println("saveCandle leng: ", leng, " instId: ", instId, " period: ", period)
logrus.Info("saveCandles leng: ", leng, " instId: ", instId, " period: ", period, " length of rsp.Data: ", len(rsp.Data))
@ -255,7 +256,7 @@ func (core *Core) SaveCandle(instId string, period string, rsp *CandleData, dura
}
}
func (candle *Candle) PushToWriteLogChan(cr *Core) error {
func (candle *Candle) PushToWriteLogChan(cr *core.Core) error {
did := candle.InstID + candle.Period + candle.Data[0].(string)
candle.Id = HashString(did)
cl, _ := candle.ToStruct(cr)
@ -292,7 +293,7 @@ func HashString(input string) string {
return hashHex[:23]
}
func (cl *Candle) ToStruct(core *Core) (*Candle, error) {
func (cl *Candle) ToStruct(core *core.Core) (*Candle, error) {
// cl.Timestamp
// 将字符串转换为 int64 类型的时间戳
ts, err := strconv.ParseInt(cl.Data[0].(string), 10, 64)
@ -340,7 +341,7 @@ func (cl *Candle) ToStruct(core *Core) (*Candle, error) {
}
// 保证同一个 period, keyName 在一个周期里SaveToSortSet只会被执行一次
func (core *Core) SaveUniKey(period string, keyName string, extt time.Duration, tsi int64) {
func (core *core.Core) SaveUniKey(period string, keyName string, extt time.Duration, tsi int64) {
refName := keyName + "|refer"
// refRes, _ := core.RedisLocalCli.GetSet(refName, 1).Result()
@ -354,7 +355,7 @@ func (core *Core) SaveUniKey(period string, keyName string, extt time.Duration,
core.SaveToSortSet(period, keyName, extt, tsi)
}
func (core *Core) findInSortSet(period string, keyName string, extt time.Duration, tsi int64) (bool, error) {
func (core *core.Core) findInSortSet(period string, keyName string, extt time.Duration, tsi int64) (bool, error) {
founded := false
ary := strings.Split(keyName, "ts:")
setName := ary[0] + "sortedSet"
@ -375,7 +376,7 @@ func (core *Core) findInSortSet(period string, keyName string, extt time.Duratio
}
// tsi: 上报时间timeStamp millinSecond
func (core *Core) SaveToSortSet(period string, keyName string, extt time.Duration, tsi int64) {
func (core *core.Core) SaveToSortSet(period string, keyName string, extt time.Duration, tsi int64) {
ary := strings.Split(keyName, "ts:")
setName := ary[0] + "sortedSet"
z := redis.Z{
@ -391,7 +392,7 @@ func (core *Core) SaveToSortSet(period string, keyName string, extt time.Duratio
}
// 根据周期的文本内容,返回这代表多少个分钟
func (cr *Core) PeriodToMinutes(period string) (int64, error) {
func (cr *core.Core) PeriodToMinutes(period string) (int64, error) {
ary := strings.Split(period, "")
beiStr := "1"
danwei := ""
@ -456,7 +457,7 @@ func (cr *Core) PeriodToMinutes(period string) (int64, error) {
//
// process func(cmd Cmder) error
// }
func (core *Core) GetRangeKeyList(pattern string, from time.Time) ([]*simple.Json, error) {
func (core *core.Core) GetRangeKeyList(pattern string, from time.Time) ([]*simple.Json, error) {
// 比如用来计算ma30或ma7倒推多少时间范围
redisCli := core.RedisLocalCli
cursor := uint64(0)
@ -513,7 +514,7 @@ func (core *Core) GetRangeKeyList(pattern string, from time.Time) ([]*simple.Jso
return res, nil
}
func (cl *Candle) SetToKey(core *Core) ([]interface{}, error) {
func (cl *Candle) SetToKey(core *core.Core) ([]interface{}, error) {
data := cl.Data
tsi, err := strconv.ParseInt(data[0].(string), 10, 64)
@ -622,3 +623,61 @@ func (cdl *CandleList) RPush(sp *Candle) (Sample, error) {
return nil, err
}
}
// TODO pixel
func (cdl *CandleList) MakePixelList(cr *core.Core, mx *MaX, score float64) (*PixelList, error) {
if mx.Data[2] != float64(30) {
err := errors.New("ma30 原始数据不足30条")
return nil, err
}
pxl := PixelList{
Count: cdl.Count,
UpdateNickName: cdl.UpdateNickName,
LastUpdateTime: cdl.LastUpdateTime,
List: []*Pixel{},
}
realLens := len(cdl.List)
cha := cdl.Count - realLens
for i := 0; i < 24; i++ {
pix := Pixel{}
pxl.List = append(pxl.List, &pix)
}
ma30Val := ToFloat64(mx.Data[1])
for h := cdl.Count - 1; h-cha >= 0; h-- {
// Count 是希望值,比如24realLens是实际值, 如果希望值和实际值相等cha就是0
cdLast := cdl.List[h-cha].Data[4]
cdLastf := ToFloat64(cdLast)
cdOpen := cdl.List[h-cha].Data[1]
cdOpenf := ToFloat64(cdOpen)
cdHigh := cdl.List[h-cha].Data[2]
cdHighf := ToFloat64(cdHigh)
cdLow := cdl.List[h-cha].Data[3]
cdLowf := ToFloat64(cdLow)
yCandle := YCandle{
Open: (cdOpenf - ma30Val) / ma30Val / score,
High: (cdHighf - ma30Val) / ma30Val / score,
Low: (cdLowf - ma30Val) / ma30Val / score,
Close: (cdLastf - ma30Val) / ma30Val / score,
}
tmi := ToInt64(cdl.List[h-cha].Data[0])
pxl.List[h].Y = (cdLastf - ma30Val) / ma30Val / score
pxl.List[h].X = float64(h)
pxl.List[h].YCandle = yCandle
pxl.List[h].Score = cdLastf
pxl.List[h].TimeStamp = tmi
}
return &pxl, nil
}
func (cr *core.Core) AddToGeneralSeriesInfoChnl(sr *SeriesInfo) {
redisCli := cr.RedisLocalCli
ab, _ := json.Marshal(sr)
if len(string(ab)) == 0 {
return
}
_, err := redisCli.Publish(ALLSERIESINFO_PUBLISH, string(ab)).Result()
if err != nil {
logrus.Debug("err of seriesinfo add to redis2:", err, sr.InstID, sr.Period)
}
}

View File

@ -1,11 +1,11 @@
package core
package model
import (
"encoding/json"
// "errors"
// "fmt"
"errors"
"fmt"
logrus "github.com/sirupsen/logrus"
// "os"
"os"
"strconv"
"time"
)
@ -89,89 +89,90 @@ func (co *Coaster) SetToKey(cr *Core) (string, error) {
return res, err
}
// func (coi *CoasterInfo) Process(cr *Core) {
// curCo, _ := cr.GetCoasterFromPlate(coi.InstID, coi.Period)
// go func(co Coaster) {
// //这里执行创建一个tray对象,用现有的co的数据计算和填充其listMap
// // TODO 发到一个channel里来执行下面的任务
// allow := os.Getenv("SARDINE_MAKESERIES") == "true"
// if !allow {
// return
// }
// srs, err := co.UpdateTray(cr)
// if err != nil || srs == nil {
// logrus.Warn("tray err: ", err)
// return
// }
// _, err = srs.SetToKey(cr)
// if err != nil {
// logrus.Warn("srs SetToKey err: ", err)
// return
// }
// //实例化完一个tray之后拿着这个tray去执行Analytics方法
// //
// // srsinfo := SeriesInfo{
// // InstID: curCo.InstID,
// // Period: curCo.Period,
// // }
// //
// // cr.SeriesChan <- &srsinfo
// }(curCo)
//
// go func(co Coaster) {
// // 每3次会有一次触发缓存落盘
// // run := utils.Shaizi(3)
// // if run {
// _, err := co.SetToKey(cr)
// if err != nil {
// logrus.Warn("coaster process err: ", err)
// fmt.Println("coaster SetToKey err: ", err)
// }
// // }
//
// }(curCo)
// }
func (coi *CoasterInfo) Process(cr *Core) {
curCo, _ := cr.GetCoasterFromPlate(coi.InstID, coi.Period)
go func(co Coaster) {
//这里执行创建一个tray对象,用现有的co的数据计算和填充其listMap
// TODO 发到一个channel里来执行下面的任务
allow := os.Getenv("SARDINE_MAKESERIES") == "true"
if !allow {
return
}
srs, err := co.UpdateTray(cr)
if err != nil || srs == nil {
logrus.Warn("tray err: ", err)
return
}
_, err = srs.SetToKey(cr)
if err != nil {
logrus.Warn("srs SetToKey err: ", err)
return
}
//实例化完一个tray之后拿着这个tray去执行Analytics方法
//
// srsinfo := SeriesInfo{
// InstID: curCo.InstID,
// Period: curCo.Period,
// }
//
// cr.SeriesChan <- &srsinfo
}(curCo)
go func(co Coaster) {
// 每3次会有一次触发缓存落盘
// run := utils.Shaizi(3)
// if run {
_, err := co.SetToKey(cr)
if err != nil {
logrus.Warn("coaster process err: ", err)
fmt.Println("coaster SetToKey err: ", err)
}
// }
}(curCo)
}
//
// TODO 类似于InsertIntoPlate函数照猫画虎就行了
//
// func (co *Coaster) UpdateTray(cr *Core) (*Series, error) {
// cr.Mu1.Lock()
// defer cr.Mu1.Unlock()
// //尝试从内存读取tray对象
// tr, trayFounded := cr.TrayMap[co.InstID]
// if !trayFounded {
// tr1, err := co.LoadTray(cr)
// if err != nil {
// return nil, err
// }
// cr.TrayMap[co.InstID] = tr1
// tr = tr1
// }
// srs, seriesFounded := tr.SeriesMap["period"+co.Period]
// err := errors.New("")
// if !seriesFounded {
// srs1, err := tr.NewSeries(cr, co.Period)
// if err != nil {
// return nil, err
// }
// tr.SeriesMap["period"+co.Period] = srs1
// } else {
// err = srs.Refresh(cr)
// }
// // if err == nil {
// // bj, _ := json.Marshal(srs)
// // logrus.Debug("series:,string"(bj))
// // }
// return srs, err
// }
//
func (co *Coaster) UpdateTray(cr *Core) (*Series, error) {
cr.Mu1.Lock()
defer cr.Mu1.Unlock()
//尝试从内存读取tray对象
tr, trayFounded := cr.TrayMap[co.InstID]
if !trayFounded {
tr1, err := co.LoadTray(cr)
if err != nil {
return nil, err
}
cr.TrayMap[co.InstID] = tr1
tr = tr1
}
srs, seriesFounded := tr.SeriesMap["period"+co.Period]
err := errors.New("")
if !seriesFounded {
srs1, err := tr.NewSeries(cr, co.Period)
if err != nil {
return nil, err
}
tr.SeriesMap["period"+co.Period] = srs1
} else {
err = srs.Refresh(cr)
}
// if err == nil {
// bj, _ := json.Marshal(srs)
// logrus.Debug("series:,string"(bj))
// }
return srs, err
}
// TODO
// func (co *Coaster) LoadTray(cr *Core) (*Tray, error) {
// tray := Tray{}
// tray.Init(co.InstID)
// prs := cr.Cfg.Config.Get("candleDimentions").MustArray()
// for _, v := range prs {
// tray.NewSeries(cr, v.(string))
// }
// return &tray, nil
// }
func (co *Coaster) LoadTray(cr *Core) (*Tray, error) {
tray := Tray{}
tray.Init(co.InstID)
prs := cr.Cfg.Config.Get("candleDimentions").MustArray()
for _, v := range prs {
tray.NewSeries(cr, v.(string))
}
return &tray, nil
}

View File

@ -1,4 +1,4 @@
package core
package model
import (
"encoding/json"

View File

@ -1,4 +1,4 @@
package core
package model
import (
"encoding/json"
@ -10,13 +10,13 @@ type Tray struct {
Count int `json:"count,number"`
Scale float64 `json:"scale,number"`
LastUpdateTime int64 `json:"lastUpdateTime,number"`
// SeriesMap map[string]*Series `json:"seriesMap"`
SeriesMap map[string]*Series `json:"seriesMap"`
}
type PixelSeries struct {
Count int64 `json:"count"`
Section int64 `json:"section"`
List []*Pixel `json:"list"`
List []*analysis/Pixel `json:"list"`
}
func (tr *Tray) Init(instId string) {
@ -43,18 +43,18 @@ func (tr *Tray) Analytics(cr *Core) {
}
// TODO 实例化一个series
// func (tr *Tray) NewSeries(cr *Core, period string) (*Series, error) {
// sr := Series{
// InstID: tr.InstID,
// Period: period,
// Count: tr.Count,
// Scale: tr.Scale,
// CandleSeries: &PixelList{},
// Ma7Series: &PixelList{},
// Ma30Series: &PixelList{},
// }
// // 自我更新
// err := sr.Refresh(cr)
// tr.SeriesMap["period"+period] = &sr
// return &sr, err
// }
func (tr *Tray) NewSeries(cr *Core, period string) (*Series, error) {
sr := Series{
InstID: tr.InstID,
Period: period,
Count: tr.Count,
Scale: tr.Scale,
CandleSeries: &PixelList{},
Ma7Series: &PixelList{},
Ma30Series: &PixelList{},
}
// 自我更新
err := sr.Refresh(cr)
tr.SeriesMap["period"+period] = &sr
return &sr, err
}

128
internal/notify/dingding.go Normal file
View File

@ -0,0 +1,128 @@
package notify
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"net/url"
"strconv"
"strings"
"time"
logrus "github.com/sirupsen/logrus"
)
const DingdingMsgType_Markdown = "markdown"
type DingdingMsg struct {
RobotName string
Topic string
Ctype string
Content string
AtAll bool
UniqueCode string
}
func (dd *DingdingMsg) AddItemListGrp(title string, level int, list []string) error {
pre := ""
if level < 1 {
err := errors.New("level is not allow " + strconv.FormatInt(int64(level), 10))
return err
}
for i := level; i > 0; i-- {
pre = pre + "#"
}
title = pre + " " + title
dd.Content += "\n"
dd.Content += title
dd.Content += "\n"
for _, v := range list {
dd.Content += v
dd.Content += "\n"
}
return nil
}
func MakeSign(baseUrl string, secure string, token string, tm time.Time) string {
tsi := tm.UnixMilli()
tsi = tsi - tsi%60000
tss := strconv.FormatInt(tsi, 10)
sign := tss + "\n" + secure
sign = ComputeHmac256(secure, sign)
sign = url.QueryEscape(sign)
url := baseUrl + "?access_token=" + token + "&timestamp=" + tss + "&sign=" + sign
return url
}
func (dd *DingdingMsg) MakeContent() []byte {
ctn := map[string]interface{}{}
ctn["msgtype"] = dd.Ctype
if dd.Ctype == DingdingMsgType_Markdown {
md := map[string]interface{}{}
md["title"] = dd.Topic
md["text"] = dd.Content
md["isAtAll"] = dd.AtAll
ctn[DingdingMsgType_Markdown] = md
}
btn, _ := json.Marshal(ctn)
return btn
}
func ComputeHmac256(secret string, message string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
func PostHeader(url string, msg []byte, headers map[string]string) (string, error) {
client := &http.Client{}
req, err := http.NewRequest("POST", url, strings.NewReader(string(msg)))
if err != nil {
return "", err
}
for key, header := range headers {
req.Header.Set(key, header)
}
resp, err := client.Do(req)
if err != nil {
logrus.Warn("postHeader err: ", err)
return "", err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func (dd *DingdingMsg) PostToRobot(rbt string, cr *Core) (string, error) {
baseUrl, _ := cr.Cfg.Config.Get("dingding").Get("baseUrl").String()
secret, _ := cr.Cfg.Config.Get("dingding").Get("robots").Get(rbt).Get("secret").String()
token, _ := cr.Cfg.Config.Get("dingding").Get("robots").Get(rbt).Get("accessToken").String()
cli := cr.RedisLocalCli
if len(dd.UniqueCode) > 0 {
unique := "ddPostUnique|" + dd.UniqueCode
exists, _ := cli.Exists(unique).Result()
if exists == 1 {
err := errors.New("20分钟内已经投递过了不再重复")
return "", err
}
cli.Set(unique, 1, 20*time.Minute).Result()
}
nw := time.Now()
url := MakeSign(baseUrl, secret, token, nw)
ctn := dd.MakeContent()
headers := make(map[string]string)
headers["Content-Type"] = "application/json;charset=utf-8"
res, err := PostHeader(url, ctn, headers)
logrus.Warn("postToRobot res:", res, string(ctn))
return res, err
}

View File

@ -1,4 +1,4 @@
package core
package utils
import (
"crypto/md5"

13
note.md Normal file
View File

@ -0,0 +1,13 @@
这个项目是用来在量化交易中定义一些基础类型和对一些走势特征进行判定的逻辑,帮看一下哪里有问题,需要修改或优化
cd /home/ubuntu/data/go/phyer.click/core &&
mkdir -p models utils config notify analysis market log &&
mv candle.go coaster.go plate.go tray.go models/ &&
mv util.go utils/ &&
mv config.go const.go config/ &&
mv dingding.go notify/ &&
mv maX.go rsi.go segmentItem.go series.go shearGorceGrp.go analysis/ &&
mv ticker.go market/ &&
mv writeLog.go log/