This commit is contained in:
@@ -40,7 +40,8 @@ func NewPipeline(repo repository.Repository, cfg PipelineConfig) Pipeline {
|
||||
|
||||
func (p Pipeline) Recompute(ctx context.Context, instrument domain.Instrument, tradeDate time.Time, spread SpreadResult) (domain.FeatureSet, error) {
|
||||
from := tradeDate.AddDate(0, 0, -p.cfg.RollingLong-5)
|
||||
candles, err := p.repo.ListDailyCandles(ctx, instrument.InstrumentUID, from, tradeDate)
|
||||
to := dateOnly(tradeDate).AddDate(0, 0, -1)
|
||||
candles, err := p.repo.ListDailyCandles(ctx, instrument.InstrumentUID, from, to)
|
||||
if err != nil {
|
||||
return domain.FeatureSet{}, err
|
||||
}
|
||||
@@ -74,8 +75,9 @@ func (p Pipeline) intervalVolume(ctx context.Context, instrument domain.Instrume
|
||||
if lookback <= 0 {
|
||||
lookback = defaultIntervalVolumeLookback
|
||||
}
|
||||
from := window.Start.On(date.AddDate(0, 0, -lookback), loc).UTC()
|
||||
to := window.End.On(date, loc).UTC()
|
||||
toDate := dateOnly(date).AddDate(0, 0, -1)
|
||||
from := window.Start.On(toDate.AddDate(0, 0, -lookback+1), loc).UTC()
|
||||
to := window.End.On(toDate, loc).UTC()
|
||||
candles, err := p.repo.ListMinuteCandles(ctx, instrument.InstrumentUID, from, to)
|
||||
if err != nil {
|
||||
return decimal.Zero, err
|
||||
@@ -84,6 +86,7 @@ func (p Pipeline) intervalVolume(ctx context.Context, instrument domain.Instrume
|
||||
}
|
||||
|
||||
func Compute(instrument domain.Instrument, candles []domain.Candle, tradeDate time.Time, spread SpreadResult, cfg PipelineConfig, entryVolume, exitVolume decimal.Decimal) (domain.FeatureSet, error) {
|
||||
candles = historicalDailyCandles(candles, tradeDate)
|
||||
if len(candles) < 2 {
|
||||
return domain.FeatureSet{}, fmt.Errorf("need at least 2 candles, got %d", len(candles))
|
||||
}
|
||||
@@ -138,6 +141,22 @@ func Compute(instrument domain.Instrument, candles []domain.Candle, tradeDate ti
|
||||
}, nil
|
||||
}
|
||||
|
||||
func historicalDailyCandles(candles []domain.Candle, tradeDate time.Time) []domain.Candle {
|
||||
tradeDay := dateOnly(tradeDate)
|
||||
out := make([]domain.Candle, 0, len(candles))
|
||||
for _, candle := range candles {
|
||||
if dateOnly(candle.TradeDate).Before(tradeDay) {
|
||||
out = append(out, candle)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func dateOnly(ts time.Time) time.Time {
|
||||
year, month, day := ts.UTC().Date()
|
||||
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
func IntervalVolume(candles []domain.Candle, lot int64) decimal.Decimal {
|
||||
if lot <= 0 {
|
||||
return decimal.Zero
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
package features
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"overnight-trading-bot/internal/domain"
|
||||
"overnight-trading-bot/internal/testutil"
|
||||
"overnight-trading-bot/internal/timeutil"
|
||||
)
|
||||
|
||||
@@ -74,6 +76,41 @@ func TestAverageIntervalVolumeUsesExecutionWindowsAcrossDays(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRecomputeExcludesTradeDateDailyCandle(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
repo := testutil.NewMemoryRepository()
|
||||
start := time.Date(2026, 6, 1, 0, 0, 0, 0, time.UTC)
|
||||
var candles []domain.Candle
|
||||
for i := 0; i < 6; i++ {
|
||||
closePrice := decimal.NewFromInt(100)
|
||||
if i == 5 {
|
||||
closePrice = decimal.NewFromInt(100000)
|
||||
}
|
||||
candles = append(candles, domain.Candle{
|
||||
InstrumentUID: "uid",
|
||||
TradeDate: start.AddDate(0, 0, i),
|
||||
Open: decimal.NewFromInt(100),
|
||||
Close: closePrice,
|
||||
VolumeLots: decimal.NewFromInt(1),
|
||||
})
|
||||
}
|
||||
if err := repo.UpsertDailyCandles(ctx, candles); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
pipeline := NewPipeline(repo, PipelineConfig{
|
||||
RollingShort: 2,
|
||||
RollingLong: 2,
|
||||
EWMALambda: 0.08,
|
||||
})
|
||||
got, err := pipeline.Recompute(ctx, domain.Instrument{InstrumentUID: "uid", Lot: 1}, start.AddDate(0, 0, 5), SpreadResult{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !got.ADV20.Equal(decimal.NewFromInt(100)) {
|
||||
t.Fatalf("ADV20=%s, want tradeDate candle excluded", got.ADV20)
|
||||
}
|
||||
}
|
||||
|
||||
func mustTOD(raw string) timeutil.TimeOfDay {
|
||||
tod, err := timeutil.ParseTimeOfDay(raw)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user