Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

79 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Decision Engine

Trading rule evaluation and signal generation service for the systematic trading platform.

Overview

The Decision Engine consumes technical indicator events from the Analytics Service, evaluates configurable trading rules, and produces:

  1. Individual Decisions (trading.decisions) - BUY/SELL/WATCH signals per stock
  2. Rankings (trading.rankings) - Cross-stock comparison: "Which stock should I buy first?"

Your Trading Strategy

The engine implements your core strategy: "Buy dips in uptrending stocks"

Primary Rules

Rule Condition Signal
Buy Dip in Uptrend RSI < 40 AND SMA_20 > SMA_50 BUY
Strong Buy Signal RSI < 35 AND SMA_20 > SMA_50 > SMA_200 BUY (highest confidence)
Weekly Uptrend SMA_20 > SMA_50 Required for buy
Monthly Uptrend SMA_50 > SMA_200 Preferred, not required

Supporting Rules

  • RSI Oversold/Overbought
  • MACD Bullish/Bearish Crossover
  • Full Trend Alignment (Golden Cross)
  • Trend Break Warning

Ranking

When you have multiple BUY signals, the ranker answers: "Which should I buy first?"

Ranking weights:

  • Dip Depth (30%): Lower RSI = better entry
  • Trend Strength (30%): Full SMA alignment preferred
  • Confidence (25%): More rules agreeing = higher confidence
  • Volatility (15%): Moderate volatility preferred

Adding New Rules

  1. Describe your rule in plain English
  2. I translate it to a Python rule class
  3. Add to config/rules.yaml

Example rule in rules/composite_rules.py:

class BuyDipInUptrendRule(Rule):
    """
    YOUR PRIMARY RULE:
    Natural Language: "Buy when RSI dips to 35-40 AND weekly uptrend is intact"
    """
    @property
    def description(self) -> str:
        return "Buy when RSI < 40 AND weekly uptrend intact (SMA_20 > SMA_50)"

Configuration

Edit config/rules.yaml to:

  • Enable/disable rules
  • Adjust thresholds (RSI levels, etc.)
  • Set rule weights for ranking

Replay mode (CLOCK_MODE)

Env var Default Purpose
CLOCK_MODE real replay reads simulated time from Redis; anything else (including a typo) is real
CLOCK_SIM_KEY sim:clock Redis key holding an ISO-8601 simulated time

The engine reads "now" through decision_engine.clock rather than calling datetime.utcnow() directly, so the e2e-replay harness can drive it with simulated time. Every relative-duration gate depends on this: signal debounce, ranking interval, context staleness, earnings staleness, stale-signal and stale-state eviction, tier cache TTL, and the trade plan's validity window.

Production behaviour is unchanged. CLOCK_MODE defaults to real, only the exact string replay switches modes, and the real path never opens a Redis connection for the clock. In replay mode initialize() fails fast if the driver has not published a simulated time, rather than producing a whole run quietly stamped with today's date.

Three things deliberately keep the wall clock, because simulated time would make them wrong rather than right:

  • EVALUATION_DURATION — a latency metric; measuring real work in simulated time records zero.
  • The trade planner's Redis circuit-breaker backoff — Redis is a real server during a replay, so its 15-second backoff must elapse in real seconds.
  • rules_cache config caching — rules are static across a run, and a simulated TTL would re-read Redis on every simulated minute.

Naive vs aware

decision_engine.clock exposes three accessors that are exact drop-in replacements preserving the tz-awareness each call site already had:

Call site used Replace with Returns
datetime.utcnow() clock.utcnow() naive UTC
datetime.now(timezone.utc) clock.now() aware UTC
time.time() clock.timestamp() float epoch

This is not cosmetic. Signal timestamps, last_update, and entry_date are naive throughout the service; returning an aware datetime from utcnow() would raise TypeError: can't subtract offset-naive and offset-aware datetimes at every comparison.

Running

# Local development
cp .env.example .env
# Edit .env with your settings
python -m decision_engine.main

The service entrypoint is the decision_engine.main module (also the Docker image's CMD).

Docker

This repo ships a Dockerfile but not a docker-compose.yml — Compose is defined at the platform level, where this service is orchestrated alongside Kafka/Redpanda, Redis, and the other trading services. To run the container standalone:

docker build -t decision-engine .
docker run --rm --env-file .env decision-engine

When run as part of the platform stack, bring it up with the platform-level Compose file (e.g. docker compose up decision-engine).

Event Schemas

Input: INDICATOR_UPDATE

{
  "event_type": "INDICATOR_UPDATE",
  "data": {
    "symbol": "AAPL",
    "indicators": {
      "RSI_14": 28.5,
      "MACD": 0.12,
      "SMA_20": 150.00,
      "SMA_50": 148.00,
      "SMA_200": 145.00
    }
  }
}

Output: DECISION_UPDATE

{
  "event_type": "DECISION_UPDATE",
  "data": {
    "symbol": "AAPL",
    "signal": "BUY",
    "confidence": 0.85,
    "primary_reasoning": "BUY DIP: deep dip (RSI: 28.5) in strong uptrend",
    "rules_triggered": [...]
  }
}

Output: RANKING_UPDATE

{
  "event_type": "RANKING_UPDATE",
  "data": {
    "signal_type": "BUY",
    "rankings": [
      {"rank": 1, "symbol": "WPM", "score": 0.92},
      {"rank": 2, "symbol": "AAPL", "score": 0.85}
    ]
  }
}

Backtesting Integration

Rules are compatible with the backtesting service via the RuleBasedStrategy adapter:

from decision_engine.adapters.backtesting import RuleBasedStrategy
from decision_engine.rules.composite_rules import BuyDipInUptrendRule

strategy = RuleBasedStrategy(
    rules=[BuyDipInUptrendRule()],
    min_confidence=0.6
)

Built with Claude Code

A large portion of this project — implementation, tests, and documentation — was written in pair-programming sessions with Claude Code, Anthropic's agentic command-line tool.

About

Python rule-evaluation and trade-plan engine — confidence scoring, pre-trade checklist, earnings gate — for a trading platform.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages