Writing Custom Filters

Writing Custom Filters

Custom filters extend the Trade Director’s risk management capabilities.

The filter interface

Individual filters

type TradeDirectorFunc interface {
    Name() string
    Evaluate(ctx *DirectorContext, req *TradeRequest) (approved bool, reason string)
    ParallelCompatible() bool
}

Batch filters

type BatchTradeDirectorFunc interface {
    Name() string
    EvaluateBatch(ctx *BatchDirectorContext, requests []*TradeRequest) (
        approved []*TradeRequest, rejections map[*TradeRequest]string)
    ParallelCompatible() bool
}

The TradeRequest

Each order generates a TradeRequest containing:

FieldTypeDescription
StrategyIDstringUnique strategy identifier
SymbolstringTrading symbol
OrderTypeOrderTypeBuy, Sell, ExitLong, ExitShort
ExecMethodOrderExecMethodAtMarket, AtLimit, etc.
QtyintOrder quantity
Pricefloat64Order price
PointValuefloat64Symbol’s point value
AssetClassAssetClassFutures, Stock, etc.
CategoryTypeCategoryTypeEnergy, Metal, Index, etc.
TDValueanySignal strength for ranking

ParallelCompatible()

Return true if the filter only examines the trade request and does not need portfolio state. Return false if the filter reads portfolio-level fields like equity, exposure, or position counts.

Guidelines:

  1. Return true if your filter only examines the trade request and strategy-local data
  2. Return false if your filter reads any ctx.Portfolio fields
  3. Conditional compatibility: Some filters are parallel-compatible only in certain modes:
func (f *PositionSizer) ParallelCompatible() bool {
    // Only parallel-compatible in "fixed" mode where no equity calculation needed
    return f.Mode == "fixed"
}

Example: time-of-day filter

type TimeOfDayFilter struct {
    BlockedHours []int
}

func (f *TimeOfDayFilter) Name() string {
    return "time-of-day"
}

func (f *TimeOfDayFilter) Evaluate(ctx *DirectorContext, req *TradeRequest) (bool, string) {
    hour := time.Unix(req.Epoch, 0).UTC().Hour()
    for _, blocked := range f.BlockedHours {
        if hour == blocked {
            return false, fmt.Sprintf("entries blocked during hour %d UTC", blocked)
        }
    }
    return true, ""
}

func (f *TimeOfDayFilter) ParallelCompatible() bool {
    return true // Does not access portfolio state
}