Welcome to our Knowledge Base
Coding an AlgoQuant Strategy
Coding an AlgoQuant Strategy
There have been a number of questions on how to create a quantitative trading strategy using AlgoQuant. This is a brief introduction to the process, which will hopefully answer all questions you my have. Basic knowledge of the Java programming language is required, although you do not need to be an expert to follow this tutorial.
Sources of Inspiration
Of course you will need an idea of the type of strategy you want to develop. There are a number of popular (such as Moving Averages and Pairs Trading) and some more exotic strategies already included in AlgoQuant. You can use them as a template and build your own ideas on top of them for rapid prototyping. If you don’t want to use any of the built in strategies you can of course develop your own from scratch, in which case you may still find existing strategies useful as a code base or as examples if you are ever unsure about how something is done.
Implementing a Strategy
Once you have an idea for a strategy, coding it up is very straightforward. During a simulation in AlgoQuant a strategy object gets notified if an event (such as a price update) occurs, so that it can then carry out an action if desired. The strategy only has to deal with the type of event it has subscribed to and hence the amount of boilerplate code you have to write is minimal.
We define a strategy by implementing the Strategy interface. To handle updates of a certain kind, all you need to do is to process that kind of updates in onEvent()
method. If any of this feels unfamiliar don’t worry. You can find an example below and there are plenty of others included in AlgoQuant.
A typical strategy will receive updates of one or more types and use the information from these updates to make orders via the broker. On each update the strategy also receives the complete state (prices for all equities, current portfolio) so that you do not need to keep track of it yourself.
Example
As an example I will show how to write an extremely simple strategy: We long a stock if the previous period’s return was positive and short otherwise (this is a special case of the Geometric Moving Average strategy with lag times 1 and 2 periods). The full source code can be found in AlgoQuant in com.numericalmethod.algoquant.execution.strategy.demo or here.
public class TutorialStrategy implements Strategy { private final double scale = 1; private final Product product; private Double previousPrice = null; public TutorialStrategy(Product product) { this.product = product; } @Override public void onEvent(TraderContext ctx, Event evt) { if (evt instanceof OrderBook) { onOrderBookUpdate(ctx); } } private void onOrderBookUpdate(TraderContext ctx) { double currentPrice = ctx.marketCondition().orderBook(product).mid(); if (previousPrice != null) { Collection orders = computeOrders( currentPrice, ctx.tradeBlotter().position(product) ); ctx.broker().sendOrder(orders); } previousPrice = currentPrice; } private Collection computeOrders( double currentPrice, double position ) { double qty = 0.; if (currentPrice >= previousPrice) { qty = scale - position; } else if (currentPrice < previousPrice) { qty = -scale - position; } if (qty == 0.) { return Collections.emptySet(); } return singletonList(newMarketOrder(product, qty)); } }
The only parameter of this strategy is a Product object that states which product we are trading with. The scale of our simulation is taken to be 1. The method onDepthUpdate receives depth updates. Note that the arguments it takes also include:
- The current time
- The market condition, which stores the most recent depths for ALL equities
- The TradeBlotter which contains all past executions and our positions for all stocks
- The broker which can be used to place orders
On each orderbook update we call computeOrders()
. This method compares the current to the previous periods’ price. If the current price is greater than or equal to the previous one, we buy according to our position (the number of stocks we hold) and scale (the number of stocks we would like to hold). If the current price is less than or equal to the previous one we sell.
The order is then passed to the broker as a market order. Finally we store the current price in previousPrice so that it can be retrieved on the next update.
Data Cleaning / Filtering
Next we need to fetch and prepare data for backtesting. When working with real world data there is often some work necessary to get it into the right format for processing. In AlgoQuant you can do this easily by using cache processors. There are built in processors for eliminating faulty data points, sampling the data at regular intervals (for example to get monthly instead of daily data), filtering for outliers and many other common duties. For example this is how we could get monthly data for the Hang Seng index.
// set up the product final Stock stock = HSI.getInstance(); // specify the simulation period DateTime begin = JodaTimeUtils.getDate(2000, 1, 1, stock.exchange().timeZone()); DateTime end = JodaTimeUtils.getDate(2012, 1, 1, stock.exchange().timeZone()); Interval interval = new Interval(begin, end); // set up the data source; we download data from Yahoo! Finance here. YahooDepthCacheFactory yahoo = new YahooDepthCacheFactory(SimTemplateYahooEOD.DEFAULT_DATA_FOLDER); SequentialCache dailyData = yahoo.newInstance(stock, interval); // clean the data using filters; we simulate using only monthly data. EquiTimeSampler monthlySampler = new EquiTimeSampler(Period.months(1)); SequentialCache monthlyData = monthlySampler.process(dailyData);
The code first defines the product and date range for the simulation and then initializes Yahoo end-of-day data as the data source. Since the Yahoo data is daily, we use an EquiTimeSampler to sample the data at a one month interval.
For some data sources you may need to clean the data first. Algoquant allows chaining of filters, so that you can for instance, first filter our NaNs or entries with missing data and then perform further processing. This process is usually extremely tedious, error prone and time consuming in Matlab/R, but in Algoquant only takes a couple of lines of code.
Backtesting the Strategy
After you have written the strategy it is time to backtest it.
// set up the data source to feed into the simulator DepthCaches depthCaches = new DepthCaches(stock, monthlyData); // construct an instance of the strategy to simulate Strategy strategy = new TutorialStrategy(stock); // set up a simulator to host the strategy Simulator simulator = new SimpleSimulatorBuilder() .withDepthUpdates(depthCaches) .useStrategyPlotter( new SimpleStrategyPlotter("tutorial on " + stock.symbol())) .build(); // here is where the actual simulation happens TradeBlotter tradeBlotter = simulator.run(strategy); // collect all trades that happen during the simulation for post-trading analysis List executions = tradeBlotter.allExecutions();
First we convert our depth cache into the required format by the simulator. It takes an instance of DepthCaches (mapping Product to SequentialCache) as an argument to facilitate running simulations containing multiple products. In this case however there is only one product, so the DepthCaches object is created with only one pair.
Now we are ready to run the simulation. You will note I have also passed in a plotter, which creates a simple plot of the prices of our product and the P&L. Here is what this looks like (recall that we are running the strategy on monthly data):
Performance Measures
The simulation records its executions in an instance of TradeBlotter. From this we can compute performance measures. In this tutorial we look at PNL and annualized information ratio and omega.
// compute the P&L ExchangeRateTable rates = new SimpleExchangeRateTable(Currencies.HKD); double pnl = new ProfitLoss().valueOf(executions, depthCaches, rates); // compute the information ratio/Sharpe ratio double initialCapital = dailyData.iterator().next().data().mid(); double benchmarkPeriodReturn = 0.0; double ir = new InformationRatioForPeriods(initialCapital, interval, Period.years(1), ReturnsCalculators.SIMPLE, benchmarkPeriodReturn ).valueOf(executions, depthCaches, rates); // compute the Omega double lossThreshold = 0.0; double omega0 = new OmegaForPeriods(initialCapital, interval, Period.years(1), ReturnsCalculators.SIMPLE, lossThreshold, new OmegaBySummation() ).valueOf(executions, depthCaches, rates);
Since AlgoQuant supports having products with different currencies in the same simulation, we need to pass in an exchange rate table which in this case only contains one currency. From this, the depth caches and executions we compute profit and loss.
For omega and information ratio we additionally need to pass in information about the timing of the calculation (length of a period, start and end date), as well as information specific to the measure. As you can see though, this also requires only a minimal coding effort.
Of course there are many other performance measures included in AlgoQuant and even those presented above can be used in different ways, by changing their period or other parameters. Another great use of measures within AlgoQuant is for dynamic strategy calibration: You can specify which measure best encompasses the belief that a strategy has been successful in order to find the best set of parameters.
Next Steps
In this tutorial I have only touched on the most basic aspects of designing and backtesting a strategy but of course there is plenty of scope within AlgoQuant to take this further: Because it is backed by SuanShu a powerful math library there are numerous tools available to design strategies based on complex statistical operations and due to its object oriented nature, every aspect can be easily extended, so that if you are ever in need of a custom simulator or an intricate performance measure, your ideas can be realized in a minimal amount of time.
A final point to mention is that once you have backtested your strategy and wish to deploy it, you only have to replace the simulated market component with a component for the real market in which you wish to trade. This is made easy by the fact that simulation in AlgoQuant works exactly like a real market, so that you do not need to modify your strategy at all (and hence risk introducing errors), but only write adapters to work with your broker and live data source.
I hope you will find this helpful when designing a strategy with AlgoQuant.