103 lines
2.3 KiB
Go
103 lines
2.3 KiB
Go
|
package xmaker
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"time"
|
||
|
|
||
|
"github.com/prometheus/client_golang/prometheus"
|
||
|
|
||
|
"github.com/c9s/bbgo/pkg/bbgo"
|
||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||
|
"github.com/c9s/bbgo/pkg/types"
|
||
|
)
|
||
|
|
||
|
var tradeVolumeWindowSignalMetrics = prometheus.NewGaugeVec(
|
||
|
prometheus.GaugeOpts{
|
||
|
Name: "xmaker_trade_volume_window_signal",
|
||
|
Help: "",
|
||
|
}, []string{"symbol"})
|
||
|
|
||
|
func init() {
|
||
|
prometheus.MustRegister(tradeVolumeWindowSignalMetrics)
|
||
|
}
|
||
|
|
||
|
type TradeVolumeWindowSignal struct {
|
||
|
Threshold fixedpoint.Value `json:"threshold"`
|
||
|
Window types.Duration `json:"window"`
|
||
|
|
||
|
trades []types.Trade
|
||
|
symbol string
|
||
|
}
|
||
|
|
||
|
func (s *TradeVolumeWindowSignal) handleTrade(trade types.Trade) {
|
||
|
s.trades = append(s.trades, trade)
|
||
|
}
|
||
|
|
||
|
func (s *TradeVolumeWindowSignal) Bind(ctx context.Context, session *bbgo.ExchangeSession, symbol string) error {
|
||
|
s.symbol = symbol
|
||
|
|
||
|
if s.Window == 0 {
|
||
|
s.Window = types.Duration(time.Minute)
|
||
|
}
|
||
|
|
||
|
if s.Threshold.IsZero() {
|
||
|
s.Threshold = fixedpoint.NewFromFloat(0.7)
|
||
|
}
|
||
|
|
||
|
session.MarketDataStream.OnMarketTrade(s.handleTrade)
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func (s *TradeVolumeWindowSignal) filterTrades(now time.Time) []types.Trade {
|
||
|
startTime := now.Add(-time.Duration(s.Window))
|
||
|
startIdx := 0
|
||
|
|
||
|
for idx, td := range s.trades {
|
||
|
// skip trades before the start time
|
||
|
if td.Time.Before(startTime) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
startIdx = idx
|
||
|
break
|
||
|
}
|
||
|
|
||
|
s.trades = s.trades[startIdx:]
|
||
|
return s.trades
|
||
|
}
|
||
|
|
||
|
func (s *TradeVolumeWindowSignal) calculateTradeVolume(trades []types.Trade) (buyVolume, sellVolume float64) {
|
||
|
for _, td := range trades {
|
||
|
if td.IsBuyer {
|
||
|
buyVolume += td.Quantity.Float64()
|
||
|
} else {
|
||
|
sellVolume += td.Quantity.Float64()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return buyVolume, sellVolume
|
||
|
}
|
||
|
|
||
|
func (s *TradeVolumeWindowSignal) CalculateSignal(ctx context.Context) (float64, error) {
|
||
|
now := time.Now()
|
||
|
trades := s.filterTrades(now)
|
||
|
buyVolume, sellVolume := s.calculateTradeVolume(trades)
|
||
|
totalVolume := buyVolume + sellVolume
|
||
|
|
||
|
threshold := s.Threshold.Float64()
|
||
|
buyRatio := buyVolume / totalVolume
|
||
|
sellRatio := sellVolume / totalVolume
|
||
|
|
||
|
sig := 0.0
|
||
|
if buyRatio > threshold {
|
||
|
sig = (buyRatio - threshold) / 2.0
|
||
|
} else if sellRatio > threshold {
|
||
|
sig = -(sellRatio - threshold) / 2.0
|
||
|
}
|
||
|
|
||
|
log.Infof("[TradeVolumeWindowSignal] sig: %f buy/sell = %f/%f", sig, buyVolume, sellVolume)
|
||
|
|
||
|
tradeVolumeWindowSignalMetrics.WithLabelValues(s.symbol).Set(sig)
|
||
|
return sig, nil
|
||
|
}
|