|
|
|
@ -1,16 +1,16 @@ |
|
|
|
package strategy |
|
|
|
package strategy |
|
|
|
|
|
|
|
|
|
|
|
import ( |
|
|
|
import ( |
|
|
|
"math" |
|
|
|
|
|
|
|
"sig-pub/pkg/types" |
|
|
|
"sig-pub/pkg/types" |
|
|
|
"sig-pub/pkg/zlog" |
|
|
|
|
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// MeanReversionV1
|
|
|
|
// MeanReversionV1
|
|
|
|
type MeanReversionV1 struct { |
|
|
|
type MeanReversionV1 struct { |
|
|
|
IIntervalSigStrategy |
|
|
|
IIntervalSigStrategy |
|
|
|
rate float64 |
|
|
|
interval types.Interval |
|
|
|
rate2 float64 |
|
|
|
period int |
|
|
|
|
|
|
|
threshold float64 |
|
|
|
|
|
|
|
buckets int |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *MeanReversionV1) New() ISigStrategy { |
|
|
|
func (s *MeanReversionV1) New() ISigStrategy { |
|
|
|
@ -20,44 +20,86 @@ func (s *MeanReversionV1) New() ISigStrategy { |
|
|
|
func (s *MeanReversionV1) Meta() StrategyMeta { |
|
|
|
func (s *MeanReversionV1) Meta() StrategyMeta { |
|
|
|
return StrategyMeta{ |
|
|
|
return StrategyMeta{ |
|
|
|
Name: "MeanReversionV1", |
|
|
|
Name: "MeanReversionV1", |
|
|
|
Desc: "均值回归策略v1", |
|
|
|
Desc: "VRVP Mean Reversion Strategy", |
|
|
|
Input: []types.InputArg{ |
|
|
|
Input: []types.InputArg{ |
|
|
|
{Name: "rate", Type: types.InputTypeUFloat, Desc: "上线影线与基线比例"}, |
|
|
|
{Name: "interval", Type: types.InputTypeString, Desc: "Target Interval (e.g., 1m, 1h)", Default: "1m"}, |
|
|
|
{Name: "rate2", Type: types.InputTypeUFloat, Desc: "上线影线之间比例"}, |
|
|
|
{Name: "period", Type: types.InputTypeInt, Desc: "VRVP calculation window", Default: 100}, |
|
|
|
|
|
|
|
{Name: "threshold", Type: types.InputTypeUFloat, Desc: "Reversion Threshold Ratio (e.g. 0.01)", Default: 0.01}, |
|
|
|
|
|
|
|
{Name: "buckets", Type: types.InputTypeInt, Desc: "VRVP Buckets", Default: 24}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *MeanReversionV1) Init(input types.Input) (err error) { // 校验参数, 并根据参数初始化策略
|
|
|
|
func (s *MeanReversionV1) Init(input types.Input) (err error) { |
|
|
|
s.rate = input.Float("rate") |
|
|
|
s.interval = types.Interval(input.String("interval")) |
|
|
|
s.rate2 = input.Float("rate2") |
|
|
|
if _, ok := types.SupportedIntervals[s.interval]; !ok { |
|
|
|
|
|
|
|
s.interval = types.Interval1m |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
s.period = input.Int("period") |
|
|
|
|
|
|
|
if s.period <= 0 { |
|
|
|
|
|
|
|
s.period = 100 |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
s.threshold = input.Float("threshold") |
|
|
|
|
|
|
|
s.buckets = input.Int("buckets") |
|
|
|
|
|
|
|
if s.buckets <= 0 { |
|
|
|
|
|
|
|
s.buckets = 24 |
|
|
|
|
|
|
|
} |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *MeanReversionV1) CandlePeriods(ctx IIntervalSigStrategyContext) (iss *types.IntervalState[int16]) { |
|
|
|
func (s *MeanReversionV1) CandlePeriods(ctx IIntervalSigStrategyContext) (iss *types.IntervalState[int16]) { |
|
|
|
iss = types.NewIntervalState[int16]() |
|
|
|
iss = types.NewIntervalState[int16]() |
|
|
|
iss.Set(types.Interval5m, 1) |
|
|
|
iss.Set(s.interval, int16(s.period)) |
|
|
|
iss.Set(types.Interval15m, 2) |
|
|
|
|
|
|
|
iss.Set(types.Interval30m, 2) |
|
|
|
|
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (s *MeanReversionV1) Update(ctx IIntervalSigStrategyContext) (side types.Side) { |
|
|
|
func (s *MeanReversionV1) Update(ctx IIntervalSigStrategyContext) (side types.Side) { |
|
|
|
// O 109744.8 H 110600 L 109507.5 C 109686.8
|
|
|
|
// Calculate VRVP period candles
|
|
|
|
k0 := ctx.Get("5m", 0) |
|
|
|
summaryObj := ctx.SummaryIndicator(s.interval, "VRVP", map[string]any{"buckets": s.buckets}) |
|
|
|
open, close, high, low := k0.OpenF64(), k0.CloseF64(), k0.HighF64(), k0.LowF64() |
|
|
|
summaryAny, ok := summaryObj.Summary(0, int16(s.period)) |
|
|
|
base := math.Abs(open - close) |
|
|
|
if !ok { |
|
|
|
rup := (high - max(open, close)) / base |
|
|
|
return |
|
|
|
rdown := (min(open, close) - low) / base |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
if k0.Ts == 1761833700000 { |
|
|
|
vrvpSummary, ok := summaryAny.(*types.VRVPSummary) |
|
|
|
zlog.Debugf("base=%.4f, rup=%.4f, rdown=%.4f", base, rup, rdown) |
|
|
|
if !ok || vrvpSummary == nil || len(vrvpSummary.Buckets) == 0 { |
|
|
|
|
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
if rup > s.rate && rup/rdown > s.rate2 { |
|
|
|
|
|
|
|
return types.SideLong |
|
|
|
// Find POC (Point of Control) - Bucket with max volume
|
|
|
|
|
|
|
|
var maxVol float64 |
|
|
|
|
|
|
|
var pocPrice float64 |
|
|
|
|
|
|
|
found := false |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for _, bucket := range vrvpSummary.Buckets { |
|
|
|
|
|
|
|
if bucket.Volume > maxVol { |
|
|
|
|
|
|
|
maxVol = bucket.Volume |
|
|
|
|
|
|
|
pocPrice = bucket.Price |
|
|
|
|
|
|
|
found = true |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if rdown > s.rate && rdown/rup > s.rate2 { |
|
|
|
|
|
|
|
return types.SideShort |
|
|
|
if !found { |
|
|
|
|
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Get Current Price
|
|
|
|
|
|
|
|
k := ctx.Get(s.interval, 0) |
|
|
|
|
|
|
|
currentPrice := k.CloseF64() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Deviation ratio
|
|
|
|
|
|
|
|
if pocPrice <= 0 { |
|
|
|
|
|
|
|
return |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
deviation := (currentPrice - pocPrice) / pocPrice |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if deviation > s.threshold { |
|
|
|
|
|
|
|
// 当前价格高于 POC, 预期回落
|
|
|
|
|
|
|
|
side = types.SideShort |
|
|
|
|
|
|
|
} else if deviation < -s.threshold { |
|
|
|
|
|
|
|
// 当前价格低于 POC, 预期回升
|
|
|
|
|
|
|
|
side = types.SideLong |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
|