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