EXAMPLE B: Reading from Multiple Data Streams - Creating a Custom Index
There are no restrictions on the number of data sources that can be accessed by a single strategy. This makes possible the calculation of intermarket spreads, custom stock baskets, currency cross rates, and anything else that requires multiple data series.The following example will demonstrate how strategies:
- trade futures contracts
- read from multiple data sources
- calculate custom indices
- use limit orders
The following strategy will determine whether energy prices, as measured by a basket of energy related futures contracts (gasoline, crude oil, heating oil), have any predictive qualities for the stock market. The stock market is represented by the SP500 futures contract as traded on the CME.
Strategy s("basket_demo"); //1
s.SetPerSideCommission(7.0); //2
s.SetPerSideSlippage(25.0); //3
s.SetOrderSizeAs(NUM_UNITS); //4
s.SetBigPointValue(250.0); //5
s.SetPyramidingEnabled(false); //6
s.SetUsingSmartLimitOrders(true); //6b
Tuple sp500 = IRecord(db, "sp.future.ohlcvoi.daily",
PRICE_MASTER | TIME_MASTER ); //7
Tuple crudeOil = IRecord(db, "cl.future.ohlcvoi.daily"); //8
Tuple heatingOil = IRecord(db, "ho.future.ohlcvoi.daily"); //9
Tuple gasoline = IRecord(db, "hu.future.ohlcvoi.daily"); //10
List energyBasket = ( crudeOil("close"),
heatingOil("close"),
gasoline("close") ); //11 Creating Basket
Double energyIndex = BalancedIndex( energyBasket, 100.0, 20); //12 Calculating Index
Double energyPercChg = (energyIndex - Prev(energyIndex)) / Prev(energyIndex); //13
Bool buySignal = IsFlat() && energyPercChg < 0.0; //14 buy stocks when energy drops
Bool exitLongSignal = energyPercChg > 0.0; //15 exit when energy rises
LimitOrder(LONG, 5, buySignal, sp500("close"), NEXT_BAR); //16 long entry
MarketOrder(EXIT_LONG, exitLongSignal, NEXT_OPEN); //17 long exit
Bool sellSignal = IsFlat() && energyPercChg > 0.0; //18 sell stocks when energy rises
Bool exitShortSignal = energyPercChg < 0.0; //19 exit when energy drops
LimitOrder(SHORT, 5, sellSignal, sp500("close"), NEXT_BAR); //20
MarketOrder(EXIT_SHORT, exitShortSignal, NEXT_OPEN); //21
s.Log( db, TRADES | DRAWDOWNS | TRANSACTIONS); //22
s.Evaluate(1983, 2007); //23
s.PrintEquityGraph(LOG_FILE); //24
s.PrintPerformanceReport(LOG_FILE); //25
s.PrintTradeReport(LOG_FILE); //26
s.PrintDrawdownReport(LOG_FILE); //27
Strategy Properties
This strategy trades futures contracts, that much is apparent merely by looking at the strategy properties!
s.SetPerSideCommission(7.0); //2
s.SetPerSideSlippage(25.0); //3 value of 1 tick
s.SetOrderSizeAs(NUM_UNITS); //4
s.SetBigPointValue(250.0); //5 Large SP500 contract - 250$ per point
s.SetUsingSmartLimitOrders(true); //6b
The 'big point value', as set in //5 is the dollar gain or loss caused by a 1 point change in the value of the underlying. Normally, for stocks, this number is set to 1. In this case it is set to 250, corresponding to a minimum tick of 0.1 and a tick value of 25 dollars. The per-side slippage is set to 25 dollars, which corresponds to 1 tick. The per-side commission is set to 7 dollars.
Line //4 tells the strategy that the 'size' argument to order functions should be interpreted as 'units' (the default), and not 'cash amounts'.Tuple sp500 = IRecord(db, "sp.future.ohlcvoi.daily",
PRICE_MASTER | TIME_MASTER ); //7
Line //7 reads data records from the table containing historical prices for the SP500, and defines it as PRICE_MASTER and TIME_MASTER, a topic already discussed in the previous example.
Accessing Multiple Data Sources
Where it gets interesting is from line //8 to //11:
Tuple crudeOil = IRecord(db, "cl.future.ohlcvoi.daily"); //8
Tuple heatingOil = IRecord(db, "ho.future.ohlcvoi.daily"); //9
Tuple gasoline = IRecord(db, "hu.future.ohlcvoi.daily"); //10
List energyBasket = ( crudeOil("close"),
heatingOil("close"),
gasoline("close") ); //11
Double energyIndex = BalancedIndex( energyBasket, 100.0, 20); //12
Lines //8 to //10 repeatedly call the IRecord() function to read data from energy related tables. The closing prices of each record are then aggreated as a list called 'energyBasket'. This list is then passed as argument to the BalancedIndex() function, which calcualtes an index, starting at a given number, in this case 100.0, and automatically rebalances exposure to each underlying in the basket at a given interval, in this case every 20 bars. Alternate algorihms could be embodied in a custom component easily.
There is no real limit to the number of time series that the DynamicIndex() function can handle. If a larger number of items, 100 or even 1000 items, needs to be placed in a List object, it makes sense to combine formula code with a C++ loop statement. This would look something like this:
std::vector<string> tableNames; //a collection of table names
List basket;
for(int i=0; c< tableNames.size(); c++)
basket.Append( IRecord(db, tableNames[c])("close") );
Combining C++ statements with formula code in such a way is quite powerful. Doing so is of course possible because, despite the appearances, formula code is executed once only. (see Modeling With Formulas)
Buy and Sell Signals
Bool buySignal = IsFlat() && energyPercChg < 0.0; //14
Bool exitLongSignal = energyPercChg > 0.0; //15
The buy signal is quite simple! It signals when the current position is 'flat' and the custom energy index drops in price. The idea is that stocks rise when energy prices fall. The signal to exit the 'long' position occurs when energy prices rise again. While these rules are very simplistic, they are sufficient for the purpose of this example.
Using Limit Orders
Limit orders are very important because they are the only order type which (usually) does not incur slippage. Slippage represents the by far largest part of transaction costs which is why limit orders are the prefered tool for the professional trader. While the use of limit orders may lead to a missed opportunity on occasion, the savings from using them add up quickly!
The only time a limit order may incur slippage is when they are created as 'smart limit orders' (as controlled by the strategy's SetUsingSmartLimitOrders() member in //6 ) and the market gaps accross the limit price (overnight), at which point the limit order is assumed to be converted to a market order and filled at the bar's open price.
LimitOrder(LONG, 5, buySignal, sp500("close"), NEXT_BAR); //16
To keep the strategy code concise, the current bar's closing price was used as limit price. This means that there is a good chance that the open price gaps below the limit price at which point the limit order is evaluated as a market order, and incurs slippage. The system actually keeps track of what percent of transactions and units execute without slippage. These metrics are part of the Performance Report shown below.
Performance
In reality, events that affect energy prices simultaneously also affect stock prices. Trading with a 1 day delay, as with this strategy, is unlikely to produce good results, as can be seen from the equity graph below:
_____________________________________________________________________
| Net
Equity Graph (EOD)
|
|
* *
*| H: 441512.5
|
** **
+|
|
**
**
**| 296005
|
** ****
***|
|
** *******
***| 212857
|
** * * *******
****|
|
******* * ***********| 129710
|
* *
******* *
***** |
|
***** ****
********
** | 46563
|
** *** ***
********
|
|*********----**-----*---*****------------------**--*-----------------|
(0.0)
|
**** **
***
* *
|
|
*****
****
* *
| -119732
|
****
***
* ******
|
|
**
**** *********
| -202880
|
******** *****
|
|
***** ***
| -286027
|
* **
|
|
**
| L: -348387.5
|03-Jan-1983
28-Dec-2007 |
|_____________________________________________________________________|
_______________________________________________
|
|
|
Strategy Performance Report
|
|_______________________________________________|
Strategy Name:
'intermarket_demo'
Operational Bar:
3.Jan.1985-00:00:00:000
Last Bar:
31.Dec.2007-00:00:00:000
Bars Processed:
6307
Simulation Length (Years):
22.99
Bars per Year:
252
Last Trade was Open:
false
Risk Free Rate Of Return:
3.5
_________________________________________________________________________________________
| SUMMARY
|
|
ALL |
LONG |
SHORT |
|_______________________________|________|_______________|_______________|_______________|
| Net Profit
| [ccy] |
389,642.50 |
913,275.00 |
-523,632.50 |
| Gross
Profit
| [ccy] |
878,937.50 | 1,148,625.00
| -269,687.50 |
| Total Costs
| [ccy] |
489,295.00 |
235,350.00 |
253,945.00 |
|
* Commission
| [ccy] |
129,920.00 |
63,350.00 |
66,570.00 |
|
* Slippage
| [ccy] |
359,375.00 |
172,000.00 |
187,375.00 |
| Winners Net
| [ccy] |
8,681,180.00 |
4,700,730.00 | 3,980,450.00 |
| Losers Net
| [ccy] | -8,291,537.50 |
-3,787,455.00 | -4,504,082.50 |
| Profit
Factor
| [rat] |
1.05 |
1.24 |
0.88 |
|_______________________________|________|_______________|_______________|_______________|
| Largest
Drawdown
| [ccy] |
-478,620.00 |
|
|
|_______________________________|________|_______________|_______________|_______________|
| Number Of
Trades
| [trad] |
1,856 |
905 |
951 |
| Percent
Winners
| [pct] |
48.55 |
53.70 |
43.64 |
|_______________________________|________|_______________|_______________|_______________|
| Sum Bars -
All Trades
| [bars] |
3,642 |
1,714 |
1,928 |
| Perc. Bars
with Position
| [pct] |
57.75 | 27.18
|
30.57 |
|
|
|
|
|
|
|_______________________________|________|_______________|_______________|_______________|
| RISK /
RETURN (ALL TRADES)
|
| Account A |
Account B |
Account C |
|_______________________________|________|_______________|_______________|_______________|
| Start
Account Balance
| [ccy] |
479,100.00 |
958,200.00 | 1,437,300.00 |
| End
Account Balance |
[ccy] |
868,742.50 | 1,347,842.50 |
1,826,942.50 |
| Annualized
Rate Of Return
| [pct] |
2.62 |
1.50 |
1.05 |
| Annualized
Volatility (StDev) | [pct] |
32.13 |
13.93 |
9.17 |
| Sharpe
Ratio
| [rat] |
-0.03 |
-0.14 |
-0.27 |
|_______________________________|________|_______________|_______________|_______________|
| Max Account
Balance
| [ccy] |
930,237.50 | 1,409,337.50
| 1,888,437.50 |
| Min Account
Balance
| [ccy] |
124,677.50 |
603,777.50 | 1,082,877.50 |
|
|
|
|
|
|
|_______________________________|________|_______________|_______________|_______________|
| TRADES
|
|
ALL |
LONG |
SHORT |
|_______________________________|________|_______________|_______________|_______________|
| Number Of
Trades
| [trad] |
1,856 |
905 |
951 |
| Ave Net
Profit
| [ccy] |
209.94 |
1,009.14 |
-550.61 |
| Ave Gross
Profit
| [ccy] |
473.57 |
1,269.20 |
-283.58 |
| Ave Costs
| [ccy] |
263.63 |
260.06 |
267.03 |
|
* Ave Slippage
| [ccy] |
193.63 |
190.06 |
197.03 |
|
* Ave Commission
| [ccy] |
70.00 |
70.00 |
70.00 |
| Ave Winner
/ Ave Loser
| [rat] |
1.11 |
1.07 |
1.14 |
| Ave Num
Transactions
| [tran] |
2.00 |
2.00 |
2.00 |
| Ave (Max)
Position
| [unit] |
5.00 |
5.00 |
5.00 |
| Ave Length
| [bars] |
1.96 |
1.89 |
2.03 |
| Ave
Positive Excursion
| [ccy] |
9,646.45 | 9,579.28
|
9,710.37 |
| Ave
Negative Excursion
| [ccy] |
-8,520.10 |
-8,663.33 |
-8,383.81 |
|_______________________________|________|_______________|_______________|_______________|
| Max
Positive Excursion
| [ccy] |
133,750.00 |
99,375.00 |
133,750.00 |
| Max
Negative Excursion
| [ccy] |
-101,875.00 |
-101,875.00 |
-73,750.00 |
| Max Num
Transactions
| [tran] |
2 |
2 |
2 |
| Max
Position Size |
[unit] |
5.00 |
5.00 |
5.00 |
| StDev Net
Profit
| [ccy] |
15,484.60 |
16,501.63 |
14,409.34 |
|
|
|
|
|
|
|_______________________________|________|_______________|_______________|_______________|
| WINNERS vs
LOSERS
|
|
ALL |
LONG |
SHORT |
|_______________________________|________|_______________|_______________|_______________|
| Number of
Winners
| [trad] |
901 |
486 |
415 |
| Number of
Losers
| [trad] |
955 |
419 |
536 |
| Average
Winner
| [ccy] |
9,635.05 |
9,672.28 |
9,591.45 |
| Average
Loser
| [ccy] |
-8,682.24 |
-9,039.27 |
-8,403.14 |
| Largest
Winner
| [ccy] |
129,180.00 |
95,430.00 |
129,180.00 |
| Largest
Loser
| [ccy] |
-102,070.00 |
-102,070.00 |
-52,820.00 |
| Max
Consecutive Winners
| [trad] |
8 |
8 |
16 |
| Max
Consecutive Losers
| [trad] |
10 |
8 |
11 |
| Ave Winner
Length
| [bars] |
2.00 |
1.93 |
2.07 |
| Ave Loser
Length
| [bars] |
1.93 |
1.85 |
2.00 |
|
|
|
|
|
|
|_______________________________|________|_______________|_______________|_______________|
| UNITS
|
|
ALL |
LONG |
SHORT |
|_______________________________|________|_______________|_______________|_______________|
| Ave Net
Profit
| [ccy] |
20.9937 |
100.9144 |
-55.0613 |
| Ave Gross
Profit
| [ccy] |
47.3565 |
126.9199 |
-28.3583 |
| Ave Costs
| [ccy] |
26.3629 |
26.0055 |
26.7029 |
| Ave
Slippage
| [ccy] |
19.3629 |
19.0055 |
19.7029 |
|_______________________________|________|_______________|_______________|_______________|
| Num Units
Traded
| [unit] |
18,560.00 |
9,050.00 |
9,510.00 |
|
* At-Market
| [unit] |
9,280.00 |
4,525.00 |
4,755.00 |
|
* On-Stop
| [unit] | 0.00 |
0.00 |
0.00 |
|
* At-Limit
| [unit] |
9,280.00 |
4,525.00 |
4,755.00 |
| Perc. Units
w/o Slippage
| [pct] |
22.55 |
23.98 |
21.19 |
|
|
|
|
|
|
|_______________________________|________|_______________|_______________|_______________|
|
TRANSACTIONS
|
|
ALL |
|
|
|_______________________________|________|_______________|_______________|_______________|
| Number of
Transactions
| [tran] |
3,712 |
|
|
|
* At-Market
| [tran] |
1,856 |
|
|
|
* On-Stop
| [tran] |
0 |
|
|
|
* At-Limit
| [tran] |
1,856 |
|
|
|
|
|
|
|
|
|_______________________________|________|_______________|_______________|_______________|
| DRAWDOWNS
|
|
ALL |
|
|
|_______________________________|________|_______________|_______________|_______________|
| Largest
Drawdown
| [ccy] |
-478,620.00 |
|
|
| Longest
Drawdown
| [bars] |
2,429 |
|
|
| Number of
Drawdowns
| [ddn] |
74 |
|
|
| Ave Length
| [bars] |
81.12 |
|
|
| Ave
Drawdown
| [ccy] |
-33,394.39 |
|
|
| Current
Drawdown
| [ccy] |
-104,890.00 |
|
|
|_______________________________|________|_______________|_______________|_______________|
While the strategy made money, it made all its money on the 'long' side, which is suspicious considering the stock market has a strong upward bias. It is very possible that almost all of the gains are the result of chance only. This is also apparent from the low 'profit factor' of 1.05 ('winners net' / 'losers net').
What is interesting about this strategy is that it used limit orders to enter a position to save on 'slippage'. The strategy does not book slippage if a limit order is filled at the limit price. The only situation where a long limit order incurs slippage is if the open price gaps below the limit price overnight in which case it is assumed that the limit order is converted to a market order and executed at the open price (since the strategy uses 'smart' limit orders)! The opposite is true for short limit orders. 'Smart' limit orders, as used here, should only be used with 'end of day' systems, where there exists a time gap between the close of one bar and the open of the next bar. 'Smart' limit orders can help a strategy perform more realistically, since executing always at the limit price would seriously understate performance!
The system keeps track of how many orders of each type were filled, as well as what percentage of orders were filled without slippage. Consider the following extract from the strategy performance report below:
|_______________________________|________|_______________|_______________|_______________|
| Number of
Transactions
| [tran] |
3,712 |
|
|
|
* At-Market
| [tran] |
1,856 |
|
|
|
* On-Stop
| [tran] |
0 |
|
|
|
* At-Limit
| [tran] |
1,856 |
|
|
|
|
|
|
|
|
There were 3712 transactions in total, of which 1856 were market orders and an identical number of limit orders.
|_______________________________|________|_______________|_______________|_______________|
| Num Units
Traded
| [unit] |
18,560.00 |
9,050.00 |
9,510.00 |
|
* At-Market
| [unit] |
9,280.00 |
4,525.00 |
4,755.00 |
|
* On-Stop
| [unit] | 0.00 |
0.00 |
0.00 |
|
* At-Limit
| [unit] |
9,280.00 |
4,525.00 |
4,755.00 |
| Perc. Units
w/o Slippage
| [pct] |
22.55 |
23.98 |
21.19 |
Recall that the previous bar's closing price was used as limit price for execution on the NEXT_BAR. The likelyhood that the next bar's open is below the limit price is thus quite high (about 50%), which is why only about half the limit orders were filled without slippage. The other half was executed 'at market' with a price better than the limit price!
Copyright (c) 2008 PERITECH. All Rights Reserved.