qbtrade/pkg/strategy/factorzoo/linear_regression.go
2024-06-27 22:42:38 +08:00

195 lines
6.4 KiB
Go

package factorzoo
import (
"context"
"git.qtrade.icu/lychiyu/qbtrade/pkg/datatype/floats"
"git.qtrade.icu/lychiyu/qbtrade/pkg/fixedpoint"
"git.qtrade.icu/lychiyu/qbtrade/pkg/indicator"
"git.qtrade.icu/lychiyu/qbtrade/pkg/qbtrade"
"git.qtrade.icu/lychiyu/qbtrade/pkg/strategy/factorzoo/factors"
"git.qtrade.icu/lychiyu/qbtrade/pkg/types"
)
type Linear struct {
Symbol string
Market types.Market `json:"-"`
types.IntervalWindow
// MarketOrder is the option to enable market order short.
MarketOrder bool `json:"marketOrder"`
Quantity fixedpoint.Value `json:"quantity"`
StopEMARange fixedpoint.Value `json:"stopEMARange"`
StopEMA *types.IntervalWindow `json:"stopEMA"`
// Xs (input), factors & indicators
divergence *factorzoo.PVD // price volume divergence
reversion *factorzoo.PMR // price mean reversion
momentum *factorzoo.MOM // price momentum from paper, alpha 101
drift *indicator.Drift // GBM
volume *factorzoo.VMOM // quarterly volume momentum
// Y (output), internal rate of return
irr *factorzoo.RR
orderExecutor *qbtrade.GeneralOrderExecutor
session *qbtrade.ExchangeSession
activeOrders *qbtrade.ActiveOrderBook
qbtrade.QuantityOrAmount
}
func (s *Linear) Subscribe(session *qbtrade.ExchangeSession) {
session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval})
}
func (s *Linear) Bind(session *qbtrade.ExchangeSession, orderExecutor *qbtrade.GeneralOrderExecutor) {
s.session = session
s.orderExecutor = orderExecutor
position := orderExecutor.Position()
symbol := position.Symbol
store, _ := session.MarketDataStore(symbol)
// initialize factor indicators
s.divergence = &factorzoo.PVD{IntervalWindow: types.IntervalWindow{Window: 60, Interval: s.Interval}}
s.divergence.Bind(store)
s.reversion = &factorzoo.PMR{IntervalWindow: types.IntervalWindow{Window: 60, Interval: s.Interval}}
s.reversion.Bind(store)
s.drift = &indicator.Drift{IntervalWindow: types.IntervalWindow{Window: 7, Interval: s.Interval}}
s.drift.Bind(store)
s.momentum = &factorzoo.MOM{IntervalWindow: types.IntervalWindow{Window: 1, Interval: s.Interval}}
s.momentum.Bind(store)
s.volume = &factorzoo.VMOM{IntervalWindow: types.IntervalWindow{Window: 90, Interval: s.Interval}}
s.volume.Bind(store)
s.irr = &factorzoo.RR{IntervalWindow: types.IntervalWindow{Window: 2, Interval: s.Interval}}
s.irr.Bind(store)
predLst := types.NewQueue(s.Window)
session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, s.Interval, func(kline types.KLine) {
ctx := context.Background()
// graceful cancel all active orders
_ = orderExecutor.GracefulCancel(ctx)
// take past window days' values to predict future return
// (e.g., 5 here in default configuration file)
a := []floats.Slice{
s.divergence.Values[len(s.divergence.Values)-s.Window-2 : len(s.divergence.Values)-2],
s.reversion.Values[len(s.reversion.Values)-s.Window-2 : len(s.reversion.Values)-2],
s.drift.Values[len(s.drift.Values)-s.Window-2 : len(s.drift.Values)-2],
s.momentum.Values[len(s.momentum.Values)-s.Window-2 : len(s.momentum.Values)-2],
s.volume.Values[len(s.volume.Values)-s.Window-2 : len(s.volume.Values)-2],
}
// e.g., s.window is 5
// factors array from day -4 to day 0, [[0.1, 0.2, 0.35, 0.3 , 0.25], [1.1, -0.2, 1.35, -0.3 , -0.25], ...]
// the binary(+/-) daily return rate from day -3 to day 1, [0, 1, 1, 0, 0]
// then we take the latest available factors array into linear regression model
b := []floats.Slice{filter(s.irr.Values[len(s.irr.Values)-s.Window-1:len(s.irr.Values)-1], binary)}
var x []types.Series
var y []types.Series
x = append(x, &a[0])
x = append(x, &a[1])
x = append(x, &a[2])
x = append(x, &a[3])
x = append(x, &a[4])
//x = append(x, &a[5])
y = append(y, &b[0])
model := types.LogisticRegression(x, y[0], s.Window, 8000, 0.0001)
// use the last value from indicators, or the SeriesExtends' predict function. (e.g., look back: 5)
input := []float64{
s.divergence.Last(0),
s.reversion.Last(0),
s.drift.Last(0),
s.momentum.Last(0),
s.volume.Last(0),
}
pred := model.Predict(input)
predLst.Update(pred)
qty := s.Quantity //s.QuantityOrAmount.CalculateQuantity(kline.Close)
// the scale of pred is from 0.0 to 1.0
// 0.5 can be used as the threshold
// we use the time-series rolling prediction values here
if pred > predLst.Mean() {
if position.IsShort() {
s.ClosePosition(ctx, one)
s.placeMarketOrder(ctx, types.SideTypeBuy, qty, symbol)
} else if position.IsClosed() {
s.placeMarketOrder(ctx, types.SideTypeBuy, qty, symbol)
}
} else if pred < predLst.Mean() {
if position.IsLong() {
s.ClosePosition(ctx, one)
s.placeMarketOrder(ctx, types.SideTypeSell, qty, symbol)
} else if position.IsClosed() {
s.placeMarketOrder(ctx, types.SideTypeSell, qty, symbol)
}
}
// pass if position is opened and not dust, and remain the same direction with alpha signal
// alpha-weighted inventory and cash
//alpha := fixedpoint.NewFromFloat(s.r1.Last())
//targetBase := s.QuantityOrAmount.CalculateQuantity(kline.Close).Mul(alpha)
////s.ClosePosition(ctx, one)
//diffQty := targetBase.Sub(position.Base)
//log.Info(alpha.Float64(), position.Base, diffQty.Float64())
//
//if diffQty.Sign() > 0 {
// s.placeMarketOrder(ctx, types.SideTypeBuy, diffQty.Abs(), symbol)
//} else if diffQty.Sign() < 0 {
// s.placeMarketOrder(ctx, types.SideTypeSell, diffQty.Abs(), symbol)
//}
}))
if !qbtrade.IsBackTesting {
session.MarketDataStream.OnMarketTrade(func(trade types.Trade) {
})
}
}
func (s *Linear) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error {
return s.orderExecutor.ClosePosition(ctx, percentage)
}
func (s *Linear) placeMarketOrder(ctx context.Context, side types.SideType, quantity fixedpoint.Value, symbol string) {
market, _ := s.session.Market(symbol)
_, err := s.orderExecutor.SubmitOrders(ctx, types.SubmitOrder{
Symbol: symbol,
Market: market,
Side: side,
Type: types.OrderTypeMarket,
Quantity: quantity,
//TimeInForce: types.TimeInForceGTC,
Tag: "linear",
})
if err != nil {
log.WithError(err).Errorf("can not place market order")
}
}
func binary(val float64) float64 {
if val > 0. {
return 1.
} else {
return 0.
}
}
func filter(data []float64, f func(float64) float64) []float64 {
fltd := make([]float64, 0)
for _, e := range data {
//if f(e) >= 0. {
fltd = append(fltd, f(e))
//}
}
return fltd
}