14 changed files with 495 additions and 27 deletions
@ -1,20 +1,48 @@
|
||||
package indicator |
||||
|
||||
import ( |
||||
vmts "sig-pub/pkg/storage/tsdb/victoria_metrics" |
||||
"sync" |
||||
) |
||||
|
||||
// load indicator plugin
|
||||
|
||||
// 热指标 自动加载/实时更新/内存缓存
|
||||
// 历史指标 实时计算
|
||||
// 自定义插件化指标
|
||||
|
||||
type IndicatorService struct { |
||||
vmdb vmts.VictoriaMetricsTSDB |
||||
} |
||||
|
||||
func NewIndicatorService() *IndicatorService { |
||||
return &IndicatorService{} |
||||
func NewIndicatorService(vmdb vmts.VictoriaMetricsTSDB) *IndicatorService { |
||||
return &IndicatorService{ |
||||
vmdb: vmdb, |
||||
} |
||||
} |
||||
|
||||
// 加载热指标
|
||||
// 订阅k线数据 更新指标
|
||||
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