Skip to main content

Overview

PriceSignal uses an NRules-based rule engine to evaluate price alerts and trading conditions in real-time. The rule engine processes incoming price data and triggers alerts when user-defined conditions are met.

Architecture

The rule engine is implemented in src/Application/Rules/Common/RuleEngine.cs and uses the following components:
  • NRules Session: Manages the inference engine session for rule evaluation
  • RuleCache: Stores and retrieves active rules
  • PriceHistoryCache: Maintains historical price data for technical indicators
  • IPrice: Price data interface for incoming market data

Core Functionality

Price Evaluation

The EvaluateRules method is called for each incoming price update:
public void EvaluateRules(IPrice price)
{
    // Add price to history cache for technical indicators
    priceHistoryCache.AddPrice(price.Symbol, price);

    // Insert current price into rule session
    session.Insert(price);
    
    // Get all rules for this symbol
    var rules = ruleCache.GetAllRules()
        .Where(r => r.Instrument.Symbol == price.Symbol)
        .ToList();

    // Insert all matching rules
    session.InsertAll(rules);

    // Fire the rules engine
    session.Fire();

    // Clean up session
    session.Retract(price);
    session.RetractAll(rules);
}

Technical Indicators

RSI Calculation

The rule engine includes built-in support for calculating the Relative Strength Index (RSI):
private decimal CalculateRSI(List<Domain.Models.Instruments.Price> prices, int period)
{
    if (prices.Count < period+1) return 0;

    var gains = 0.0m;
    var losses = 0.0m;
    
    for (int i = 1; i <= period; i++)
    {
        var change = prices[prices.Count - i].Close - prices[prices.Count - i - 1].Close;
        if (change > 0)
        {
            gains += change;
        }
        else
        {
            losses -= change;
        }
    }

    var avgGain = gains / period;
    var avgLoss = losses / period;
    if (avgLoss == 0) return 100;
    var rs = avgGain / avgLoss;
    if (rs == 0) return 0;

    return 100 - (100 / (1 + rs));
}

Rule Lifecycle

  1. Price arrives from Binance WebSocket stream
  2. Cache updated with latest price data
  3. Price inserted into NRules session
  4. Rules loaded for the specific symbol
  5. Rules inserted into session
  6. Engine fires and evaluates all conditions
  7. Session cleaned by retracting price and rules

Performance Considerations

  • Rules are filtered by symbol before insertion to minimize evaluation overhead
  • Session objects are reused across evaluations
  • Price history is cached to avoid database queries during rule evaluation
  • Rules and prices are retracted after each evaluation to prevent memory leaks

Dependencies

public class RuleEngine(
    RuleCache ruleCache, 
    PriceHistoryCache priceHistoryCache, 
    ILogger<RuleEngine> logger, 
    ISession session, 
    IServiceProvider serviceProvider)

Build docs developers (and LLMs) love