You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

154 lines
4.5 KiB

package strategy
import (
"sort"
"sig-pub/pkg/types"
)
// MeanReversionV1
type MeanReversionV1 struct {
IIntervalSigStrategy
interval types.Interval
period int
threshold float64
buckets int
dominanceRatio float64 // POC volume dominance ratio (vs 2nd highest)
rsiPeriod int
rsiThreshold float64
}
func (s *MeanReversionV1) New() ISigStrategy {
return &MeanReversionV1{}
}
func (s *MeanReversionV1) Meta() StrategyMeta {
return StrategyMeta{
Name: "MeanReversionV1",
Desc: "VRVP Mean Reversion Strategy with Volume Dominance and RSI Filter",
Input: []types.InputArg{
{Name: "interval", Type: types.InputTypeString, Desc: "Target Interval (e.g., 1m, 1h)", Default: "1m"},
{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},
{Name: "dominance_ratio", Type: types.InputTypeUFloat, Desc: "POC Volume Dominance Ratio (e.g. 1.2)", Default: 1.2},
{Name: "rsi_period", Type: types.InputTypeInt, Desc: "RSI Period", Default: 14},
{Name: "rsi_threshold", Type: types.InputTypeUFloat, Desc: "RSI Threshold (e.g. 30 for 30/70)", Default: 30},
},
}
}
func (s *MeanReversionV1) Init(input types.Input) (err error) {
s.interval = types.Interval(input.String("interval"))
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
}
s.dominanceRatio = input.Float("dominance_ratio")
if s.dominanceRatio < 1.0 {
s.dominanceRatio = 1.0
}
s.rsiPeriod = input.Int("rsi_period")
if s.rsiPeriod <= 0 {
s.rsiPeriod = 14
}
s.rsiThreshold = input.Float("rsi_threshold")
if s.rsiThreshold <= 0 || s.rsiThreshold >= 50 {
s.rsiThreshold = 30 // Default to standard 30 (implying 70 upper)
}
return
}
func (s *MeanReversionV1) CandlePeriods(ctx IIntervalSigStrategyContext) (iss *types.IntervalState[int16]) {
iss = types.NewIntervalState[int16]()
// We need enough candles for both VRVP and RSI
// VRVP needs 'period' candles.
// RSI needs 'rsiPeriod' candles (maybe +1).
// To be safe, we take the max.
needed := int16(s.period)
if int16(s.rsiPeriod+5) > needed {
needed = int16(s.rsiPeriod + 5)
}
iss.Set(s.interval, needed)
return
}
func (s *MeanReversionV1) Update(ctx IIntervalSigStrategyContext) (side types.Side) {
// 1. Get VRVP Summary
summaryObj := ctx.SummaryIndicator(s.interval, "VRVP", map[string]any{"buckets": s.buckets})
// Calculate for the last 'period' candles
summaryAny, ok := summaryObj.Summary(0, int16(s.period))
if !ok {
return
}
vrvpSummary, ok := summaryAny.(*types.VRVPSummary)
if !ok || vrvpSummary == nil || len(vrvpSummary.Buckets) < 2 {
return
}
// 2. Find POC (Point of Control) and Second Highest Volume
// Create a slice of buckets to sort
type volBucket struct {
Price float64
Volume float64
}
sortedBuckets := make([]volBucket, len(vrvpSummary.Buckets))
for i, b := range vrvpSummary.Buckets {
sortedBuckets[i] = volBucket{Price: b.Price, Volume: b.Volume}
}
// Sort descending by volume
sort.Slice(sortedBuckets, func(i, j int) bool {
return sortedBuckets[i].Volume > sortedBuckets[j].Volume
})
pocBucket := sortedBuckets[0]
secondBucket := sortedBuckets[1]
// Check Dominance
if pocBucket.Volume < secondBucket.Volume*s.dominanceRatio {
// POC is not dominant enough
return
}
pocPrice := pocBucket.Price
if pocPrice <= 0 {
return
}
// 3. Get Current Price
k := ctx.Get(s.interval, 0)
currentPrice := k.CloseF64()
// 4. Calculate RSI for Confirmation
rsiSeries := ctx.Indicator(s.interval, "RSI", map[string]any{"window": s.rsiPeriod})
currentRSI := rsiSeries.Get(0)
// 5. Generate Signal
deviation := (currentPrice - pocPrice) / pocPrice
if deviation > s.threshold {
// Price is significantly higher than POC, expect reversion (Sell)
// Filter: RSI should be overbought (> 100 - threshold, e.g. > 70)
if currentRSI > (100 - s.rsiThreshold) {
side = types.SideShort
}
} else if deviation < -s.threshold {
// Price is significantly lower than POC, expect reversion (Buy)
// Filter: RSI should be oversold (< threshold, e.g. < 30)
if currentRSI < s.rsiThreshold {
side = types.SideLong
}
}
return
}