Tutorial 301 - Sell in May and Go Away


This chapter serves as an introduction on how to define trading systems. So far, in previous chapters, we relied extensively on class in_stream for data access. But in_stream objects don't allow us to trade. To trade we need to switch to using instances of class instrument. Instrument objects are also declared as strategy members, just like in_stream objects, but they also offer trade-management functionality. instrument objects allow strategies to send orders, monitor order status, cancel orders, modify orders, as well as other related functionality. instrument objects also monitor positions including for multi-leg trades. A strategy can have any number of instrument members, which means that a single strategy can trade pairs, baskets, spreads or any other instrument combination that comes to mind.

 

Tutorial 301 aims to verify the old market wisdom which says: "Sell in May and go Away". The basic idea is that there is a yearly cycle where stocks go up from the beginning of November to the beginning of May, and then move sideways or down until the end of October. October is said to be a particularly dangerous month in the stock market - you have been warned! 

So, how to verify this market wisdom? Well, lets take 100 years of Dow Jones Industrial Average (DJIA) data and run a simple simulation that buys at the start of November and sells at the start of May.

 

We can't simply trade on the '1st' of the month, since that may fall on a week-end. To determine the beginning of a new month we'll have to keep a series of dates, and wait for the month to roll-over. So don't be surprised by the declaration of a series of dates in the code below.

 

Stock index futures weren't known back then so we'll have to keep things simple. We'll pretend that the DJIA index can be traded like any exchange traded stock and that the value of this hypothetical share is equal to the nominal value of the index itself.  

 

One issue that arises over such a long running periods is that the value of both the dollar and the stock index have changed a lot. Around the year 1920, one hypothetical share of DJIA cost around 100 dollars, and now it costs around 17000 dollars. For the purpose of this example, each entry transaction will involved buying a number of shares corresponding to a fixed 100,000 dollars. You can then easily switch to a fixed number of shares instead, but the results will look somewhat hyperbolic which makes comparing performance between later and earlier years difficult.

 

Tutorial 301:

 

 

 

 

 

 

 

(1)

 

 

 

 

(2)

(3)

 

(4)

 

 

 

(5)

 

 

 

(6)

 

 

 

(7)

 

 

 

 

 

(8)

 

 

 

 

(9)

 

(10)

 

 

 

 

 

 

 

 

(11)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(12)

 

 

 

 

 

 

(13)

 

 

 

(14)

#include "tsa.h"

#include "tsa-graphics.h"

 

using namespace tsa;

 

    class my_strategy : public strategy

    {

        instrument dow30;

        chart      ch;

    public:

        void on_start(void)   override

        {

            dow30.connect("djia.daily");

            dow30.tick(0.1, 0.1);

           

            ch.max_bars_per_chart(500);

            ch.max_charts_to_save(15);

        }

 

        series<date> dates;

 

        void on_bar_close(void)     override

        {

            dates.push((date)timestamp());

            if (dates.size() < 2) return;

 

            if (dates[0].month() == 10 && dates[1].month() == 9){  

                int dollar_amount = 100000.0;

                int num_shares_to_buy = dollar_amount / (int)dow30.close[0];

                dow30.buy(num_shares_to_buy).tag("entry");

            }

 

            if (dates[0].month() == 5 && dates[1].month() == 4){

                if (dow30.is_long()){

                    dow30.sell( dow30.position_size() ).tag("exit");

                }

            }

 

            tsa_assert(!dow30.is_short());

 

            ch << chart::pane(300)

                    << dow30.close

                    << plot::orders(dow30)

                << chart::pane(150)

                    << plot::position(dow30);

        }

 

        void on_transaction(const transaction& t)   override

        {

            if (t.order_tag() == "entry"){

                std::cout << "Bought " << std::to_string((int)t.quantity())

                          << " shares on " << t.timestamp().to_string() << '\n';

            }

            if (t.order_tag() == "exit"){

                tsa_assert(dow30.is_flat());           

                std::cout << "Exited trade on " << t.timestamp().to_string()

                          << " Strategy PL: " << net_profit() << std::endl;

            }

        }

    };

 

    try {

 

        fast::database db("output/demo_db", create_db);

 

        std::cout << "Importing Dow-30 data " << std::endl;

        if (!db.table_exists("djia.daily"))

            import_from_file__DJIA_daily(db);

 

        my_strategy s;

        s.name("tutorial_301");

        s.attach(db);

        s.output_base_path(os::path("output"));

        s.enable_reports();

        s.auto_open_reports();

        s.verbose(std::cout);

 

        s.run();

 

Program Output

Tutorial:    301

=================================

Importing Dow-30 data

Strategy evaluation - start

Bought 2066 shares on 1901-10-02T00:00:00.0

Exited trade on 1902-05-02T00:00:00.0 Strategy PL: -82.64

Bought 2054 shares on 1902-10-02T00:00:00.0

Exited trade on 1903-05-02T00:00:00.0 Strategy PL: -3964.7

Bought 2901 shares on 1903-10-02T00:00:00.0

Exited trade on 1904-05-03T00:00:00.0 Strategy PL: -2456.18

Bought 2351 shares on 1904-10-03T00:00:00.0

Exited trade on 1905-05-02T00:00:00.0 Strategy PL: 30481.3

Bought 1661 shares on 1905-10-03T00:00:00.0

Exited trade on 1906-05-02T00:00:00.0 Strategy PL: 36012.5

Bought 1425 shares on 1906-10-02T00:00:00.0

Exited trade on 1907-05-02T00:00:00.0 Strategy PL: 24683.7

Bought 2008 shares on 1907-10-02T00:00:00.0

Exited trade on 1908-05-02T00:00:00.0 Strategy PL: 28257.9

Bought 1717 shares on 1908-10-02T00:00:00.0

Exited trade on 1909-05-03T00:00:00.0 Strategy PL: 38611.5

(...likes skipped...)

 

The strategy printed information on transactions directly to the terminal, but it should have also opened a web browser and displayed some charts and reports related to the strategy. It does this because we called strategy::auto_open_reports().

 

The following chart shows the strategy's cumulative equity. The strategy worked very well from around 1942 onward. Overall 62% of trades were winners. Analysing the risk is not so easy. We bought a constant 100,000 dollars with every entry trade, so the drawdown during the great depression was quite significant.

 

 But we are not so much interested in performance here as we are by how the strategy itself was defined. We're breaking a lot of new ground here in terms of how we used the library.   

 

(1)

    class my_strategy : public strategy

    {

        instrument dow30;

 

To trade we need a member of type instrument. Instrument objects allow us to stream data in the same way in_stream objects do.  Instrument objects can represent any kind of financial instrument such as shares, futures, options, currencies, commodities, CFD's etc.

(2)

dow30.connect("djia.daily");

 

This connects the instrument to a database table called djia.daily. Note that for this to work the strategy itself needs to be attached to a database containing such a table. 

(3)

dow30.tick(0.1, 0.1);

 

In the introduction we said that we would trade the DJIA index like we would a share.

The instrument::tick() member is used to set both the instrument's minimum tick value, as well as the corresponding cash value ('dollar' value in this case)

(4)

            ch.max_bars_per_chart(500);

            ch.max_charts_to_save(15);

 

Note: Version 3.0 of the Trading System API pre-generates all reports as HTML, including charts. This may change in future versions, and reports may become more interactive, but for now, you may want to limit the number of charts produced, so that reports don't take too long to produce.

(5)

(6)

        series<date> dates;

 

        void on_next(void)     override

        {

            dates.push((date)timestamp());

            if (dates.size() < 2) return;

 

As mentioned in the introduction, we can't just buy on the first of the month since that may be a week-end or a holiday. So we declare a series of dates, and we update it with the latest timestamp corresponding to the current strategy interval/bar.

(7)

            if (dates[0].month() == 10 && dates[1].month() == 9){  

                int dollar_amount = 100000.0;

                int num_shares_to_buy = dollar_amount / (int)dow30.close[0];

                dow30.buy(num_shares_to_buy).tag("entry");

            }

Here we compare the month of the current bar to the month of the previous bar as stored in our date series. If the month number turns from October (9) to November (10) then we buy as many shares as we can afford to buy with 100,000 dollars. The instrument::buy() member immediately sends a market order to buy the given number of units. The function actually returns a reference to the internal order object (class order_ref). We could retain a copy of this reference if we needed to check order status. We usually do this for limit orders and stop orders. In this case, we simply call the order_ref::tag() member to give the order a 'nick-name' with which we can identify it later.

 

(8)

            if (dates[0].month() == 5 && dates[1].month() == 4){

                if (dow30.is_long()){

                    dow30.sell( dow30.position_size() ).tag("exit");

                }

            }

 

These instructions exit the long position on the first trading day in the month of May. Note how we only sell if the position is actually long, as returned by instrument::is_long(). Since we don't want any dependency on the strategy start date, we don't make any assumptions about whether we first reach November or May.  Note also that the 'sell quantity' is set by the instrument::position_size() member. This is important since the position is not a constant.

(9)

                tsa_assert(!dow30.is_short());

 

"Inspect, don't expect!". In this case, we expect that our position is either long or flat. So we verify that it is not short using instrument::is_short(). When deploying a strategy it is important to build in automatic checks on position size and direction as well as on strategy equity. The last thing a system trader needs is a rogue algorithm. So, verify any assumptions you may have about your position(s) and, if possible, automatically reconcile your strategy position(s) with your broker's system as often as is reasonably possible.

(10)

            ch << chart::pane(300)

                    << dow30.close

                    << plot::orders(dow30)

                << chart::pane(150)

                    << plot::position(dow30);

 

These are instructions to plot some values on a chart. Since we don't have an o-h-l-c series, we'll only be able to plot the close. Note that we can show orders on the price chart by inserting an object of type plot::orders() which requires an instrument argument. The second pane shows the strategy's current position via plot::position().

(11)

        void on_transaction(const transaction& t)   override

        {

            if (t.order_tag() == "entry"){

                std::cout << "Bought " << std::to_string((int)t.quantity())

                          << " shares on " << t.timestamp().to_string() << '\n';

            }

            if (t.order_tag() == "exit"){

                tsa_assert(dow30.is_flat());           

                std::cout << "Exited trade on " << t.timestamp().to_string()

                          << " Strategy PL: " << net_profit() << std::endl;

            }

        }

This simple strategy does not need to respond to transaction events. Transaction events have many uses. For example one could place bracket orders in response to opening a new position. Or one could adjust a related order in response to a partial fill.

In this particular case, we use the strategy::on_transaction() event handler to print a few messages whenever a transaction occurs. More on event handlers in the next section. The transaction::order_tag() member tells us the 'nick-name' we gave the order.

(12)

        fast::database db("output/demo_db", create_db);

 

        if (!db.table_exists("djia.daily"))

            import_from_file__DJIA_daily(db);

 

Here we create a database (if it doesn't already exist) and import the daily price series for the Dow Jones Industrial Average from a .csv file. See the definition of import_from_file__DJIA_daily() for more details.

(13)

        s.output_base_path(os::path("output"));

        s.enable_reports();

        s.auto_open_reports();

        s.verbose(std::cout);

 

Strategy simulations produce reports, but only if you enable them with strategy::enable_reports(). The strategy::auto_open_reports() member automatically opens the reports in a web-browser if one is installed on your computer. Otherwise you will have to look in the strategy output directory as defined via strategy::output_base_path().

(14)

s.run();

 

Previously we passed a date or date-time range to the strategy::run() member. If you are streaming the data from a database table then it is possible to call strategy::run() without any arguments. In such cases, the strategy will fetch the start and end time-points from the database table to which the scheduling instrument is connected.