In Tutorial 302 we shall explore order management. To do so we will create a simple trend following strategy based on two moving averages. When the trend is up, the strategy waits for a small price pull-back before placing a limit-order to buy a few ticks below the previous bar's low. The strategy only does this between November and April, in line with the Sell in May And Go Away adage. If this limit order is not filled within 5 bars, the order is cancelled and the system waits for the next opportunity. If the trend turns down, the system exits the trade. This strategy will keep adding on to the position until it reaches 5 contracts.

 

Tutorial 302:

 

 

 

 

 

 

 

 

(1)

 

 

 

(2)

(3)

 

(4)

 

 

 

 

 

 

 

 

 

 

 

 

 

(5)

 

 

 

 

 

 

(6)

 

 

 

 

 

 

 

 

(7)

 

 

 

 

 

(8)

 

 

 

 

 

 

 

 

(9)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

(10)

     class my_strategy : public strategy

          {

                    instrument sp500;

                    chart      ch;

          public:

                    void on_start(void)   override

                    {

                              sp500.connect("sp500.daily");

                              sp500.tick(0.1, 25.0);

                              sp500.commission(3.0);

                    }

 

                    series<double> sma_short, sma_long;

                    bool   trend_turns_down, trend_up, in_correction;

 

                    void on_prepare_dsl(void)   override

                    {

                              sma_short = SMA(sp500.close, 4);

                              sma_long = SMA(sp500.close, 60);

 

                              // Use VAR() to assign series to non-series variables (scalars).

                              // This simplifies syntax in non-DSL code.

                              VAR(trend_up) = sma_short > sma_long;

                              VAR(in_correction) = sp500.close < SHIFT(sp500.low,1);

                              VAR(trend_turns_down) = CROSSES_BELOW(sma_short, sma_long);

                    }

 

                    order_ref entry_order;

 

                    void on_bar_close(void)      override

                    {

 

                bool dangerous_month = timestamp().month() < 11 && timestamp().month() > 4;

 

                if (trend_up && !dangerous_month && in_correction)

                {

                     if (!entry_order.active() && sp500.position_size() < 5)

                                   {

                                             entry_order = sp500.buy_limit(1, sp500.close[0] - sp500.ticks(5));

                                             entry_order.tag("long-entry");

                                   }

                }

 

                              if (entry_order.active())

                              {

                                        if (entry_order.age_in_bars() > 5)

                                                  entry_order.cancel();

                              }

 

                              if (sp500.is_long() && trend_turns_down)

                              {

                                        sp500.sell(sp500.position_size()).tag("long-exit");

                                        if (entry_order.active()){

                                                  entry_order.cancel();

                                        }

                              }

 

                              ch << chart::pane(300)

                                            << plot::ohlc(sp500, color::gold)

                                                  << sma_short

                                                  << sma_long

                                                  << plot::orders(sp500)

                                                  << plot::transactions(sp500)

 

                                 << chart::pane(150)

                                                  << plot::position(sp500);

                    }

 

          };

 

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

                    import_from_file__SP500_daily(db);

 

                    my_strategy s;

                    s.name("301_trading");

                    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

 

This tutorial introduces a strategy's ability to place limit orders. The limit orders are shown as a green lines on the chart above. When the green line stops, the order was either filled or cancelled. If you ever encounter a continuous green line on your chart, you likely forgot to cancel a limit order somewhere.

 

Limit orders allow us to explore how to handle situations where orders are not immediately filled, or where there is uncertainty about whether the order will be filled at all. Such orders must be monitored to ensure they are filled or partially filled, or that they are eventually cancelled when they have grown 'stale'. In order to monitor orders we need to retain a reference to them. All order functions, such as instrument::buy(), instrument::sell_limit(), instrument::buy_stop(), return objects of class order_ref. class order_ref has 'pass by value' semantics and represents a reference to an internally managed order object ( see class order). You will unlikely ever need to interact with order objects directly, except maybe under special circumstances.

 

Objects of class order_ref can be declared as members of a newly defined strategy. The order_ref default constructor puts the object in an 'undefined' state (where order_ref::defined() returns false). At this point it is possible to assign to it the return value of one of the instrument's order functions like in this example:

 

order_ref lim_order;

lim_order = mkt.buy_limit(5, 100.0);

assert(lim_order.defined());

assert(lim_order.active());

lim_order = mkt.buy_limit(5, 155.5);  //exception! - must only assign to 'non-active' order_ref objects

 

order_ref objects retain a lock on the underlying order object and thereby prevent it from being destroyed. That is, you can still access the order object, even if the order has been previously cancelled or filled. You cannot assign a new value to an order-reference as long as the order it references is still 'active'. This is a safeguard to prevent users from inadvertently cancelling or changing the wrong order. In order to reuse an order_ref object, its order_ref::release() member must be invoked first, or the underlying order must be filled, cancelled, or expired, in other words it must be 'inactive'.

 

Lets take a closer look at the strategy definition:

 

(1)

                    void on_start(void)   override

                    {

                              sp500.connect("sp500.daily");

                              sp500.tick(0.1, 25.0);

                              sp500.commission(3.0);

                    }

 

The data series we are using is for the original S&P500 futures contract, not the e-mini, so the tick-size is 0.1 of a point, with each tick worth 25 dollars. The commission is set as 3 dollars per-side.

(2)

(3)

                    series<double> sma_short, sma_long;

                    scalar<bool>   trend_turns_down, trend_up, in_correction;

 

This declares some series<double> members to hold the short and long moving averages. It also declares some scalar<bool> members which was introduced in the opening paragraph above. Objects of this type only exposes the 'current value' in a series. This simplifies syntax somewhat as you can access its value like a regular variable - without the use of the subscript operator[].

(4)

                    void on_prepare(void)   override

                    {

                              sma_short = SMA(sp500.close, 4);

                              sma_long = SMA(sp500.close, 60);

                              trend_up = sma_short > sma_long;

 

                in_correction = sp500.close < SHIFT(sp500.low,1);

                trend_turns_down = CROSSES_BELOW(sma_short, sma_long); //X_DOWN()

                    }

 

This is our DSL code, as covered in chapter 2. We use this notation because the functions are optimized and we don't need to worry about series-bounds-error exceptions. By the time on_next() is called, all indicators have valid values and are immediately usable.

(5)

order_ref entry_order;

 

The order reference member. As soon as we send our limit-order to buy with a call to instrument::limit_order(), we get back a reference to the internally managed order object. We need this order-reference so that we can check the status of the order. We only want to have one limit-order active at any one point in time. This order either gets filled or we need to cancel it and replace it with a new order. We use the order_ref::active() member to check if an order is 'active'. An order is 'active' as soon as it is sent, even before it is acknowledged. It stays active either until it is filled in its entirety or 'confirmed cancelled'.

(6)

                if (trend_up && !dangerous_month && in_correction)

                {

                     if (!entry_order.active() && sp500.position_size() < 5)

                                   {

                                             entry_order = sp500.buy_limit(1, sp500.close[0] - sp500.ticks(5));

                                             entry_order.tag("long-entry");

                                   }

                }

 

To place a limit buy order, we first need to check our conditions. This is where using the scalars comes in handy as we'd otherwise have to write trend_up[0] and in_correction[0] if these had been declared as series. Once we know the conditions are right, we need to check that there isn't already an active order in place. We do this with !entry_order.active(). We also check the the position_size() hasn't reached our maximum intended position of 5 contracts.

(7)

                              if (entry_order.active())

                              {

                                        if (entry_order.age_in_bars() > 5)

                                                  entry_order.cancel();

                              }

 

If the order is not filled within 6 bars, then we cancel it. Note that the order stays 'active' until the cancel request is acknowledged. As soon as the order becomes in-active, the strategy is able to send a new limit order in (6).

(8)

                              if (sp500.is_long() && trend_turns_down)

                              {

                                        sp500.sell(sp500.position_size()).tag("long-exit");

                                        if (entry_order.active()){

                                                  entry_order.cancel();

                                        }

                              }

 

This eventually exits the position when the trend turns down. We sell at the market the equivalent of the current long position. We also need to cancel any limit buy order that may be active.  Note that this requires further safeguards as the entry order may be filled before the cancel command arrives. This would result in a reject event, and the strategy's on_order_cancel_rejected() event handler would be called, giving you an opportunity to take remedial action.

(9)

                              ch << chart::pane(300)

                                            << plot::ohlc(sp500, color::gold)

                                                  << sma_short

                                                  << sma_long

                                                  << plot::orders(sp500)

                                                  << plot::transactions(sp500)

 

                                 << chart::pane(150)

                                                  << plot::position(sp500);

 

This creates the chart-book. One of the charts produced is shown above. 

(10)

                    my_strategy s;

                    s.name("tutorial_302");

                    s.attach(db);

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

                    s.enable_reports();

                    s.auto_open_reports();

                    s.verbose(std::cout);

 

                    s.run();

 

Not much new here. We attach the database and we set the output directory where the strategy saves its reports. We instruct it to automatically open the reports once ready and then we launch the simulation with a call to strategy::run(). Omitting time-period information simply tells the strategy to iterate over all available data. In this case this would be since 1982 since this is when the S&P futures contract started trading.