I work in the business of algorithmic power trading, which is the automated trading of various power-related products in regulated electricity markets. Products include short-term inter-jurisdiction arbitrage, financial transmission rights, and more.

This year, my employer is expanding its trading operations to a new class of products. Since there is no overlap between this new work and our current operations, I got to design a technology stack most suited for the task. This technology stack includes Haskell, most importantly because wrong or unexpected trading decisions can (and have) cost us dearly.

In this blog post, I want to show you the basics of how we designed the framework in which to express trading strategies.

## The fundamentals of trading strategies

The fundamental pieces of trading operations are *strategies*. In algorithmic trading, strategies are computer programs that decide what to trade, and how to trade it, at any given moment.

Let’s take the example of a simple trading strategy that is only concerned with AAPL stock. The current stock price is about 190 USD today; our example strategy is defined thusly:

- If the AAPL price rises above 200, sell our holdings (if we have any);
- If the AAPL price falls below 180, buy 10 shares;

In this case, the result of this strategy is some signal to buy or sell AAPL stock. We can run our strategy in a loop:

```
import qualified Control.Monad
data Action = Buy Int
| Sell
| Hold
type Price = Double
main :: IO ()
= Control.Monad.forever $ do
main <- fetchMostRecentPrice
aaplPrice let action = myStrategy aaplPrice
executeMarketAction actionwhere
myStrategy :: Price -> Action
myStrategy aapl_price| aapl_price > 200 = Sell
| aapl_price < 180 = Buy 10
| otherwise = Hold
fetchMostRecentPrice :: IO Price
= (...)
fetchMostRecentPrice
executeMarketAction :: Action -> IO ()
= (...) execureMarketAction
```

and boom, you have a simple trading system!

Once you have a good idea for a strategy, you should test it on historical data. This is called *backtesting*. Backtesting strategies is, by definition, much more computationally intensive than live trading, since you are evaluating your strategy on much more data. We often backtest strategies on 5-10 years’ worth of data when it makes sense, and sometimes more.

I will also note that it is easiest to have strategies that can run in various contexts (including backtesting and live operations) if the strategy is *pure* (in the mathematical sense). It is in the quest for purity and performance that we decided to implement the trading system for a new asset class in Haskell.

## Trading strategies in Haskell

For the simplicity of presentation, we will only consider strategies that involve prices. The simplest such strategies are strategies which depend on the *most recent* price:

```
newtype Strategy
= MkStrategy { runStrategy :: Price -> Action }
```

`Strategy`

is a type of functions, from the most recent `Price`

known to some market action. This is only re-packaging the example above.

Let’s build a backtesting framework. There are two parts here:

- determine historical market actions;
- simulate the effects of market actions.

In practice, the two parts of backtesting are handled simultaneously. However, for simplicity, I will only consider the first part here.

The nature of this problem is well-suited to streaming approaches; I will use `pipes`

^{1}:

```
-- From the `time` package
import Data.Time ( UTCTime )
-- From the `pipes` package
import Pipes ( Producer, (>->) )
import qualified Pipes
import qualified Pipes.Prelude as Pipes
-- | From a stream of input features, produce a stream
-- of output 'Market' actions
backtestStrategy :: Monad m
=> Strategy r
-> Producer (UTCTime, Price) m () -- ^ stream of timestamped AAPL prices
-> m BacktestResults
backtestStrategy strat prices = prices
>-> Pipes.map (\(k, f) -> (k, runStrategy strat f))
>-> simulateMarketActions
-- The following is out-of-scope
data BacktestResults = MkBacktestResults (...)
simulateMarketActions :: Consumer (UTCTime, Action) m BacktestResults
= (...) simulateMarketActions
```

## More expressive strategies

I have a problem with the above definition of `Strategy`

: I’m limited to strategies based on the single, most recent `Price`

. What if I had a good idea for a strategy which involves the last 10 price values? Our `Strategy`

type is not expressive enough: it only takes one type of *feature*, while we want to support a wide range of features.

I will define a `Strategy`

type which removes restrictions on the input feature:

```
newtype Strategy r
= MkStrategy { runStrategy :: r -> Action }
```

with the understanding that the data of type `r`

is somehow *derived* from prices.

What are some features derived from prices, that we might be interested in? * Price history, e.g. most recent N ticks; * Price aggregations, e.g. average of most recent M ticks; * Rolling aggregations, e.g. N-tick history of the averages of M ticks;

Every conceptual feature described above has some free parameters. We don’t want to have separate strategies like `Strategy PriceHistoryForPast10Ticks`

and `Strategy PriceHistoryForPast20Ticks`

. More specifically, for every feature of type `r`

, there is a type of parameters `p`

which describes the parameters of `r`

.

For example^{2}:

```
-- from the `javelin` pacakge
import Data.Series ( Series )
newtype PriceHistory
= MkPriceHistory (Series UTCTime Price)
data NumTicks
= MkNumTicks { numTicks :: Int }
```

## Typed features and their parametrization

We could list all possible features in a big sum type:

```
data Feature
= FPrice Price
| FPriceHistory (Series UTCTime Price)
| FAveragePrice Price
| (...)
```

However, it’s not possible to control what features go in what strategy. We can do better.

We want to be able to link the types `PriceHistory`

and `NumTicks`

such that they are used together when backtesting, to ensure type safety. This is the domain of indexed type families, or type families for short. We will amend our `Feature`

typeclass and `backtestStrategy`

function to take into account feature parametrization:

```
class Feature r where
-- For every instances `r` of `Feature`,
-- there is an associated type `Parameters r` which the user
-- needs to specify. See examples below.
type Parameters r
deriveFeature :: Monad m
=> Parameters r
-> Producer (UTCTime, Price) m ()
-> Producer (UTCTime, r) m ()
backtestStrategy :: (Feature r, Monad m)
=> Strategy r
-> Parameters r
-> Producer (UTCTime, Price) m ()
-> m BacktestResults
backtestStrategy strat params prices = deriveFeature params prices
>-> Pipes.map (\(k, feature) -> (k, runStrategy strat feature))
>-> simulateMarketActions
```

Let’s look at two example instances of `Feature`

. The simplest is the basic feature or `Price`

:

```
type NoParameters = ()
instance Feature Price where
-- The `Price` feature has no free parameters
type Parameters Price = NoParameters
deriveFeature :: Monad m
=> NoParameters
-> Producer (UTCTime, Price) m ()
-> Producer (UTCTime, Price) m ()
= prices deriveFeatures _ prices
```

This is easy because there are no parameters. What about looking at the price history?

```
newtype PriceHistory
= MkPriceHistory (Series UTCTime Price)
newtype NumTicks
= MkNumTicks { numTicks :: Int }
instance Feature PriceHistory where
type Parameters PriceHistory = NumTicks
deriveFeature :: Monad m
=> NumTicks
-> Producer (UTCTime, Price) m ()
-> Producer (UTCTime, PriceHistory) m ()
MkPriceHistoryParameters numTicks) prices
deriveFeature (= prices >-> accumulate numTicks
>-> Pipes.map (\xs -> (maximum $ Series.index xs, MkPriceHistory xs))
where
-- out of scope, see end of blog post for link to source
accumulate :: Functor m
=> Int
-> Pipe (UTCTime, a) (Series UTCTime a) m ()
= (...) accumulate
```

Finally, as an example of the power of this approach, we’ll create a strategy which combines two features.

First, we’ll extend the `Feature`

class to combine two features `a`

and `b`

into one `(a, b)`

feature:

```
instance (Feature a, Feature b) => Feature (a, b) where
type Parameters (a, b) = (Parameters a, Parameters b)
deriveFeature :: Monad m
=> Parameters (a, b)
-> Producer (UTCTime, Price) m ()
-> Producer (UTCTime, (a, b)) m ()
deriveFeature (paramsA, paramsB) prices = Pipes.zipWith (\(k,a) (_, b) -> (k, (a, b)))
(deriveFeature paramsA prices) (deriveFeature paramsB prices)
```

Second, we’ll define a simple strategy that compares the most recent price against the average price of the last 10 ticks:

```
import Data.Series ( fold, mean )
finalStrategy :: Strategy (PriceHistory, Price)
finalStrategy = MkStrategy $ \(MkPriceHistory history, price)
-> let avgPrice = fold mean history
in case price `compare` avgPrice of
GT -> Sell
LT -> Buy 10
EQ -> Hold
```

It is trivial to backtest this strategy like so:

```
backtestFinalStrategy :: Monad m
=> Producer (UTCTime, Price) m ()
-> m BacktestResults
= backtestStrategy finalStrategy ( MkNumTicks 10, () ) backtestFinalStrategy
```

and voilà!

## Conclusion

In this post, I have shown you how to define trading strategies with typed feature parametrization, which is a neat use of type families.

*All code is available in this Haskell module.*

If you are unfamiliar with

`pipes`

, you should check out its tutorial.↩︎We are storing the price history in a

`Series`

, which comes from the`javelin`

package that I created specifically for this work.↩︎