14 changed files with 495 additions and 27 deletions
@ -1,20 +1,48 @@ |
|||||||
package indicator |
package indicator |
||||||
|
|
||||||
|
import ( |
||||||
|
vmts "sig-pub/pkg/storage/tsdb/victoria_metrics" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
// load indicator plugin
|
// load indicator plugin
|
||||||
|
|
||||||
// 热指标 自动加载/实时更新/内存缓存
|
// 热指标 自动加载/实时更新/内存缓存
|
||||||
// 历史指标 实时计算
|
// 历史指标 实时计算
|
||||||
// 自定义插件化指标
|
// 自定义插件化指标
|
||||||
|
|
||||||
type IndicatorService struct { |
type IndicatorService struct { |
||||||
|
vmdb vmts.VictoriaMetricsTSDB |
||||||
} |
} |
||||||
|
|
||||||
func NewIndicatorService() *IndicatorService { |
func NewIndicatorService(vmdb vmts.VictoriaMetricsTSDB) *IndicatorService { |
||||||
return &IndicatorService{} |
return &IndicatorService{ |
||||||
|
vmdb: vmdb, |
||||||
|
} |
||||||
} |
} |
||||||
|
|
||||||
// 加载热指标
|
// 加载热指标
|
||||||
// 订阅k线数据 更新指标
|
// 订阅k线数据 更新指标
|
||||||
func (svc *IndicatorService) Init() { |
func (svc *IndicatorService) Init() { |
||||||
|
// Regist(RSI)
|
||||||
|
// if Indicators[RSI]
|
||||||
|
} |
||||||
|
|
||||||
|
// 注册
|
||||||
|
func (svc *IndicatorService) Register(indicatorName string, indicatorInterface any) { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// Indicator 获取指标
|
||||||
|
// key: 指标名称-指标参数hash
|
||||||
|
// args: 策略参数(柯里化), 如 window, 6, round, 14...
|
||||||
|
func (svc *IndicatorService) Indicator(indicatorName string, args ...any) { |
||||||
|
// 调用方: strategy, webview, other indicator
|
||||||
|
// strategy: IsCross(RSI6[10:1], RSI14[10:1])...
|
||||||
|
// webview: RSI6(stime, etime, interval)
|
||||||
|
// other indicator: RSI6[10] * MACD[10]
|
||||||
|
} |
||||||
|
|
||||||
|
// kline -> indicator root -> /internal/force_flush -> all indicators (wg concurrent) -> all strategy (concurrent)
|
||||||
|
type Ind struct { |
||||||
|
LiveMu sync.RWMutex // 实时k线锁, k线更新后指标更新时写锁
|
||||||
} |
} |
||||||
|
|||||||
@ -0,0 +1,35 @@ |
|||||||
|
package indicator |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"sig-pub/api/pb" |
||||||
|
"sig-pub/pkg/types" |
||||||
|
) |
||||||
|
|
||||||
|
type RSI struct { |
||||||
|
// types.IntervalWindow
|
||||||
|
window int // 窗口大小
|
||||||
|
} |
||||||
|
|
||||||
|
func (ind *RSI) IntervalWindow() { |
||||||
|
// 指标参数注入
|
||||||
|
} |
||||||
|
|
||||||
|
func (ind *RSI) QueryRange(exchange pb.ExchangeType, instId string, interval types.Interval, rsi int) (query string, err error) { |
||||||
|
var r types.MeticMatrix |
||||||
|
_ = r |
||||||
|
intervalAdder, ok := types.SupportedIntervals[interval] |
||||||
|
if !ok { |
||||||
|
err = fmt.Errorf("unsupport interval %s", interval) |
||||||
|
return |
||||||
|
} |
||||||
|
minutes := intervalAdder(0, int64(rsi)) / 1000 / 60 |
||||||
|
|
||||||
|
query = fmt.Sprintf(` |
||||||
|
100 - 100 / (1 + ( |
||||||
|
avg_over_time(clamp_min(delta(%s{kind="close", interval="%s", exchange="%s"}), 0)[%dm]) / |
||||||
|
avg_over_time(abs(clamp_max(delta(%s{kind="close", interval="%s", exchange="%s"}), 0))[%dm]) |
||||||
|
)) |
||||||
|
`, instId, interval, exchange, minutes, instId, interval, exchange, minutes) |
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
package vmts |
||||||
|
|
||||||
|
import "github.com/govalues/decimal" |
||||||
|
|
||||||
|
type VMMetricKline struct { |
||||||
|
Metric struct { |
||||||
|
Name string `json:"__name__"` |
||||||
|
Interval string `json:"interval"` |
||||||
|
Kind string `json:"kind"` |
||||||
|
} `json:"metric"` |
||||||
|
Values []decimal.Decimal `json:"values"` |
||||||
|
Timestamps []int64 `json:"timestamps"` |
||||||
|
} |
||||||
|
|
||||||
|
const ( |
||||||
|
QueryStatusSuccess = "success" |
||||||
|
QueryStatusError = "error" |
||||||
|
) |
||||||
|
|
||||||
|
type ResponseQuery struct { |
||||||
|
Status string `json:"status"` // success, error
|
||||||
|
ErrorType string `json:"errorType"` // 422
|
||||||
|
Error string `json:"error"` |
||||||
|
Stats struct { |
||||||
|
SeriesFetched string `json:"seriesFetched"` |
||||||
|
ExecutionTimeMsec int `json:"executionTimeMsec"` |
||||||
|
} `json:"stats"` |
||||||
|
Data struct { |
||||||
|
ResultType string `json:"resultType"` // matrix, vector
|
||||||
|
Result []struct { |
||||||
|
Metric map[string]string `json:"metric"` // {__name__: 'BTC_USDT', exchange: 'OKX', interval: '5m', kind: 'close'}
|
||||||
|
Values [][]any `json:"values"` // data.type = matrix [[1759975200, '122407.82'], [1759975500, '122385.92']]
|
||||||
|
Value []any `json:"value"` // data.type = vector [1759975200, '122407.82']
|
||||||
|
} `json:"result"` |
||||||
|
} `json:"data"` |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
package types |
||||||
|
|
||||||
|
// MeticMatrix 指标向量矩阵
|
||||||
|
type MeticMatrix struct { |
||||||
|
Name string `json:"name"` // 指标名称
|
||||||
|
Exchange string `json:"exchange"` |
||||||
|
Interval string `json:"interval"` |
||||||
|
Kind string `json:"kind"` |
||||||
|
Timestamps []int64 `json:"timestamps"` // len(timestamps) == len(values)
|
||||||
|
Values []float64 `json:"values"` |
||||||
|
} |
||||||
@ -0,0 +1,164 @@ |
|||||||
|
package conver |
||||||
|
|
||||||
|
import ( |
||||||
|
"math" |
||||||
|
"strconv" |
||||||
|
) |
||||||
|
|
||||||
|
func ToInt64(inter any, defaultVal ...int64) int64 { |
||||||
|
var def int64 = 0 |
||||||
|
if len(defaultVal) > 0 { |
||||||
|
def = defaultVal[0] |
||||||
|
} |
||||||
|
|
||||||
|
switch v := inter.(type) { |
||||||
|
default: |
||||||
|
return def |
||||||
|
case int: |
||||||
|
return int64(v) |
||||||
|
case int8: |
||||||
|
return int64(v) |
||||||
|
case int16: |
||||||
|
return int64(v) |
||||||
|
case int32: |
||||||
|
return int64(v) |
||||||
|
case int64: |
||||||
|
return v |
||||||
|
case uint8: |
||||||
|
return int64(v) |
||||||
|
case uint16: |
||||||
|
return int64(v) |
||||||
|
case uint32: |
||||||
|
return int64(v) |
||||||
|
case uint64: |
||||||
|
return int64(v) |
||||||
|
case float32: |
||||||
|
return int64(v) |
||||||
|
case float64: |
||||||
|
if math.IsNaN(v) { |
||||||
|
return def |
||||||
|
} |
||||||
|
return int64(v) |
||||||
|
case bool: |
||||||
|
if v { |
||||||
|
return 1 |
||||||
|
} |
||||||
|
return 0 |
||||||
|
case string: |
||||||
|
a, e := strconv.Atoi(v) |
||||||
|
if e != nil { |
||||||
|
return def |
||||||
|
} |
||||||
|
return int64(a) |
||||||
|
case *uint64: |
||||||
|
if v == nil { |
||||||
|
return def |
||||||
|
} |
||||||
|
return int64(*v) |
||||||
|
case *int64: |
||||||
|
if v == nil { |
||||||
|
return def |
||||||
|
} |
||||||
|
return *v |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func ToFloat64(inter any, defaultVal ...float64) (r float64) { |
||||||
|
var def float64 = 0 |
||||||
|
if len(defaultVal) > 0 { |
||||||
|
def = defaultVal[0] |
||||||
|
} |
||||||
|
defer func() { |
||||||
|
if math.IsNaN(r) { |
||||||
|
r = def |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
switch v := inter.(type) { |
||||||
|
default: |
||||||
|
return def |
||||||
|
case int: |
||||||
|
return float64(v) |
||||||
|
case int8: |
||||||
|
return float64(v) |
||||||
|
case int16: |
||||||
|
return float64(v) |
||||||
|
case int32: |
||||||
|
return float64(v) |
||||||
|
case int64: |
||||||
|
return float64(v) |
||||||
|
case uint8: |
||||||
|
return float64(v) |
||||||
|
case uint16: |
||||||
|
return float64(v) |
||||||
|
case uint32: |
||||||
|
return float64(v) |
||||||
|
case uint64: |
||||||
|
return float64(v) |
||||||
|
case float32: |
||||||
|
return float64(v) |
||||||
|
case float64: |
||||||
|
return float64(v) |
||||||
|
case *float64: |
||||||
|
if v == nil { |
||||||
|
return def |
||||||
|
} |
||||||
|
return *v |
||||||
|
case *int64: |
||||||
|
if v == nil { |
||||||
|
return def |
||||||
|
} |
||||||
|
return float64(*v) |
||||||
|
case string: |
||||||
|
a, e := strconv.ParseFloat(v, 64) |
||||||
|
if e != nil { |
||||||
|
return def |
||||||
|
} |
||||||
|
return a |
||||||
|
} |
||||||
|
} |
||||||
|
func ToBool(inter any, defaultVal ...bool) bool { |
||||||
|
var def bool |
||||||
|
if len(defaultVal) > 0 { |
||||||
|
def = defaultVal[0] |
||||||
|
} |
||||||
|
|
||||||
|
switch v := inter.(type) { |
||||||
|
default: |
||||||
|
return def |
||||||
|
case bool: |
||||||
|
return v |
||||||
|
case int: |
||||||
|
return v > 0 |
||||||
|
case int8: |
||||||
|
return v > 0 |
||||||
|
case int16: |
||||||
|
return v > 0 |
||||||
|
case int32: |
||||||
|
return v > 0 |
||||||
|
case int64: |
||||||
|
return v > 0 |
||||||
|
case uint8: |
||||||
|
return v > 0 |
||||||
|
case uint16: |
||||||
|
return v > 0 |
||||||
|
case uint32: |
||||||
|
return v > 0 |
||||||
|
case uint64: |
||||||
|
return v > 0 |
||||||
|
case float32: |
||||||
|
return v > 0 |
||||||
|
case float64: |
||||||
|
return v > 0 |
||||||
|
case string: |
||||||
|
a, e := strconv.ParseBool(v) |
||||||
|
if e == nil { |
||||||
|
return a |
||||||
|
} |
||||||
|
b, e := strconv.Atoi(v) |
||||||
|
if e != nil { |
||||||
|
return def |
||||||
|
} |
||||||
|
return b > 0 |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
package retry |
||||||
|
|
||||||
|
import ( |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// DoWithFixDelay 根据固定延迟重试函数
|
||||||
|
func DoWithFixDelay[T any](maxRetryTimes uint32, delay time.Duration, handler func(retryTimes uint32) (T, error)) (r T, lastErr error) { |
||||||
|
for retry := range maxRetryTimes { |
||||||
|
if retry > 0 { |
||||||
|
time.Sleep(delay) |
||||||
|
} |
||||||
|
r, err := handler(retry) |
||||||
|
if err != nil { |
||||||
|
lastErr = err |
||||||
|
continue |
||||||
|
} |
||||||
|
return r, nil |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// DoWithStepDelay 根据步进延迟重试函数 1s, 2s, 4s...
|
||||||
|
func DoWithStepDelay[T any](maxRetryTimes uint32, initialDelay time.Duration, handler func(retryTimes uint32) (T, error)) (r T, lastErr error) { |
||||||
|
for retry := range maxRetryTimes { |
||||||
|
if retry > 0 { |
||||||
|
time.Sleep(time.Duration(1<<(retry-1)) * initialDelay) |
||||||
|
} |
||||||
|
r, err := handler(retry) |
||||||
|
if err != nil { |
||||||
|
lastErr = err |
||||||
|
continue |
||||||
|
} |
||||||
|
return r, nil |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
package retry |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
func TestRetryStep(t *testing.T) { |
||||||
|
t.Logf("start retry: unix=%d", time.Now().Unix()) |
||||||
|
r, err := DoWithStepDelay(5, time.Second, func(retryTimes uint32) (uint32, error) { |
||||||
|
t.Logf("retry %d times, unix=%d", retryTimes, time.Now().Unix()) |
||||||
|
if retryTimes < 4 { |
||||||
|
return retryTimes, fmt.Errorf("test error: %d", retryTimes) |
||||||
|
} |
||||||
|
return retryTimes, nil |
||||||
|
}) |
||||||
|
if err != nil { |
||||||
|
t.Error(err) |
||||||
|
return |
||||||
|
} |
||||||
|
if r != 4 { |
||||||
|
t.Errorf("error result: %d", r) |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue