diff --git a/config/fmaker.yaml b/config/fmaker.yaml new file mode 100644 index 000000000..e1993fab3 --- /dev/null +++ b/config/fmaker.yaml @@ -0,0 +1,26 @@ +sessions: + binance: + exchange: binance + envVarPrefix: binance + + +exchangeStrategies: +- on: binance + fmaker: + symbol: BTCUSDT + interval: 1m + spread: 0.15% + amount: 300 # 11 + +backtest: + sessions: + - binance + startTime: "2022-01-01" + endTime: "2022-05-31" + symbols: + - BTCUSDT + account: + binance: + balances: + BTC: 1 # 1 + USDT: 45_000 # 30_000 diff --git a/pkg/cmd/builtin.go b/pkg/cmd/builtin.go index c8f9fbbc7..1c9164694 100644 --- a/pkg/cmd/builtin.go +++ b/pkg/cmd/builtin.go @@ -10,6 +10,7 @@ import ( _ "github.com/c9s/bbgo/pkg/strategy/ewoDgtrd" _ "github.com/c9s/bbgo/pkg/strategy/factorzoo" _ "github.com/c9s/bbgo/pkg/strategy/flashcrash" + _ "github.com/c9s/bbgo/pkg/strategy/fmaker" _ "github.com/c9s/bbgo/pkg/strategy/funding" _ "github.com/c9s/bbgo/pkg/strategy/grid" _ "github.com/c9s/bbgo/pkg/strategy/kline" diff --git a/pkg/strategy/fmaker/A18.go b/pkg/strategy/fmaker/A18.go new file mode 100644 index 000000000..e0c456a21 --- /dev/null +++ b/pkg/strategy/fmaker/A18.go @@ -0,0 +1,90 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +//go:generate callbackgen -type A18 +type A18 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *A18) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *A18) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateA18(recentT, indicator.KLineClosePriceMapper) + if err != nil { + log.WithError(err).Error("can not calculate") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *A18) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *A18) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +// CLOSE/DELAY(CLOSE,5) +func calculateA18(klines []types.KLine, valClose KLineValueMapper) (float64, error) { + window := 5 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var closes types.Float64Slice + + for _, k := range klines { + closes.Push(valClose(k)) + } + + delay5 := closes.Index(4) + curr := closes.Index(0) + alpha := curr / delay5 + + return alpha, nil +} diff --git a/pkg/strategy/fmaker/A2.go b/pkg/strategy/fmaker/A2.go new file mode 100644 index 000000000..8f7239259 --- /dev/null +++ b/pkg/strategy/fmaker/A2.go @@ -0,0 +1,102 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +//go:generate callbackgen -type A2 +type A2 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *A2) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *A2) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateA2(recentT, KLineLowPriceMapper, KLineHighPriceMapper, indicator.KLineClosePriceMapper) + if err != nil { + log.WithError(err).Error("can not calculate") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *A2) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *A2) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +// (-1 * DELTA((((CLOSE - LOW) - (HIGH - CLOSE)) / (HIGH - LOW)), 1)) +func calculateA2(klines []types.KLine, valLow KLineValueMapper, valHigh KLineValueMapper, valClose KLineValueMapper) (float64, error) { + window := 2 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var lows types.Float64Slice + var highs types.Float64Slice + var closes types.Float64Slice + + for _, k := range klines { + lows.Push(valLow(k)) + highs.Push(valHigh(k)) + closes.Push(valClose(k)) + } + + prev := ((closes.Index(1) - lows.Index(1)) - (highs.Index(1) - closes.Index(1))) / (highs.Index(1) - lows.Index(1)) + curr := ((closes.Index(0) - lows.Index(0)) - (highs.Index(0) - closes.Index(0))) / (highs.Index(0) - lows.Index(0)) + alpha := (curr - prev) * -1 // delta(1 interval) + + return alpha, nil +} + +func KLineLowPriceMapper(k types.KLine) float64 { + return k.Low.Float64() +} + +func KLineHighPriceMapper(k types.KLine) float64 { + return k.High.Float64() +} diff --git a/pkg/strategy/fmaker/A3.go b/pkg/strategy/fmaker/A3.go new file mode 100644 index 000000000..35e0cc3b8 --- /dev/null +++ b/pkg/strategy/fmaker/A3.go @@ -0,0 +1,108 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "math" + "time" +) + +//go:generate callbackgen -type A3 +type A3 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *A3) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *A3) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateA3(recentT, KLineLowPriceMapper, KLineHighPriceMapper, indicator.KLineClosePriceMapper) + if err != nil { + log.WithError(err).Error("can not calculate pivots") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *A3) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *A3) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +// SUM((CLOSE = DELAY(CLOSE, 1)?0:CLOSE-(CLOSE>DELAY(CLOSE, 1)?MIN(LOW, DELAY(CLOSE, 1)):MAX(HIGH, DELAY(CLOSE, 1)))), 6) +func calculateA3(klines []types.KLine, valLow KLineValueMapper, valHigh KLineValueMapper, valClose KLineValueMapper) (float64, error) { + window := 6 + 2 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var lows types.Float64Slice + var highs types.Float64Slice + var closes types.Float64Slice + + for _, k := range klines { + lows.Push(valLow(k)) + highs.Push(valHigh(k)) + closes.Push(valClose(k)) + } + + a := 0. + sumA := 0. + for i := 1; i <= 6; i++ { + if closes.Index(len(closes)-i) == closes.Index(len(closes)-i-1) { + a = 0. + } else { + if closes.Index(len(closes)-i) > closes.Index(1) { + a = closes.Index(len(closes)-i) - math.Min(lows.Index(len(lows)-i), closes.Index(len(closes)-i-1)) + } else { + a = closes.Index(len(closes)-i) - math.Max(highs.Index(len(highs)-i), closes.Index(len(closes)-i-1)) + } + } + sumA += a + } + + alpha := sumA // sum(a, 6 interval) + + return alpha, nil +} diff --git a/pkg/strategy/fmaker/A34.go b/pkg/strategy/fmaker/A34.go new file mode 100644 index 000000000..5062c1317 --- /dev/null +++ b/pkg/strategy/fmaker/A34.go @@ -0,0 +1,96 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +//go:generate callbackgen -type A34 +type A34 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *A34) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *A34) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateA34(recentT, indicator.KLineClosePriceMapper) + if err != nil { + log.WithError(err).Error("can not calculate pivots") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *A34) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *A34) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateA34(klines []types.KLine, valClose KLineValueMapper) (float64, error) { + window := 12 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var closes types.Float64Slice + + for _, k := range klines { + closes.Push(valClose(k)) + } + + c := closes.Last() + + sumC := 0. + for i := 1; i <= 12; i++ { + sumC += closes.Index(len(closes) - i) + } + + meanC := sumC / 12 + + alpha := meanC / c + + return alpha, nil +} diff --git a/pkg/strategy/fmaker/R.go b/pkg/strategy/fmaker/R.go new file mode 100644 index 000000000..278dfdcaa --- /dev/null +++ b/pkg/strategy/fmaker/R.go @@ -0,0 +1,93 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +var zeroTime time.Time + +type KLineValueMapper func(k types.KLine) float64 + +//go:generate callbackgen -type R +type R struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *R) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *R) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateR(recentT, indicator.KLineOpenPriceMapper, indicator.KLineClosePriceMapper) + if err != nil { + log.WithError(err).Error("can not calculate pivots") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *R) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *R) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateR(klines []types.KLine, valOpen KLineValueMapper, valClose KLineValueMapper) (float64, error) { + window := 1 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var opens types.Float64Slice + var closes types.Float64Slice + + for _, k := range klines { + opens.Push(valOpen(k)) + closes.Push(valClose(k)) + } + + ret := opens.Index(0)/closes.Index(0) - 1 // delta(1 interval) + + return ret, nil +} diff --git a/pkg/strategy/fmaker/S0.go b/pkg/strategy/fmaker/S0.go new file mode 100644 index 000000000..78ea60522 --- /dev/null +++ b/pkg/strategy/fmaker/S0.go @@ -0,0 +1,88 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +//go:generate callbackgen -type S0 +type S0 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *S0) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *S0) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateS0(recentT, indicator.KLineClosePriceMapper) + if err != nil { + log.WithError(err).Error("can not calculate") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *S0) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *S0) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateS0(klines []types.KLine, valClose KLineValueMapper) (float64, error) { + window := 20 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var closes types.Float64Slice + + for _, k := range klines { + closes.Push(valClose(k)) + } + + sma := types.Float64Slice.Sum(closes[len(closes)-window:len(closes)-1]) / float64(window) + alpha := sma / closes.Last() + + return alpha, nil +} diff --git a/pkg/strategy/fmaker/S1.go b/pkg/strategy/fmaker/S1.go new file mode 100644 index 000000000..85fdac13d --- /dev/null +++ b/pkg/strategy/fmaker/S1.go @@ -0,0 +1,99 @@ +package fmaker + +import ( + "fmt" + "math" + "time" + + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" +) + +//go:generate callbackgen -type S1 +type S1 struct { + types.IntervalWindow + Values types.Float64Slice + EndTime time.Time + + UpdateCallbacks []func(value float64) +} + +func (inc *S1) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *S1) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + correlation, err := calculateS1(recentT, inc.Window, KLineAmplitudeMapper, indicator.KLineVolumeMapper) + if err != nil { + log.WithError(err).Error("can not calculate correlation") + return + } + inc.Values.Push(correlation) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(correlation) +} + +func (inc *S1) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *S1) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateS1(klines []types.KLine, window int, valA KLineValueMapper, valB KLineValueMapper) (float64, error) { + length := len(klines) + if length == 0 || length < window { + return 0.0, fmt.Errorf("insufficient elements for calculating VOL with window = %d", window) + } + + sumA, sumB, sumAB, squareSumA, squareSumB := 0., 0., 0., 0., 0. + for _, k := range klines { + // sum of elements of array A + sumA += valA(k) + // sum of elements of array B + sumB += valB(k) + + // sum of A[i] * B[i]. + sumAB = sumAB + valA(k)*valB(k) + + // sum of square of array elements. + squareSumA = squareSumA + valA(k)*valA(k) + squareSumB = squareSumB + valB(k)*valB(k) + } + // use formula for calculating correlation coefficient. + corr := (float64(window)*sumAB - sumA*sumB) / + math.Sqrt((float64(window)*squareSumA-sumA*sumA)*(float64(window)*squareSumB-sumB*sumB)) + + return -corr, nil +} + +func KLineAmplitudeMapper(k types.KLine) float64 { + return k.High.Div(k.Low).Float64() +} diff --git a/pkg/strategy/fmaker/S2.go b/pkg/strategy/fmaker/S2.go new file mode 100644 index 000000000..b52f49c11 --- /dev/null +++ b/pkg/strategy/fmaker/S2.go @@ -0,0 +1,95 @@ +package fmaker + +import ( + "fmt" + "math" + "time" + + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" +) + +//go:generate callbackgen -type S2 +type S2 struct { + types.IntervalWindow + Values types.Float64Slice + EndTime time.Time + + UpdateCallbacks []func(value float64) +} + +func (inc *S2) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *S2) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + correlation, err := calculateS2(recentT, inc.Window, indicator.KLineOpenPriceMapper, indicator.KLineVolumeMapper) + if err != nil { + log.WithError(err).Error("can not calculate correlation") + return + } + inc.Values.Push(correlation) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(correlation) +} + +func (inc *S2) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *S2) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateS2(klines []types.KLine, window int, valA KLineValueMapper, valB KLineValueMapper) (float64, error) { + length := len(klines) + if length == 0 || length < window { + return 0.0, fmt.Errorf("insufficient elements for calculating VOL with window = %d", window) + } + + sumA, sumB, sumAB, squareSumA, squareSumB := 0., 0., 0., 0., 0. + for _, k := range klines { + // sum of elements of array A + sumA += valA(k) + // sum of elements of array B + sumB += valB(k) + + // sum of A[i] * B[i]. + sumAB = sumAB + valA(k)*valB(k) + + // sum of square of array elements. + squareSumA = squareSumA + valA(k)*valA(k) + squareSumB = squareSumB + valB(k)*valB(k) + } + // use formula for calculating correlation coefficient. + corr := (float64(window)*sumAB - sumA*sumB) / + math.Sqrt((float64(window)*squareSumA-sumA*sumA)*(float64(window)*squareSumB-sumB*sumB)) + + return -corr, nil +} diff --git a/pkg/strategy/fmaker/S3.go b/pkg/strategy/fmaker/S3.go new file mode 100644 index 000000000..bd585d48c --- /dev/null +++ b/pkg/strategy/fmaker/S3.go @@ -0,0 +1,91 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +//go:generate callbackgen -type S3 +type S3 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *S3) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *S3) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateS3(recentT, indicator.KLineClosePriceMapper, indicator.KLineOpenPriceMapper) + if err != nil { + log.WithError(err).Error("can not calculate") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *S3) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *S3) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateS3(klines []types.KLine, valClose KLineValueMapper, valOpen KLineValueMapper) (float64, error) { + window := 2 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var closes types.Float64Slice + var opens types.Float64Slice + + for _, k := range klines { + closes.Push(valClose(k)) + opens.Push(valOpen(k)) + } + + prevC := closes.Index(1) + currO := opens.Index(0) + alpha := currO / prevC + + return alpha, nil +} diff --git a/pkg/strategy/fmaker/S4.go b/pkg/strategy/fmaker/S4.go new file mode 100644 index 000000000..5e204dc0d --- /dev/null +++ b/pkg/strategy/fmaker/S4.go @@ -0,0 +1,88 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +//go:generate callbackgen -type S4 +type S4 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *S4) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *S4) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateS4(recentT, indicator.KLineClosePriceMapper) + if err != nil { + log.WithError(err).Error("can not calculate") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *S4) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *S4) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateS4(klines []types.KLine, valClose KLineValueMapper) (float64, error) { + window := 2 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var closes types.Float64Slice + + for _, k := range klines { + closes.Push(valClose(k)) + } + + currC := closes.Index(0) + alpha := 1 / currC + + return alpha, nil +} diff --git a/pkg/strategy/fmaker/S5.go b/pkg/strategy/fmaker/S5.go new file mode 100644 index 000000000..0cc4c54b8 --- /dev/null +++ b/pkg/strategy/fmaker/S5.go @@ -0,0 +1,96 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +//go:generate callbackgen -type S5 +type S5 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *S5) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *S5) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateS5(recentT, indicator.KLineVolumeMapper) + if err != nil { + log.WithError(err).Error("can not calculate pivots") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *S5) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *S5) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateS5(klines []types.KLine, valVolume KLineValueMapper) (float64, error) { + window := 10 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var volumes types.Float64Slice + + for _, k := range klines { + volumes.Push(valVolume(k)) + } + + v := volumes.Last() + + sumV := 0. + for i := 1; i <= 10; i++ { + sumV += volumes.Index(len(volumes) - i) + } + + meanV := sumV / 10 + + alpha := -v / meanV + + return alpha, nil +} diff --git a/pkg/strategy/fmaker/S6.go b/pkg/strategy/fmaker/S6.go new file mode 100644 index 000000000..e4db9e4f2 --- /dev/null +++ b/pkg/strategy/fmaker/S6.go @@ -0,0 +1,98 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +//go:generate callbackgen -type S6 +type S6 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *S6) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *S6) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateS6(recentT, indicator.KLineHighPriceMapper, indicator.KLineLowPriceMapper, indicator.KLineClosePriceMapper, indicator.KLineVolumeMapper) + if err != nil { + log.WithError(err).Error("can not calculate") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *S6) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *S6) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateS6(klines []types.KLine, valHigh KLineValueMapper, valLow KLineValueMapper, valClose KLineValueMapper, valVolume KLineValueMapper) (float64, error) { + window := 2 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var highs types.Float64Slice + var lows types.Float64Slice + var closes types.Float64Slice + var volumes types.Float64Slice + + for _, k := range klines { + highs.Push(valHigh(k)) + lows.Push(valLow(k)) + closes.Push(valClose(k)) + volumes.Push(valVolume(k)) + + } + + H := highs.Last() + L := lows.Last() + C := closes.Last() + V := volumes.Last() + alpha := (H + L + C) / 3 * V + + return alpha, nil +} diff --git a/pkg/strategy/fmaker/S7.go b/pkg/strategy/fmaker/S7.go new file mode 100644 index 000000000..d5f0b5f70 --- /dev/null +++ b/pkg/strategy/fmaker/S7.go @@ -0,0 +1,92 @@ +package fmaker + +import ( + "fmt" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "time" +) + +//go:generate callbackgen -type S7 +type S7 struct { + types.IntervalWindow + + // Values + Values types.Float64Slice + + EndTime time.Time + + UpdateCallbacks []func(val float64) +} + +func (inc *S7) Last() float64 { + if len(inc.Values) == 0 { + return 0.0 + } + return inc.Values[len(inc.Values)-1] +} + +func (inc *S7) calculateAndUpdate(klines []types.KLine) { + if len(klines) < inc.Window { + return + } + + var end = len(klines) - 1 + var lastKLine = klines[end] + + if inc.EndTime != zeroTime && lastKLine.GetEndTime().Before(inc.EndTime) { + return + } + + var recentT = klines[end-(inc.Window-1) : end+1] + + val, err := calculateS7(recentT, indicator.KLineOpenPriceMapper, indicator.KLineClosePriceMapper) + if err != nil { + log.WithError(err).Error("can not calculate") + return + } + inc.Values.Push(val) + + if len(inc.Values) > indicator.MaxNumOfVOL { + inc.Values = inc.Values[indicator.MaxNumOfVOLTruncateSize-1:] + } + + inc.EndTime = klines[end].GetEndTime().Time() + + inc.EmitUpdate(val) + +} + +func (inc *S7) handleKLineWindowUpdate(interval types.Interval, window types.KLineWindow) { + if inc.Interval != interval { + return + } + + inc.calculateAndUpdate(window) +} + +func (inc *S7) Bind(updater indicator.KLineWindowUpdater) { + updater.OnKLineWindowUpdate(inc.handleKLineWindowUpdate) +} + +func calculateS7(klines []types.KLine, valOpen KLineValueMapper, valClose KLineValueMapper) (float64, error) { + window := 2 + length := len(klines) + if length == 0 || length < window { + return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) + } + var opens types.Float64Slice + var closes types.Float64Slice + + for _, k := range klines { + opens.Push(valOpen(k)) + closes.Push(valClose(k)) + + } + + O := opens.Last() + C := closes.Last() + alpha := -(1 - O/C) + + return alpha, nil +} diff --git a/pkg/strategy/fmaker/a18_callbacks.go b/pkg/strategy/fmaker/a18_callbacks.go new file mode 100644 index 000000000..c6bd0c45e --- /dev/null +++ b/pkg/strategy/fmaker/a18_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type A18"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *A18) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *A18) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/a2_callbacks.go b/pkg/strategy/fmaker/a2_callbacks.go new file mode 100644 index 000000000..d1fdf00f3 --- /dev/null +++ b/pkg/strategy/fmaker/a2_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type A2"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *A2) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *A2) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/a34_callbacks.go b/pkg/strategy/fmaker/a34_callbacks.go new file mode 100644 index 000000000..fb128efad --- /dev/null +++ b/pkg/strategy/fmaker/a34_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type A34"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *A34) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *A34) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/a3_callbacks.go b/pkg/strategy/fmaker/a3_callbacks.go new file mode 100644 index 000000000..ad83cd8be --- /dev/null +++ b/pkg/strategy/fmaker/a3_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type A3"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *A3) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *A3) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/r_callbacks.go b/pkg/strategy/fmaker/r_callbacks.go new file mode 100644 index 000000000..afc55e417 --- /dev/null +++ b/pkg/strategy/fmaker/r_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type R"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *R) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *R) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/s0_callbacks.go b/pkg/strategy/fmaker/s0_callbacks.go new file mode 100644 index 000000000..1d384c83b --- /dev/null +++ b/pkg/strategy/fmaker/s0_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type S0"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *S0) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *S0) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/s1_callbacks.go b/pkg/strategy/fmaker/s1_callbacks.go new file mode 100644 index 000000000..5d7eb0119 --- /dev/null +++ b/pkg/strategy/fmaker/s1_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type S1"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *S1) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *S1) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { + cb(value) + } +} diff --git a/pkg/strategy/fmaker/s2_callbacks.go b/pkg/strategy/fmaker/s2_callbacks.go new file mode 100644 index 000000000..c65a7af71 --- /dev/null +++ b/pkg/strategy/fmaker/s2_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type S2"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *S2) OnUpdate(cb func(value float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *S2) EmitUpdate(value float64) { + for _, cb := range inc.UpdateCallbacks { + cb(value) + } +} diff --git a/pkg/strategy/fmaker/s3_callbacks.go b/pkg/strategy/fmaker/s3_callbacks.go new file mode 100644 index 000000000..01a6ea01e --- /dev/null +++ b/pkg/strategy/fmaker/s3_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type S3"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *S3) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *S3) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/s4_callbacks.go b/pkg/strategy/fmaker/s4_callbacks.go new file mode 100644 index 000000000..0d0058440 --- /dev/null +++ b/pkg/strategy/fmaker/s4_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type S4"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *S4) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *S4) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/s5_callbacks.go b/pkg/strategy/fmaker/s5_callbacks.go new file mode 100644 index 000000000..65f7f9a8f --- /dev/null +++ b/pkg/strategy/fmaker/s5_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type S5"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *S5) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *S5) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/s6_callbacks.go b/pkg/strategy/fmaker/s6_callbacks.go new file mode 100644 index 000000000..33daec76e --- /dev/null +++ b/pkg/strategy/fmaker/s6_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type S6"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *S6) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *S6) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/s7_callbacks.go b/pkg/strategy/fmaker/s7_callbacks.go new file mode 100644 index 000000000..fec9457d7 --- /dev/null +++ b/pkg/strategy/fmaker/s7_callbacks.go @@ -0,0 +1,15 @@ +// Code generated by "callbackgen -type S7"; DO NOT EDIT. + +package fmaker + +import () + +func (inc *S7) OnUpdate(cb func(val float64)) { + inc.UpdateCallbacks = append(inc.UpdateCallbacks, cb) +} + +func (inc *S7) EmitUpdate(val float64) { + for _, cb := range inc.UpdateCallbacks { + cb(val) + } +} diff --git a/pkg/strategy/fmaker/strategy.go b/pkg/strategy/fmaker/strategy.go new file mode 100644 index 000000000..fa7693c94 --- /dev/null +++ b/pkg/strategy/fmaker/strategy.go @@ -0,0 +1,532 @@ +package fmaker + +import ( + "context" + "fmt" + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" + "github.com/sajari/regression" + "github.com/sirupsen/logrus" + "gonum.org/v1/gonum/floats" + "math" +) + +const ID = "fmaker" + +var fifteen = fixedpoint.NewFromInt(15) +var three = fixedpoint.NewFromInt(3) +var two = fixedpoint.NewFromInt(2) + +var log = logrus.WithField("strategy", ID) + +func init() { + bbgo.RegisterStrategy(ID, &Strategy{}) +} + +type IntervalWindowSetting struct { + types.IntervalWindow +} + +type Strategy struct { + *bbgo.Graceful + *bbgo.Notifiability + *bbgo.Persistence + + Environment *bbgo.Environment + Symbol string `json:"symbol"` + Market types.Market + Interval types.Interval `json:"interval"` + Quantity fixedpoint.Value `json:"quantity"` + + // persistence fields + Position *types.Position `json:"position,omitempty" persistence:"position"` + ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_stats"` + + Spread fixedpoint.Value `json:"spread" persistence:"spread"` + + activeMakerOrders *bbgo.LocalActiveOrderBook + //closePositionOrders *bbgo.LocalActiveOrderBook + orderStore *bbgo.OrderStore + tradeCollector *bbgo.TradeCollector + + session *bbgo.ExchangeSession + + bbgo.QuantityOrAmount + + S0 *S0 + S1 *S1 + S2 *S2 + S3 *S3 + S4 *S4 + S5 *S5 + S6 *S6 + S7 *S7 + + A2 *A2 + A3 *A3 + A18 *A18 + A34 *A34 + + R *R + + // StrategyController + bbgo.StrategyController +} + +func (s *Strategy) ID() string { + return ID +} + +func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { + log.Infof("subscribe %s", s.Symbol) + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: types.Interval15m}) + +} + +func (s *Strategy) placeOrder(ctx context.Context, price fixedpoint.Value, qty fixedpoint.Value, orderExecutor bbgo.OrderExecutor) { + submitOrder := types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeSell, + Type: types.OrderTypeLimit, + Price: price, + Quantity: qty, + } + createdOrders, err := orderExecutor.SubmitOrders(ctx, submitOrder) + if err != nil { + log.WithError(err).Errorf("can not place orders") + } + s.orderStore.Add(createdOrders...) + s.activeMakerOrders.Add(createdOrders...) + //s.tradeCollector.Process() +} + +func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { + base := s.Position.GetBase() + if base.IsZero() { + return fmt.Errorf("no opened %s position", s.Position.Symbol) + } + + // make it negative + quantity := base.Mul(percentage).Abs() + side := types.SideTypeBuy + if base.Sign() > 0 { + side = types.SideTypeSell + } + + if quantity.Compare(s.Market.MinQuantity) < 0 { + return fmt.Errorf("order quantity %v is too small, less than %v", quantity, s.Market.MinQuantity) + } + + submitOrder := types.SubmitOrder{ + Symbol: s.Symbol, + Side: side, + Type: types.OrderTypeMarket, + Quantity: quantity, + //Price: closePrice, + Market: s.Market, + } + + //s.Notify("Submitting %s %s order to close position by %v", s.Symbol, side.String(), percentage, submitOrder) + + createdOrders, err := s.session.Exchange.SubmitOrders(ctx, submitOrder) + if err != nil { + log.WithError(err).Errorf("can not place position close order") + } + + s.orderStore.Add(createdOrders...) + s.activeMakerOrders.Add(createdOrders...) + return err +} +func (s *Strategy) InstanceID() string { + return fmt.Sprintf("%s:%s", ID, s.Symbol) +} + +func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + // initial required information + s.session = session + //s.prevClose = fixedpoint.Zero + + // first we need to get market data store(cached market data) from the exchange session + //st, _ := session.MarketDataStore(s.Symbol) + + s.activeMakerOrders = bbgo.NewLocalActiveOrderBook(s.Symbol) + s.activeMakerOrders.BindStream(session.UserDataStream) + + //s.closePositionOrders = bbgo.NewLocalActiveOrderBook(s.Symbol) + //s.closePositionOrders.BindStream(session.UserDataStream) + + s.orderStore = bbgo.NewOrderStore(s.Symbol) + s.orderStore.BindStream(session.UserDataStream) + + if s.Position == nil { + s.Position = types.NewPositionFromMarket(s.Market) + } + + // calculate group id for orders + instanceID := s.InstanceID() + //s.groupID = util.FNV32(instanceID) + + // Always update the position fields + s.Position.Strategy = ID + s.Position.StrategyInstanceID = instanceID + + s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.Position, s.orderStore) + s.tradeCollector.OnTrade(func(trade types.Trade, profit, netProfit fixedpoint.Value) { + // StrategyController + if s.Status != types.StrategyStatusRunning { + return + } + + s.Notifiability.Notify(trade) + s.ProfitStats.AddTrade(trade) + + if profit.Compare(fixedpoint.Zero) == 0 { + s.Environment.RecordPosition(s.Position, trade, nil) + } else { + log.Infof("%s generated profit: %v", s.Symbol, profit) + p := s.Position.NewProfit(trade, profit, netProfit) + p.Strategy = ID + p.StrategyInstanceID = instanceID + s.Notify(&p) + + s.ProfitStats.AddProfit(p) + s.Notify(&s.ProfitStats) + + s.Environment.RecordPosition(s.Position, trade, &p) + } + }) + + s.tradeCollector.OnPositionUpdate(func(position *types.Position) { + log.Infof("position changed: %s", s.Position) + s.Notify(s.Position) + }) + s.tradeCollector.BindStream(session.UserDataStream) + st, _ := session.MarketDataStore(s.Symbol) + + riw := types.IntervalWindow{Window: 1, Interval: s.Interval} + s.R = &R{IntervalWindow: riw} + s.R.Bind(st) + + s0iw := types.IntervalWindow{Window: 20, Interval: s.Interval} + s.S0 = &S0{IntervalWindow: s0iw} + s.S0.Bind(st) + + s1iw := types.IntervalWindow{Window: 20, Interval: s.Interval} + s.S1 = &S1{IntervalWindow: s1iw} + s.S1.Bind(st) + + s2iw := types.IntervalWindow{Window: 20, Interval: s.Interval} + s.S2 = &S2{IntervalWindow: s2iw} + s.S2.Bind(st) + + s3iw := types.IntervalWindow{Window: 2, Interval: s.Interval} + s.S3 = &S3{IntervalWindow: s3iw} + s.S3.Bind(st) + + s4iw := types.IntervalWindow{Window: 2, Interval: s.Interval} + s.S4 = &S4{IntervalWindow: s4iw} + s.S4.Bind(st) + + s5iw := types.IntervalWindow{Window: 10, Interval: s.Interval} + s.S5 = &S5{IntervalWindow: s5iw} + s.S5.Bind(st) + + s6iw := types.IntervalWindow{Window: 2, Interval: s.Interval} + s.S6 = &S6{IntervalWindow: s6iw} + s.S6.Bind(st) + + s7iw := types.IntervalWindow{Window: 2, Interval: s.Interval} + s.S7 = &S7{IntervalWindow: s7iw} + s.S7.Bind(st) + + a2iw := types.IntervalWindow{Window: 2, Interval: s.Interval} + s.A2 = &A2{IntervalWindow: a2iw} + s.A2.Bind(st) + + a3iw := types.IntervalWindow{Window: 8, Interval: s.Interval} + s.A3 = &A3{IntervalWindow: a3iw} + s.A3.Bind(st) + + a18iw := types.IntervalWindow{Window: 5, Interval: s.Interval} + s.A18 = &A18{IntervalWindow: a18iw} + s.A18.Bind(st) + + a34iw := types.IntervalWindow{Window: 12, Interval: s.Interval} + s.A34 = &A34{IntervalWindow: a34iw} + s.A34.Bind(st) + + session.UserDataStream.OnStart(func() { + log.Infof("connected") + }) + + outlook := 1 + + //futuresMode := s.session.Futures || s.session.IsolatedFutures + cnt := 0 + + //var prevEr float64 + session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { + + //if kline.Interval == types.Interval15m && kline.Symbol == s.Symbol && !s.Market.IsDustQuantity(s.Position.GetBase(), kline.Close) { + // if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil { + // log.WithError(err).Errorf("graceful cancel order error") + // } + // s.ClosePosition(ctx, fixedpoint.One) + // s.tradeCollector.Process() + //} + if kline.Symbol != s.Symbol || kline.Interval != s.Interval { + return + } + + if err := s.activeMakerOrders.GracefulCancel(ctx, s.session.Exchange); err != nil { + log.WithError(err).Errorf("graceful cancel order error") + } + + cnt += 1 + if cnt < 15+1+outlook { + return + } + + r := new(regression.Regression) + r.SetObserved("Return Rate Per Interval") + r.SetVar(0, "S0") + r.SetVar(1, "S1") + r.SetVar(2, "S2") + //r.SetVar(2, "S3") + r.SetVar(3, "S4") + r.SetVar(4, "S5") + r.SetVar(5, "S6") + r.SetVar(6, "S7") + r.SetVar(7, "A2") + r.SetVar(8, "A3") + r.SetVar(9, "A18") + r.SetVar(10, "A34") + + var rdps regression.DataPoints + + for i := 1; i <= 15; i++ { + s0 := s.S0.Values[len(s.S0.Values)-i-outlook] + s1 := s.S1.Values[len(s.S1.Values)-i-outlook] + s2 := s.S2.Values[len(s.S2.Values)-i-outlook] + //s3 := s.S3.Values[len(s.S3.Values)-i-1] + s4 := s.S4.Values[len(s.S4.Values)-i-outlook] + s5 := s.S5.Values[len(s.S5.Values)-i-outlook] + s6 := s.S6.Values[len(s.S6.Values)-i-outlook] + s7 := s.S7.Values[len(s.S7.Values)-i-outlook] + a2 := s.A2.Values[len(s.A2.Values)-i-outlook] + a3 := s.A3.Values[len(s.A3.Values)-i-outlook] + a18 := s.A18.Values[len(s.A18.Values)-i-outlook] + a34 := s.A34.Values[len(s.A34.Values)-i-outlook] + + ret := s.R.Values[len(s.R.Values)-i] + rdps = append(rdps, regression.DataPoint(ret, types.Float64Slice{s0, s1, s2, s4, s5, s6, s7, a2, a3, a18, a34})) + } + //for i := 40; i > 20; i-- { + // s0 := preprocessing(s.S0.Values[len(s.S0.Values)-i : len(s.S0.Values)-i+20-outlook]) + // s1 := preprocessing(s.S1.Values[len(s.S1.Values)-i : len(s.S1.Values)-i+20-outlook]) + // s2 := preprocessing(s.S2.Values[len(s.S2.Values)-i : len(s.S2.Values)-i+20-outlook]) + // //s3 := s.S3.Values[len(s.S3.Values)-i-1] + // s4 := preprocessing(s.S4.Values[len(s.S4.Values)-i : len(s.S4.Values)-i+20-outlook]) + // s5 := preprocessing(s.S5.Values[len(s.S5.Values)-i : len(s.S5.Values)-i+20-outlook]) + // a2 := preprocessing(s.A2.Values[len(s.A2.Values)-i : len(s.A2.Values)-i+20-outlook]) + // a3 := preprocessing(s.A3.Values[len(s.A3.Values)-i : len(s.A3.Values)-i+20-outlook]) + // a18 := preprocessing(s.A18.Values[len(s.A18.Values)-i : len(s.A18.Values)-i+20-outlook]) + // a34 := preprocessing(s.A18.Values[len(s.A18.Values)-i : len(s.A18.Values)-i+20-outlook]) + // + // ret := s.R.Values[len(s.R.Values)-i] + // rdps = append(rdps, regression.DataPoint(ret, types.Float64Slice{s0, s1, s2, s4, s5, a2, a3, a18, a34})) + //} + r.Train(rdps...) + r.Run() + er, _ := r.Predict(types.Float64Slice{s.S0.Last(), s.S1.Last(), s.S2.Last(), s.S4.Last(), s.S5.Last(), s.S6.Last(), s.S7.Last(), s.A2.Last(), s.A3.Last(), s.A18.Last(), s.A34.Last()}) + log.Infof("Expected Return Rate: %f", er) + + q := new(regression.Regression) + q.SetObserved("Order Quantity Per Interval") + q.SetVar(0, "S0") + q.SetVar(1, "S1") + q.SetVar(2, "S2") + //q.SetVar(2, "S3") + q.SetVar(3, "S4") + q.SetVar(4, "S5") + q.SetVar(5, "S6") + q.SetVar(6, "S7") + q.SetVar(7, "A2") + q.SetVar(8, "A3") + q.SetVar(9, "A18") + q.SetVar(10, "A34") + + var qdps regression.DataPoints + + for i := 1; i <= 15; i++ { + s0 := math.Pow(s.S0.Values[len(s.S0.Values)-i-outlook], 1) + s1 := math.Pow(s.S1.Values[len(s.S1.Values)-i-outlook], 1) + s2 := math.Pow(s.S2.Values[len(s.S2.Values)-i-outlook], 1) + //s3 := s.S3.Values[len(s.S3.Values)-i-1] + s4 := math.Pow(s.S4.Values[len(s.S4.Values)-i-outlook], 1) + s5 := math.Pow(s.S5.Values[len(s.S5.Values)-i-outlook], 1) + s6 := s.S6.Values[len(s.S6.Values)-i-outlook] + s7 := s.S7.Values[len(s.S7.Values)-i-outlook] + a2 := math.Pow(s.A2.Values[len(s.A2.Values)-i-outlook], 1) + a3 := math.Pow(s.A3.Values[len(s.A3.Values)-i-outlook], 1) + a18 := math.Pow(s.A18.Values[len(s.A18.Values)-i-outlook], 1) + a34 := math.Pow(s.A34.Values[len(s.A34.Values)-i-outlook], 1) + + ret := s.R.Values[len(s.R.Values)-i] + qty := math.Abs(ret) + qdps = append(qdps, regression.DataPoint(qty, types.Float64Slice{s0, s1, s2, s4, s5, s6, s7, a2, a3, a18, a34})) + } + //for i := 40; i > 20; i-- { + // s0 := preprocessing(s.S0.Values[len(s.S0.Values)-i : len(s.S0.Values)-i+20-outlook]) + // s1 := preprocessing(s.S1.Values[len(s.S1.Values)-i : len(s.S1.Values)-i+20-outlook]) + // s2 := preprocessing(s.S2.Values[len(s.S2.Values)-i : len(s.S2.Values)-i+20-outlook]) + // //s3 := s.S3.Values[len(s.S3.Values)-i-1] + // s4 := preprocessing(s.S4.Values[len(s.S4.Values)-i : len(s.S4.Values)-i+20-outlook]) + // s5 := preprocessing(s.S5.Values[len(s.S5.Values)-i : len(s.S5.Values)-i+20-outlook]) + // a2 := preprocessing(s.A2.Values[len(s.A2.Values)-i : len(s.A2.Values)-i+20-outlook]) + // a3 := preprocessing(s.A3.Values[len(s.A3.Values)-i : len(s.A3.Values)-i+20-outlook]) + // a18 := preprocessing(s.A18.Values[len(s.A18.Values)-i : len(s.A18.Values)-i+20-outlook]) + // a34 := preprocessing(s.A18.Values[len(s.A18.Values)-i : len(s.A18.Values)-i+20-outlook]) + // + // ret := s.R.Values[len(s.R.Values)-i] + // qty := math.Abs(ret) + // qdps = append(qdps, regression.DataPoint(qty, types.Float64Slice{s0, s1, s2, s4, s5, a2, a3, a18, a34})) + //} + q.Train(qdps...) + + q.Run() + + log.Info(s.S0.Last(), s.S1.Last(), s.S2.Last(), s.S3.Last(), s.S4.Last(), s.S5.Last(), s.S6.Last(), s.S7.Last(), s.A2.Last(), s.A3.Last(), s.A18.Last(), s.A34.Last()) + + log.Infof("Return Rate Regression formula:\n%v", r.Formula) + log.Infof("Order Quantity Regression formula:\n%v", q.Formula) + + //s0 := preprocessing(s.S0.Values[len(s.S0.Values)-20 : len(s.S0.Values)-1]) + //s1 := preprocessing(s.S1.Values[len(s.S1.Values)-20 : len(s.S1.Values)-1-outlook]) + //s2 := preprocessing(s.S2.Values[len(s.S2.Values)-20 : len(s.S2.Values)-1-outlook]) + ////s3 := s.S3.Values[len(s.S3.Values)-i-1] + //s4 := preprocessing(s.S4.Values[len(s.S4.Values)-20 : len(s.S4.Values)-1-outlook]) + //s5 := preprocessing(s.S5.Values[len(s.S5.Values)-20 : len(s.S5.Values)-1-outlook]) + //a2 := preprocessing(s.A2.Values[len(s.A2.Values)-20 : len(s.A2.Values)-1-outlook]) + //a3 := preprocessing(s.A3.Values[len(s.A3.Values)-20 : len(s.A3.Values)-1-outlook]) + //a18 := preprocessing(s.A18.Values[len(s.A18.Values)-20 : len(s.A18.Values)-1-outlook]) + //a34 := preprocessing(s.A18.Values[len(s.A18.Values)-20 : len(s.A18.Values)-1-outlook]) + //er, _ := r.Predict(types.Float64Slice{s0, s1, s2, s4, s5, a2, a3, a18, a34}) + //eq, _ := q.Predict(types.Float64Slice{s0, s1, s2, s4, s5, a2, a3, a18, a34}) + eq, _ := q.Predict(types.Float64Slice{s.S0.Last(), s.S1.Last(), s.S2.Last(), s.S4.Last(), s.S5.Last(), s.S6.Last(), s.S7.Last(), s.A2.Last(), s.A3.Last(), s.A18.Last(), s.A34.Last(), er}) + log.Infof("Expected Order Quantity: %f", eq) + //if float64(s.Position.GetBase().Sign())*er < 0 { + // s.ClosePosition(ctx, fixedpoint.One, kline.Close) + // s.tradeCollector.Process() + //} + //prevEr = er + + //spd := s.Spread.Float64() + + // inventory = m * alpha + spread + AskAlphaBoundary := (s.Position.GetBase().Mul(kline.Close).Float64() - 100) / 10000 + BidAlphaBoundary := (s.Position.GetBase().Mul(kline.Close).Float64() + 100) / 10000 + + log.Info(s.Position.GetBase().Mul(kline.Close).Float64(), AskAlphaBoundary, er, BidAlphaBoundary) + + BidPrice := kline.Close.Mul(fixedpoint.One.Sub(s.Spread)) + BidQty := s.QuantityOrAmount.CalculateQuantity(BidPrice) + BidQty = BidQty //.Mul(fixedpoint.One.Add(fixedpoint.NewFromFloat(eq))) + + AskPrice := kline.Close.Mul(fixedpoint.One.Add(s.Spread)) + AskQty := s.QuantityOrAmount.CalculateQuantity(AskPrice) + AskQty = AskQty //.Mul(fixedpoint.One.Add(fixedpoint.NewFromFloat(eq))) + + if er > 0 || (er < 0 && er > AskAlphaBoundary/kline.Close.Float64()) { + submitOrder := types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeBuy, + Type: types.OrderTypeLimitMaker, + Price: BidPrice, + Quantity: BidQty, //0.0005 + } + createdOrders, err := orderExecutor.SubmitOrders(ctx, submitOrder) + if err != nil { + log.WithError(err).Errorf("can not place orders") + } + s.orderStore.Add(createdOrders...) + s.activeMakerOrders.Add(createdOrders...) + s.tradeCollector.Process() + + //submitOrder = types.SubmitOrder{ + // Symbol: s.Symbol, + // Side: types.SideTypeSell, + // Type: types.OrderTypeLimitMaker, + // Price: kline.Close.Mul(fixedpoint.One.Add(s.Spread)), + // Quantity: fixedpoint.NewFromFloat(math.Max(math.Min(eq, 0.003), 0.0005)), //0.0005 + //} + //createdOrders, err = orderExecutor.SubmitOrders(ctx, submitOrder) + //if err != nil { + // log.WithError(err).Errorf("can not place orders") + //} + //s.orderStore.Add(createdOrders...) + //s.activeMakerOrders.Add(createdOrders...) + //s.tradeCollector.Process() + } + if er < 0 || (er > 0 && er < BidAlphaBoundary/kline.Close.Float64()) { + submitOrder := types.SubmitOrder{ + Symbol: s.Symbol, + Side: types.SideTypeSell, + Type: types.OrderTypeLimitMaker, + Price: AskPrice, + Quantity: AskQty, //0.0005 + } + createdOrders, err := orderExecutor.SubmitOrders(ctx, submitOrder) + if err != nil { + log.WithError(err).Errorf("can not place orders") + } + s.orderStore.Add(createdOrders...) + s.activeMakerOrders.Add(createdOrders...) + s.tradeCollector.Process() + + //submitOrder = types.SubmitOrder{ + // Symbol: s.Symbol, + // Side: types.SideTypeBuy, + // Type: types.OrderTypeLimitMaker, + // Price: kline.Close.Mul(fixedpoint.One.Sub(s.Spread)), + // Quantity: fixedpoint.NewFromFloat(math.Max(math.Min(eq, 0.003), 0.0005)), //0.0005 + //} + //createdOrders, err = orderExecutor.SubmitOrders(ctx, submitOrder) + //if err != nil { + // log.WithError(err).Errorf("can not place orders") + //} + //s.orderStore.Add(createdOrders...) + //s.activeMakerOrders.Add(createdOrders...) + //s.tradeCollector.Process() + } + + }) + + return nil +} + +func tanh(x float64) float64 { + y := (math.Exp(x) - math.Exp(-x)) / (math.Exp(x) + math.Exp(-x)) + return y +} + +func mean(xs []float64) float64 { + return floats.Sum(xs) / float64(len(xs)) +} + +func stddev(xs []float64) float64 { + mu := mean(xs) + squaresum := 0. + for _, x := range xs { + squaresum += (x - mu) * (x - mu) + } + return math.Sqrt(squaresum / float64(len(xs)-1)) +} + +func preprocessing(xs []float64) float64 { + //return 0.5 * tanh(0.01*((xs[len(xs)-1]-mean(xs))/stddev(xs))) // tanh estimator + return tanh((xs[len(xs)-1] - mean(xs)) / stddev(xs)) // tanh z-score + return (xs[len(xs)-1] - mean(xs)) / stddev(xs) // z-score +}