Using IIF(), IF() and Switch() functions

Many newcomers to AFL are confused by the IF(), IIF() and Switch(). This post gives a few simple examples of their use. The IF() and Switch() are program flow control statements, the IIF() is a function that acts on all elements of an input array and returns an output array.

In all but the simplest applications the Switch() is the preferred method to the IF() to change program flow. It can be used to code complex decision trees and state machines, for example as these are often needed in automated trading systems.

For more detailed explanations click IF(), IIF(), or Switch(). A search of the afl library will also get you many more examples.

The IIF() function

It is possible to use if()s to individually test and modify each bar in an array for a condition. An example on how this would be done is shown in the function below (copied from the AmiBroker help). This function is an AFL equivalent for the IIF() function.

function IIF_AFLconditioninputAinputB )
{
    result Null;
    for( bar 0bar BarCountbar++ )
    {
       if( conditionbar ] )
           resultbar ] = inputAbar ];
       else
           resultbar ] = inputBbar ];
     }
    return result;
}

While the above approach works, using the IIF() function always provides a better and faster solution. The IIF() is very powerful and should be used whenever possible. Below are a few simple examples to get started. btw, It is highly unlikely that you will be able to improve on execution time by using a loop or writing a DLL.

To color all bars that fall on a Monday White:

Color IIfDayOfWeek()==1colorWhitecolorBlack);
PlotC"Close"colorstyleBar );

IIF()s Can be nested. This example colors Monday bars White, Wednesday bars Blue and Friday bars Yellow:

DayOfWeek();
Color IIf(D==1colorWhiteIIf(D==3colorBlueIIf(D==5colorYellowcolorBlack)));
PlotC"Close"colorstyleBar );

The IF() Statement

One of the most common applications for the if() is to select what you want to see on your chart:


PlotC"Close"colorBlackstyleBar );
ShowMA10 ParamToggle"Moving Average""MA|EMA");

if ( ShowMA10 )
{
PlotMAC10 ), "MA10"colorWhitestyleLine );
}
else
{
PlotEMAC10 ), "MA10"colorWhitestyleLine );
}

In the above example the IF() basically selects one of two sections of code.  To select one of many options you could the use the else-if extension:


SelectedIndicator ParamList"Show""MA10,MA50,MA100");

if ( SelectedIndicator == "MA10" )
{
PlotMAC10 ), "MA10"colorBluestyleLine );
}
else
if ( SelectedIndicator == "MA10" )
{
PlotEMAC50 ), "MA10"colorRedstyleLine );
}
else
if ( SelectedIndicator == "MA100" )
{
PlotEMAC100 ), "MA100"colorYellowstyleLine );
}

The Switch() Statement

When there are many conditions, the lengthy If() expressions can become confusing, difficult to compose, and difficult to modify. In such cases it is often better to use the Switch() statement. Using a simple Switch() the last example looks much cleaner:


SelectedIndicator ParamList"Show""MA10,MA50,MA100");
switch ( SelectedIndicator )
{

case "MA10":
PlotMAC10 ), "MA10"colorBluestyleLine );
break;

case "MA50":
PlotMAC50 ), "MA50"colorBluestyleLine );
break;

case "MA100":
PlotMAC100 ), "MA100"colorBluestyleLine );
break;
}

There are times that you will have many individually named variables that you would like to process in a Switch() statement. Even though the Switch() can only accept a single variable name as argument you can use the method below to work around this limitation:

function SayOnceText )
{
    if ( StaticVarGetText"Lastsay" ) != Text )
    {
        SayTextFalse );
        StaticVarSetText"LastSay"Text );
    }
}

RequestTimedRefresh(1);
Trigger1 ParamTrigger"Trigger 1""TRIGGER1" );
Trigger2 ParamTrigger"Trigger 2""TRIGGER2" );
Trigger3 ParamTrigger"Trigger 3""TRIGGER3" );
Trigger4 ParamTrigger"Trigger 4""TRIGGER4" );

switch( )
{
    case Trigger1:
    SayOnce"One" );
    break;

    case Trigger2:
    case Trigger4:
    SayOnce"2 or 4" );
    break;

    case Trigger3:
    SayOnce"Three" );
    break;

    default:
    SayOnce"Default" );
    // Here you can place code that will execute
    // repeatedly while no other case is true
}

The Switch() argument can be a string or number. Using strings makes code easier to read. Another advantage of using the Switch() is that they format nicely using Edit->Prettify Selection in you editor window, using too many else-if statements tends to run the if()s of the page. As shown above you can stack case statements to have multiple conditions trigger the same task.

To implement a simple State Machine you pass the system “state” to the Switch(). This way you can have any event trigger any sequence of tasks, and do so in any desired order. In a real application the SayOnce() functions in the example code below would be replaced by the task you want to be performed in the state. The next state would usually be conditionally set inside each state, for example you only want to proceed to the next state after an order is filled, or a price is crossed. You can use multilevel Switch()s or if()s inside each case section. This use of Switch() statements is very useful in Automated Trading systems. For example to process order status (Pending, Filled, Error, etc) and parsing TWS error messages.

Since states are saved in a Static Variables they remain valid over multiple AFL executions, and can last indefinitely. You can also save states in Persistent Variables.

States are processed in sequential afl executions, i.e., if you change the state in a case statement this next state will be processed in the next AFL refresh. In some applications this delay can cause problems. To ensure responsive code you might want to use a 0.1 second refresh rate. You could remove the delay by using the Switch() inside a loop/while statement, anf loop until a stable state is reached.

function SayOnceText )
{
    if ( StaticVarGetText"Lastsay" ) != Text )
    {
        SayTextFalse );
        StaticVarSetText"LastSay"Text );
    }
}

RequestTimedRefresh(1);
if( ParamTrigger"Reset System""RESET" ) ) StaticVarSetText"State""RESET" );
if( ParamTrigger"Task 1""TASK 1" ) ) StaticVarSetText"State""TASK1" );
if( ParamTrigger"Task 2""TASK 2" ) ) StaticVarSetText"State""TASK2" );
if( ParamTrigger"Task 3""TASK 3" ) ) StaticVarSetText"State""TASK3" );

State StaticVarGetText"State" );
switch( State )
{
    case "RESET":
    SayOnce"Reset" );
    StaticVarSetText"State""READY" );
    break;

    case "READY":
    SayOnce"Ready" );
    StaticVarSetText"State""IDLE" );
    break;

    case "TASK1":
    SayOnce"Task 1" );
    StaticVarSetText"State""IDLE" );
    break;

    case "TASK2":
    SayOnce"Task 2" );
    StaticVarSetText"State""TASK1" );
    break;

    case "TASK3":
    SayOnce"Task 3" );
    StaticVarSetText"State""TASK2" );
    break;

    case "IDLE":
    SayOnce"Idle" );
    break;
}

The TDash GUI, Bar-Replay version


The TDash code can now be used in Bar-Replay, IBc/TWS interfacing will be the next phase.

To be worked on:

  1. In Bar-Replay you can only place one trade/bar. I don’t think it is possible to changes this.
  2. The Backtester doesn’t scale in/out as the TWS would. This may be solved later.
  3. The code has not been optimized for speed, this may be a problem for some of you. I am using TDash with a 60-day 5-Sec Local DB — so far no major problems.
  4. Buttons for functions yet to be implemented are grayed-out.

Coding level:


This is an advanced afl project. While this code can be used as is, it is intended for the experienced programmer who may just borrow some ideas, functions, or snippets of the code, to create their own trading GUI.
Sorry, I have no time to provide detailed help in using this program.

Demo Video:

TDash Features:

  1. Modify Pending orders by dragging their Markers
  2. Increment/Decrement price, and Cancel pending orders from the markers
  3. Place multiple orders to scale in/out (But Backtester code is not ready for scaling yet)
  4. A floating order bar (QBar) to place MKT, LMT, and STP orders
  5. When placing a LMT order at the wrong side of the price the order changes to a MKT. This allows you to place MKT and LMT orders without having to change type each time.
  6. The use of composites to log multiple trading sessions for long term Backtesting
  7. 3D buttons for Trigger, Toggle, and Rotate functions
  8. Flexibility in custom sizing all objects
  9. Auto-sizing of button layout
  10. Ability to dynamically hide/show buttons and button groups
  11. Use button to control indicators on the main chart
  12. Optional Auto-Backtests when an order is filled to create real-time tradelists and statistics

Misc. Notes:

For the arrows to display properly you need to set Preferences -> Intraday -> “Time of first tick inside bar” or “START time of interval”. Be sure to select REPLAY data by setting the DATA selector in the SETUP button group to REPLAY.

The look of buttons and their layout can be changed in the Parameter window. These parameters are for experimenting, they can be hard-coded when you have decided what you want. To prevent very large buttons when you are re-sizing the TDash pane you can set their maximum height and width in the Parameter window. Button functions have arguments to place buttons groups anywhere in the window/pane.

You can change many other features, like button shape and shadow width, group header height, colors etc. You can make similar adjustments for the QBar and markers. In addition to the settings in the Parameter window the Button3D() function has size, offset, and color arguments you can change from afl. Using these functions you can create dynamically configured button panels, i.e., show/hide buttons or groups, change dimensions and color, to meet virtually any requirement.

At this time there are only three button functions: Trigger, Toggle, and Rotate. The Rotate button is used to select items from a short list. Buttons states are persistent. Grayed-out buttons are not functional yet, or are deactivated from afl because they are not required in the current button layout.

To learn more about buttons, toggle TIPS in the SETUP group to ON, and hover your cursor over the buttons. Except for MKT orders the QBar tracks your vertical cursor position. When MKT type is selected (in the ORDERS button group) the QBar position is fixed at the vertical center of the TDash window and orders will be placed at the last price. LMT@LastPrice or a STP order. For the moment the number of shares traded is set in the parameter window.

STP and LMT prices are monitored during Bar-Replay and fill when the price crosses the threshold. MKT orders will fill immediately. Bracket orders and Pattern orders haven’t been implemented yet. Pattern orders you probably haven’t heard of yet: a pattern order can contain a complex order pattern and are very handy when trading fast, it allows you to place any number of orders with a single click.

Markers for pending orders have a point to the left, a Position Marker is pointed at both ends. Order markers can be dragged; incremented, decremented, and canceled from pop-up controls that appear when you hover your cursor to the right of the marker.

Position Markers have a colored line extending onto the main chart that ends at the entry arrow. Inside the TDash window there is a small profit histogram that shows you current trade profit. If this histogram doesn’t show adjust the X-Offset for Markers to make space.

To run this code you need to apply the TDashMain104.afl to the left top window, the TDash104.afl to the right top window, and TDashInclude104 to the indicator pane below the TDash window (the include is only shown for easy access). The left bottom window can be used for other indicators. To run Auto/Manual Backtests you need to copy TDashBacktest.js to the \TDash\JS folder in your AmiBroker folder

Eventually the TDash window will be extended to the right (perhaps place or extend it on another monitor) to make room for a collection of custom gfx trading indicators.