second version
This commit is contained in:
@@ -112,6 +112,7 @@ type RiskConfig struct {
|
||||
MaxClockDriftSec int `env:"MAX_CLOCK_DRIFT_SEC" envDefault:"2"`
|
||||
ReconciliationWindowHours int `env:"RECONCILIATION_WINDOW_HOURS" envDefault:"72"`
|
||||
ReconciliationSkewSec int `env:"RECONCILIATION_SKEW_SEC" envDefault:"10"`
|
||||
CommissionToleranceRUB decimal.Decimal `env:"COMMISSION_TOLERANCE_RUB" envDefault:"0.01"`
|
||||
CashUsageBuffer decimal.Decimal `env:"CASH_USAGE_BUFFER" envDefault:"0.95"`
|
||||
RiskBudgetPerInstrumentPct decimal.Decimal `env:"RISK_BUDGET_PER_INSTRUMENT_PCT" envDefault:"0.005"`
|
||||
MinOrderNotionalRUB decimal.Decimal `env:"MIN_ORDER_NOTIONAL_RUB" envDefault:"1000"`
|
||||
@@ -198,6 +199,9 @@ func (c *Config) Validate() error {
|
||||
if c.Risk.ReconciliationSkewSec < 0 {
|
||||
return errors.New("RISK_RECONCILIATION_SKEW_SEC must be non-negative")
|
||||
}
|
||||
if c.Risk.CommissionToleranceRUB.IsNegative() {
|
||||
return errors.New("RISK_COMMISSION_TOLERANCE_RUB must be non-negative")
|
||||
}
|
||||
if c.Commission.FreeOrderCountPolicy != "submitted" {
|
||||
return fmt.Errorf("COMM_FREE_ORDER_COUNT_POLICY must be submitted, got %q", c.Commission.FreeOrderCountPolicy)
|
||||
}
|
||||
@@ -210,6 +214,9 @@ func (c *Config) Validate() error {
|
||||
if (c.App.Mode == domain.ModeSandbox || c.App.Mode == domain.ModeLiveReadonly || c.App.Mode == domain.ModeLiveTrade) && c.TInvest.Token == "" {
|
||||
return fmt.Errorf("TINVEST_TOKEN is required for APP_MODE=%s", c.App.Mode)
|
||||
}
|
||||
if (c.App.Mode == domain.ModeSandbox || c.App.Mode == domain.ModeLiveReadonly || c.App.Mode == domain.ModeLiveTrade) && c.TInvest.AccountID == "" {
|
||||
return fmt.Errorf("TINVEST_ACCOUNT_ID is required for APP_MODE=%s", c.App.Mode)
|
||||
}
|
||||
if c.TInvest.UseSandbox && c.App.Mode != domain.ModeSandbox {
|
||||
return errors.New("TINVEST_USE_SANDBOX=true is only valid with APP_MODE=sandbox")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"overnight-trading-bot/internal/domain"
|
||||
"overnight-trading-bot/internal/timeutil"
|
||||
)
|
||||
|
||||
func TestValidateRequiresAccountIDForBrokerModes(t *testing.T) {
|
||||
cfg := minimalBrokerConfig(domain.ModeSandbox)
|
||||
cfg.TInvest.AccountID = ""
|
||||
err := cfg.Validate()
|
||||
if err == nil || !strings.Contains(err.Error(), "TINVEST_ACCOUNT_ID") {
|
||||
t.Fatalf("Validate err=%v, want TINVEST_ACCOUNT_ID requirement", err)
|
||||
}
|
||||
}
|
||||
|
||||
func minimalBrokerConfig(mode domain.Mode) Config {
|
||||
return Config{
|
||||
App: AppConfig{
|
||||
Mode: mode,
|
||||
Timezone: "Europe/Moscow",
|
||||
ShutdownTimeoutSec: 30,
|
||||
},
|
||||
TInvest: TInvestConfig{
|
||||
Token: "token",
|
||||
AccountID: "account",
|
||||
},
|
||||
DB: DBConfig{DSN: "user:pass@tcp(localhost:3306)/bot"},
|
||||
Execution: ExecutionConfig{
|
||||
EntrySignalTime: mustTOD("18:10:00"),
|
||||
EntryWindowStart: mustTOD("18:20:00"),
|
||||
EntryWindowEnd: mustTOD("18:38:30"),
|
||||
NoNewEntryAfter: mustTOD("18:38:30"),
|
||||
ExitWatchStart: mustTOD("09:50:00"),
|
||||
ExitNotBefore: mustTOD("10:03:00"),
|
||||
ExitWindowStart: mustTOD("10:05:00"),
|
||||
ExitWindowEnd: mustTOD("10:25:00"),
|
||||
HardExitDeadline: mustTOD("10:45:00"),
|
||||
QuoteDepth: 20,
|
||||
OrderPollIntervalMS: 500,
|
||||
},
|
||||
Risk: RiskConfig{
|
||||
APIOutageHaltSec: 180,
|
||||
ReconciliationWindowHours: 72,
|
||||
ReconciliationSkewSec: 10,
|
||||
CommissionToleranceRUB: decimal.NewFromFloat(0.01),
|
||||
},
|
||||
Commission: CommissionConfig{FreeOrderCountPolicy: "submitted"},
|
||||
}
|
||||
}
|
||||
|
||||
func mustTOD(raw string) timeutil.TimeOfDay {
|
||||
tod, err := timeutil.ParseTimeOfDay(raw)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tod
|
||||
}
|
||||
Reference in New Issue
Block a user