thirteenth version
This commit is contained in:
@@ -27,6 +27,7 @@ type PipelineConfig struct {
|
||||
EntryWindow timeutil.Window
|
||||
ExitWindow timeutil.Window
|
||||
IntervalVolumeLookback int
|
||||
TradingDays []time.Time
|
||||
Location *time.Location
|
||||
}
|
||||
|
||||
@@ -39,6 +40,11 @@ func NewPipeline(repo repository.Repository, cfg PipelineConfig) Pipeline {
|
||||
return Pipeline{repo: repo, cfg: cfg}
|
||||
}
|
||||
|
||||
func (p Pipeline) WithTradingDays(days []time.Time) Pipeline {
|
||||
p.cfg.TradingDays = days
|
||||
return p
|
||||
}
|
||||
|
||||
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)
|
||||
to := dateOnly(tradeDate).AddDate(0, 0, -1)
|
||||
@@ -94,8 +100,9 @@ func Compute(instrument domain.Instrument, candles []domain.Candle, tradeDate ti
|
||||
var overnight []float64
|
||||
var lastROn decimal.Decimal
|
||||
var lastRDay decimal.Decimal
|
||||
calendar := tradingCalendarFrom(cfg.TradingDays)
|
||||
for i := 1; i < len(candles); i++ {
|
||||
if !consecutiveDailyCandles(candles[i-1].TradeDate, candles[i].TradeDate) {
|
||||
if !consecutiveDailyCandles(candles[i-1].TradeDate, candles[i].TradeDate, calendar) {
|
||||
continue
|
||||
}
|
||||
rOn, err := OvernightReturn(candles[i].Open, candles[i-1].Close)
|
||||
@@ -207,12 +214,34 @@ func historicalDailyCandles(candles []domain.Candle, tradeDate time.Time) []doma
|
||||
return out
|
||||
}
|
||||
|
||||
func consecutiveDailyCandles(previous, current time.Time) bool {
|
||||
type tradingCalendar map[string]struct{}
|
||||
|
||||
func tradingCalendarFrom(days []time.Time) tradingCalendar {
|
||||
if len(days) == 0 {
|
||||
return nil
|
||||
}
|
||||
calendar := make(tradingCalendar, len(days))
|
||||
for _, day := range days {
|
||||
calendar[dateOnly(day).Format("2006-01-02")] = struct{}{}
|
||||
}
|
||||
return calendar
|
||||
}
|
||||
|
||||
func consecutiveDailyCandles(previous, current time.Time, calendar tradingCalendar) bool {
|
||||
prevDay := dateOnly(previous)
|
||||
currentDay := dateOnly(current)
|
||||
if !currentDay.After(prevDay) {
|
||||
return false
|
||||
}
|
||||
if len(calendar) > 0 {
|
||||
tradingDays := 0
|
||||
for day := prevDay.AddDate(0, 0, 1); !day.After(currentDay); day = day.AddDate(0, 0, 1) {
|
||||
if _, ok := calendar[day.Format("2006-01-02")]; ok {
|
||||
tradingDays++
|
||||
}
|
||||
}
|
||||
return tradingDays == 1
|
||||
}
|
||||
weekdays := 0
|
||||
for day := prevDay.AddDate(0, 0, 1); !day.After(currentDay); day = day.AddDate(0, 0, 1) {
|
||||
if day.Weekday() != time.Saturday && day.Weekday() != time.Sunday {
|
||||
|
||||
@@ -174,6 +174,47 @@ func TestComputeAllowsWeekendGap(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeAllowsHolidayGapWithTradingCalendar(t *testing.T) {
|
||||
monday := time.Date(2026, 1, 5, 0, 0, 0, 0, time.UTC)
|
||||
wednesday := monday.AddDate(0, 0, 2)
|
||||
candles := []domain.Candle{
|
||||
{InstrumentUID: "uid", TradeDate: monday, Open: decimal.NewFromInt(100), Close: decimal.NewFromInt(100), VolumeLots: decimal.NewFromInt(1)},
|
||||
{InstrumentUID: "uid", TradeDate: wednesday, Open: decimal.NewFromInt(101), Close: decimal.NewFromInt(100), VolumeLots: decimal.NewFromInt(1)},
|
||||
}
|
||||
got, err := Compute(domain.Instrument{InstrumentUID: "uid", Lot: 1}, candles, wednesday.AddDate(0, 0, 1), SpreadResult{}, PipelineConfig{
|
||||
RollingShort: 1,
|
||||
RollingLong: 1,
|
||||
EWMALambda: 0.08,
|
||||
TradingDays: []time.Time{monday, wednesday},
|
||||
}, decimal.Zero, decimal.Zero)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := decimal.RequireFromString("0.01")
|
||||
if !got.ROn.Equal(want) {
|
||||
t.Fatalf("ROn=%s, want %s across holiday gap", got.ROn, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComputeRejectsMissingTradingDayWithTradingCalendar(t *testing.T) {
|
||||
monday := time.Date(2026, 1, 5, 0, 0, 0, 0, time.UTC)
|
||||
tuesday := monday.AddDate(0, 0, 1)
|
||||
wednesday := monday.AddDate(0, 0, 2)
|
||||
candles := []domain.Candle{
|
||||
{InstrumentUID: "uid", TradeDate: monday, Open: decimal.NewFromInt(100), Close: decimal.NewFromInt(100), VolumeLots: decimal.NewFromInt(1)},
|
||||
{InstrumentUID: "uid", TradeDate: wednesday, Open: decimal.NewFromInt(101), Close: decimal.NewFromInt(100), VolumeLots: decimal.NewFromInt(1)},
|
||||
}
|
||||
_, err := Compute(domain.Instrument{InstrumentUID: "uid", Lot: 1}, candles, wednesday.AddDate(0, 0, 1), SpreadResult{}, PipelineConfig{
|
||||
RollingShort: 1,
|
||||
RollingLong: 1,
|
||||
EWMALambda: 0.08,
|
||||
TradingDays: []time.Time{monday, tuesday, wednesday},
|
||||
}, decimal.Zero, decimal.Zero)
|
||||
if err == nil {
|
||||
t.Fatal("expected missing trading day to make overnight pair unavailable")
|
||||
}
|
||||
}
|
||||
|
||||
func flatCandles(start time.Time, count int) []domain.Candle {
|
||||
candles := make([]domain.Candle, 0, count)
|
||||
for i := 0; i < count; i++ {
|
||||
|
||||
Reference in New Issue
Block a user