Session Timing

True System Automation means you program your trading system to start and stop at designated times while you go play golf, right?

This means that in addition to having a Master Enable/Disable switch you need to automate the start and stop of your trading session. For example, you might want to start your session at 9:30 AM and have it automatically end at 4:00 PM.

The code below uses the ParamDate() and ParamTime() to set the session start and stop dates/times, and compares these parameters to the system Date and Time that are returned by the Now() function. It generates various session-states that are displayed in the Title, when you incorporate this code into your system you will use these states to perform specific actions, like Close all Positions, Cancel all Orders, etc. The BlinkText() is not really needed but was thrown in for fun.

function BlinkTextText )
{
ABAxisColor colorWhite;
if( Status("redrawaction") )
{
BlinkStateStaticVarGet("BlinkState");
if( IsNullBlinkState) ) StaticVarSet("BlinkState"False);
if( BlinkState )
{
Text EncodeColor(colorBrightGreen)+Text+EncodeColor(ABAxisColor);
StaticVarSet("BlinkState"False);
}
else
{
Text EncodeColor(ABAxisColor)+Text;
StaticVarSet("BlinkState"True);
}
}
return Text;
}
RequestTimedRefresh);
DisableSessionTiming ParamToggle("Auto Session Timing","ENABLED|DISABLED",1);
ParamDateNumber  ParamDate("Date"Now(1), 0);
ParamStartTime   ParamTime("Start","09:30:00");
ParamEndTime   ParamTime("End","15:59:00");
RTTimeNumber  Now(4);
RTDateNumber  Now(3);
InSessionDate  RTDateNumber == ParamDateNumber;
PreSessionTime  RTTimeNumber &ltParamStartTime;
PostSessionTime  RTTimeNumber &gtParamEndTime;
InSessionTime  NOT PreSessionTime OR PostSessionTime );
PrevInSession   StaticVarGet("InSession");
if( DisableSessionTiming )  InSession 1;
else     InSession InSessionDate AND InSessionTime;
StartSessionTrigger LastValue(InSession) &gtPrevInSession;
EndSessionTrigger LastValue(InSession) &ltPrevInSession;
StaticVarSet("InSession"InSession);
OutOfSessionColor  ParamColor("Out of Session",colorBlack);
PlotNOT InSession,"",colorBlack,styleArea|styleOwnScale|styleNoLabel,0,1);
Title "\n"+BlinkTextNow(0) )+"\n"+
"Session Status: "+
WriteIfDisableSessionTiming"Session Timing Disabled",
WriteIfNOT InSessionDate,"Out of Session date, ","")+
WriteIf(PreSessionTime AND InSessionDate,"Waiting for Start, ","")+
WriteIf(StartSessionTrigger"Start Trigger, ","")+
WriteIf(InSessionTime"In Progress, ","")+
WriteIf(EndSessionTrigger"End Trigger, ","")+
WriteIf(PostSessionTime"Completed",""));

Edited by Al Venosa

The Master AT switch

When you are using an Automated Trading system, you need a master switch to allow you to Enable/Disable all automated action. It is very important for this switch to be Off when you start AmiBroker because the last thing you want is to see is that orders are going out right after launching AmiBroker.

You cannot use the ParamToggle() because this function resumes the last state it was in before you closed AmiBroker, i.e. if it was Enabled when AmiBroker shut down, then it would be Enabled after startup. You need a function that always starts up Disabled, no matter under what condition AmiBroker closed.

To create a switch that is always Off at the time of startup, you use two ParamTrigger()s, one to turn On Automation and one to turn Off Automation.

AutoTrading nz(StaticVarGet("AutoTrading"));<p>ATonTrigger ParamTrigger("Start AutoTrading","START");<p>AToffTrigger ParamTrigger("Stop AutoTrading","STOP");<p>if( ATonTrigger )StaticVarSet("AutoTrading",1);<p>else if( AToffTrigger )StaticVarSet("AutoTrading",0);<p>AutoTrading StaticVarGet("AutoTrading");<p>Title "\nAutoTrading "+WriteIfAutoTrading"On","Off");<p>

Edited by Al Venosa

Pane and Window Execution order

Few users pay attention to the order in which tasks performed in the various Amibroker panes and Windows are executed. The order of pane-executions can have a major impact on the performance of a fast, Real-Time trading system. It is easy to assume that the pane-formulas execute top down, but they don’t. In fact, the order of execution is unpredictable and should, for all practical purposes, be considered Random by the user.

For example, when using Static or Persistent Variables, if you initialize a StaticVariable in the top pane for use in the lower pane, you want the top pane to execute first so that the newly assigned value can be used immediately, i.e., during the same refresh in the lower pane and not at the next Chart Refresh, which may happen a second or more later.

Now consider that the lower pane executes first. In this case, it would require waiting either until the next quote or, if you use the function RequestTimedRefresh(1), the next second (whichever comes first). In this case, the Static Variable would contain a value from the previous Refresh, which means the value of the Static Variable would lag by one Chart refresh. If the formula in the lower pane is placing orders with LMT prices based on the last quote (the one that triggered the refresh) and since the LMT price was calculated in the top pane during the previous Refresh, the LMT price used would be based on the previous and not the last quote. In most cases, this would decrease your probability of a LMT fill.

An obvious solution is both to append your ordering code to the code that initializes your Static variable and to execute in the same pane. However, this may be difficult to do if your formulas are long, or if each formula generates charts with different scales and/or incompatible Titles. Even so, if you decide to solve the problem this way, you should consider using #include files.

Another solution is to force a RefreshALL at the end of your initializing formula. This will refresh all panes without delay, but now some panes may be executed more than once for each Chart Refresh. You will have to decide if this is significant in your system. The following two lines can be appended to your code and will force a RefreshAll. Be aware that the minimum RefreshAll interval is one second, so if your quotes arrive faster, this may not work very well.

oAB = CreateObject("Broker.Application");
oAB.RefreshAll();

If order of execution is critical, you can make the RefreshAll() conditional on the count of a pane-counter and only force a RefreshAll if the pane execution order demands it. To find the pane execution order, you can add a pane execution counter to all pane formulas and log the count to DebugView. If the count is lower in, say, the upper pane compared to the lower, that means it was executed in the upper pane earlier. If the count is greater, it was executed later than the lower pane.

To be able to identify the formula being executed in your log, place the formula name at the very top of your formula:

Filename = StrLeft(_DEFAULT_NAME(),StrLen(_DEFAULT_NAME())-2);

To add the counter you append the following code to the very end of your formula:

PaneCounter = Nz(StaticVarGet("PaneCounter"));
StaticVarSet("PaneCounter",PaneCounter+1);
CounterDisplayLine = Filename +", ChartID: "+GetChartID()+", PaneCounter: "+NumToStr( PaneCounter,1.0,False);
_TRACE( "# "+CounterDisplayLine );

This will show the order and time of execution, in seconds, in the DebugView window:

snag-0770.jpg

You can study/analyze pane execution order without disturbing your code by Inserting the 4-line code below multiple times in the same window, thus creating multiple panes. Then change the order of panes using the small arrows in the menu that pops up when you hover your mouse pointer over the top right corner of the pane. In this example the pane order is shown in the Chart Title.

Filename = StrLeft(_DEFAULT_NAME(),StrLen(_DEFAULT_NAME())-2);
PaneCounter = Nz(StaticVarGet("PaneCounter"));
StaticVarSet("PaneCounter",PaneCounter+1);
Title = Filename +", PaneCounter: "+NumToStr( PaneCounter,1.0,False);

snag-0771.jpg

The above capture shows that the execution order is not top down. You may observe that, in your case, the execution order tracks the ChartID. This is because the panes were inserted in sequence one after another, but this would not normally be the case.

While this topic may not be important to everyone, it will become critical when you start tinkering with High-Frequency trading systems.

Edited by Al Venosa

Price Bound Checking

When you start to design your own trading system, you will soon realize the need to custom define the BuyPrice, SellPrice, ShortPrice, and CoverPrice. Similarly, you may want to set your trade delays using the in-code function SetTradeDelays(d,d,d,d). Such hard-coded definitions will override the default trade prices and delays set in the Automatic Analysis (AA) Settings, and this will prevent you from inadvertently using the default settings. The problem is that defining your own price arrays increases the risk of introducing pricing errors, such as Price Bound Errors.

AmiBroker provides a function called SetOption(“PriceBoundChecking”,TRUE/FALSE) to Enable/Disable Price Bound Checking. Enabling this option will adjust prices so that they fall within the High-Low range and mask your pricing errors. The problem, of course, is that in practical trading these prices are impossible to trade, and your Backtests will not reflect realistic conditions. The best way to handle this is to turn Off Price Bound Checking and write your own error detection code. The code below checks your trade prices for Out of Bounds errors, displays the number of errors it finds, and plots a red marker (bar) at the bottom of your chart where the error occurs.

SetOption("PriceBoundChecking",False);
BuyError = ( BuyPrice > H OR BuyPrice < L ) AND Buy;
SellError = ( SellPrice > H OR SellPrice < L ) AND Sell;
ShortError = ( ShortPrice > H OR ShortPrice < L ) AND Short;
CoverError = ( CoverPrice > H OR CoverPrice < L ) AND Cover;
PricingErrors = BuyError OR SellError OR ShortError OR CoverError;
Plot(PricingErrors ,"Bound Errors",colorRed,styleArea|styleOwnScale,0,10);
Title = "# Price Bound Errors: "+NumToStr(Cum(PricingErrors),1.0,False)+"\n";

Edited by Al venosa

Persistent Variables (v3)

Static Variables retain their values as long as AmiBroker is running. If you shut down AmiBroker or experience a computer crash, your Static Variables lose their values. This can create serious problems in Automated Trading. For example, suppose you experience a computer crash while you have many pending orders. After restarting everything, you are unable to modify the orders, and so you are forced to use the TWS to manually clean up the mess and restart.

To prevent this situation, you can use Persistent variables that store their values on your Hard Disk. They will remain there until you delete them. Using Persistent variables, if you experience a crash or shut down your system during the night, the persistent variables will automatically be reloaded when you power up again.

Persistent variables can also be used to save ticker-specific system parameters. For example, you could run an optimization and save the optimized parameters in a Persistent Variable encoded with the Ticker’s name. Below are examples for saving text and numerical values. Arrays are not included here because arrays are better handled in other ways. Included in the code below is a handy function to create Persistent lists and to remove items from these lists. The list functions are useful when you wish to save lists of Symbols and/or OrderIDs dynamically.

Note that these parameters must be stored in a specific folder on your Hard Disk (see first code line below for an example of a typical recommended path). You must assign the desired location to the string variable PersistentPath. Remember that Persistent variables are Global just like Static variables, and their names may have to be encoded with ChartIDs and/or Symbol names to prevent them from being modified by different programs.

PersistentPath = "C:\\Program Files\\AmiBroker\\PersistentVariables\\";

function PersistentVarSetText( VarName, String )
{
global PersistentPath;
fh = fopen( PersistentPath+VarName+".pva","w" );
if( fh )
{
fputs( String, fh );
fclose( fh );
}
return fh;
}

function PersistentVarGetText( VarName )
{
global PersistentPath;
fh = fopen( PersistentPath+VarName+".pva","r" );
if( fh )
{
String = fgets( fh );
fclose( fh );
}
else string = "";
return String;
}

function PersistentVarSet( VarName, Number )
{
global PersistentPath;
String = NumToStr(Number);
fh = fopen( PersistentPath+VarName+".pva","w" );
if( fh )
{
fputs( String, fh );
fclose( fh );
}
return fh;
}


function PersistentVarGet( VarName )
{
global PersistentPath;
fh = fopen( PersistentPath+VarName+".pva","r" );
if( fh )
{
String = fgets( fh );
fclose( fh );
Number = StrToNum(String);
}
else Number = Null;
return Number;
}

function PersistentListAdd( VarName, String )
{
SubStrExists = 0;
if( String != "" )
{
List = PersistentVarGetText( VarName );
for( i=0; ( LoopItem = StrExtract( List, i ) ) != ""; i++ )
{
if( LoopItem == String ) SubStrExists = 1;
}
if( NOT SubStrExists )
{
List = List + String+",";
PersistentVarSetText( VarName, List );
}
}
Return List;
}

function PersistentListRemove( VarName, String )
{
if( String != "" )
{
List = PersistentVarGetText( VarName );
NewList = "";
for( i=0; ( LoopItem = StrExtract( List, i ) ) != ""; i++ )
{
if( LoopItem != String ) NewList = NewList + LoopItem +",";
}
PersistentVarSetText( VarName, NewList );
}
Return NewList;
}

function PersistentVarAddLineVarNameLine )
{
    global PersistentPath;
    fh 0;
    if ( Line != "" )
    {
        fh fopenPersistentPath VarName ".pva""a" );
        if ( fh )
        {
            fputsLine "\n"fh );
            fclosefh );
        }
    }
    return fh;
}

// Here is an example on how to export lines from an Exploration
// Look for the file in at the path defined at the top of this post
Filter Status"LastBarInTest" );
AddColumnC"C"1.2 );
ToFile Name() + "," NumToStrLastValue), 1.2 );
PersistentVarAddLine"MyData"ToFile );

The following function was kindly contributed by suresh (see comment) and can be used to delete Persistent variables:

function PersistentVarRemoveVarName )
{
global PersistentPath;
Fn=PersistentPath VarName ".pva";
fh=fdeleteFn ) ;
return fh;
}

Edited by Al Venosa

Setting up your TWS for Automatic Trading

This is a Quick-Start introduction to setting up your default settings in the TWS simulator and/or the actual TWS for automatic trading. Please refer to the official TWS documentation for more information on this and related topics.

For AmiBroker and the IBc to communicate with the TWS, you have to configure the TWS as follows:

Trader Workstation Configuration

In some of the later topics, you will learn about the TWS export file, which is read to obtain the actual prices at which your orders were filled. For this feature to work properly, you have to configure your TWS with the naming conventions shown below.

TWS Auto Export setup

The Export filenames are different for each IB account you use, and they are saved on your hard drive at the paths shown below:

Real.Trades. This filename is for your real-money trading account (C:\jts\Real.Trades).

Simulated.Trades. This filename is for your Simulated (Paper-Trader) account (C:\jts/Simulated.Trades).

Demo.Trades. This filename is for the eDemo account (C:\jts\Demo.Trades).

Be aware that exported trade lists are not date-stamped and will be overwritten the next day you trade.

Edited by Al Venosa

Plotting Trade-Prices

Instead of the traditional Arrows, which don’t really give you much useful information about your trade (such as filled prices), you can use PlotText() to plot exact Trade Prices, as in the example below:

Plot Price example

//Dummy system to generate some signals (Do NOT trade!)
Short = Cover = 0;
ZC = Zig(C,1);
Buy = T1 = Ref(ZC,1)>ZC;
Sell = T1 = Ref(ZC,1)<ZC;
BuyPrice = ZC;
SellPrice = ZC;
Equity(1);
// plotting prices at the correct level
Plot(C,"Close",colorBlack,styleBar);
PlotShapes( IIf(Buy, shapeSmallCircle, shapeNone),colorBrightGreen, 0, BuyPrice, 0 );
PlotShapes( IIf( Sell, shapeSmallCircle, shapeNone),colorRed, 0 ,SellPrice, 0 );
FirstVisibleBar = Status( "FirstVisibleBar" );
Lastvisiblebar = Status("LastVisibleBar");
for( b = Firstvisiblebar; b <= Lastvisiblebar AND b < BarCount; b++)
{
if( Buy[b] ) PlotText("\n Buy\n "+NumToStr(BuyPrice[b],1.2),b,BuyPrice[b],colorBrightGreen);
else if( Sell[b] ) PlotText("\n Sell\n "+NumToStr(SellPrice[b],1.2),b,SellPrice[b],colorRed);
}

Edited by Al Venosa

Removing Static Variables

Trading systems may use hundreds or even thousands of Static Variables, and the associated memory usage will eventually slow down your system. To prevent this you need to clear them whenever possible. Since at this time AmiBroker doesn’t have a function to remove all Static Variables from memory, you can remove them by referencing each of them by their name, i.e. StaticVarRemove( StaticVarName ).

Using an indexed name will allow you to use a loop to remove Static Variables from memory. For example, a naming convention such as BuyPrice+ NumToStr(n,1.0,false), where n could be the DateNum(), TimeNum(), DateTime(), BarIndex(), etc., would allow you to use a simple loop to generate all possible names and remove those that return non-null values.

Below are two examples of how to remove StaticVariables with this technique. Be careful when using BarNum() because the bar number changes if you use SetBarsRequired(), or if you use Quick-AFL, or if your data exceed the amount set in your DataBase settings or in Preferences.

procedure ClearAllTrades()
{
for( T=0; T<=BarCount; T++)
{
TradeNumStr = NumToStr(T,1.0,False);
TradeName = "Trade"+TradeNumStr;
StaticVarRemove( TradeName );
}
}

Here is an example of how to remove from memory Ticker-specific OrderIDs that were, for example, used in a portfolio trading system:

procedure RemoveBasketIDs()
{
global TradingBasket;
for( NT=0; ( Ticker = StrExtract( TradingBasket, NT ) ) != ""; NT++ )
{
StaticVarRemove(Ticker+"-OID");
StaticVarRemove(Ticker+"-Action");
StaticVarRemove(Ticker+"-Qty");
}
}

Edited by Al Venosa

Keying Static Variables

When you trade in RT using automated trading, you must make sure that your Static Variables are unique to your system, i.e. they should not be global. Global variables can be accessed from anywhere, and you must make sure that there are no naming conflicts when you run different programs simultaneously in different panes or windows. You can do this by Keying the Static Variable name with a unique string. While you could type in this unique key each time you use a Static Variable, a simpler way is to define a Global key that changes with the environment that the formula runs in.

An easy way to do this is to replace (wrap) the Amibroker StaticVariable functions with custom functions that have an “x” prefixed to the AmiBroker function name, such as: xStaticVarSet(). The advantage of using a simple name modification is that you can quickly convert the functions from one type to the other using the AFL editor’s Replace function.

The Static Variables in the code below are keyed with the Name() and ChartID and, in most cases, this works fine. However, you can also key Static Variables with only the Name(), the System’s filename, or other unique strings. How you key a Static Variable depends on the required scope of the Static Variable. For example, to make a Static Variable common to all tickers traded you need to remove the Name() part from the key. Below are four functions that you can substitute for the standard ones:

global SVKey;
SVKey Name()+NumToStr(GetChartID(),1.0,False);

procedure xStaticVarSetSNameSValue )
{
global SVKey;
InIndicator Status("Action") == 1;
if( InIndicator StaticVarSet(Sname+SVKeySvalue);
}

function xStaticVarGetSName )
{
global SVKey;
if( IsNull( Var = StaticVarGet(Sname+SVKey) ) ) Var = 0;;
return Var;
}

procedure xStaticVarSetTextSNameSValue )
{
global SVKey;
InIndicator Status("Action") == 1;
if( InIndicator StaticVarSetText(Sname+SVKeySvalue);
}

function xStaticVarGetTextSName )
{
global SVKey;
return StaticVarGetText(Sname+SVKey);
}

Edited by Al Venosa

Static Variables in RT Systems

Fact: Without the use of Static Variables you cannot develop an automated trading system.

AmiBroker executes your AFL programs one line at the time, top to bottom. Unlike most conventional programming languages, it doesn’t ever hang around and wait for events unless you specifically want to do this (not recommended). In addition, AFL never re-uses values assigned to variables during the previous pass through the code, i.e. all variables are re-initialized at each chart refresh. This means that unless you use Static Variables you can never assign a value to a variable and retain its value over any length of time or pass it from one refresh to the next. RT Trading requires you to save many variables, such as Signals, OrderIDs, Position and Order status, and Timing.

In the case of transient signals, such as when the price briefly crosses your limit price, a buy signal is generated, but it will disappear on the next pass through the code if the RT condition no longer exists. When this happens, an order is transmitted, but you have no record that this ever happened. Then, if the same event takes place again a few seconds later, you’ll get another buy signal and another order goes out. With these multiple orders being sent to the TWS, you’ll be broke before you know what hit you.

This is where Static Variables offer the solution you need: they maintain values of variables and arrays until you change them or until you shut down AmiBroker. They give you a means to carry forward values from one execution to the next. This is the key mechanism used to prevent multiple orders from going out; there is no other way to do this. Of course, you could always check your position size, but, due to various delays, your code could execute several times before your position change was even acknowledged. It is a risk not worth taking and simply wouldn’t work.

Without the use of Static Variables, you cannot develop an RT Automated Trading system. Reading up on them in the users’ manual and understanding how they work and are implemented in AFL will prove to be your best investment in time.

Edited by Al Venosa.

« Previous PageNext Page »