I am always amused when retail traders are offered and then do use black boxes with complex frameworks to build automatic trading strategies. Usually that goes with an explanation how cool those platforms are and how easy to build a strategy using them. They forget about vendor lock-in and the most terrible model of shared state with events hell like “OnData”, “OnOrder”, “OnBarOpen”, “OnBarClose”, etc. The more I talk with people, the more I realize that CEP is the approach that desks and serious traders use.

Spreads is not a framework for financial markets but a generic CEP library that allows to model markets as data series and use pure math and functional data transformations.

The simplest trivial example is simple moving average trend following strategy. This example is written in F# interactive console.  First, we generate trending artificial data where trend changes each 40 points:

let quotes : Series<DateTime, float> = // data is produced outside
    let mutable previous = 1.0
    let sm = SortedMap()
    let now = DateTime.UtcNow
    let mutable trend = -1.0
    let mutable cnt = 0
    for i in 0..500 do
      previous <- previous*(1.0 + rng.NextDouble()*0.002 - 0.001 + 0.001 * trend)
      sm.Add(now.AddSeconds(-((500-i) |> float)*0.2), previous)
      cnt <- cnt + 1
      if cnt % 40 = 0 then trend <- -trend

Task.Run((fun _ -&gt;

    while not ct.IsCancellationRequested do
      Thread.Sleep(500)
      previous &lt;- previous*(1.0 + rng.NextDouble()*0.002 - 0.001 + 0.001 * trend)
      sm.Add(DateTime.UtcNow, previous)
      cnt &lt;- cnt + 1
      if cnt % 40 = 0 then trend &lt;- -trend
  ), ct) |&gt; ignore
sm :&gt; Series&lt;DateTime, float&gt;</pre>

Then we calculate simple moving average over 20 points. The second argument allows using incomplete windows, e.g. for the first 10 data points the average is calculated over them. With false, the first SMA point is calculated only at the 20th quotes point.

let sma = quotes.SMA(20, true)
Our trading rule is that if the current price is above SMA, we go long, and we go short otherwise. This is a classic trend following strategy and it works quite well in the long run on emerging markets and some commodities. We calculate our target position as:
let targetPosition = (quotes / sma - 1.0).Map(fun deviation -> double <| Math.Sign(deviation))
This target position series is live - its values are updated in real-time together with quotes. To execute our strategy, we must prepare storage for actual positions and trades:
// we must keep track of actual position
let actualPositionWritable = SortedMap<DateTime,float>()
let realTrades = SortedMap<DateTime,float>()
let actualPosition = actualPositionWritable :> Series<,>
actualPosition.Do((fun k v ->
    Console.WriteLine(“Actual position: “ + k.ToString() + ” : “ + v.ToString())
  ), ct)
Then we could feed our target position to a trader. Trader is “functional”, instead of receiving orders it receives desired state and does its best to move actual state to the desired state. Here for simplicity we have a very dangerous assumption that trades are executed  immediately after a signal. Such an assumption should be avoided in real world backtesting. We use Do() extension method that invokes an action over each key/value in a series sequentially:
targetPosition.Do(
  (fun k v ->
    if k <= DateTime.UtcNow.AddMilliseconds(-400.0) then
      // simulate historical trading
      let qty =
        if actualPositionWritable.IsEmpty then v
        else (v - actualPositionWritable.Last.Value)
      if qty <> 0.0 then
        Console.WriteLine(k.ToString() +  “ : Paper trade: “ + qty.ToString())
        actualPositionWritable.AddLast(k, v)
    else
      // do real trading
      let qty =
        if actualPositionWritable.IsEmpty then failwith “must test strategy before real trading”
        else (v - actualPositionWritable.Last.Value)
      if qty <> 0.0 && k > actualPositionWritable.Last.Key then // protect from executing history
        let tradeTime = DateTime.UtcNow.AddMilliseconds(5.0)
        Console.WriteLine(tradeTime.ToString() +  “ : Real trade: “ + qty.ToString())
        realTrades.AddLast(tradeTime, qty)
        actualPositionWritable.AddLast(tradeTime, v)
  ), ct)
Now the trading is started and we could see our trades in the FSI console every several seconds.

Finally, we calculate our P&L. Because the market was trending by construction, we have earned a lot of money with this strategy:

let returns = quotes.ZipLag(1u, fun c p -> c/p - 1.0)
let myReturns = actualPosition.Repeat() * returns
let myAumIndex = myReturns.Scan(1.0, fun st k v -> st*(1.0 + v))
Here, we use ZipLag() to calculate price returns. Then we calculate returns of our position and aggregate them as running product (no trading costs in this example). All these and above series are live, we could use the Do() method to print the values in real-time:
// Print live data
myAumIndex.Do((fun k v ->
    Console.WriteLine(“AUM Index: “ + k.ToString() + ” : “ + v.ToString())
  ), ct)
 

Note that from the technical point of view there is nothing special that links the calculations to financial markets. Instead of financial data, we could use data from some sensors, e.g. temperature, and instead of trading we could turn off/on heating if temperature goes above or below some target value.

The whole example is here. You could select all code, press Alt+Enter and watch how AUM increases while using Spreads library! :)