88 lines
2.5 KiB
Go
88 lines
2.5 KiB
Go
package signal
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/shopspring/decimal"
|
|
|
|
"overnight-trading-bot/internal/domain"
|
|
)
|
|
|
|
func sd(raw string) decimal.Decimal {
|
|
v, err := decimal.NewFromString(raw)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return v
|
|
}
|
|
|
|
func baseCandidate() Candidate {
|
|
return Candidate{
|
|
TradeDate: time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC),
|
|
Instrument: domain.Instrument{
|
|
InstrumentUID: "uid",
|
|
Ticker: "TRUR",
|
|
ClassCode: "TQTF",
|
|
Lot: 1,
|
|
MinPriceIncrement: sd("0.01"),
|
|
Currency: "RUB",
|
|
Enabled: true,
|
|
},
|
|
Features: domain.FeatureSet{
|
|
MuOn60: sd("0.002"),
|
|
MuOn252: sd("0.001"),
|
|
SigmaOn60: sd("0.01"),
|
|
TStatOn60: sd("2"),
|
|
WinOn60: sd("0.60"),
|
|
NetEdgeBps: sd("20"),
|
|
SpreadBps: sd("5"),
|
|
TickBps: sd("1"),
|
|
ADV20: sd("10000000"),
|
|
},
|
|
TradingStatus: domain.TradingStatusNormal,
|
|
FreeOrderOK: true,
|
|
}
|
|
}
|
|
|
|
func TestEngineEnter(t *testing.T) {
|
|
engine := New(Config{
|
|
MinTStat60: sd("1.25"),
|
|
MinWinRate60: sd("0.55"),
|
|
MinNetEdgeBps: sd("10"),
|
|
MinADVRUB: sd("5000000"),
|
|
MaxSpreadBpsDefault: sd("20"),
|
|
MaxSpreadBpsMoneyMarket: sd("5"),
|
|
MaxSpreadBpsBondFunds: sd("10"),
|
|
MaxSpreadBpsEquityFunds: sd("25"),
|
|
MaxTickBps: sd("10"),
|
|
RequireZeroCommission: true,
|
|
MaxPositions: 5,
|
|
})
|
|
sig := engine.Evaluate(baseCandidate())
|
|
if sig.Decision != domain.DecisionEnter || sig.RejectReason != "" {
|
|
t.Fatalf("unexpected signal: %+v", sig)
|
|
}
|
|
}
|
|
|
|
func TestEngineFirstRejectReason(t *testing.T) {
|
|
engine := New(Config{MinTStat60: sd("1.25"), MinWinRate60: sd("0.55"), MinNetEdgeBps: sd("10"), MinADVRUB: sd("5000000"), MaxSpreadBpsDefault: sd("20"), MaxTickBps: sd("10"), RequireZeroCommission: true})
|
|
c := baseCandidate()
|
|
c.Features.MuOn60 = decimal.Zero
|
|
c.Features.NetEdgeBps = decimal.Zero
|
|
sig := engine.Evaluate(c)
|
|
if sig.RejectReason != ReasonMuShort {
|
|
t.Fatalf("reason=%s", sig.RejectReason)
|
|
}
|
|
}
|
|
|
|
func TestEngineUsesSkipForCapacityReasons(t *testing.T) {
|
|
engine := New(Config{MinTStat60: sd("1.25"), MinWinRate60: sd("0.55"), MinNetEdgeBps: sd("10"), MinADVRUB: sd("5000000"), MaxSpreadBpsDefault: sd("20"), MaxTickBps: sd("10"), RequireZeroCommission: true, MaxPositions: 1})
|
|
c := baseCandidate()
|
|
c.OpenPositions = 1
|
|
sig := engine.Evaluate(c)
|
|
if sig.Decision != domain.DecisionSkip || sig.RejectReason != ReasonMaxPositions {
|
|
t.Fatalf("unexpected skip signal: %+v", sig)
|
|
}
|
|
}
|