24 changed files with 647 additions and 227 deletions
@ -0,0 +1,47 @@ |
|||||||
|
package sig |
||||||
|
|
||||||
|
import ( |
||||||
|
"sig-pub/pkg/indicator" |
||||||
|
"sig-pub/pkg/types" |
||||||
|
"sig-pub/pkg/types/series" |
||||||
|
) |
||||||
|
|
||||||
|
// IndicatorState
|
||||||
|
// ema, obv 指标递归计算时的状态存储
|
||||||
|
type IndicatorState struct { |
||||||
|
indicator.IIndicatorState |
||||||
|
kSeries *KlineSeries |
||||||
|
state map[string]*types.RingSeries[float64] |
||||||
|
} |
||||||
|
|
||||||
|
func NewIndicatorState(kSeries *KlineSeries) *IndicatorState { |
||||||
|
return &IndicatorState{ |
||||||
|
kSeries: kSeries, |
||||||
|
state: make(map[string]*types.RingSeries[float64]), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *IndicatorState) ring(k string) *types.RingSeries[float64] { |
||||||
|
ring, ok := s.state[k] |
||||||
|
if !ok { |
||||||
|
ring = types.NewRingSeries[float64](indicator.MaxWindow, 8) |
||||||
|
s.state[k] = ring |
||||||
|
// 从 kSeries0 开始 calc ind 初始化
|
||||||
|
// 递归初始值
|
||||||
|
} |
||||||
|
return ring |
||||||
|
} |
||||||
|
|
||||||
|
func (s *IndicatorState) Set(k string, v float64) { |
||||||
|
s.ring(k).Push(v) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *IndicatorState) Get(k string, offset int16) (v float64, ok bool) { |
||||||
|
offset -= 1 |
||||||
|
return s.ring(k).Get(int(offset)) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *IndicatorState) Series(k string, offset, count int16) (v series.Floats, ok bool) { |
||||||
|
offset -= 1 |
||||||
|
return s.ring(k).Series(int(offset), int(count)) |
||||||
|
} |
||||||
@ -0,0 +1,41 @@ |
|||||||
|
package indicator |
||||||
|
|
||||||
|
import "sig-pub/pkg/types" |
||||||
|
|
||||||
|
// OBV 成交量平衡指标
|
||||||
|
// 1. 初始状态:OBV_0 = 0。
|
||||||
|
// 2. 若 Close_t > Close_{t-1}:OBV_t = OBV_{t-1} + Volume_t。
|
||||||
|
// 3. 若 Close_t < Close_{t-1}:OBV_t = OBV_{t-1} - Volume_t。
|
||||||
|
// 4. 平盘:OBV_t = OBV_{t-1}。
|
||||||
|
// 状态:前一 OBV 值。用于判断资金流入/流出
|
||||||
|
type OBV struct { |
||||||
|
} |
||||||
|
|
||||||
|
func (c *OBV) Meta() IndicatorMeta { |
||||||
|
return IndicatorMeta{ |
||||||
|
Name: "obv", |
||||||
|
Input: []types.InputArg{}, // todo 无参指标tsdb存储
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *OBV) CandlePeriods(ctx IIndicatorContext) int16 { |
||||||
|
return 2 |
||||||
|
} |
||||||
|
|
||||||
|
func (c *OBV) Calculate(ctx IIndicatorContext) (vector float64) { |
||||||
|
obvPrev, ok := ctx.State().Get("obv", 1) |
||||||
|
if !ok { |
||||||
|
obvPrev = 0 |
||||||
|
} |
||||||
|
k := ctx.Get(0) |
||||||
|
cmp := k.Close.Cmp(ctx.Get(1).Close) |
||||||
|
if cmp > 0 { |
||||||
|
vector = obvPrev + k.VolF64() |
||||||
|
} else if cmp < 0 { |
||||||
|
vector = obvPrev - k.VolF64() |
||||||
|
} else { |
||||||
|
vector = obvPrev |
||||||
|
} |
||||||
|
ctx.State().Set("obv", vector) |
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
package indicator |
||||||
|
|
||||||
|
import "sig-pub/pkg/types" |
||||||
|
|
||||||
|
// WOBV 波动加权 OBV
|
||||||
|
// 1. 状态:WOBV_{t-1}。
|
||||||
|
// 2. 更新:WOBV_t = WOBV_{t-1} + [ (Close - Open) / (High - Low) × Volume_t ]。
|
||||||
|
// https://www.95sca.cn/archives/76688
|
||||||
|
// WOBV小策略: https://zhuanlan.zhihu.com/p/422341694
|
||||||
|
type WOBV struct { |
||||||
|
} |
||||||
|
|
||||||
|
func (c *WOBV) Meta() IndicatorMeta { |
||||||
|
return IndicatorMeta{ |
||||||
|
Name: "wobv", |
||||||
|
Input: []types.InputArg{}, // todo 无参指标tsdb存储
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *WOBV) CandlePeriods(ctx IIndicatorContext) int16 { |
||||||
|
return 2 |
||||||
|
} |
||||||
|
|
||||||
|
func (c *WOBV) Calculate(ctx IIndicatorContext) (vector float64) { |
||||||
|
wobvPrev, ok := ctx.State().Get("wobv", 1) |
||||||
|
if !ok { |
||||||
|
wobvPrev = 0 |
||||||
|
} |
||||||
|
|
||||||
|
k := ctx.Get(0) |
||||||
|
wf := (k.CloseF64() - k.OpenF64()) / (k.HighF64() - k.LowF64()) |
||||||
|
wobv := wobvPrev + wf*k.VolF64() |
||||||
|
ctx.State().Set("wobv", wobv) |
||||||
|
|
||||||
|
vector = wobv |
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,69 @@ |
|||||||
|
package types |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
// RingSeries 环形数组, 后入先出
|
||||||
|
type RingSeries[T any] struct { |
||||||
|
values []T |
||||||
|
capacity int // 总长度
|
||||||
|
length int // 当前长度
|
||||||
|
head int // 下一个读取位置
|
||||||
|
tail int // 下一个丢弃位置
|
||||||
|
full bool // 环形数组是否已满
|
||||||
|
} |
||||||
|
|
||||||
|
func NewRingSeries[T any](capacity, init int) *RingSeries[T] { |
||||||
|
if capacity <= 0 { |
||||||
|
panic(fmt.Errorf("ring series capacity must > 0")) |
||||||
|
} |
||||||
|
return &RingSeries[T]{ |
||||||
|
capacity: capacity, |
||||||
|
values: make([]T, 0, init), |
||||||
|
head: -1, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (r *RingSeries[T]) Length() int { |
||||||
|
return r.length |
||||||
|
} |
||||||
|
|
||||||
|
func (r *RingSeries[T]) Push(v T) (ok bool) { |
||||||
|
if !r.full { |
||||||
|
r.values = append(r.values, v) |
||||||
|
r.head++ |
||||||
|
r.length++ |
||||||
|
r.full = r.length == r.capacity |
||||||
|
return |
||||||
|
} |
||||||
|
r.values[r.tail] = v |
||||||
|
r.head = r.tail |
||||||
|
r.tail = (r.tail + 1) % r.capacity |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Get 0当前, 1前一个
|
||||||
|
func (r *RingSeries[T]) Get(offset int) (v T, ok bool) { |
||||||
|
if offset < 0 || offset >= r.length || r.length == 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
i := r.head - offset |
||||||
|
if i < 0 { |
||||||
|
i = i + r.capacity |
||||||
|
} |
||||||
|
return r.values[i], true |
||||||
|
} |
||||||
|
|
||||||
|
func (r *RingSeries[T]) Series(offset, count int) (v []T, ok bool) { |
||||||
|
if offset < 0 || offset >= r.length || count <= 0 || count > r.length { |
||||||
|
return |
||||||
|
} |
||||||
|
v = make([]T, count) |
||||||
|
for i := range count { |
||||||
|
v[i], ok = r.Get(offset) |
||||||
|
if !ok { |
||||||
|
return |
||||||
|
} |
||||||
|
offset++ |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
package types |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func TestRingSeries(t *testing.T) { |
||||||
|
rs1 := NewRingSeries[int](1, 1) |
||||||
|
rs1.Push(1) |
||||||
|
r1, ok := rs1.Get(0) |
||||||
|
if !ok { |
||||||
|
t.Error(ok) |
||||||
|
return |
||||||
|
} |
||||||
|
if r1 != 1 { |
||||||
|
t.Error(r1) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
rs2 := NewRingSeries[int](10, 3) |
||||||
|
for i := range 11 { |
||||||
|
rs2.Push(i) |
||||||
|
} |
||||||
|
for i := range 10 { |
||||||
|
fmt.Println(rs2.Get(i)) |
||||||
|
} |
||||||
|
fmt.Println("--------------------") |
||||||
|
fmt.Println(rs2.Series(0, 1)) |
||||||
|
} |
||||||
Loading…
Reference in new issue