fourth version
Deploy / Test, build and deploy (push) Failing after 3m7s

This commit is contained in:
2026-06-08 07:36:52 +00:00
parent 52a935b8b4
commit b9efa98758
20 changed files with 824 additions and 91 deletions
+106 -35
View File
@@ -8,6 +8,7 @@ import (
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/shopspring/decimal"
@@ -19,35 +20,40 @@ import (
)
type Config struct {
EntrySlippageBps decimal.Decimal
ExitSlippageBps decimal.Decimal
CommissionRoundtripBps decimal.Decimal
RiskBufferBps decimal.Decimal
InitialEquity decimal.Decimal
OutputDir string
RollingShort int
RollingLong int
EWMALambda float64
MinTStat60 decimal.Decimal
MinWinRate60 decimal.Decimal
MinNetEdgeBps decimal.Decimal
MinADVRUB decimal.Decimal
MaxSpreadBps decimal.Decimal
MaxTickBps decimal.Decimal
RequireZeroCommission bool
MaxPositions int
MaxPositionPct decimal.Decimal
MaxTotalExposurePct decimal.Decimal
MaxParticipationRate decimal.Decimal
CashUsageBuffer decimal.Decimal
RiskBudgetPct decimal.Decimal
MinOrderNotionalRUB decimal.Decimal
AssumedSpreadBps decimal.Decimal
AssumedTickBps decimal.Decimal
Lot int64
UseMinuteModel bool
EntryWindow TimeWindow
ExitWindow TimeWindow
EntrySlippageBps decimal.Decimal
ExitSlippageBps decimal.Decimal
CommissionRoundtripBps decimal.Decimal
RiskBufferBps decimal.Decimal
InitialEquity decimal.Decimal
OutputDir string
RollingShort int
RollingLong int
EWMALambda float64
MinTStat60 decimal.Decimal
MinWinRate60 decimal.Decimal
MinNetEdgeBps decimal.Decimal
MinADVRUB decimal.Decimal
MaxSpreadBps decimal.Decimal
MaxSpreadBpsMoneyMarket decimal.Decimal
MaxSpreadBpsBondFunds decimal.Decimal
MaxSpreadBpsEquityFunds decimal.Decimal
MaxTickBps decimal.Decimal
RequireZeroCommission *bool
MaxPositions int
MaxPositionPct decimal.Decimal
MaxTotalExposurePct decimal.Decimal
MaxParticipationRate decimal.Decimal
CashUsageBuffer decimal.Decimal
RiskBudgetPct decimal.Decimal
MinOrderNotionalRUB decimal.Decimal
AssumedSpreadBps decimal.Decimal
AssumedSpreadBpsByFundType map[string]decimal.Decimal
InstrumentFundTypes map[string]string
AssumedTickBps decimal.Decimal
Lot int64
UseMinuteModel bool
EntryWindow TimeWindow
ExitWindow TimeWindow
}
type TimeWindow struct {
@@ -120,6 +126,15 @@ func (cfg Config) withDefaults() Config {
if cfg.MaxSpreadBps.IsZero() {
cfg.MaxSpreadBps = decimal.NewFromInt(20)
}
if cfg.MaxSpreadBpsMoneyMarket.IsZero() {
cfg.MaxSpreadBpsMoneyMarket = decimal.NewFromInt(5)
}
if cfg.MaxSpreadBpsBondFunds.IsZero() {
cfg.MaxSpreadBpsBondFunds = decimal.NewFromInt(10)
}
if cfg.MaxSpreadBpsEquityFunds.IsZero() {
cfg.MaxSpreadBpsEquityFunds = decimal.NewFromInt(25)
}
if cfg.MaxTickBps.IsZero() {
cfg.MaxTickBps = decimal.NewFromInt(10)
}
@@ -132,8 +147,9 @@ func (cfg Config) withDefaults() Config {
if cfg.AssumedTickBps.IsZero() {
cfg.AssumedTickBps = cfg.MaxTickBps
}
if !cfg.RequireZeroCommission && cfg.CommissionRoundtripBps.IsZero() {
cfg.RequireZeroCommission = true
if cfg.RequireZeroCommission == nil {
requireZero := true
cfg.RequireZeroCommission = &requireZero
}
if cfg.MaxPositions == 0 {
cfg.MaxPositions = 5
@@ -260,7 +276,7 @@ func (e Engine) RunWithMinuteCandles(candlesByInstrument map[string][]domain.Can
Lots: lots,
Notional: notional,
NetPnL: pnl,
SpreadBps: e.cfg.AssumedSpreadBps,
SpreadBps: c.spreadBps,
SlippageBps: e.cfg.EntrySlippageBps.Add(e.cfg.ExitSlippageBps),
OvernightGap: c.overnightGap,
CapacityRUB: capacity,
@@ -355,6 +371,7 @@ type candidate struct {
buy decimal.Decimal
sell decimal.Decimal
netEdge decimal.Decimal
spreadBps decimal.Decimal
adv decimal.Decimal
q05Abs decimal.Decimal
overnightGap decimal.Decimal
@@ -381,7 +398,8 @@ func (e Engine) evaluateCandidate(instrumentUID string, candles []domain.Candle,
return candidate{}, false, nil
}
rawEdge := decimal.NewFromFloat(short.Mean).Mul(decimal.NewFromInt(10_000))
cost := e.cfg.AssumedSpreadBps.
spreadBps := e.assumedSpreadBps(instrumentUID)
cost := spreadBps.
Add(e.cfg.EntrySlippageBps).
Add(e.cfg.ExitSlippageBps).
Add(e.cfg.CommissionRoundtripBps).
@@ -389,7 +407,7 @@ func (e Engine) evaluateCandidate(instrumentUID string, candles []domain.Candle,
netEdge := rawEdge.Sub(cost)
adv := features.ADV(history, e.cfg.Lot, 20)
switch {
case e.cfg.RequireZeroCommission && e.cfg.CommissionRoundtripBps.IsPositive():
case e.requireZeroCommission() && e.cfg.CommissionRoundtripBps.IsPositive():
return candidate{}, false, nil
case !decimal.NewFromFloat(short.Mean).IsPositive() || !decimal.NewFromFloat(long.Mean).IsPositive():
return candidate{}, false, nil
@@ -399,7 +417,7 @@ func (e Engine) evaluateCandidate(instrumentUID string, candles []domain.Candle,
return candidate{}, false, nil
case netEdge.LessThan(e.cfg.MinNetEdgeBps):
return candidate{}, false, nil
case e.cfg.AssumedSpreadBps.GreaterThan(e.cfg.MaxSpreadBps):
case spreadBps.GreaterThan(e.maxSpreadBps(instrumentUID)):
return candidate{}, false, nil
case e.cfg.AssumedTickBps.GreaterThan(e.cfg.MaxTickBps):
return candidate{}, false, nil
@@ -425,6 +443,7 @@ func (e Engine) evaluateCandidate(instrumentUID string, candles []domain.Candle,
buy: buy,
sell: sell,
netEdge: netEdge,
spreadBps: spreadBps,
adv: adv,
q05Abs: q05Abs,
overnightGap: gap,
@@ -432,6 +451,58 @@ func (e Engine) evaluateCandidate(instrumentUID string, candles []domain.Candle,
}, true, nil
}
func (e Engine) requireZeroCommission() bool {
return e.cfg.RequireZeroCommission != nil && *e.cfg.RequireZeroCommission
}
func (e Engine) assumedSpreadBps(instrumentUID string) decimal.Decimal {
fundType := normalizedFundType(e.cfg.InstrumentFundTypes[instrumentUID])
if !fundType.IsZeroValue {
if spread, ok := e.cfg.AssumedSpreadBpsByFundType[fundType.Key]; ok {
return spread
}
return e.maxSpreadBpsForFundType(fundType.Raw)
}
return e.cfg.AssumedSpreadBps
}
func (e Engine) maxSpreadBps(instrumentUID string) decimal.Decimal {
fundType := normalizedFundType(e.cfg.InstrumentFundTypes[instrumentUID])
if fundType.IsZeroValue {
return e.cfg.MaxSpreadBps
}
return e.maxSpreadBpsForFundType(fundType.Raw)
}
func (e Engine) maxSpreadBpsForFundType(fundType string) decimal.Decimal {
switch {
case strings.Contains(fundType, "money"):
return e.cfg.MaxSpreadBpsMoneyMarket
case strings.Contains(fundType, "bond"):
return e.cfg.MaxSpreadBpsBondFunds
case strings.Contains(fundType, "equity"):
return e.cfg.MaxSpreadBpsEquityFunds
default:
return e.cfg.MaxSpreadBps
}
}
type normalizedType struct {
Raw string
Key string
IsZeroValue bool
}
func normalizedFundType(raw string) normalizedType {
raw = strings.ToLower(strings.TrimSpace(raw))
if raw == "" {
return normalizedType{IsZeroValue: true}
}
key := strings.ReplaceAll(raw, "-", "_")
key = strings.ReplaceAll(key, " ", "_")
return normalizedType{Raw: raw, Key: key}
}
func prepareCandles(candlesByInstrument map[string][]domain.Candle) map[string][]domain.Candle {
prepared := make(map[string][]domain.Candle, len(candlesByInstrument))
for instrumentUID, candles := range candlesByInstrument {