Você está na página 1de 500

Content Overview

Please note: the online manual on the Zorro website is permanently updated and thus can contain new features that
are not yet available in all Zorro versions. If in doubt, please use the [Help] manual that comes with your Zorro version.
A more extensive German introduction in strategy development with Zorro can be found in "Das Börsenhackerbuch".

This manual contains:

• A description of Zorro's user interface and operation modes:


o Getting Started
o Test
o Train
o Trade
o Result

• An introduction into systematic financial trading, with a glossary of terms and an overview of trading methods and
technical analysis.
o Trading 101
o Bars and Candles
o Trading Strategies
o Technical Indicators
o Brokers

• A programming tutorial for the absolute beginner, introducing the concept of a script language as well as variables,
functions, branches, and loops.
o What is a Script?
o Workshop 1: Variables
o Workshop 2: Functions
o Workshop 3: Branches and Loops

• A trade strategy course with code examples. It covers basic trading strategies, optimizing, walk forward analysis,
portfolio strategies, money management, machine learning, and trading robots. It uses new trading algorithms such as
spectral filters and signal pattern analysis.
o Workshop 4: Trend Trading
o Workshop 5: Counter Trend Trading
o Workshop 6: Money Management
o Workshop 7: Artificial Intelligence
o Workshop 8: Anatomy of a Scam Robot

• Descriptions of all commands, indicators, functions, and variables of the lite-C script language, with code examples.
They are listed by category in the index tree of this manual.

• Tips & Tricks for achieving a general income with Zorro's automated strategies:
• Income by Trading
• The Z1 ... Z12 Systems

1
Welcome to Zorro! Getting started
Zorro is a free development system for algorithmic trading. Technically it's mainly a frontend to a script language for
developing, testing and trading strategies based on advanced data analysis methods. It can train strategies for optimal
robustness, test them with walk-forward methods, and trade them in real time. These basic steps are reflected in Zorro's
control panel.

Control panel

We admit that the default user interface is not particularly pretty. But you can define your own control panels for your
trading scripts. And the default one is designed for minimum space (less than 600 pixels), so it can run on any small
netbook screen in the background and you don't need to look at it all the time. If you do, the profit it shows will hopefully
make up for the austere design.

8,5

User Your user ID and password from the broker; or your MT4
account number.
Account Select your broker (f.i. FXCM or MT4) and the account type
(Demo, Real, or additional user-specific accounts).

Server Server operation; shows the current server time and asset price
in [Trade] mode. The square is green when logged in, and red
when the connection was lost.
Strategy

Script Select the strategy script, or [Change] for changing the folder,
or [New] for writing a new script. Scripts are located in
the Strategy subfolder. See Scripts for what's included,
and Script for how it works.

Asset Select the asset for trading or testing. On multi-asset scripts,


select the asset to be displayed in the result chart. Add more
assets by editing History\AssetsFix.csv.
Period Bar period in minutes; can be pre-set from one minute to one
day (1440 minutes). This is the frequency with which your
strategy makes trade decisions.
Slider1..3 General purpose sliders for manually adjusting strategy
parameters or for displaying values. More control functions are
available through a user-defined panel.
Buttons

Help Open this manual on your local PC.

Edit Edit the selected strategy script in the script editor.

Test Test the strategy with historical price data. Several test modes
can be set up in the script, such as single-step, in-sample, out-
of-sample, or walk-forward tests.
Train Train the strategy by optimizing parameters and/or by
generating trade rules through a trade simulation with historical
price data.
Trade Trade the strategy live. Zorro will log in to the broker and start
earning (or losing) money with the selected script. Click the
button again to stop trading.
Result Trigger a user function in the script. Normally opens a chart
window with a plot of the strategy performance, and a text panel
with the performance analysis.
Result

Progress State of the trading or backtest. Profits are green, losses red.

Info Current account and trade situation, or the result of the backtest.

Messages Displays anything that Zorro has to tell you. A double click
copies the content into the clipboard

2
What you need

Some traders have a desk full of monitors that display an impressive show of indicators and price curves. This is good
for boasting on trader forums, but for nothing else. Don't buy racks of monitors, but invest money in two PCs:

• For trading, any cheap laptop or notebook with Windows XP or above will do, as long as it has a good and stable
Internet connection. When using a desktop computer, run it on an uninterruptible power supply (UPS). This won't be
necessary with a mobile computer that can overcome temporary power failure with its integrated battery. The trading
PC should be dedicated to trading and have no other tasks. Sharing your daily work resources with a live trading Zorro
on the same computer is not really recommended. Make sure to disable automatic Windows updates - otherwise, the
next automatic reboot will kill Zorro and quickly separate you from your money. We also recommend having a remote
access program on the trading machine, such as TeamViewer®. This way you can access Zorro from your hotel room
while on vacation - and you might be often on vacation when a machine is earning money for you.
• Alternatively to the cheap laptop, you can run Zorro on a Virtual Private Server (VPS). They are available from many
Internet providers or online services for a small fee. The advantage is that power and internet problems will be less
likely than at home, and Zorro will continue trading even when your house burns down. The only disadvantage is that
you can't see your current profits at a glance when passing by your computer desk. For setting up Zorro on a VPS,
look under Brokers.
• For developing and testing strategies, get a fast desktop computer with several cores, such as an Intel® i7 machine
with at least 16 GB RAM. You'll have to test and optimize strategies, and for a WFO test of a portfolio strategy with
100 different assets and algorithms, the computer can't be fast enough. Often you'll test several variants of a strategy
together for comparison. Multiple cores, such as 4 or 8, will allow you to run WFO tests faster, or to run many Zorro
instances simultaneously without performance loss. If your computer at home has no sufficient power, you have also
the alternative option to rent VPS computer time in the cloud.

PC setup

Your Windows PC can be set up in a way that scripts - such as trade strategies - can neither be edited nor executed.
These 'features' are called the Windows UAC (User Access Control) and the Windows DEP (Data Execution
Prevention). Normally, Zorro is installed in your user directory (Users/ YourName/Zorro) for stepping around those
restrictions. If you want to install it in the Program Files directory instead, it is recommended to disable the UAC first.
Otherwise it will create shadow copies of your strategies in your user folder (normally
under Users/YourName/AppData/Local/VirtualStore/Program Files), and redirect all file access to the shadow copy.
The purpose is to prevent users from directly editing application data. For disabling the UAC, select the UAC control
panel from "Settings", then drag the UAC slider all the way to the bottom. For setting up Zorro on a VPS, look
under Brokers.

Playing around with Zorro

• Let Zorro do something: Select a script under Script, select EUR/USD under Asset, then click [Test] (some of the
strategy scripts require to [Train] before). For strategies the profit is automatically displayed. After clicking [Result] a
chart viewer pops up and displays a chart with the detailed development of your equity. For learning more about
strategy development with Zorro, look into the system development tutorial or get the German Black Book of
Financial Hacking.
• Backtest a new strategy f.i. when someone posted a Zorro script on a trader forum: Select [New Script] under Script,
copy and paste the strategy into the editor window that opens, save it (File / Save As) under a name
like MyStrategy.c in the Strategy folder, select MyStrategy under Script, then click [Test].
• Earn virtual money: Open a demo account with your preferred broker (for instance, with FXCM
visit http://www.fxcm.co.uk and click on Free FXCM Practice Account). Five minutes later you should have a
$50,000 demo account and can log in and start trading. Start Zorro, select FXCM, enter the user ID and password that
you got, select a Z strategy, and click [Trade]. For other brokers the procedure is similar - open a MT4 demo account,
select MT4 in Zorro's [Account] scrollbox, and enter your account number in the [User] field. Wait a couple of months
and observe the money (hopefully) accumulate on your account.
• Earn real money: Read the rest of this manual to make sure that you understood how it works. Open a real money
account - preferably a microlot account - with the broker of your choice; for details see the Brokers page. Once the
account is established, transfer some money onto it. Start Zorro, select the account and begin trading with your own
strategy or with a Z system. Click [Stop] if you had enough.
• Go from here: Read through the next pages, starting with Trading 101; work through the workshops, starting
with Workshop 1; and if you can't wait with earning money, read how to achieve regular income with one of the
included Z systems. If you already know trading and technical analysis, you can skip the trading and strategy
chapters; if you already know C++ or lite-C, you can skip workshops 1-3.

3
• Get help: If you have a question or an issue with Zorro, ask on the user forum. For in-depth support, subscribe a
support ticket on the Zorro download page. If you need help with developing a trading strategy, contact the oP
group system development service.

What's new?
The free Zorro version is fully functional and can trade with real money. Read here how to get started. Before investing
money in the included strategies, please read the income chapter carefully. If you're new to financial trading,
read Trading 101 and the subsequent chapters about strategy development and technical analysis. For developing
your own trade strategies, learn in the Tutorial how to code them. If you have no broker account yet, read about how
to open a demo or live account under Brokers. Visit the Zorro user forum regularly for information, tips, tricks, and
updates.

If you don't own Zorro S, withdraw your profits regularly from the broker account; the free Zorro version stops trading
when the account balance exceeds $7000 (see profit limits for details). If you live in the US, check if your account is
restricted by NFA Compliance Rule 2-43(b) (see trading for details). Set NFA = 1 either in Z.ini and/or in your account
list (Accounts.dta).

Updating from Zorro 1.50 to Zorro 1.54

• When live trading with a previous Zorro version, please read the instructions on the Trading page about updating a
live system. The TRADE format was changed, so trades opened with the previous Zorro version can not be resumed
with the new version.
• If you've modified Zorro.ini or Z.ini, your modifications will be overwritten by the installation. For Z.ini you can use
individual .ini files (Z1.ini, Z2.ini, etc.) that won't be overwritten. Check the Z systems chapter about the meanings of
any new .ini settings.
• The EXTRADATA flag was replaced by the LEAN flag that has the opposite meaning. Historical data is now stored in
noncompressed format by default.
• The MT4 bridge was updated to version 1.13. Please re-install the bridge as described here. If you
modified Zorro.mq4, you can keep the old version since it was unchanged.
• For testing a Z system, make sure to get the recent 2017 historical price data from the Zorro download page or with
the Download script. Testing won't work without the recent price data.
• Subscribers will automatically receive an update to the latest version shortly after a new Zorro release.

Zorro 1.50 / 1.54 bug list

• Symbols not beginning with a letter were ignored (fixed in V 1.51.1).


• The ZigZag indicator sometimes plotted wrong lines (fixed in V 1.51.7).
• The total rollover cost was not calculated in live trading mode when the broker API did not provide accumulated rollover
for trades (fixed in V 1.51.8).
• Since about October 15, from time to time trades are rejected by Oanda due to a too high precision of the stop loss
(fixed in V 1.51.9).
• Slippage simulation was not 100% neutral in some cases, but affected by the price slope inside the current bar (fixed
in V 1.52.0).
• MT4 bridge V1.11 did not properly filter away price outliers by some MT4 servers, which could cause price and profit
spikes on the chart (fixed in V1.52.2).
• Not being able to close a trade due to a market holiday could lead to removal of that trade from Zorro's internal list
after a day (fixed in 1.52.6). In that case the trade must be manually closed.
• Setting a new stop loss of an open trade with exitLong/exitShort required that the trade already had a stop loss; it
did not work for trades with no stop (fixed in V1.52.4). Workaround: enter the trade with a distant stop when you want
to modify the stop loss afterwards with exitLong/exitShort.
• Under some circumstances, a trade with prices belonging to a different asset was painted in the chart (fixed in 1.52.7).
• Partially closing trades could close the whole trade under some circumstances (fixed in 1.52.7).
• The day functions returned a wrong value on time zones containing a UTC midnight transition (fixed in 1.54.4).
• Some bar offset / bar zone combinations with daily bars caused the Friday bar to be skipped (fixed in 1.55.1).

4
Zorro 1.54 new features

• Functions for loading arbitrary data, such as option chains, futures, order book content etc. from CSV files have been
implemented.
• The wdatef function parses time/data parameters from formatted strings.
• The dataFromQuandl function can access Quandl™ datasets for backtests as well as for live trading (Zorro
S required).
• Up to 8 additional asset specific values or strings can be stored in the asset list.
• The contract functions are for trading and analyzing options and futures.
• Real money accounts can now be connected in read-only mode with an account list.
• The EXTRADATA flag of version 1.50 was replaced by the LEAN flag that has the opposite meaning. Historical data
is now stored in noncompressed format by default.
• Option and future chains can be restricted to certain trading classes with the SET_CLASS command.
• The IB bridge now supports the SET_LIMIT command for getting a better fill price.
• The IB bridge now supports stock CFDs (type STKCFD).
• Prices are now stored with 5 digits precision in the trade spreadsheet.
• Option trades are now based on the real options ask price instead of the extrinsic price.
• The MinutesPerDay variable prevents "Not Enough Bars" errors of assets that are only traded a few hours per day.
• The between function now also works for cyclic ranges.
• PlotPeriod sets the update rate of the chart on the trade status page.
• The OrderLimit variable allows sending limit orders for filling with best price within a time period.
• The season indicator can be used to predict price movements based on seasonal strength.
• qnorm is the inverse of the cdf function.
• Assets can now be excluded from Z3 and Z7 with the Exclude line in z.ini.
• The Oanda Plugin will probably be included in the free Zorro version in future versions, as alternative to FXCM.
• The equity or balance curve of a test run is now also exported in CSV format for further evaluation.
• MT4 bridge 1.13 now supports a lock command for preventing that trade parameters sent by different Zorro instances
interfere with each other.

Zorro 1.56 new features (in development)

• If an asset is not available from the broker, it can be downloaded from Yahoo or Quandl by using
special Symbol codes.
• The callback function can be called from the broker plugin, allowing the script to react immediately on certain API
events.
• In HFT mode, high frequency trading systems can be realistically simulated.

Planned for future Zorro versions

• MT5 platform and Oanda V20 support (high priority).


• NxCore integration and low-latency broker API calls for running and backtesting HFT systems (in development).
• Drawdown alert by comparison with the backtest equity curve through the Cold Blood Index.
• Artificial assets from a linear combination of real assets, for basket trading or multi-asset arbitrage.
• Train and execute a TensorFlow graph.

You can vote for the priority of those features or suggest other features for the next Zorro versions on the Zorro user
forum.

5
Zorro Release History
Zorro 1.54 (released February 2017)

• Functions for loading arbitrary data, such as option chains, futures, order book content etc. from CSV files have been
implemented.
• The wdatef function parses time/data parameters from formatted strings.
• The dataFromQuandl function can access Quandl™ datasets for backtests as well as for live trading (Zorro
S required).
• Up to 8 additional asset specific values or strings can be stored in the asset list.
• The contract functions are for trading and analyzing options and futures.
• Real money accounts can now be connected in read-only mode with an account list.
• The EXTRADATA flag of version 1.50 was replaced by the LEAN flag that has the opposite meaning. Historical data
is now stored in noncompressed format by default.
• Option and future chains can be restricted to certain trading classes with the SET_CLASS command.
• The IB bridge now supports the SET_LIMIT command for getting a better fill price.
• The IB bridge now supports stock CFDs (type STKCFD).
• Prices are now stored with 5 digits precision in the trade spreadsheet.
• Option trades are now based on the real options ask price instead of the extrinsic price.
• The MinutesPerDay variable prevents "Not Enough Bars" errors of assets that are only traded a few hours per day.
• The between function now also works for cyclic ranges.
• PlotPeriod sets the update rate of the chart on the trade status page.
• The OrderLimit variable allows sending limit orders for filling with best price within a time period.
• The season indicator can be used to predict price movements based on seasonal strength.
• qnorm is the inverse of the cdf function.
• Assets can now be excluded from Z3 and Z7 with the Exclude line in z.ini.
• The equity or balance curve of a test run is now also exported in CSV format for further evaluation.
• MT4 bridge 1.13 now supports a lock command for preventing that trade parameters sent by different Zorro instances
interfere with each other.

Zorro 1.50 (released September 2016)

• Orders for binary options and other special order types can now be sent via MT4 bridge with
the SET_ORDERTEXT command (V 1.47.1).
• Currency strength functions have been added for detecting currency trends that affect several markets (V 1.47.2).
• Live trading results can be verified by retesting (V 1.47.2; Zorro S required).
• Live trading systems can retrain automatically in predefined intervals with the ReTrainDays variable (V 1.47.2; Zorro
S required).
• The sliders can now be moved with slider(num,pos) commands (V 1.47.2).
• The default data format was changed from .bar to .t6 for including volume and other data streams in the price history
(V 1.47.2). .bar data can still be used.
• With the marketVol function, trade or tick volume of an asset can be used in a trading algorithm (V 1.47.3; Zorro S
only).
• The Fill mode determines how orders are filled in the simulation (V 1.47.4).
• Numbers can be passed to the Command variable via command line (V 1.47.4; Zorro S required).
• The number of optimize steps per parameter was increased to 1000 (V 1.47.7).
• Price ticks are now internally stored in a different format that used less memory for large high-resolution backtests. (V
1.47.7).
• Disquieting broker messages - such as "An internal server error occurred, our engineers have been notified" - are now
filtered out by the Oanda plugin (V 1.47.8).
• Three Volatility indicators are now supported - Chaikin, Min/Max, and StdDev based volatilities (V1.50.2).

Zorro 1.46 (released July 2016)

• The assetAdd function allows adding assets via script without editing the asset list.
• The assetList function loads an asset list immediately.
• Price history files can be shared among several Zorro installations when a HistoryFolder is set up in Zorro.ini.
• With the "\r" character in a print/printf statement that jumps back to begin on the current line, a running counter or
similar effects can be realized in the message window.
• The mouse function can be used for automated clicking "Buy" and "Sell" buttons on a broker's web interface.
6
• Individual asset lists for the Z systems can now be entered in the Z.ini setup file.
• A new free trading system Z8 was added to the Z systems.
• The Oanda plugin now supports sub-accounts.
• The LINE flag in a plot command plots thick lines in the chart.
• [Change Folder] in the Strategy scrollbox selects strategies from a different folder.

Zorro 1.44 (released May 2016)

• The Oanda plugin allows direct trading with Oanda™ (for Zorro S or subscription).
• Headers for authorization or other purposes can now be included in the http_send function.
• The TradeTest script opens a panel with buttons for manually trading, useful for testing broker plugins.
• Files can now be selected in a dialog box with the file_select function.
• Clicking on [Result] during test or trading triggers the click function. This can be used for performing a calculation or
plotting an interim result.
• The IB bridge was modified for downloading larger price history.
• The GET_POSITION command is now supported by the IB bridge.
• The BALANCED flag produces a balanced samples distribution for all machine learning algorithms.
• A set of normalization functions was added for better adapting indicators to machine learning algorithms.
• The SIGNALS method exports samples for experimenting with them in R sessions.
• A default neural function was included in the r.h header. The documentation now got a R example for a 'deep learning'
strategy.
• The Covariance between two series was added to the indicator library.
• The TRADESIZE flag can be used for training strategies that rely on different trade sizes, f.i. Martingale systems.
• The 28 Currencies history was updated until the end of 2015.
• Day gives the current day number of the simulation.
• Optimal capital allocation among portfolio components can be calculated with the Markowitz efficient frontier.
• Symbols on a histogram or statistics chart can now have individual colors.
• The plotHeatmap function can plot correlation heatmaps.
• The color function can be used for plotting heatmaps or multiple color curves.
• Date and time can be printed more easily with the strdate function.
• A set of matrix functions was added.
• By default Zorro is now installed in the User folder instead of the Program Files folder.

Zorro 1.42 (released February 2016)

• The STEPWISE flag can be used for debugging trade behavior by single stepping through a test session.
• Variables can be debugged with the watch function.
• When a script crashes, the name of the faulty function is now displayed.
• A negative PlotBars number zooms to the end of the chart.
• The MT4 Bridge can now draw horizontal lines and text in the MT4 chart window.
• The MT4 Bridge latency time was reduced from ~100 ms to ~30 ms by optimizing the data transfer.
• Control panels can now be defined for entering and displaying strategy parameters.
• The Leverage parameter was changed to reflect account leverage instead of buying power.
• The location of the .trd files moved from the Log to the Data folder.
• The last potentially orphaned trade is now displayed in the status page.
• The Shannon Entropy was added to the indicators.
• Price data of stocks and indices can now be downloaded from Yahoo with the assetHistory function.
• The Download script is now controlled with a panel; editing the script is not necessary anymore.
• Assets can now be tested even with no entry in the asset list. An error message will then be issued and default asset
parameters will be used.
• Trade loops can now be executed from inside a TMF.
• The wdate function returns the date and time of a bar in the Windows DATE format.
• Trades can be simulated in an unrealistic naive mode for special purposes.
• Individual text editors (such as Notepad++) and chart viewers can be used by editing the Zorro.ini file.

Zorro 1.40.2 (released November 2015)

• Zorro can now trade with Interactive Brokers accounts through the IB plugin.
• Asset lists and account lists are now in .csv format for easier editing. Leverage and Symbol have been added to the
asset parameters.
7
• Correlograms can now be plotted with the profile library.
• Trade slippage, magic number, and other parameters can now be set up in the MT4 bridge via brokerCommand.
• The type of an asset can be determined with the assetType function.
• The frameSync function can be used to snychronize the time frame to full hours or days.
• A fixed WFO test/training time can be set up with WFOPeriod.
• Curves stores balance or equity curves during the training process for further evaluation.
• The putvar/getvar functions share variables globally among scripts.
• The lock function can synchronize the behavior of multiple Zorros (Zorro S only).
• The sort functions can now be used without data length limits.
• The randomize function can shuffle a price or equity curve with or without replacement.
• Percentile calculates upper or lower percentiles of a data series.
• ProfitFactor calculates the profit factor of a balance or equity curve.
• The market function can be used to limit trading to certain hours of the day.
• The plotData function returns plot data arrays that can be used for further evaluation.
• Plotting data is now pre-sampled, which makes plotting huge data sets - f.i. a backtest of several years based on
minute or second bars - up to 100 times faster than before.
• The BrokerAccount function is now optional (account data is normally unavailable in a FIX API implementation).
• The Montecarlo module has been integrated in Zorro, so an external plugin is not required anymore.
• Periodic I/O tasks can be realized with the tock function.
• The sample period of the Spectrum function can now be set up independently.
• The Z.ini file can now be modified while trading, and is updated at the begin of the next bar.
• The Z7 system got a modified and supposedly more robust algorithm.
• loop(Assets) loops over all assets from the asset list.

Zorro 1.34 (released August 2015)

• WFO training time can be minimized by using several CPU cores (Zorro S only).
• The Zorro window can be started minimized with the -h command line option.
• The AssetZone variable allows individual time zones for assets in a portfolio system.
• The pattern analyzer behavior can be set up with the PatternCount and PatternRate variables.
• A negative series length can be used for generating static, non-shifting series.
• The ZigZag indicator was added to the indicators list.
• Machine learning models and script parameters can now be trained at the same time (see Training). In the previous
version this was only possible for the integrated machine learning methods, but not for the general NEURAL method.
• The bar function allows user-defined special bars such a Range Bars or Renko Bars.
• Training parameters now really produces a HTML file with parameter histograms (in the previous version this was not
yet fully included).
• The R lectures by Harry Georgakopoulos have been included in the Zorro documentation.
• Zorro will now detect margin calls when Capital is set.
• The seed function can initiate a deterministic random number sequence.
• The HH and LL functions now also accept a bar offset.
• The Ichimoku indicator was added to the collection.
• The TO_CSV print target prints into a CSV file.
• The strmid function returns a substring from a string.
• The asset list AssetsCur.dta contains all 28 major currency pairs.
• Price history of all 28 major currency pairs is available on the download page.
• The Z2 system has been improved and got better exit algorithms and a new counter trend algo (A2).
• The new Z7 system, based on the machine learning algorithm from Workshop 7, was included.

Zorro 1.32 (released June 2015)

• The R bridge runs R functions from lite-C scripts and allows coding a trade strategy completely in R, using the newest
and sexiest AI algorithms.
• Verbose = 30 now stops the blackbox recording at the first error.
• The strw function converts strings to wide character strings.
• The strf function returns a formatted string.
• The strx function replaces sub-strings in a string.
• The TO_FILE and TO_ANY print target prints messages to the log file and window in all modes.

8
• The TICKS mode was changed. Ticks are now executed in the order of their time stamp, not sorted by asset or trade
as before. This makes testing slower, but removes the special restrictions for tick functions, TMFs, and virtual hedging.
The old testing method can still be used by setting the FAST flag.
• The rev function reverses a series so that it now starts with the oldest data.
• The SHUFFLE flag randomizes a price curve and thus helps determining if profit is caused by a real edge or by artifacts
or randomness.
• The plotWFOCycle and plotWFOProfit functions can be used for analyzing the profit curve over several WFO cycles.
• The BarZone variable can be used to shift daily bars to a certain local time zone.
• Chaikin Volatility was added to the standard indicators.
• The BINARY flag enables the simulation of binary trading.
• Training now plots all parameter charts in a HTML page, also for portfolios and WFO.

Zorro 1.30 (released April 2015)

• The OptimalFRatio variable modifies OptimalF factors for preventing large component margin differences in portfolio
systems.
• For scalping strategies, bar periods down to 100 ms are now possible with Zorro S. The BarPeriod variable is now
of type var instead of int (check possible compatibility issues, f.i. in print/printf statements).
• Asset specific parameters can be stored in the AssetVar variables.
• All Z systems can now be retrained with Zorro S by clicking the [Train] button (even while trading). Price history of all
assets from 2008 and above must be available in the History folder. The recent prices are updated and the parameters
of the last WFO cycle are trained. Retraining the Z systems is normally not necessary, but was requested by many
users.
• Several small improvements have been implemented in the Z systems, among them different OptimalF factors for
the backtest and for live trading, and a different profit lock method.
• The Z3 system now also trades US indexes.
• A chart with the current equity curve and other information is now included in the trade status page.
• If the trade volume is controlled by setting both Margin and Lots, the Lots variable now determines the minimum
number of lots per trade. Trades can be automatically skipped when Margin is below the minimum.
• The backtest can now use T1 (tick based) historical price data.
• The assetHistory function can now be used to produce T1 price history files.
• The seconds function is now of type var instead of int (check possible compatibility issues, f.i. in print/printf
statements). Its fractional part contains fractions of a second in milliseconds precision.
• The user-supplied tick function can be used to evaluate incoming price quotes.
• The AutoCompile flag determines whether scripts are always compiled, or only when they were modified.
• Plot names beginning with '#' won't appear in the chart legend.
• The test performance can now be further evaluated with the user-supplied evaluate function.
• The UO (Universal Oscillator) by John Ehlers was added to the indicator library.
• The Risk column of the status page now displays the current risk of a trade instead of the initial risk.

Zorro 1.28 (released February 2015)

• The WebFolder variable can now be set up globally for displaying live trade status on a web site.
• The strtext function can be used to read strings from an .ini file.
• The DPO oscillator was added to the TA functions.
• The AGC and EMA functions now also accept an alpha parameter instead of a time period.
• Some Zorro properties - for instance, the automatic deleting of old log files - can now be set up in the Zorro.ini file.
• The commission per asset can now be set up in the AssetsFix.csv file and by script in the Commission variable. The
simulation now simulates a spread/commission account, instead of a pure spread account.
• In the SNB floor removal aftermath, many brokers reduced their maximum leverage from 400:1 or 200:1 to 100:1. The
simulated default account (AssetsFix.csv) was also changed to 100:1 leverage, which affects the profit of most
systems.
• The Z4 and Z5 systems are expired and have been removed from the strategy pool.
• The Z3 system now also got an equity curve trading mechanism.
• The currently profitable and suspended components of the Z12 system are now displayed in a asset/algo matrix with
green and red rectangles.

Zorro 1.26 (released October 2014)

• The number of open lots per asset can be evaluated with the LotsPool and LotsPhantom variables.
• The Market Meanness Index (MMI) was added to the indicator library.
9
• Haiken Ashi prices (HA) were added to the indicator library.
• The Z systems have been improved and retrained. New algorithms have been added to the Z4 and Z5 systems for
working with very low price volatility.

Zorro 1.24 (released June 2014)

• The AssetFrame variable can be used to skip quoteless bars of assets in a portfolio system.
• The TradeCosts script lists the relative trade costs of all main assets.
• The Script scrollbox now 'remembers' the last selected script.
• Hedge mode 5 now minimizes the number of open trades by closing trades partially if required.
• exitLong/Short can now close trades partially.
• The MT4 bridge and the FXCM plugin have been adapted to partially closing trades.
• TrailSpeed raises the stop faster before breakeven, this way preventing that a winning trade turns back into a loser.
• The Hurst exponent can determine trending state of a price curve.
• The Alligator indicator was added to the library.
• The predict function can predict crossovers several bars before they happen.
• The Momentum variable indicates the 'strength' of crossovers, peaks, or valleys.
• The saveStatus/loadStatus functions can preserve variables, open trades, and slider positions when the system is
stopped or restarted.
• The AlgoVar variables are now automatically saved, thus keeping their content when a trading system is stopped or
restarted.
• Invalid Margin values are now indicated with an error message.
• The NFA flag is now ignored in training mode.
• While trading, Zorro now displays a detailed lists of open trades and performance statistics in a HTML file that is
updated every minute.

Zorro 1.22 (released April 2014)

• The MT4 bridge was updated to version 1.3 that supports MT4 version 600 and above.
• The Multisession plugin allows to trade with multiple brokers, instances, and accounts even with the free Zorro
version.
• Placing the stop level at the wrong side of the price in a TMF is now automatically corrected.
• The ATR function now adapts to the TimeFrame.
• A new Virtual Hedging mode allows to combine trades opened on the same bar to a single net trade.
• The Keltner Channel was added to the indicator library.
• The PlotDate variable can be used to zoom the chart to a certain date.
• The print function can print to various targets, f.i. to a message box or to the performance report.
• Zorro now only logs in to the broker when in trade mode. In test or train mode, missing assets or price data will produce
an error message. The Download script now needs trade mode for updating prices or asset data.
• The exec function can be used to open an external program, document, URL, or batch file.
• A Monte Carlo plugin is now available for a Monte Carlo analysis of strategy scripts and external trade lists.
• The annual return is now calculated from the maximum margin instead of the average margin. This produces slightly
more pessimistic returns.
• The R2 coefficient that measures equity curve linearity is now included in the performance report.
• A small example script for converting .csv price history files to Zorro's .bar format was added (needs
the file_write function).
• The History string can be used for selecting between different sets of historical data files.
• The file_write function can be used to store the content of a string, series, or array in a file.
• The NumInRange function can be used to generate price distribution statistics while trading.
• The ShannonGain indicator calculates the expected gain of the next bar period, based on Shannon probability.
• A description of using NeuroShell™ and other DLL-based indicators for Zorro was added to the conversion chapter.
• Trade management functions (TMF) can now be triggered by entry or exit limits, thus allowing for additional entry/exit
conditions or trade chains.
• TickSmooth can remove outliers from incoming price ticks.
• The TickTime variable can be used to save CPU resources by defining a minimum time between script executions.
• The plotProfit functions plot the daily, weekly, monthly, or quarterly profit or loss in the price chart.
• The Z5 system got a new algorithm for the "Stop" slider that re-enters trades closed due to the risk limit. This greatly
improves the profit in situations when the risk limit is exceeded.

10
Zorro 1.20 (released November 2013)

• The PRELOAD flag allows loading lookback price data from the price history on trade start.
• The DominantPhase function can detect turning points of the dominant cycle in a price curve even before they
happen.
• The account selection system was implemented for multiple accounts and/or multiple MT4 clients (Zorro S only).
• A new FXCM plugin is available where the wrong trade profit issue is fixed, so the SET_PATCH command is not
required anymore.
• Bar charts by plotBar are now automatically aligned so that the chart always starts with the first bar.
• The MT4 bridge now supports multiple MT4 instances on the same PC (Zorro S only). To connect to a particular MT4
account, either the account number can be manually entered in Zorro's [User] field, or the account selection
system can be used.
• The -d command line option allows to pass a #define statement to the script (Zorro S only). This way many different
tasks can be automatized with the same script.
• The ALLCYCLES flag produces a portfolio analysis of all sample cycles.
• The plot command now supports plotting different symbols in the chart.
• A price data gap check can be activated with the GapDays variable.
• New indicators by John Ehlers (HighPass2, StochEhlers) have been converted to lite-C by DdlV, together with an
example script of a trade system.
• A Filter script has been added for testing and displaying Zorro's spectral filter functions.
• The sine and square wave generators can now produce hyperbolic chirps for filter testing.
• Margin at 0 now prevents trades; previously 1 lot was opened, which was a common source of mistakes.
• A backup of the .trd file is now stored at trade start.
• Several different money management methods are now discussed at the end of workshop 6.
• A Gap script was added as an example for a simple gap trading system.
• The Z5 system was improved for adapting to periods of low volatility.
• The new Z4 system was especially designed for minimal budgets in the range of $100 .. $400.

Zorro 1.16 (released September 2013)

• The -quiet command line flag suppresses message boxes.


• FTP and HTTP functions have been added for accessing the content of websites or sending emails or files to or from
remote servers.
• A new Virtual Hedging mode was implemented and replaces the HEDGING flag. It can greatly reduce the market
exposure and the trade duration, and improve profit due to smaller trade costs.
• A drawdown chart was added to the performance chart.
• Chart colors can be individually set up with the color variables.
• The contribution weights of portfolio components are now listed in the portfolio analysis.
• New trades can now be opened from inside a TMF.
• The Simulate script simulates a trade system by importing trades from a .CSV file.
• Entry and exit time of a trade are now stored with tick precision in the .CSV file (previously it was with bar precision
only).
• Some users had problems to set up a trading system on a VPS, so a VPS installation service was added to the
download page.
• The Z5 system now uses virtual hedging.

Zorro 1.14 (released August 2013)

• While trading, a click on [Result] prints a list of open trades with entry, current, and stop prices.
• The login function can be used for temporarily logging out from the broker.
• The memory function can be used for determining the current memory footprint of the script, and for finding memory
leaks.
• The reverse functions are convenient for limiting the number of trades. Their use is explained in workshop 5.
• The msg function can now be used for modeless message boxes, useful for trade alerts without interrupting the script.
• The stop loss distance is now displayed in the daily profit/loss reports with Verbose >= 2.
• OptimalF factors are now also calculated for long and short trades together. This gives a more precise result than
averaging the long and short OptimalF factors.
• A seconds function was added for evaluating intrabar time.
• The EntryDelay variable can be used for improving profits by entering trades at the optimal moment.
• A step by step description of adding new assets and downloading price data was added to the manual.
• The Capital variable allows to set up an initial capital and calculate the CAGR.
11
• The advise function can now generate trading rules for multiple assets and algos.
• The BarPeriod variable can now be set from an optimize call, except for WFO.
• TimeFrame can now generate individual time frames that are aligned to external events.
• assetHistory can now be used for updating or adding new assets without downloading historic price data.
• The state of the input sliders are now stored together with the open trades, and restored when trading is resumed.
• The state of the Account scrollbox is now preserved when Zorro is restarted.
• The Weekend variable can now be set to 2 for preventing that bars end during the weekend.
• The Verbose variable can now be used to set up message verbosity and diagnostics mode via script. It is also added
to the Z.ini file for the included systems.
• The diag.txt file is now separately stored for every strategy.

Zorro 1.12 (released July 2013)

• Slippage is now recorded while live trading, and displayed in the performance report in total and per trade.
• An additional algorithm (VO) was added to the Z1 system, increasing the annual return to about 280%
• After 6 months live test the Z3 and Z5 trade systems have been released.
• C source generated by machine learning functions is now stored in .c files instead of .rul.
• The plotMAEGraph function can produce MAE distribution graphs and similar charts.
• The plotGraph function draws lines and polygons in a chart.
• The DOT type can be used for plotting a dotted curve.
• The Trail distance can now be negative for raising the stop loss on any bar regardless if the trade is in profit or not.
• Due to an internal loop optimization, trades are now entered faster, thus reducing slippage.
• The MUTE flag prevents playing sounds (in the case that Zorro trades from your bedroom).
• NumTotalCycles can be used to repeat a full simulation cycle many times.
• The pattern analyzer can now generate 'fuzzy patterns' with the FUZZY|PATTERN method.
• The pattern analyzer can now generate pattern finding functions in C that can be exported to other platforms.
• The equalF and eq functions have been added to the fuzzy logic set.

Zorro 1.10 (released May 2013)

• Zorro can now run as a MT4 expert advisor using the MT4 Bridge.
• The Spectrum function can be used to find hidden cycles in a price curve.
• A new workshop was added for trading with machine learning algorithms.
• AI rules and strategy parameters can now be generated at the same time.
• The PATTERN analyzer automatically finds profitable candle patterns for price action trading.
• The FrameOffset variable allows to generate trade signals at different times within the same time frame of a multi time
frame strategy.
• The -diag command line option can be used for finding the reason of a crash or similar problem that terminates the
script.

Zorro 1.06 (released March 2013)

• The AVG flag now allows to plot a value as an average curve over all oversampling cycles.
• A Laguerre filter was added to the filters.
• Comparing a function pointer with a var or float value - this can happen when forgetting the () of a function call - will
now generate a compiler error.
• The info and progress commands display text and color signals in Zorro's info window and progress bar.
• Zorro can now be started from external programs with command line options.
• Seasonal analysis functions have been implemented. They are also be available as an add-on for Zorro 1.05.
• The Z12, Z12fx, and Z12nfa combined strategies have been removed because they were found less profitable than
trading Z1 and Z2 separately. Reason is an internal mechanism that evaluated open trade profits for trade decisions,
which does not work well across opposite strategies such as the Z1 trend trading and the Z2 counter trend trading
systems.

Zorro 1.05 (released January 2013)

• The Total down time - the time spent below a preceding equity peak - is now displayed in the performance report.
• The timer function can be used for precisely determining execution times.
• Asymmetric slippage can be simulated by setting the Slippage variable to a negative value.
• dayPivot calculates the pivot point of the previous day.
12
• The stock exchange working hours for the day functions can be changed through the variables StartMarket and
EndMarket.
• The week start and end time can be changed through the variables StartWeek and EndWeek.
• The DIAG flag prints the execution time for opening and closing positions into the log file.
• The TICKS flag now also handles entry limits with per-tick resolution.
• Trade functions can now also be used for individual entry limits.
• Price history files are not anymore automatically downloaded at the begin of a new year - this confused beginners and
was a bad idea anyway. Instead the assetHistory function was implemented.
• A new broker plugin with the ForexConnect™ API interface has been implemented. It provides a more stable
connection than the previously used Order2Go™ broker plugin.
• Logging in to the broker with several Zorro instances on the same PC will now generate an error message.
• The Z1 / Z2 system components are now optionally filtered with the results of an out-of-sample test with the real trading
parameters. This reduces the backtest performance, but should improve the real trading performance.

Zorro 1.04 (released December 2012)

• Fuzzy logic functions have been added.


• The EndDate variable can be used to determine a fixed backtest period.
• The sortData / sortIdx function can be used for sorting series and arrays.
• Examples for converting MQL4™ EAs, EasyLanguage™ scripts and NinjaScripts™ to Zorro were added to the
manual.
• Phantom trades are now displayed with winged brackets { } for better distinction from normal trades.
• exitLong/Short now also cancels pending trades. Previously the only way to cancel pending trades was a loop over
all trades.
• The number of pending trades can now be evaluated with the NumPendingLong/Short system variables.
• Open and closed trades can be enumerated with the forOpenTrades and forAllTrades macros.
• The AssetsList file can be changed in the script for simulating a different broker or account.
• A Grid Trading strategy as an example of what better not to trade was added to the Get Rich Quick section.
• If some assets are not found or could not be subscribed, as with certain UK accounts, Z1 and Z2 now just continue
with the rest of the assets. Previously the strategy was aborted with an error message. If all components of an asset
are excluded in the Z12.fac file, the asset is now not downloaded from the broker anymore, thus allowing a faster start
of the strategies.
• The dst function calculates the daylight saving time.
• The day functions now automatically skip weekends, making them easier to use without the need to check the current
day of the week.
• The strvar function reads a variable from a string.
• The Zorro setup program is now Windows 8 compatible. Previously it had to be started in Win7 compatibility mode
under Windows 8.

Zorro 1.03 (released November 2012)

• NFA compliant behavior (no hedging, no closing, no stop), as required for US based accounts, can now be activated
with the NFA flag.
• The TrailLock variable can be used for automatically locking a percentage of open trade profits, or for moving the stop
to break even when a certain profit level is reached.
• The TrailStep variable can be used for adaptive trailing.
• NZD/USD was added to the default assets.
• The chart viewer now also works under Win XP.
• The indicators.c library is now pre-compiled, which saves ~1 second compile time when starting a strategy.
• Trading costs are now separately listed in the performance report.
• The Z1 and Z2 strategies were modified; they now use the same parameters for long and short trades. Previously they
used different parameters. Using the same parameters has advantages and disadvantages; training is faster, the
strategy is more robust, and the parameter quality is better due to the higher number of trades. On the other hand,
parameters now don't consider asymmetries in the markets - but there are anyway few asymmetries with currencies
and CFDs. We found that the advantages outweigh the disadvantages.
• The Z1 and Z2 strategies now also trade with NZD/USD and use phantom trades for drawdown reduction. NFA
compliant versions for US based accounts were included.
• Workshop 6 was also modified for using the same parameters for long and short trades.
• A new chapter about filtering trades was added to workshop 4.

Zorro 1.01 (released October 2012)


13
• Ehler's Relative Vigor Index (RVI) was added to the indicators. The source code can be found in indicators.c.
• Anchored WFO can be activated by setting NumWFOCycles to a negative number.
• The Detrend variable can be used to manipulate the price curve and detect or remove trend bias.
• The day functions can be used for seasonal trading, gap trading, or daily price action.
• The Stop, TakeProfit, Trail, and Entry parameters now also accept absolute prices, which allows faster conversion
of some strategies to script.
• The content of Zorro's message window can be copied to the clipboard by double clicking on it.
• By default, a backtest now runs over 5 years (previously it was 4 years).
• By default, a FXCM microlot account is now simulated (previously it was a minilot account).
• Forex-only versions of the Z1 and Z2 strategies have been included.

Zorro 0.99 (released August 2012)

• File functions now allow exporting data in CSV or text format to Excel or other tools.
• Assets are now automatically subscribed.
• The new broker interface allows to connect Zorro to any broker with API support.
• Zorro can now automatically re-optimize its parameters, AI rules, and factors while live trading. For starting a new
optimization cycle, click the [Train] button during trading. A second Zorro instance will pop up, load new price data
from the server, and generate a new parameter set. The new parameters are then automatically updated to the trading
instance.
• Workshop 6 was included.
• The TimeFrame variables allow multiple time frames in the same strategy.

14
Scripts
The following scripts are included as examples and can be started from the scrollbox:

Analysis
Calculates the mean, standard deviation, skew, and kurtosis of the price curve of the selected asset.

CalculatePi
Example script that calculates the first 1000 digits of Pi.

Combine
Example script that combines the parameter and factor files from two strategies into one. The strategies must have
the same number of WFO cycles. The names can be edited in the script.

CSVExport
Small script for exporting the price history of the selected asset to a .csv file.

CSVfromHistory
Small script for converting a .t6 price history file to a .csv file, f.i. for using it in R. The input/output file names can be
edited in the script.

CSVtoHistory
Small script for converting price history in arbitrary .csv formats to Zorro's .t6 format, optionally split into separate year
files. The input/output file names and the format definition can be edited in the script.

Distribution
Price distribution chart, comparing the distributions of two assets.

Download
Script for adding new assets or updating the price history of selected assets. Executable with panel; source code in
the Source folder.

Ehlers
The system from the paper "Predictive Indicators for Effective Trading Strategies" by John Ehlers, converted to lite-C
by DdlV.

Filter
Test Zorro's frequency filter functions.

Gap
A simple gap trading system based on a publication by David Bean as an example of time dependent trading.

Grid
For your grid trading experiments. Better do not trade this system with real money!

Indicatortest
Displays the curves of some indicators as described in the Indicators chapter.

Luxor
The system from the book by Jaeckle and Tomasini. A curve fitted system: produces huge profits in the backtest
period used in the book, but would fail miserably in real trading.

15
Mandelbrot
Example of a script using the Windows API.

Martingale
For your martingale experiments. Better do not trade this one with real money!

PlotCurve
A script that just plots a price curve with the selected asset and bar period.

Predict
Predict peaks, valleys, and crossovers in a price curve.

RandomPrice
Compares a price curve with a random curve, as described in the Strategy chapter.

RandomWalk
Displays the statistics of price movements in the same direction, as described in the Strategy chapter.

Simulate
Script for simulating a trade system by importing a trade list from a Zorro-generated .csv file. Demonstrates how to
import data from a spreadsheet. Generates a chart and a performance report. The script can be modifid for importing
other trade lists in different file formats.

Spectrum
Spectral analysis chart.

TradeCosts
Lists the ratio of spread to daily volatility of the main assets. Find out which assests are least expensive to trade.

TradeTest
A script that opens a panel with buttons for manually opening and closing positions. Mostly used for testing a Broker
Plugin.

TradeOptions
A script that opens a panel with buttons for manually opening and closing SPY options. Mostly used for testing
a Broker Plugin.

Workshop1 .. Workshop8
Scripts containing the code examples from the workshops.

Z1 ... Z12
Zorro's included trade systems. Executable systems only (no source code).

See also:
Workshops

16
Testing a strategy
Testing has the purpose to determine a strategy's profit expectancy in live trading. It is not possible to calculate this with
some algorithm or formula; the only way to find out it really trading it for a couple years. A proxy of this is the backtest:
simulating the strategy with a couple years of historical price data. Problem is that a simple backtest will normally not
produce a result that is representative for live trading. Backtest results are tainted by various sorts of bias (see Backtest
theory) that all must taken into account and eliminated from the result. Thus, testing a strategy is surprisingly complex.

Testing a script with Zorro

For quickly testing a strategy, just select the script and click [Test]. Depending on the strategy, a test can be finished in
a second, or it can run for several minutes on complex portfolio strategies. If the test requires strategy parameters,
capital allocation factors, or trade rules, they must be generated in a [Train] run before. Otherwise Zorro will complain
with an error message that the parameter, factor, or rule file was not found.

If the script needs to be recompiled, or if it contains any global or static variables that must be reset, click [Edit] before
[Test]. This also resets the sliders to their default values. Otherwise the static and global variables and the sliders keep
their settings from the previous test run.

If the test needs historical price data that is not available, a dialog will pop up and propose to download the missing data
set from the broker. Downloaded price data is stored in the History folder in the way described under Data Import. If
the broker does not offer price data for all years in the test period, substitute the missing years manually with files of
length 0 to prevent the download dialog. No trades take then place during those years.

All test results - performance report, log file, trade list, chart - are stored in the Log folder. If the folder is cluttered with
too many old log files and chart images, every 60 days a dialog will pop up and propose to automatically delete files that
are older than 30 days. The minimum number of days can be set up in the Zorro.ini configuration file.

The test simulates a real broker account with a given leverage, spread, rollover, commission, and other asset
parameters. By default, a microlot account with 100:1 leverage is simulated. If your account is very different, download
your actual account data from the broker as described under data import. Simulated slippage, spread, rollover,
commission, or pip cost can alternatively be set up in the script. If the NFA flag is set, either directly in the script or
through the NFA parameter of the selected account, the simulation runs in NFA mode.

The test is not a real historical simulation. It rather simulates trades as if they were entered today, but with a historical
price curve. For a real historical simulation, the spread, pip costs, rollovers and other asset and account parameters
had to be changed during the simulation according to their historical values. This can indeed be done by script, but is
normally not recommended for strategy testing because it would add artifacts to the results.

The test runs through one or many sample cycles, either with the full historical price data set, or with out-of-sample
subsets. It generates a number of equity curves that are then used for a Monte Carlo Analysis of the strategy. The
optional Walk Forward Analysis applies different parameter sets for staying always out-of-sample. Several switches
(see Mode) affect test and data selection. During the test, the progress bar indicates the current position within the test
period. The lengths of its green and red area display the current gross win and gross loss of the strategy. The result
window below shows some information about the current profit situation, in the following form:

3: +8192 +256 214/299

3: Current oversampling cycle (if any).


+8192 Current balance.
+256 Current value of open trades.
214 Number of winning trades so far.
/299 Number of losing trades so far.

After the test, a performance report and - if the LOGFILE flag was set - a log file is generated in the Log folder. The
result window displays the annual return (if any) in percent of the required capital, and the annual gain/loss in pips. Note
that due to different pip costs of the assets, a portfolio strategy can end with a negative pip value even when the annual
return is positive, or vice versa.

17
After the test, the info window displays the annual return in percent and in pips, and the message window contains a
short version of the performance report. The content of the message window can be copied to the clipboard by double
clicking on it. The following performance figures are displayed (for details see performance report):

Median AR Annual return by Monte Carlo analysis at 50% confidence level.


Profit Total profit of the system in units of the account currency.
MI Average monthly income of the system in units of the account currency.
DD Maximum balance-equity drawdown.
Capital Required initial capital.
Trades Number of trades in the backtest period.
Win Percentage of winning trades.
Avg Average profit/loss of a trade in pips.
Bars Average number of bars of a trade. Fewer bars mean less exposure to risk.
PF Profit factor, gross win divided by gross loss.
SR Sharpe ratio. Should be > 1 for good strategies.
UI Ulcer index, the average drawdown percentage. Should be < 10% for ulcer prevention.
R2 Determination coefficient, the equity curve linearity. Should be close to 1 for good strategies.
AR Annual return of the simulation, for non-reinvesting systems.
CAGR Compound annual growth rate, for reinvesting systems.

If [Result] is clicked after the test, the performance sheet and the trades & equity chart are displayed
(see performance).

Single step mode

For debugging the trade behavior, a test can be run in single step mode by setting the STEPWISE flag. In this mode
execution pauses after every bar, and the buttons change their behavior. [Step] moves one bar forward, [Skip] moves
to the next bar at which a trade opens or closes. A HTML browser window will pop up and display the current chart and
open trade status on every step. The window is refreshed every two seconds.

18
The stepwise change of variables and indicators can be made visible either in the message window with
a watch statement, in the browser window with print(TO_HTML, ...), or on the chart with plot. Setting PlotBars to a
negative value displays only the last part of the chart, f.i. -300 displays the last 300 bars. For debugging single loops or
function calls, watch ("!...", ...) statements can be used.

Stepwise debugging normally begins at the end of the LookBack period. For beginning at a certain date or bar number,
set the STEPWISE flag dependent on a condition, f.i. if(date() >= 20150401) set(STEPWISE);.

M1 vs. T1 price data

Zorro can backtest strategies either with 1-minute price ticks with volume (M1 data, .t6 files) or with quote based price
ticks (T1 data, .t1 files, Zorro S required). The .t6 and .t1 file formats are described in the import chapter. In T1 data,
any tick represents a price change by a new price quote. In M1 data, a tick represents one minute. Because many price
quotes can arrive in a single second, T1 data contains a lot more price ticks than M1 data. Using T1 data affects the
backtest, especially when the TICKS flag is set. Trade management functions (TMFs) are called more often,
the tick function is executed more often, and trade entry and exit conditions are simulated with higher precision.

For using T1 historical price data, just set the History string in the script accordingly:

History = ".t1";

Zorro will then load its price history from .t1 files. Make sure that you have downloaded the required files before, either
with the Download script, or from the Zorro download page.

A backtest with T1 data in TICKS mode takes a long time, and needs a lot of memory. It is not required for normal
strategies, but can make sense when you want to test scalping strategies with very short bar periods, such as one or
two minutes, or when your strategy relies on immediate reaction on price quotes. Due to the high memory requirement,
not more than one or two years can be backtested with T1 data.

Backtest realism

Zorro backtests are as close to real trading as possible, especially when the TICKS flag is set, which causes trade
functions and stop, profit, or entry limits to be evaluated at every price tick in the historical data. Theoretically a backtest
should generate precisely the same trades and return the same profit or loss as live trading the script during the same
time period. This is largely the case, but the following effects can cause differences:

• In live trading price ticks arrive in irregular intervals, more frequently, and with more outliers than the simulated ticks in
a backtest. This results in small price differences, especially with the price() function that calculates the average of
price ticks within a bar. Indicators derived from prices can be different as well and occasionally trigger a trade signal
in the backtest where it didn't occur in live trading, or vice versa.
• In live trading, account parameters such as spread, rollover, slippage, and pip cost can change. In the backtest they
have fixed values set up by the script and the account model, in order to not add artifacts to the test result. Changes
in account parameters affects the entry and exit prices of trades and cause differences in profit or loss.
• Backtests have a different 'granularity' than live trading, which can greatly affect the result when it depends on small
stop loss and takeprofit distances, as with scalping systems. For determining the influence of granularity, run a test
with and without TICKS and compare the results. If it's more than slightly different, use TICKS for testing. Scalping
systems would require T1 data for a backtest - see the previous section - but we have yet to see a profitable scalping
system.
• If the backtest simulates an account model with a different lot size, trades can be skipped in the test where they are
entered in live trading, or vice versa. Different margins and leverages can greatly affect the performance. See asset
list for details about the account models.

Backtest theory

The likeliness that the strategy exploits real inefficiencies depends on in which way it was developed and optimized.
Backtest results are mostly worthless when they are based on in-sample data - the same price data that was also used
for developing the strategy or optimizing the strategy parameters. This gives too optimistic test results because it's clear
that the strategy is profitable with the very historical data it was developed for. This effect is called overfitting or curve
fitting - a strategy that is fit to a historical price curve is not guaranteed to produce that same profits with future price
curves. To avoid wrong test results due to overfitting, Zorro allows to use different data sets for optimizing and testing
(see Training). This way the strategy is tested with out-of-sample, 'unseen' price data. If it's then still profitable, it's

19
relatively likely that it will also generate profits in real trading. Tests should normally always use out-of-sample data,
even though you'll then get worse backtests.

A similar problem arises when testing several assets and strategies and selecting the most profitable of them. This
selection process already introduces bias, meaning that the backtest results will not be reached in real trading. Although
this is not as bad as overfitting, it has a measurable effect when you have many different trade rules and assets to select
from, as in portfolio strategies (such as Zorro's Z1, Z2, and Z12 systems).

Another problem arises when the strategy performance is very sensitive on parameter changes. Some trade algorithms
cause such an effect - for instance when the strategy modifies its own trade lot size dependent on previous results, as
in a martingale system. Backtest performance with such a strategy is highly dependent on chance, even when out of
sample data is used, and thus mostly worthless. Zorro's optimization algorithm does not select the best, but the most
robust parameter values; still, strategies with high parameter sensitivity are high risk and should be avoided.

Backtest optimism and pessimism

In a nutshell, the following factors can cause bias in the test result:

• Curve fitting bias affects all strategies that use the same price data set for test and training. It generates largely too
optimistic results and the illusion that a strategy is profitable when it isn't. This sort of bias that can be minimized by
using the methods discussed under training.
• Market fitting bias is caused by splitting the data horizontally (SKIP1..SKIP3) or by selecting a test period not from
the end, but randomly out of the price data. The test then uses data that is, although out-of-sample, from the same
market situation as in training. Market fitting bias is not as bad as curve fitting bias. It can be completely avoided by
vertically splitting the data (DataSplit) or performing a Walk Forward Analysis.
• Peeking bias is caused by letting knowledge of the future affect the trade algorithm. This generates too optimistic
results. The most common example of peeking bias is calculating trade margins with capital allocation factors
(OptimalF) that are generated from the whole test data set. To prevent this, run also a test with fixed margins or lot
sizes.
• Data mining bias (or selection bias) is caused by the mere act of developing a strategy with historical price data.
Selecting the most profitable from a pool of possible algorithms, or selecting the most profitable from several asset
combinations, already causes bias in the test result. This bias is often confused with curve fitting bias, but also affects
strategies that have no parameters to be fit. The only way to completely avoid data mining bias is not testing strategies
at all. Since this is not practical, there are methods to at least estimate the effect of data mining bias on the result - for
instance comparing the performance with a distribution of strategy performances with randomized (shuffled) price
data.
• Trend bias affects all 'asymmetric' strategies that use different algorithms, parameters, or capital allocation factors for
long and short trades. An extreme case are strategies that enter only long or only short trades. Price trends in the test
or training period can then greatly distort the result. This can be prevented by detrending the trade signals or the trade
results. Another sort of trend bias can be caused by rollover profit in combination with long-term trades.
• Granularity bias is a consequence of different price data resolution in test and in real trading. Historic prices only
change once per bar, while real time price quotes can arrive several times per seconds. This causes stops and profit
targets to be triggered at different moments. For reducing granularity bias, use the TICKS flag, especially when a trade
management function is used.
• Sample size bias is the effect of the test period length on the results. Values derived from maxima and minima - such
as drawdown - are usually proportional to the square root of the number of trades. This generates more pessimistic
results on large test periods.

20
Training a strategy
Just like an athlete, a strategy should be trained, ideally in regular intervals. Training means optimizing the parameters,
but its main purpose is not getting the highest profit out of the strategy. Sometimes training can even reduce the backtest
profit. The main purposes of training are

• determining the dependence of the strategy on certain parameters,


• making the strategy more robust, i.e. less sensitive to market changes or random noise in the price curve,
• and adapting the strategy to different assets, or - while live trading - to the current market regime.

Training a strategy involves testing a set of strategy parameters with historical or recent price data, and selecting the
most stable parameter values. For example, imagine a trading system that buys and sells a position when the price
curve crosses its n-bar Simple Moving Average. This strategy can be trained to find the optimal value of n between 20
and 200. For this purpose, some trade platforms come with an optimizer for finding the highest performance peak in the
parameter space. Zorro's optimize function does not seek performance peaks; instead it looks for performance ranges
and places the parameters into their centers. This does not necessarily result in the maximum backtest performance,
but in the highest likeliness to reproduce the hypothetical performance in real trading.

For preparing a strategy to be trained, set the PARAMETERS flag and assign optimize calls to all optimizable
parameters. The code for training the Simple Moving Average system in the above example would look like this:

// n-bar Moving Average system (just for example - don't trade this!)
function run()
{
set(PARAMETERS);
var TimePeriod = optimize(100,20,200,10);
var* Price = series(price());
var* Average = series(SMA(Price,TimePeriod));
if(crossOver(Price,Average))
enterLong();
else if(crossUnder(Price,Average))
exitLong();
}

Now click [Train]. Zorro will now run through several optimization cycles for finding the most robust parameter
combination. Zorro optimizes very fast; still, dependent on the number of parameters, assets, and trade algorithms,
optimization can take from a few seconds up to several hours for a complex portfolio strategy. The progress bar indicates
the time you have to wait until it's finished:

The displayed values per line are the loop and parameter number, the optimization step, the parameter value, the result
of the objective function, and the number of winning and losing trades.

The optimal parameters are stored in a *.par file in the Data folder, and are automatically used by Zorro when testing
or trading the system. Parameters are optimized and stored separately per asset and algorithm in a portfolio system;
therefore all optimize calls must be located inside the asset/algo loop if there is any. When the FACTORS flag is
set, capital allocation factors are also generated and stored in a *.fac file in the Data folder. When the strategy
contains AI functions and the RULES flag is set, decision tree or perceptron rules are generated and and stored in
21
a *.c file in the Data folder. By setting flags accordingly, parameters, factors, and rules can be generated either together
or independent of each other.

Training shows if the strategy is robust, i.e. insensitive to small parameter changes. Therefore it goes hand in hand with
strategy development. When training a strategy with the LOGFILE flag set, Zorro shows the performance variance over
all parameter ranges on a HTML page, like this:

The charts are stored in the Log folder and end with the number of the parameter, f.i. _p1 for the first, _p2 for the second
parameter and so on. The red bars are the result of the objective function, which is by default the profit factor of the
training period, corrected by a factor for preferring a higher number of trades. The dark blue bars show the number of
losing trades and the light blue bars show the number of winning trades. In walk forward optimization, the charts display
the average results over all WFO cycles.

Training is critical and should not be omitted even when a strategy appears to be already profitable with its default
parameters. Most strategies only work within certain parameter ranges. Those ranges are usually different for every
asset and also vary with the market situation - a bullish or bearish market has different optimal parameters than a market
with no trend. Due to the asymmetry of most markets, long and short trades often also require different parameters.
Naturally, parameters can only be optimized to a specific historical price curve, therefore they perform best with that
particular price curve and will perform not as good with any other data - such as real time prices in live trading. This
effect is called overfitting. It varies with the strategy, and is discussed below.

22
Select carefully the parameters that you train. Parameters that are fundamental to the strategy - for instance, the
market opening hour when the strategy exploits the overnight gap - must never be trained, even if training improves the
test result. Otherwise you're in danger to replace a real market inefficiency by a random training effect.

For avoiding overfitting bias when testing a strategy, parameter values are typically optimized on a training data set (the
"in-sample" data), and then tested on a different data set (the "out-of-sample" or "unseen" data) to simulate the process
of trading the system. If the optimized parameters do not perform well on out-of-sample data, the strategy is not suited
for trading. Zorro offers three different methods for optimizing and testing a strategy with unseen data:

Horizontal split optimization

This method divides the price data into one-week segments (a different segment length can be set up
through DataSkip). Usually two of every three weeks are then used for the optimization, and every third week is skipped
by the optimization and used for the strategy test. The LookBack period is the part of the price curve required by the
strategy before the first trade can be entered, for functions that use past price data as input, such as moving averages
or frequency filters. Thus a preceding lookback period must be cut off from the start of the data for both training and
testing.

Simulation period
Parameters + AI Rules
LookBack
Test

For setting up this method, set different SKIP1, SKIP2, SKIP3 flags for training and testing. Usually, SKIP3 is set in
train mode and and SKIP1+SKIP2 in test mode; any other combination will do as well as long as two weeks are used
for training and one week for testing. A script setup for horizontal split optimization looks like this:

function run() // horizontal split optimization


{
if(Train) set(PARAMETERS+SKIP3);
if(Test) set(PARAMETERS+SKIP1+SKIP2);
...
}

Horizontal split optimization is the preferred training method for quick tests while developing the strategy, or for testing
when you don't have much price data. It is the fastest method, uses the largest possible data set for optimization and
test, and gives usually quite realistic test results. For checking the consistency you can test and train also with different
split combinations, f.i. SKIP1 for training and SKIP2+SKIP3 for testing. The disadvantage is that the test, although it
uses different price data, runs through the same market situation as the training, which can make the result too optimistic.

Vertical split optimization

Alternatively, you can select only a part of the data period - such as 75% - for training. The rest of the data is then used
exclusively for testing. This separates the test completely from the training period. Again, a lookback period is split off
for functions that need past price data.

Simulation period
LookBack Parameters Test
+ AI Rules

For setting up this method, use DataSplit to set up the length of the training period. A script setup for vertical split
optimization looks like this:

function run() // vertical split optimization


{
set(PARAMETERS);
DataSplit = 75; // 75% training, 25% test period
...
}

23
Vertical split optimization has the advantage that the test happens after the training period, just as in live trading. The
disadvantage is that this restricts testing to a small time window, which makes the test quite inaccurate and subject to
fluctuations. It also does not use the full simulation period for optimization, which reduces the quality of the parameters
and rules.

Walk Forward Analysis and Optimization

For combining (almost) the advantages of the two previous methods, Zorro offers an analysis and optimization algorithm
called Walk Forward Optimization (WFO in short; sometimes also called "Time Series Cross-Validation"). This
method trains and tests the strategy in several cycles using a data frame that "walks" over the simulation period:

WFOCycle Simulation period


1 LookBack Parameters + Rules Test1
2 LookBack Parameters + Rules Test2
3 LookBack Parameters + Rules Test3
4 LookBack Parameters + Rules Test4
5 LookBack Parameters + Rules
OOS Test LookBack Test5
WFO Test Look Back Test1 Test2 Test3 Test4
Rolling Walk Forward Optimization

WFOCycle Simulation period


1 LookBack Parameters + Rules Test1
2 LookBack Parameters + Rules Test2
3 LookBack Parameters + Rules Test3
4 LookBack Parameters + Rules Test4
5 LookBack Parameters + Rules
Test Test1 Test2 Test3 Test4
Anchored Walk Forward Optimization

The parameters and rules are stored separately for every cycle in files ending with "_n.par" resp. "_n.c", where n is
the number of the cycle. This way the strategy can be tested repeatedly without having to generate all the values again.
The test cycle is divided into (n-1) test periods that use the parameters and factors generated in the preceding cycle.
The last training cycle has no test period, but can be used to generate parameters and rules for real trading.

For setting up this method, use DataSplit to set up the length of the training period in percent, and set
either NumWFOCycles to the number of cycles, or WFOPeriod to the length of one cycle. A script setup for Walk
Forward Optimization looks like this:

function run() // walk forward optimization


{
set(PARAMETERS);
DataSplit = 90; // 90% training, 10% test
NumWFOCycles = 10; // 10 cylces, 9 test periods
...
}

The WFO test splits the test period into several segments according to the WFO cycles, and tests every segment
separately. The test result in the performance report is the sum of all segments. The parameters from the last cycle
are not included in the WFO test as they are only used for real trading. A special OOS test can be used for out-of-
sample testing those parameters with price data from before the training period.

WFO can be used to generate a 'parameter-free' strategy - a strategy that does not rely on an externally optimized
parameter set, but optimizes itself in regular intervals while trading. This makes optimization a part of the strategy. WFO
simulates this method and thus tests not only the strategy, but also the quality of the optimization process. Overfitted
strategies - see below - will show bad WFO performance, profitable and robust strategies will show good WFO
performance. The disadvantage of WFO is that it's slow because it normally runs over many cycles. Like vertical split
optimization, it uses only a part of the data for optimizing parameters and factors. Depending on the strategy, this causes

24
sometimes worse, sometimes better results than using the full data range. Still, WFO is the best method for generating
robust and market independent strategies.

Another advantace of WFO is that the training time can be strongly reduced by using several CPU cores.

Combining rules and parameters

If a strategy requires training of both rules and parameters in a more or less complex way, it is important to know which
depends on which. Often several training runs in a certain order are required. With Zorro S, multiple training runs can
be automatized with a batch file that starts training instances with different -d command line options for different script
flags and behavior. The following cases are possible:

• Parameters depend on rules. This is the usual case; an example would be the Workshop 7 system with additionally
optimizing the Stop distance after rules have been generated. This requires two training runs. In the first run, set only
the RULES flag for generating the rules with the default values for the parameters. In the second training run, set only
the PARAMETERS flag. The parameters are then optimized using the rules from the first run. In [Test] and [Trade]
mode set both RULES and PARAMETERS so that the generated rules and the trained parameters are used.
• Rules depend on parameters. An example would be a machine learning system with a neural network, where the
parameter is the number of neurons in the central layer. In [Train] mode set both RULES and PARAMETERS flags.
The training run will now automatically repeat through many cycles. For every parameter step a new set of rules will
be generated. At the end of the process, the rules with the most robust performance are stored in the Data folder. In
[Test] and [Trade] mode set the RULES flag only. PARAMETERS needs not be set, as the parameter only affects
rule generation and has no further relevance for the script. !! Training rules and parameters at the same time only
works with single assets, not with a portfolio system that contains loops with asset or algo calls. If required, train all
portfolio components separately and combine the resulting .c files. The Combine.c script can be used as a template
for automatically combining parameters and rules from different assets or algos. Machine learning models (.ml files)
can be combined with an appropriate R function.
• Rules and parameters affect each other. An example would be a candle pattern trading system similar to Workshop
7, but with additionally optimizing the frame zone for finding the best bar start time for the selected asset. Set
both RULES and PARAMETERS flags. The training run will now repeat through many cycles. For every parameter
step a new set of rules will be generated. The rules with the most robust performance are then selected. Using those
rules, the parameters will be optimized again so that the training run ends up with the best combination of parameters
and rules. As in the previous case, portfolio components must be trained separately and the resulting .c and .par files
must be combined.
• Rules depend on primary parameters, and secondary parameters depend on rules. This also requires two steps. First
generate the primary parameters and the rules as described for the previous case. The secondary parameters must
be set directly to their default values without optimize calls. Afterwards, replace the optimize calls for the primary
parameters by the direct values from the generated .par file, and replace the default values of the secondary
parameters by optimize calls. Then do a second training run with only PARAMETERS set. In [Test] and [Trade] mode
set both RULES and PARAMETERS and leave the optimize calls for the secondary parameters.

Training results

Before training a system the first time, make sure to run a [Test] with the raw, unoptimized script. Examine the log and
check it for error messages, such as skipped trades or wrong parameters. You can this way identify and fix script
problems before training the script. Training won't produce error messages - trades are just not executed when trade
parameters are wrong.

After training, files are generated which contain the rules, parameters, and factors. A Zorro strategy can thus consist of
up to 4 files that determine its trading behavior:

• The strategy script. It contains the basic trade rules, is written by the user, and stored in the Strategy folder. It either
has the file name *.c (the * stands for the strategy name) or *.x, dependent on if it's in lite-C or compiled to executable
machine language. Compiled scripts start faster, and can be used to hide the underlying souce code (the included Z
strategies are compiled). The strategy script is the only file that is really required for a trade strategy, and 90% of this
documentation is for learning how to write it. The 3 other files are optional.
• The strategy parameters. They are generated by optimize calls when training a strategy. They are stored
in Data\*.par. This file contains the parameters in the order they appear in the script. If the strategy does set up
the asset itself, training generates different parameter files dependent on the asset selected in the [Asset] scrollbox.
• The OptimalF capital allocation factors. Capital allocation factors tell Zorro how to distribute the capital among the
trades, dependent on the assets and trade rules. They are automatically generated in a training run, but can also be
manually edited by the user. They are stored in Data\*.fac.
25
• The trade rules and models, generated by the advise function. Rules are small scripts that contain lite-C functions,
stored in Data\*.c. Models generated by external machine learning algorithms are stored in Data\*.ml.

All 4 files, except for the executable *.x, are normal text files and can be edited with any plain text editor. Note that
executable strategies only work with the Zorro version for which they were compiled, as they rely on the format of the
data structures that are contained in include\trading.h. If something is changed with those data structures, which can
happen from one Zorro version to the next, the strategies must be compiled again.

Overfitting

The optimizing process naturally selects parameters that fare best with the used price data. Therefore, an optimized
strategy is always more or less 'curve fitted' to the used price curve. This is normal, but bears a danger when a strategy
uses too many parameters that have an impact on the result, or reacts too sensitive on certain parameter values.
Optimizing can then generate a parameter combination that produces high profit with the used price curve and time
period, but is otherwise not profitable. That means the strategy works perfectly with the given price data, but fails
miserably when used in real trading.

For instance, imagine the n-bar Moving Average trading system described above. It can turn out that a 40-bar Moving
Average produced the highest return over the test period, but the results for an 20-, 30-, 40-, and 50-bar Moving Average
were all negative. The 40-bar result was just a peak - an anomaly of the price data set that will not be reproduced in real
trading. Such anomalies exist very often in price data. If the parameters are adapted to such an anomaly, the strategy
is overfitted and won't work well. Especially neural networks, genetic algorithms, or brute force optimization methods
tend to overfit strategies. They produce 'panda bears' that are perfectly adapted to a specific data set, but can't survive
anywhere else.

For this reason, many strategy developers recommend that parameters should not be optimized at all. Problem is that
all strategy scripts normally have internal constants and values that affect the result and have to be selected somehow
- and this selection process constitutes already an optimization and a possible overfitting. Aside from that, unoptimized
strategies can not adapt to different assets and market situations. They are unsuited for a regular income. Zorro takes
therefore another approach: it attempts to find not the best, but the most robust strategy parameters.

Imagine that in the mentioned n-bar Moving Average strategy, all time periods between 100 and 150 bars produced
positive results, although they were less profitable than the 40-bar peak. It can be expected that a time period parameter
from the 100..150 range is much more likely to be successful in real trading. Therefore Zorro's optimize function does
not attempt to find the parameter value with the highest return. Instead it looks for parameter ranges with positive results,
and selects a value from the center of such a range.

Seven suggestions to reduce overfitting

• Keep strategies simple and don't use too many parameters, filters, and extra conditions. A strategy should not use
more than 3 optimized parameters for entering trades and another 3 for exiting them. Too many parameters increase
the likeliness of overfitting, and will cause worse results in WFO and in real trading.
• While developing the strategy, check out which parameters are best candidates for optimization. Optimize only those
that largely afffect the strategy performance. Check out the parameter charts. They give valuable information about
the effect of a parameters. Don't use a parameter that does not generate a 'broad hill', but instead narrow peaks, single
lines, or a chaotic parameter performance chart.
• Keep the optimize ranges small, as large ranges tend to produce 'local maxima' and generate an overfitted strategy.
For the same reason, do not use too small optimize steps. Dependent on the range and importance of the parameter,
don't select steps smaller than 5% to 10% of the parameter range.
• Optimize entry parameters first and exit parameters afterwards. When your exit system is complex, optimize entry
parameters with a simplified exit such as a simple stop. For using a simple exit while the entry parameters are
optimized, evaluate the current parameter number (ParCycle), f.i. if(Train && ParCycle <= NumParameters-3)
setExitSimple();.
• Do not optimize parameters more than twice, i.e. NumOptCycles should not exceed 2. Higher values increase the
danger of overfitting.
• Get enough trades for optimizing. The more trades are simulated, the better is the quality of the parameter and rule
set. Set NumYears accordingly, and don't use too many WFO cycles with too small optimization periods. As a rule of
thumb, you should have at least 20 trades per parameter and asset. Use oversampling for generating more trades
from different price curves with resampled bars.
• Always use different price data for [Train] and [Test] mode by using WFO or setting DataSplit or SKIP1, SKIP2, SKIP3
accordingly.

26
Trading a strategy
Click [Trade]. Zorro will connect to the broker and start spending your money.

A trading session can only be started when the market is open and price quotes are available for all traded assets.
Otherwise you'll see an error message with the name of the unavailable asset. As soon as the session is running, Zorro
simply waits through weekends and market closures. When logged in, the colored square next to the Server window
will change to green. At first Zorro will download the initial price history for the LookBack period of the strategy.

If price history of some assets is not available or can not be downloaded, you'll see an error message directly at start.
Check if the assets you want to trade are really provided by your broker and you entered their correct names in the asset
list. When trading with an MT4 broker, price history often needs a long time to upload the first time. In that case you
have to start the trading session several times until all price history is loaded correctly. Some brokers, like IB, apply a
time limit to downloading price history. When you see a message like "Historical Market Data ... pacing violation",
you must wait 10 minutes until you can start the session again.

Permanent issues with history download from certain brokers can be overcome by using the PRELOAD flag. In this
case you can download price history from other sources before starting the session. This is especially recommended
when your strategy requires .t1 data, which is slow to load and not provided by most brokers.

After downloading the prices, Zorro checks if there were any open trades from the previous session. The session status
is stored in a .trd file in the Data folder. Any open trades will be resumed when they were not closed inbetween. After
that, Zorro will run through the LookBack period and then execute the run function after every bar period.
The tick function and the TMFs run whenever a new price quote arrives from the broker.

The free Zorro version allows to trade with only one Zorro instance. Zorro S can trade with several Zorro instances,
brokers, and accounts simultaneously on the same PC.

While trading, Zorro will detect when the .par, .c, .fac or .ml files that contain the parameters, rules, capital allocation
factors, or machine learning models are updated by a re-training process. In that case they are automatically reloaded
at the begin of the next bar period. It is recommended to retrain strategy parameters and rules regularly (see WFO) for
taking care of market changes. This is especially required for parameter-free strategies that have been walk-forward
optimized.

27
The Server Window displays the server date and time, and the current price of the asset selected by the [Asset]
scrollbox if that asset is also used in the strategy (otherwise its price is not available). Arriving price quotes are indicated
with a blinking asterisk. If the price is currently not available, for instance due to a server problem, XXXXX is displayed
instead. The tiny rectangle is green when connected to the server, and red when the connection was lost. Make sure
that the displayed time is the correct UTC time and that the local time on your PC is also set up correctly - Zorro can not
trade properly when either time is wrong.

The left segment of the Progress Bar indicates the closed profit of the strategy since starting it, the right segment
indicates the total profit or loss of all open trades (including phantom trades). When the right segment is green, your
trades are in the profit zone; when the left segment is green, your account is in the profit zone. Otherwise the segment
is red.

The Result Window below displays information about the account and profit situation, in the following form:

4567 +1412 +50 !/\\'

4567 Current equity, the sum of balance and value of all open trades of the account.
+1412 Money earned (or lost) by the strategy since the start, in account currency units.
+50 Current open trade value of the strategy, in account currency units.
!/\\' Current trades. ! is a trade with locked profit, / a winning, \ a losing, and ' a pending trade.

The Trade Status Page (see below) gives the most detailed view into the current account and trade situation.

Note that some trading restrictions apply to the free Zorro version. If you see in the Result Window that your equity
has grown above the allowed limit, withdraw profit from your account.

Messages

When something happens, Zorro prints a message in the message window. The messages listed below are built in the
software; their 'verbosity' can be set up with the Verbose variable. Additional messages can be generated by the
strategy script using the printf function.

In a message, a trade position is identified like this:

[AUD/USD:CY:S1234] - Short trade (S) with AUD/USD, CY algorithm identifier, and a trade number ending
with 1234. The trade number can be used to identify the trade in the broker's platform. If a trade is partially closed, the
trade number of the 'remaining' trade can change.

{AUD/USD:CY:s1234} - Short phantom trade (lowercase s and winged brackets). Phantom trades are simulated, not
executed.

(AUD/USD:CY:L) - Long pending trade (round parentheses). Pending trades are not yet opened and thus have no trade
number.

If a message - especially a broker error, see below - is too long and outside the right window border, the content of the
message window can be copied by double clicking into the window, then pasted in a text editor.

All messages are also recorded in a log file named demo.log or real.log, dependent on whether Zorro is trading with a
demo or real account. Details about Zorro's log files can be found under export.

Startup messages

The following messages are generated at start:

Loading AUD/USD prices... 1000 h

Zorro downloaded 1000 hours recent price history from the broker's server and used it for the LookBack period. If an
asset is not available for trading - which can happen with assets that US residents are not allowed to trade, f.i. some
CFDs - an error message is issued and the asset must be excluded from the strategy.
28
Loading AUD/USD prices... 910+111 h

The PRELOAD flag was set and historic price data was available. Zorro read 910 hours price data from its history files
and downloaded additional 111 hours price history from the broker's server. For portfolio strategies the number of
downloaded hours can differ from asset to asset.

Loading AUD/USD prices... 1000 h, gap 16 h

There is a 16 hour gap between the end of the price data and the current time. This happens when the price server was
not recently updated, for instance when trading is started during or just after a weekend or holiday. This gap is normally
not harmful because prices don't change noticably during a weekend.

Loading AUD/USD prices... 835 h, 65 bars added

The price server could not cover the full LookBack period in time; 65 bars had to be substituted by Zorro. This can
happen with some MT4 brokers. MT4 servers sometimes get the missing data after a waiting time; in that case the
strategy just needs to be restarted after a minute. Added bars are normally not harmful, as missing prices are
interpolated. But when a large number of bars has been added, indicators with long time periods can be less accurate
at strategy start and cause reduced performance during the first time. Use the PRELOAD flag for overcoming price
server limitations.

Loading AUD/USD prices... failed

The asset is available, but price data can not be downloaded. This can happen with MT4 servers that need a long time
for sending assets that have not been requested before (f.i. because no chart with that asset was opened in the MT4
client). In such a case, start the strategy again after a minute - chances are that the MT4 server meanwhile got its act
together.

[AUD/USD:CY:L4401] continuing

Zorro is resuming a previously closed or interrupted trading session and continues the trades that were opened in the
previous session.

[AUD/USD:CY:L4401] externally closed

While Zorro was not trading, the trade was externally closed, either through a 'safety net' stop loss or manually in the
broker platform.

Read Trend.par Trend.c Trend.fac

The parameters, rules, and capital allocation factors of the Trend strategy were loaded. This message is generated at
start and again when the strategy was re-trained (see below).

Trade messages - normal trades

The following messages are generated when trades are entered or closed:

[AUD/USD:CY:S] Skipped - Lots/Margin 0

The trade was not entered because Lots or Margin was set to 0.

[AUD/USD:CY:S] Skipped - Margin 20, Min 45

The trade was not entered because MARGINLIMIT is set and the allocated Margin of 20 (in account currency units)
was too low for buying 1 lot of the asset, which would require a margin of 45.

[AUD/USD:CY:S] Skipped - Risk 40, Min 90

The trade was not entered because the allowed Risk of 40 (in account currency units) was too low to cover the trade
risk of 90 (see RISKLIMIT).
29
[AUD/USD:CY:S] Skipped - Total Margin 450

The trade was not entered because MARGINLIMIT is set and the account has not enough free margin to cover
the Risk of the trade in a safe distance from a margin call. Either reduce the trade volume, or close open trades, or add
some money to the account. You can see the current free margin in the broker platform, f.i. in the Account status of the
FXCM Trading Station.

[AUD/USD:CY:S] Skipped - Total Risk 900

The trade was not entered because there's not enough balance in the account to safely cover the total risk of all open
trades (see RISKLIMIT).

(AUD/USD:CY:S) Short 3@0.87400 Entry stop

A pending trade was opened, with an entry stop at 0.87400. If the price drops to the entry stop within EntryTime, 3 lots
will be bought at 0.87400 plus slippage. Note that pending trades are not sent to the broker.

(AUD/USD:CY:S) Short 3@0.87600 Entry limit

A pending trade was opened, with an entry limit at 0.87600. If the price rises to the entry limit within EntryTime, 3 lots
will be bought at 0.87600 plus slippage. Note that pending trades are not sent to the broker.

(AUD/USD:CY:S) Missed entry 0.87400 after 2 bars

The pending trade was not executed because the entry price was not met within the allowed time period (EntryTime).
This message is not displayed in the window, but in the log file.

(AUD/USD:CY:S) Missed entry 0.87400 at cancellation

The pending trade was not executed because the entry price was not met until the next exit command. This message
is not displayed in the window, but in the log file.

(AUD/USD:CY:S) Order expired after 48 hours

The trade was not executed because the order was entered just before the market was closed at a weekend or holiday,
and EntryTime was not long enough to cover that period. This message is not displayed in the window, but in the log
file.

(AUD/USD:CY:S) Balance limit exceeded

The trade was not executed because you're already too rich. Withdraw capital until your account balance stays below
the limit for the free Zorro version.

[AUD/USD:CY:S4400] Short 1@0.87310 Risk 116 ptlsx

A short trade was entered at market. 1 lot was bought short at the price 0.87310. The risk of this trade - the maximum
amount that can be lost due to a stop loss - is 116 units of the account currency. Other exit conditions are displayed
with letters: "p" for a profit target, "t" for trailing, "l" for a profit lock, "s" for a fixed trailing step, and "x" for a fixed exit
time. In the log file the entry time is also listed.

{AUD/USD:CY:l4500} Long 3@0.87310 Risk 200

A long phantom trade - mind the winged brackets and the lowercase l - was entered at market. 3 imaginary lots were
bought at the price 0.87310.

[AUD/USD:CY:l4500] Long 3@0.87310 Pool trade

A pool trade in virtual hedging mode was entered at market in reaction to the opening or closing of phantom
trades. 3 lots were bought at the price 0.87310.

30
[AUD/USD:CY:S4400] Cover 1@0.85934: +105 at 17:01

A short trade was closed by an exitShort call. 1 lot was covered at the price 0.85934 at 17:01 UTC. The profit
was 105 account currency units.

[AUD/USD:CY:L4501] Sell % 1@0.85945: +33 at 17:01

A long trade was partially closed by an exitLong call. 1 lot was sold at the price 0.85945 at 17:01 UTC. The profit
was 33. The rest of the position remains open. Some brokers change the trade ID when a trade is partially closed; in
that case the displayed ID is for the remaining open position.

[AUD/USD:CY:L4401] Reverse 1@0.85945: +33 at 17:01

A long trade was closed by opening a short trade in opposite direction.

[AUD/USD:CY:S4400] Exit 1@0.85934: +105 at 17:01

A trade was closed by the trade management function. 1 lot was sold at the price 0.85934. The profit was 105.

[AUD/USD:CY:S2210] Closed 1@0.80201: +20.5 at 17:01

The trade was manually closed in the broker platform. 1 lot was sold at the price 0.80201. The profit was 20.5.

[AUD/USD:CY:L1200] Stop 1@0.78713: -49.30 at 17:25

The trade was stopped out at 17:25 UTC. 1 lot was sold at the price 0.78713. The loss was 49.30.

[AUD/USD:CY:S4400] Trail 1@0.79414 Stop 0.80012

The trade trailed its stop loss. The new stop is 0.80012. In TICKS mode and in life trading, a trade can trail many times
within a bar. For not flooding the window with trail messages, only the first trailing per bar is printed, further trailing is
not. Therefore the stop limit at the end of the bar be different than the displayed value.

Trade messages - options and futures

The following messages are generated when option or future contracts are entered, closed, or exercised:

[SPY:COT:LP0103] Buy 1 Put 20170118 210.0 100@1.16 at 20:00

Buy a SPY put option with expiration at January 18, 2017 and strike price 210 at a $116 premium (100 * 1.16).

[SPY:COT:SC0104] Write 1 Call 20170118 215.0 100@1.09 at 20:00

Sell short a SPY call option with expiration at January 18, 2017 and strike price 215 for a $109 premium (100 * 1.09).

[SPY::LP6633] Exercise 1 Put 212.0 100@209.84: +124$ at 19:00

Exercise a SPY put option with a strike price 212 at an underlying price of 209.84, and sell the underlying for a total win
of $124.

[SPY::LC6634] Sell 1 Call 210.0 100@1.46: -22.00 at 19:00

Sell back a SPY call option at $1.46, for a total loss of $22.

[SPY::LC6634] Cover 1 Call 210.0 100@1.46: 22.00 at 19:00

Buy back a SPY call option at $1.46, for a total win of $22.

[SPY::LP5727] Expired 1 Put 207.0 100@209.50: -190$ at 19:00


31
A SPY put option expired out of the money at current underlying price 209.50. The $190 premium was lost.

[SPY::LC5728] Expired 1 Call 207.0 100@209.50: +112$ at 19:00

A SPY call option expired in the money at current underlying price 209.50. The underlying was sold at 2.50 price
difference (209.50 - 207). The total profit, minus the premium, was $112.

Other messages

Thursday 24.1.2012 --- Profit $880 ---

If Verbose is at 2 or above, Zorro will display the previous day's profit/loss and a list of all currently open trades at the
begin of every trading day, in the following format:

[AUD/USD:CY:S4400] +116$ s0.8733 c0.8729 e0.8731

The trade made 116$ profit, stop price is at 0.8733, current price is 0.8729, and entry price was 0.8731.

Weekend entered at 02.01.2012 23:00

It's weekend or holiday. Zorro takes a break and suspends trading until Sunday night when the Sydney stock market
opens again (see also Weekend, StartWeek and EndWeek).

!ZDAS Exception - there is no tradeable price

[AUD/USD:CY:L] Can't open 1@0.82345 at 21:05:30

All messages that begin with an exclamation mark ("!") are information, errors, warnings, diagnostics, or other
messages from the broker API (see Error Messages). A frequent message is about not being able to open or close a
trade because no price quote is currently available or trading is temporarily suspended ("Instrument trading
halted", "There is no tradeable price" etc.). This happens especially when assets are traded outside business hours.
If a trade can not be closed, Zorro will attempt in regular intervals to close it again.

Trade status, chart and trade log

While trading, a HTML page named after the script and asset (f.i. MySystem_EURUSD.htm) in the Log folder displays
a list with the live status of all open trades, a chart, and the current performance statistics. This document is generated
when prices are available, and automatically updates once per minute. It can be displayed with any web browser. The
HTML style can be changed by editing Source\status.htm and Source\zorro.css. When trading on a VPS, the HTML
document can be stored in the server's public web folder (see WebFolder) for observing the current trade status via
Internet on your PC or mobile phone. An instruction for setting up a VPS to display your trade status on the Internet can
be found under Brokers.

The trade list can look like this (again, phantom trades in {..} winged brackets and pending trades in (..) parentheses):

Trade ID Lots Entry Entry Price Stop Trail Target Risk Profit Pips
Time
[XAG/USD:LP:S0323] 1020323 170 24.07. 20.85 19.786 20.47 20.27 ---- -82$ 135$ 102.4p
07:51
[US30:LP:L7369] 1027369 9 10.08. 16566 17068 16388 16876 ---- - 345$ 495.4p
23:51 219$
[USD/JPY:VO:L1488] 1031488 17 19.08. 102.90 103.70 102.44 103.66 ---- -96$ 95$ 77.7p
19:51
[USD/JPY:VO:L1589] 1031589 17 19.08. 102.96 103.70 102.45 103.72 ---- -96$ 88$ 71.9p
23:51
[USD/CHF:HU:S4997] 1034997 10 27.08. 0.9147 0.9152 0.9168 0.9126 ---- -19$ -6$ -7.0p
15:51
[EUR/USD:CT:S5098] 1035098 1 27.08. 1.3197 1.3183 1.3351 ---- ---- -12$ 1$ 12.3p
19:51

32
[USD/CHF:LP:S5399] 1035399 14 28.08. 0.9137 0.9152 0.9178 0.9095 ---- -51$ -20$ -17.1p
07:51
[USD/CHF:LS:S5300] 1035300 20 28.08. 0.9137 0.9152 0.9178 ---- ---- -72$ -28$ -17.1p
07:51
{USD/CAD:HU:s7873} 1027873 3 11.08. 1.0926 1.0850 1.1095 1.0757 ---- -36$ 16$ 75.9p
19:51
{XAG/USD:HP:l0586} 1030586 130 18.08. 19.591 19.786 19.244 ---- ---- -94$ 15$ 14.7p
07:51
{XAG/USD:HP:l1791} 1031791 130 20.08. 19.449 19.786 19.027 ---- ---- -87$ 29$ 29.1p
07:51
(XAU/USD:LP:L) ---- 5 ---- 1293 1293 ---- ---- ---- ---- ---- ----
(XAU/USD:LS:L) ---- 17 ---- 1293 1293 ---- ---- ---- ---- ---- ----
(XAG/USD:VO:L) ---- 20 ---- 19.755 19.786 ---- ---- ---- ---- ---- ----

Stop, Trail, and Target are the real current stop / trail / takeprofit limits of the trade (the Stop Loss that you see in the
broker platform is different and not used for the stopping the trade). Risk is the maximum possible loss when negative,
and the minimum profit of the trade when positive. Profit is the current profit or loss, retrieved from the broker API for
real trades, or calculated (including spread, rollover, and commission) for phantom trades. Pips is the volume-neutral
profit or loss in pips. Due to slippage or lack of liquidity, trades can always close with a worse result than projected
under Risk and Profit.

A chart with the current price, the equity curve and additional curves is displayed in the status page. The chart is
updated on every bar or every hour, whichever is least frequent. The properties of the chart, such as PlotWidth etc.,
can be set up in the script. If a different chart in [Test] mode is required, different chart properties can be set up with the
conditions if(is(TRADEMODE)) or if(is(TESTMODE)).

When the [Result] button is clicked, a chart and a performance report is immediately created in the Log folder, and
a list of all open trades with entry, current, and stop prices and current profit/loss is also printed in the message window.
The message window content is stored in the log file, so any event is recorded for later evaluation.

All closed trades are separately recorded in a CSV spreadsheet (see Data Export) for further evaluation or statistics.
The spreadsheet can be read back by Zorro for simulating a test with real trades generated by another platform;
the Simulate script can be used for that. !! Take care not to keep the trade spreadsheet open in Excel or another
program while Zorro is trading, or else it can not write into it.

Dependent on the broker plugin and on SET_PATCH parameters, the displayed entry or exit prices are not the open
and close prices of the trade, but the market prices at trade entry or exit, which can slightly differ. While Zorro always
records ask prices in the log, the entry and exit prices in the spreadsheet follow the usual notation of ask or bid prices
dependent on trade type.

Manually closing trades

Normally you should refrain from tampering with the strategy, as it is (hopefully) already optimized and any manual
intervention will most likely reduce the performance. Still, in some situations you might want to close individual trades,
or lock an individual profit by placing a stop loss. You can do this directly in the broker platform (Metatrader4™ or FXCM
Trading Station™). If supported by the broker API, Zorro will detect when a trade is manually closed, and also close it
internally.

For closing all open trades in case of an emergency, click [Stop]. You'll then be asked first if you want to close all trades
or not, and afterwards if you want to stop trading or not. Click first [Yes] for closing all trades, then [No] for continuing
trading. All trades opened by this strategy will be closed; other open trades are not affected.

NFA compliance

US based accounts are restricted by NFA Compliance Rule 2-43(b). The rule requires that trades are served in a first-
in, first-out order. You can not hedge positions or open more than one trade per asset, you can not place stop loss or
profit targets, and you can not close trades. Instead of closing a position, a new position must be opened in opposite
direction. You can also not trade certain assets on US FXCM accounts, such as gold and silver CFDs; and your leverage
is limited, usually to below 50. You're also not allwoed to open more than 4 trades per week, dependent on your account
deposit. All those restrictions are well-meant and intended to prevent US citizens from losing their money and suing
their brokers (of course they do nevertheless). Zorro can step around all NFA restrictions - except for low leverage,

33
missing assets, and limited trades - when the NFA flag is set. It must be set for all US based accounts, except when
trading through MT4. It must not be set for all other accounts. If it is set wrongly, you'll get error messages like "!Error -
can't close trade" on US accounts, and you'll get a massive number of open trades on non-US accounts as no trade
is ever closed. So make sure that you know whether you live in the US or not. Note that the performance of trade
systems can be inferior in NFA compliant mode.

Stopping and resuming a live trading system

While trading, Zorro stores the currently open trades, the status of the sliders, and component specific variables
(AlgoVar) in a .trd file in the Data folder. This way trading can be stopped and resumed without losing track of open
trades. The name of the file is composed from the script name plus the selected asset plus a "_d" in case of a demo
account. If a trade can not be resumed - for instance when it was externally closed - an error message will be printed at
restarting the trading session, and Zorro will resume the other trades. Because any new trade overwrites the .trd file,
its previous content is stored in a .bak file at session start.

The content of any .trd file can be loaded with loadStatus. For looking into its open trades, you can use a short script
like this:

function run()
{
if(Bar >= 1) {
SaveMode = SV_TRADES+SV_HTML;
loadStatus("Data\\Z12_d.trd");
quit();
}
}

After a test run of this script, the open trades are printed in the HTML status page.

For updating Zorro to a new version, stop trading (but do not close the open trades), close Zorro, then install the new
version into the same folder and start trading again. Please read the What's New page for checking if further actions
are required.

For migrating a live trading system to a different PC or VPS, stop trading without closing the open trades, then copy the
whole Zorro folder over to the new PC. Start trading on the new PC. Zorro will resume the old trades.

!! The .trd file can be simply copied over to the Data folder of a different Zorro installation for continuing the session
from there. When stopping a trade session and starting a new session with the same script on a different broker or
account, make sure to delete the .trd file so that open trades from the last session are not resumed with the new account
or broker. This is especially important with an NFA account, since there is no way to automatically determine if trades
were opened or closed on such an account.

Re-training

While trading, Zorro S can re-train the system for adapting it to the current market situation, and can also run a backtest
over the live trading period. For details see Retraining and Retesting.

Switching / updating strategies

Zorro updates come out every couple of months, normally with improved versions of the Z strategies. When developing
your own strategies, you'll also often get new ideas and improve the script so that is generates a few pips more than the
previous version. For the transition of a live trading system from the old version to a new Zorro and/or strategy version,
you have several options:

• If the new strategy is fully compatible to the old one - meaning that the parameters, the trade functions, and the
components haven't changed - click [Stop], then click [No] for not closing open trades, and [Yes] to stop trading. Close
all Zorro instances, and install the update (if any) in the same folder. Then restart trading with the new software and/or
the new strategy. All open trades will be continued.
• If the new strategy has a different Required Capital than the old one - this is often the case for the Z systems - run a
test with the old one at the current Margin setting, note the capital, then install the new one with Margin adjusted so
that the same capital is required.
• If the new strategy version is not compatible to its predecessor and can't continue its trades, you can run both versions
in parallel for some time (Zorro S is required for this). Install the new Zorro version into a second folder with a name
34
like Zorro2 (you can have any number of Zorro installations on the same PC). Then set the Margin slider of the old
Zorro to 0. This way Zorro will continue to handle its opened trades, but will not open new trades. Start a new Zorro
instance from the Zorro2 folder and let it trade the new strategy in parallel. Close the old Zorro when all its trades are
closed. This way you'll get a smooth transition from the old to the new version.
• If the new strategy is not compatible to the old one and you don't own Zorro S, set the Margin slider to 0 and wait a
couple days until all open trades are closed. Then install the update in the same folder and start trading the new
strategy.
• If the new strategy has a different name - f.i. you previously traded strategy A and want to continue with
strategy B while taking over the open trades - stop the old strategy. Do not close its trades when asked. Open
the Data folder and locate the .trd file (in our example, A.trd for real trading, or A_d.trd for demo trading). Rename it
to B.trd resp. B_d.trd. Then start the B strategy. The open trades from A will be continued with B.

Errors and crash recovery

Zorro is designed to trade without human intervention. However, for keeping a program running for months and years,
some special precautions are necessary. !! Never switch off an active trading system (not even in the interest of energy
saving). Broker APIs often won't resume the session after being interrupted, and will then require a manual restart. Set
the PC energy settings accordingly. Disable automatic Windows updates, as some of them reboot the PC. Screen
savers won't harm.

Zorro will display an error message when the broker can not open or close a certain trade. There can be many reasons
for this, such as an interruption of the connection to the broker, a server failure, lack of liquidity, or a closed market. If a
trade was not opened, it is up to the script to cancel the trade or retry. If a trade can not be closed, Zorro will automatically
close it again in regular intervals until the market opens or liquidity is available. If the close command repeatedly fails
because the trade is unknown to the broker, Zorro will assume that it was already closed, stop the close attempts, and
remove it from its internal trade list.

When the broker server goes down or the Internet breaks down, Zorro will be thrown out of the trade session. This often
happens on Saturdays due to server maintenance. It is normally no reason to worry. Zorro will just log in again in
repeated intervals until the problem is solved and it can resume the session. Every such login attempt prints a dot in the
message window.

If the connection to the broker server is seriously broken for some reason and does not automatically establish again,
click [Stop], then click [No] for not closing open trades and [No] for continuing trading. Zorro will log out, close the broker
library, then open it and log in again. This solves problems with a 'hung' broker interface or library.

In case of a PC breakdown or software crash, re-start Zorro and click [Trade] again. Zorro will read its last trade state
from the .trd file (see above) and continue trading at the point where it was shut down. The continued trades are
displayed in the message window (f.i. [AUD/USD:CY:S4400] continuing...]. If you see an error message instead, a
particular trade could not be continued - it might have hit a stop loss and been closed by the broker inbetween. For
checking, open the broker's trade platform, f.i. the FXCM Trading Station, and compare the ID numbers of the open
trades to the trades continued by Zorro.

If the PC is malfunctioning and you have to continue trading on a different PC, install Zorro there and then copy
the *.trd file from the old to the new Zorro installation. Zorro will then resume the open trades on the new PC. Note that
when you have no access rights to the Programs directory on your PC - f.i. when you logged in without administrator
rights - the Data folder is located in the ProgramData directory.

35
Retraining and retesting
Upon a mouse click, a live trading strategy is retrained for adapting its parameters to the current market situation, or
retested for comparing the live results with a backtest over the same time period (Zorro S only). Retraining and retesting
are executed by a second Zorro process, this way avoiding any interruption of the trading process. The newly trained
parameters, rules, or machine learning models are automatically loaded by the trading system at the begin of next bar.

Retraining

The retraining process is triggered either automatically, or by clicking the [Train] button while trading. A second Zorro
instance will pop up and start a training run, while the first Zorro continues trading. In this training run
the ReTrain boolean variable is nonzero. The script can then download new price data from the server, and generate a
new parameter and AI rule set (see also workshop 5). After finishing the training run, the second Zorro closes. The
new parameters, rules and factors are then automatically loaded by the trading instance. Example of a retraining
statement in the script:

if(ReTrain) {
EndDate = 0; // set the end date to the current date
UpdateDays = -1; // reload new price data from the server
SelectWFO = -1; // select the last cycle for re-optimization
reset(FACTORS); // don't re-train factors
}

If the trading system is connected to a broker that does not provide price history (such as MT4 or IB), the recent price
history can be downloaded by another Zorro instance with the Download script, and copied into the History folder of
the trading system.

A trading system can be retrained even without a ReTrain statement in the script. In that case just download price
history and train the system with another Zorro instance. Training produces new .c, .par, .fac, or .ml files in
the Data folder. You can train on a different PC and then copy the files into the Data folder of the trading system.
Zorro will detect that the files were updated, and load them automatically at the end of the current bar.

Re-training a WFO system is required in intervals corresponding to the length of a WFO test cycle, which is displayed
in the performance report. The system normally needs not to be retrained more often, with one exception. If live trading
results of a certain component or asset are exceptionally bad, the market could have been changed - for instance it
could have got a different dominant cycle. In that case an extraordinary training run can be useful for getting the strategy
back on track. On the other hand, re-training continues open trades and running functions with different parameters,
which can cause a slight reduction in profit. So don't re-train too often.

The retraining process is normally started manually, but can also be performed automatically in regular intervals by
setting the ReTrainDays variable accordingly.

Retesting

The retesting process is triggered by clicking the [Test] button while trading. Its purpose is verifying whether the script
behaves in the simulation exactly as in live trading. For this, Zorro downloads historical data and then performs a
backtest over the period since the begin of trading. The log, profits, and trades from the backtest can then be compared
to the live trading session.

For retesting the trading session, a second Zorro instance will perform the actual test while the first Zorro continues
trading. The start date of the trading session and the bar offset are transferred to the second instance via command
line. In the test run the ReTest boolean variable is nonzero and can be used to set up test parameters in the script. After
finishing the test, the second Zorro closes. The trade list testtrades.csv can then be compared to trades.csv.
Example of a retesting statement in the script:

if(ReTest) {
StartDate = Command[0]; // get start date and bar offset from the trading Zorro via command line
BarOffset = Command[1];
EndDate = 0; // set the end date to the current date
UpdateDays = -1; // reload new price data from the server
NumSampleCycles = 0; // no sample cycles
SelectWFO = -1; // use parameters from the last WFO cycle
}

36
Retest a system when at least about 50 trades have been entered; retests with few trades make not much sense. Make
sure that the test runs with exactly the same parameters - bar offset, trade volume, etc. - as live trading. For retesting
as accurate as possible, the price data should come from the same source as in live trading, and the asset list should
reflect the current trading account parameters. Some differences between test and trading results are still unavoidable;
they are listed below.

Remarks

• Retraining and retesting use the command line for passing parameters and script name to the training or test process.
The script name must be command line compliant.
• The retrain and retest processes need recent price history. It can be automatically downloaded
per UpdateDays setting. When the broker API does not provide price history (as with MT4 or IB) or when the account
only supports a single connection (as with Oanda), download recent price history from another source with
the Download script prior to retraining or retesting.
• Even when from the same source, historical prices can differ from live prices due to outlier filtering, gap filling,
differences between PC and server time, and Internet latency. The average price of a bar (price function) is
approximated in testing with M1 historical data, and thus different to its live value. Price differences can cause trades
to enter or exit at a different bar. Depending on the sensitivity of the strategy to small price differences, the test result
in those cases can be very different to the live trading result.
• Cumulative indicators - such as LowPass, HighPass, DominantPeriod, etc. - are influenced by all past bars and
therefore have slightly different values between simulation and live trading at the begin of the trading session. This can
affect entry and exit conditions of the first trades. The differences disappear over time.
• For comparable results, all asset data - such as spread, slippage, rollover, account currency rate - must be based on
the same broker account. Even when from the same account, asset data varies in live trading, but has normally
constant values in the test. This can cause profit differences between test and live trading.
• For comparable results, sliders and other strategy-affecting parameters must have the same values in trading and test.
Sliders must not have been moved during the trading session.
• Retraining invalidates further retesting since the parameters or rules are modified. You can either retrain or retest a
strategy, but not retest after retraining.
• Retesting or retraining large portfolio strategies, such as the Z systems, can require a large amount of free RAM (~4
GB) and thus might not work on a low-end VPS.

37
Portfolio trading and money management
Trading with a single asset and a single trade algorithm is usually not enough to generate a regular income. The returns
fluctuate too much and can include years with no income (or even a negative one). Strategies to live from normally use
a portfolio of many assets and many trade rules. For a relatively smooth income curve, you normally should have more
than 10 assets and more than 10 trade algorithms in your strategy. This gives you more than hundred asset/algorithm
combinations, which are the components of the strategy. They all require different strategy parameters, have different
performance, and require different capital allocation and reinvestment factors for achieving the optimal overall profit
from the portfolio.

Reinvesting profits - the square root rule

Many traders believe they should invest a fixed percentage - such as 1%, or for the more daring, 2% - of their account
balance per trade. This is one of the most common reasons of unexpected margin calls, especially with automated
systems. Here's why:

The maximum drawdown of any trade system increases over time. The longer you trade, the higher is the probability of
a long loss streak and the bigger the depth of the drawdown. That's why a system, tested over 10 years, has a worse
maximum drawdown than the same system tested over only 5 years. When modeling drawdown depth mathematically
with a diffusion model, the maximum drawdown of a break-even system is proportional to the square root of the number
of trades, and therefore also to the square root of the trading time. This also means that drawdowns have no limit. A
trading system will suffer a drawdown of any depth when you wait long enough.

Drawdown also increases with the invested amount: When investing twice the volume you'll get twice the drawdown.
Thus, when you reinvest a fixed percentage of your balance, the maximum drawdown grows with the balance. And the
balance of a profitable system also grows proportional to the trading time.

When summing up both effects, you'll get an overproportional drawdown growth with trading time: the drawdown grows
proportionally to time to the power of 1.5. The 1 comes from the reinvested profit, the 0.5 from the square root of the
number of trades (in fact the exponent will be slightly higher than 1.5 as reinvested profit also grows overproportionally,
but that shall not bother us here). In any case your drawdowns will grow faster than your account balance. At some
point, a drawdown will inevitably exceed the balance, causing a margin call. That will happen later or sooner, dependent
on the system and the reinvested percentage.

Therefore better don't invest a fixed balance percentage, no matter how often it's recommended in trading books or
seminars. There are several methods to overcome the drawdown growth issue. One method is to reinvest only an
amount proportional to the square root of the capital growth. Thus when your capital doubles, increase the trade volume
only by a factor of about 1.4 (the square root of 2), i.e. 40%. Example: You're trading with a Margin of $50. Your account
doubles from an initial $1000 to $2000. You can now increase your Margin to $70 (= 1.4 * $50) for reinvesting your
gain.

Another method is investing a variable percentage - for instance the OptimalF factor, see below - that is calculated from
the real equity curve and regularly updated so that it decreases when the drawdowns increase. In both cases, the
drawdowns of your system will then only grow at the same rate as your account balance, so you stay away from a
margin call.

In workshop 6 you can find code examples of several methods for correctly (nor not) reinvesting profits.

Withdrawing profits

If you do not reinvest, but withdraw your profits regularly, keep a part of your profit on the account for the very same
reason. As explained above, the expectancy value of the maximum drawdown depth grows with the square root of
trading time. So your account balance must grow by the same factor for keeping pace with the expected drawdown.
Thus, when your account doubled, you can remove only 60% of your profit and should let 40% stay on the account.
This lets your account grow by the required factor 1.4 (again, the square root of 2).

Example: You start with a capital of $1000 and want to withdraw profit whenever the system won $300. Thus the first
withdrawal is at $1300 account balance. Your investment grew by factor 1.3; the square root of 1.3 is 1.14. $1140 must
stay on the account and you can withdraw $160. - Now your system made another $ 300. The account balance is
now $1440, but the total growth (without the withdrawn amount) is $1600 / $1000 = 1.6. The square root
of 1.6 is 1.265. $1265 must stay, and $175 can be withdrawn.
38
What if you want to withdraw not the whole amount, but reinvest the rest? Here are some simple formulae that help you
calculate what you can withdraw, what you can reinvest, and what should remain on the account:

Balance on account: C+P-W


Must stay on account: C*f
Available to withdraw: C+P-W - C*f
Available to reinvest: C*f - W/f

where C is your initial capital at the first start of the system, P the total profit so far, W the total withdrawal so far
(including what you're just withdrawing), and f the square root growth factor sqrt(1+P/C). Example: You start with a
capital of $1000 and won $300, so your balance is now $1300. How much capital do you have for reinvestment when
you withdraw $50?

Balance on account: C+P-W = $1300


Must stay on account: C*f = $1000 * sqrt(1+$300/$1000) = $1000 * 1.14 = $1140
Available to withdraw: C+P-W - C*f = $1300 - $1140 = $160
Available to reinvest: C*f - W/f = $1140 - $50/1.14 = $1096

So when withdrawing $50 from a $300 win, you can increase your investment from $1000 to $1096 (not to $1250 as
you might have expected). Consider the difference a tax that you pay to the god of statistics. Unfortunately you'll have
to pay a real tax for that, too...

Allocating capital

Zorro can automatically calculate the optimal capital allocation factor - named OptimalF - separately for every
component in a portfolio strategy. It uses a computer algorithm that evaluates the balance curve for calculating the
percentage of the gained capital to be reinvested for maximum profit. For instance, if OptimalF is 0.05, then the
maximum margin for trading that component is 5% of the profit. The margin can be smaller - for the reasons mentioned
above not the full profit but only its square root should be reinvested - but it must not be higher. This algorithm was
developed by Ralph Vince and described in many publications (see links)*.

When the FACTORS flag is set, the OptimalF factors are calculated in a special test run at the end of the [Train]
process, and stored in a file Data\*.fac. It's a simple text file that looks like this:

AUD/USD:ES .036 1.14 45/87 0.1


AUD/USD:ES:L .036 1.14 45/87 0.1
AUD/USD:ES:S .000 ---- 0/0 0.0
EUR/USD:VO .027 2.20 24/23 3.3
EUR/USD:VO:L .027 1.58 12/11 0.9
EUR/USD:VO:S .032 2.90 12/12 2.5
NAS100:ES .114 1.42 63/90 4.6
NAS100:ES:L .101 1.39 33/44 2.1
NAS100:ES:S .128 1.46 30/46 2.5
USD/CAD:BB .030 1.41 19/25 1.3
USD/CAD:BB:L .030 1.41 19/25 1.3
USD/CAD:BB:S .000 ---- 0/0 0.0
USD/CAD:HU .012 1.74 48/36 3.3
USD/CAD:HU:L .066 1.42 24/20 0.2
USD/CAD:HU:S .012 1.79 24/16 3.1
USD/CHF:CT .104 1.60 16/17 0.6
USD/CHF:CT:L .104 1.60 16/17 0.6
USD/CHF:CT:S .000 ---- 0/0 0.0
USD/CHF:CY .025 1.10 21/24 0.1
USD/CHF:CY:L .025 1.10 21/24 0.1
USD/CHF:CY:S .000 ---- 0/0 0.0
USD/CHF:HP .025 1.45 31/48 3.2
USD/CHF:HP:L .000 ---- 0/0 0.0
USD/CHF:HP:S .025 1.45 31/48 3.2
USD/CHF:VO .011 3.93 17/8 7.6
USD/CHF:VO:L .011 3.93 17/8 7.6
USD/CHF:VO:S .000 ---- 0/0 0.0

The first column identifies the component; it consists of the asset name and the algorithm identifier. "S" or "L" are
separate statistics for short or long trades. The second column contains the OptimalF factors for that component. The
further columns display the profit factor, the number of winning and losing trades, and the weight of the component; they
are normally not used in strategies.

39
As the factors are stored in a simple text file, they can be edited anytime with a text editor, even while trading. The
higher the factor, the more capital should be reinvested by the strategy component. Zorro detects if factors have been
changed, and automatically reloads them. If the factors are evaluated in the strategy, as in some of the Z strategies, a
component can be excluded from further trading by setting its factor to zero, or by placing a minus sign in front of it for
making it negative.

Variables

The following variables can be used for evaluating or generating OptimalF factors in the script:

OptimalF
OptimalFLong
OptimalFShort
OptimalF factor, and separate factors for long and short trades of the current strategy component that is selected with
the asset and algo functions. The margin to be invested per trade can be calculated by Margin = OptimalF * Capital.
In [Train] mode or when the FACTORS flag is not set, the OptimalF factors are always 1. If a component is
unprofitable, its OptimalF factor is zero.

OptimalFRatio
Generate OptimalF factors with the given ratio of the highest to the lowest factor (default = 0 = no ratio). For instance,
at OptimalFRatio = 3 large factors are reduced and small factors are increased so that the highest OptimalF factor is
3 times the lowest factor. The average of all factors remains unchanged. Useful for preventing large component
margin differences when using OptimalF factors in portfolio systems.

Remarks

• Every algo and asset call switches the OptimalF variables to the factors belonging to the new component.
• In Ralph Vince's publications, OptimalF is defined in a different way, requiring a formula containing the maximum loss
for calculating the number of lots of a trade. Zorro's OptimalF factors are already adjusted by the maximum loss, and
thus can be directly multiplied with the earned capital for getting the optimal margin.
• OptimalF factors are calculated over the whole test period, even when WFO is enabled. This slightly violates the out-
of-sample test philosophy. Therefore when using OptimalF factors for reinvesting profits, the real trading performance
can be worse than the performance predicted by a WFO test.
• In a portfolio system, OptimalF is separately calculated for any component. The correlations of components do not
affect the calculation.
• OptimalF is affected by maximum losses in the trade history, and thus tends to decrease when the test period
increases. The reason is the same as the drawdown dependency on the test period discussed under Reinvesting
profits above.
• If the balance curve has very little drawdown, theoretically the full capital can be invested in that component for
maximum profit. OptimalF is then set to 0.999. Investing the full capital is not recommended in real trading, as the
balance curve is not guaranteed to continue this way in the future. If a component is unprofitable, OptimalF is set
to 0.000.
• Trading with portfolio strategies and money management is explained in workshop 6.
• Markowitz weights can be used alternatively for allocating capital to portfolio components. They have the
disadvantage of not considering reinvestment, but the advantage of minimizing the variance of the total portfolio.

Examples of different investment methods


// reinvest the square root of your portfolio component profits, separately for long and short trades
if(GoLong)
Margin = OptimalFLong * Capital * sqrt(1 + (WinLong-LossLong)/Capital);
else
Margin = OptimalFShort * Capital * sqrt(1 + (WinShort-LossShort)/Capital);

// reinvest the square root of your portfolio component profits


Margin = OptimalFLong * Capital * sqrt(1 + ProfitClosed/Capital);

// reinvest the square root of your total profits


Margin = OptimalFLong * Capital * sqrt(1 + (WinTotal-LossTotal)/Capital);

40
Monte Carlo Analysis
Zorrocan run a Monte Carlo analysis for its own strategies, as well as for trade lists that are generated by other software
and stored in .csv files. The Monte Carlo method improves the performance analysis. It generates not a single
sequence of trades and a single equity curve, but a distribution of many possible curves. Compared to standard
simulation, the Monte Carlo method produces results that are more accurate and less subject to random fluctuations.

When using Monte Carlo analysis, the equity curve from the backtest is randomly resampled many times. This generates
many different equity curves that each represent a different order of trades and different price movements inside each
trade. The curves are analyzed, and their results are sorted according to their performance. This way a confidence level
is assigned to every result. The confidence level determines how many of the curves had the same or better performance
than the given result.

Without Monte Carlo analysis, the annual rate of return is calculated from the backtest equity curve. For example, it
might be found that the annual return over the curve was 200%. With Monte Carlo analysis, hundreds or thousands of
different equity curves are analyzed, and the annual return determined from them might be, for instance, 145% with
95% confidence. This means that of all the thousands of possible outcomes of the simulation, 95% had annual return
rates better than or equal to 145%.

Monte Carlo analysis is particularly helpful in estimating the risk and capital requirement of a strategy. The maximum
historical drawdown is normally used as a measure of risk, but this means we're basing our risk calculations on a
historical price curve that won't repeat exactly. Even if the statistical distribution of trades is the same in the future, the
sequence of those trades and their equity movements are largely a matter of chance. Calculating the drawdown based
on one particular sequence is thus somewhat arbitrary: with a sequence of several losses in a row, you can get a very
large drawdown. But the same trades arranged in a different order, such that the losses are evenly dispersed, might
produce a negligible drawdown. This randomness in the result can be eliminated by Monte Carlo analysis that takes
many different equity curves and many different trade sequences into account.

Example of the result distribution in the performance report:

Confidence level AR DDMax Capital

10% 236% 1440$ 1390$


20% 227% 1550$ 1470$
30% 218% 1680$ 1570$
40% 209% 1830$ 1680$
50% 202% 1940$ 1760$
60% 193% 2140$ 1900$
70% 186% 2340$ 2040$
80% 174% 2730$ 2320$
90% 165% 3080$ 2580$
95% 145% 4010$ 3580$
100% 104% 5640$ 4710$

The first column identifies the confidence level; in the next columns the annual return, the maximum drawdown, and the
required capital at that level are listed. The most likely result is at 50% confidence level (here, 202% annual return). This
means that half the simulations had this or a better result, and half a worse result. The higher the confidence level, the
more pessimistic are the results. The result at 100% confidence level is the worst of all simulations; thus the probability
to get this (or an even worse) result is 1/n, where n is the number of simulations. Note that the capital can be smaller
than the maximum drawdown when the test period was longer than 3 years (capital and annual return are based on a
3-years normalized drawdown).

The following variables are used for Monte Carlo simulation:

MonteCarlo
Number of simulations for Monte Carlo analysis (default: 200). Every simulation generates a different equity curve.
The more simulations, the more accurate is the result, but the more time is needed for the computation. If set to 0, no
Monte Carlo simulation is performed.

Confidence
Confidence level for the performance analysis in percent (0..100); determines the calculation of the main
performance parameters such as annual return, drawdown, required capital, etc. The higher the confidence level, the

41
more pessimistic are the results. At 0 (default) the performance parameters are calculated from the real equity curve
and not from the Monte Carlo simulation.

Remarks

• If the Capital variable is set, performance parameters are derived from the given capital, not from
the Confidence level.
• Monte Carlos Analysis is biased when trade returns are strongly correlated (i.e. the strategy produces long winning
and losing streaks) or strongly anticorrelated (winning and losing alternate). In the former case Monte Carlo Analysis
can display too optimistic results, otherwise too pessimistic results.
• Monte Carlo analysis does not absolutely guarantee a minimum performance. If the strategy was expired or
was tested in a wrong way (f.i. in-sample), live trading returns can be worse than the Monte Carlo performance even
at 100% confidence level.

Examples
function run()
{
MonteCarlo = 1000; // 1000 simulations
Confidence = 75; // 75% confidence level
...

42
Performance analysis
Zorro can analyze the performance of its own strategy scripts, as well as of trade lists that are generated by other
software and exported as .csv files. For the latter case an example can be found in the Simulate script.

A click on [Result] produces a chart and a performance report with portfolio analysis for the previous [Test]. Chart and
report are stored in the Log folder and displayed in Zorro's image viewer and text editor. The chart is a .png image file,
the report is a simple .txt file, so both can be easily posted to websites or imported in documents. Additionally, the equity
or balance curves from test or training are exported to a *.dbl file and can be further evaluated with third party software,
f.i. an R data analysis package.

In [Trade] mode the performance report is part of the .htm status report. It can be displayed in a web browser and is
updated every minute. It also contains a list of all open and pending trades, as well as any specific information that
is printed by the script.

A chart example:

By default, the chart shows equity and drawdown profiles, the price curve, and the trades of the asset selected with the
[Asset] scrollbox. Trade entry and exit points are marked with green or red dots for winning or losing trades. The blue
equity profile is summed up from the whole portfolio and is averaged over all oversampling cycles. If Capital is
invested, it adds to the equity curve. The red spikes below are the drawdown profile (the "underwater equity"). On large
scales the price curve is displayed as candles. The trade, candle, and equity colors can be set up with
the Color parameters. Any element can be removed from the chart by setting its corresponding Color parameter to 0.
The size and scale of the chart can be set up with plot parameters (click [Test] again when the script was changed).
The price curve and the displayed trades can be selected with the [Assets] scrollbox before clicking [Result]. The
number of bars per chart can be limited with the PlotBars variable.

Performance report

The performance is calculated either from the trade sequence and equity curve of the simulation, or from a distribution
of many sequences and equity curves using Monte Carlo analysis. This is an example of a performance report:

Walk-Forward Test Z4 portfolio

Simulated account AssetsFix.csv (NFA)


Bar Period 4 hours (avg 266 min)

Simulation period 06.06.2004-02.05.2012 (9483 bars)


Test period 10.06.2007-02.05.2012 (6259 bars)
WFO test cycles 11 x 569 bars (137 days)
WFO training cycles 12 x 3224 bars (111 weeks)
Lookback period 700 bars (169 days)
Monte Carlo cycles 100
Fill mode Realistic (slippage 5.0 sec)

43
Gross win/loss 60452$ / -43672$ (+16252 pips)
Virtual win/loss 60024$ / -44112$
Average profit 4309$/year, 359$/month, 17$/day
Max drawdown -2025$ 12% (MAE -2287$ 14%)
Total down time 78% (TAE 93%)
Max down time 119 days from May 2009
Max open margin 656$
Max open risk 1863$
Trade volume $10164943 (2610626$/year)
Transaction costs -637$ spr, -365$ slp, -906$ rol, -1102$ com
Capital required $2681

Number of trades 1370 (351/year)


Percent winning 44%
Max win/loss 810$ / -407$
Avg trade profit 12$ 10.4p (+99.0p / -56.5p)
Avg trade slippage -0.27$ 0.2p (+1.1p / -1.4p)
Avg trade bars 16 (+23 / -10)
Max trade bars 141 (34 days)
Time in market 355%
Max open trades 14
Max loss streak 17 (uncorrelated 13)

Annual return 161%


Profit factor 1.38 (PRR 1.28)
Sharpe ratio 1.94
Kelly criterion 0.63
Ulcer index 12%
Cycle performance 1.39 1.40 1.31 1.33 1.37 1.38

Confidence level AR DDMax Capital

10% 236% 1440$ 1390$


20% 227% 1550$ 1470$
30% 218% 1680$ 1570$
40% 209% 1830$ 1680$
50% 202% 1940$ 1760$
60% 193% 2140$ 1900$
70% 186% 2340$ 2040$
80% 174% 2730$ 2320$
90% 166% 3080$ 2580$
95% 146% 4010$ 3580$
100% 104% 5640$ 4710$

Portfolio analysis OptF ProF Win/Loss Wgt% Cycles

AUD/USD avg .014 1.36 281/475 11.4 XXXXXXXXXX/


EUR/USD avg .006 1.41 91/115 6.2 XXXXXX\XXXX

GER30 avg .030 1.33 34/45 2.5 X/X/\.\XXXX


SPX500 avg .077 1.75 96/162 16.4 XXXXXXXXXXX
USD/JPY avg .006 1.69 130/175 8.7 XXXXXXXXXX/
XAG/USD avg .008 1.35 113/152 5.3 \\XXXXXXXX/

BB avg .010 1.29 82/111 2.6 X/\XXX/\XXX


CT avg .019 1.38 106/126 7.6 XXXX\XXXXXX
CY avg .017 1.83 60/66 5.6 /XXX\/XXX/X
ES avg .025 1.34 163/275 7.4 XXXXX/XXXXX
HP avg .017 1.34 332/440 19.2 XXXXXXXXXXX
LP avg .009 1.54 106/219 11.3 \XXXXXXXXXX
VO avg .014 1.71 217/288 24.2 /XXXXXXXXXX

AUD/USD:ES .036 1.14 45/87 0.1 \\/\\//\\//


AUD/USD:ES:L .036 1.14 45/87 0.1 \\/\\//\\//
AUD/USD:ES:S .000 ---- 0/0 0.0 ...........
AUD/USD:HP .024 1.18 76/102 2.2 X/\XX\/XX\/
AUD/USD:HP:L .024 1.13 47/68 1.2 //./\\///\/
AUD/USD:HP:S .043 1.35 29/34 1.0 \/\\/./\\\/
AUD/USD:LP .029 1.66 75/149 7.5 \/X///X\/X/
AUD/USD:LP:L .029 1.80 42/71 6.8 \//////\/\/
AUD/USD:LP:S .058 1.23 33/78 0.6 \/\///\\///
EUR/USD:CT .009 1.11 22/33 0.5 X/\X\X\X/XX
EUR/USD:CT:L .027 1.22 10/19 0.7 \/\/\\\\.//
EUR/USD:CT:S .000 0.91 12/14 -0.2 /.\\\/\//\\
EUR/USD:HP .036 1.32 45/59 2.4 \///\/\\\/\
EUR/USD:HP:L .036 1.32 45/59 2.4 \///\/\\\/\
EUR/USD:HP:S .000 ---- 0/0 0.0 ...........
EUR/USD:VO .027 2.20 24/23 3.3 .X.//.\X/XX
EUR/USD:VO:L .027 1.58 12/11 0.9 ././...//\\
EUR/USD:VO:S .032 2.90 12/12 2.5 .\../.\\.//
GER30:BB .038 1.03 2/4 0.0 /......\\/\
GER30:BB:L .038 1.03 2/4 0.0 /......\\/\
44
GER30:BB:S .000 ---- 0/0 0.0 ...........
GER30:CT .256 1.92 3/2 0.6 \/./...\/..
GER30:CT:L .256 1.92 3/2 0.6 \/./...\/..
GER30:CT:S .000 ---- 0/0 0.0 ...........
GER30:ES .267 1.82 2/3 0.2 ..\/\../..\
GER30:ES:L .000 ---- 0/0 0.0 ...........
GER30:ES:S .267 1.82 2/3 0.2 ..\/\../..\
GER30:VO .051 1.31 27/36 1.7 ////\.\\/\/
GER30:VO:L .051 1.31 27/36 1.7 ////\.\\/\/
GER30:VO:S .000 ---- 0/0 0.0 ...........
SPX500:ES .110 1.44 17/21 1.3 //\/\///\\\
SPX500:ES:L .110 1.44 17/21 1.3 //\/\///\\\
SPX500:ES:S .000 ---- 0/0 0.0 ...........
SPX500:LP .006 1.04 17/55 0.2 \\/\\\\\//\
SPX500:LP:L .006 1.04 17/55 0.2 \\/\\\\\//\
SPX500:LP:S .000 ---- 0/0 0.0 ...........
USD/JPY:BB .057 1.51 24/36 0.6 \/\\/\/.\./
USD/JPY:BB:L .000 ---- 0/0 0.0 ...........
USD/JPY:BB:S .057 1.51 24/36 0.6 \/\\/\/.\./
USD/JPY:CT .016 1.46 16/19 2.0 \./..\/.\//
USD/JPY:CT:L .016 1.46 16/19 2.0 \./..\/.\//
USD/JPY:CT:S .000 ---- 0/0 0.0 ...........
USD/JPY:HP .024 2.24 34/29 6.4 .XX/\/\/.\/
USD/JPY:HP:L .024 1.22 9/13 0.3 .\//\/\..\/
USD/JPY:HP:S .028 2.65 25/16 6.0 ./\/.../.\/
XAG/USD:CT .038 1.87 13/13 0.6 ....\///\\/
XAG/USD:CT:L .000 ---- 0/0 0.0 ...........
XAG/USD:CT:S .038 1.87 13/13 0.6 ....\///\\/
XAG/USD:HP .018 1.43 44/46 1.1 \\/X/\//X//
XAG/USD:HP:L .105 4.16 6/3 0.3 .\./....\/.
XAG/USD:HP:S .014 1.33 38/43 0.8 \\/\/\/////
XAG/USD:VO .011 1.39 40/62 3.0 .\/\//\\\//
XAG/USD:VO:L .000 ---- 0/0 0.0 ...........
XAG/USD:VO:S .011 1.39 40/62 3.0 .\/\//\\\//

The following table shows the meaning of the values. Most of the calculated values are only valid for a profitable report
(Gross Win > Gross Loss); they are meaningless when the profit is negative. Indicated values with a "$" suffix are in
units of the account currency (not necessarily US-$), indicated values with a "p" or "pips" suffix are in pips.

Performance report
Bar period Bar period in seconds, minutes, or hours, and average bar duration in minutes. Variations in bar
duration are caused by weekends, holidays, or special bars.
Simulation Time of the WFO run (usually 4..5 years) and the number of bars, without the preceding
period lookback period.
Test period Time and bar number of the test; simulation period without training and lookback.
WFO test cycles Number and length of the WFO test cycles (this is also the recommended re-training period).
WFO train cycles Number and length of the WFO training cycles.
Lookback period Amount of data to be collected before test or training begins.
Monte Carlo Number of Monte Carlo simulation cycles, and selected confidence level (if any) for the
cycles following performance figures.
(Confidence)
Fill mode Simulated fill mode (Naive or Realistic) and Slippage.
Avg ticks per bar Average number of price ticks per bar in the simulation; for single asset strategies.
Spread Spread in pips, and long/short rollover fee in account currency units; for single asset strategies.
Commission Roundturn commission in account currency units; for single asset strategies.
Contracts per lot Lot size; for single asset strategies.

Gross win (loss) Sums of all wins and all losses in currency units, and the overall volume-neutral result in pips.
Note that there can be a negative pip result and a positive gross win (or vice versa) due to
different assets pip costs and different trade volumes.
Virtual win (loss) Sums of all wins and all losses of phantom trades in currency units, for virtual hedging.
Normally worse than the gross win / loss due to higher transaction costs.
Average profit Annual, monthly, and daily profit (workdays only), calculated from the difference between start
and end balance.
Max drawdown Maximum drawdown during the test period, in account currency units and in percent from the
(MAE) gross profit. The drawdown is the difference between a balance peak and the lowest subsequent

45
equity valley (balance = account value, equity = balance plus value of the all open
trades). MAE (maximum adverse excursion) is the difference between an equity peak and the
lowest subsequent equity valley. Drawdowns can be affected by any single trade and are thus
subject to high random fluctuations (see remarks below).
Total down time The percentage of time below a preceding balance peak and below a preceding equity peak
(TAE) (TAE - time in adverse excursion). Many strategies have about 90% down time even though
they are still profitable.
Max down time Longest duration from a balance peak to the subsequent equity valley.
Max open margin Maximum total margin used during the backtest period.
Max open risk Maximum loss when all open trades hit their initial Stop at the worst possible moment.
Trade volume Total and annualized value of all assets bought and sold, in units of the account currency (see
remarks).
Transaction Total costs of spread (Spr), slippage (Slp), swap/rollover (Rol) and commission (Com) for all
costs trades. Slippage and rollover can increase the profit in some cases; costs are then positive. In
test mode the simulated costs are displayed, in trade mode the real costs.
Capital required Required initial capital for trading the strategy. For strategies that reinvest profits
(Capital variable nonzero), the displayed value is the minimum initial capital for avoiding a
margin call in the test period. Otherwise it's the sum of normalized drawdown and maximum
margin. This amount would be required when the strategy is entered at the worst possible
moment of the simulation, i.e. directly at the balance peak preceding the largest drawdown.

Number of trades Number of trades in the backtest period. Only real trades are counted, not phantom or pending
trades.
Percent winning Percentage of winning trades.
Max win (loss) Maximum win and loss of all trades.
Avg trade profit Average return of a trade in account currency units and in volume-neutral pips; separately listed
for winning (+) and losing (-) trades. Robust strategies should return a multiple of the spread.
Avoid systems that generate either many trades with small average returns, or few trades with
very large average returns.
Avg trade Average slippage cost of a trade in account currency units and in pips; separately listed for
slippage positive (+) and negative (-) slippage. In test mode it's the simulated slippage, in trade mode the
real slippage.
Avg trade bars Average number of bars of a trade; separately for winning (+) and losing (-) trades.
Max trade bars Maximum time a trade was open.
Time in market Total time of all trades compared to the backtest time. This can be more than 100% when
several trades are open at the same time. The smaller the time in market, the less exposed is
the capital to the market risk.
Max open trades Maximum number of simultaneously open trades.
Max loss streak Maximum number of consecutive losses during the test, and the theoretical number under the
(uncorrelated) assumption of uncorrelated returns, i.e. equally distributed wins and losses. If the real number
is noticeably higher, wins and losses tend to cluster with this strategy, and an equity curve
trading algorithm could improve the performance.

Annual return Return on investment per year in percent; annualized profit divided by the required initial capital.
As this value depends on drawdown, it can be subject to random fluctuations (see remarks).
Annual growth Compound annual growth rate (CAGR) of the investment; the nth root of the total equity growth,
rate where n is the number of years in the test period. Only displayed for strategies that re-invest
profits.
Profit factor Gross win divided by gross loss. The pessimistic return ratio (PRR) is the profit factor adjusted
(PRR) by the number of trades; it gives a worse result when the number of trades is low.
Sharpe ratio Annualized ratio of per-bar profit mean and standard deviation, calculated from the equity curve.
The Sharpe ratio is a good performance gauge (see remarks) and should be > 1 for tradeable
strategies.
Kelly criterion Ratio of bar profit mean and variance; optimal investment factor for a single-asset, single-algo
strategy to maximize the profit.

46
R2 coefficient Coefficient of determination; the similarity of the equity curve with a straight line ending up at
the same profit. The closer R2 is to 1, the steadier are the profits and the better they will be
possibly reproduced in real trading (see remarks).
Ulcer index Mean drawdown percentage; a measure of length and depth of drawdowns (see remarks). The
higher the ulcer index, the stronger your stomach must be for trading the script. The ulcer index
should be < 10% for preventing ulcer.
Cycle Separate profit factors of the oversampling cycles. High profit differences between cycles are
performance a sign of an unstable strategy.

Monte Carlo Performance analysis (see Monte Carlo Method) by evaluating many possible equity curves
analysis with different distributions of trades and returns. A strong serial correlation of trade returns can
cause Monte Carlo results higher than the result from the real equity curve.
Confidence level Confidence level of the following performance parameters. F.i. at 95% confidence level, 95% of
all simulations generated the same or better results, and 5% generated worse results.
AR Annual return at the given confidence level.
DDMax Maximum drawdown (not normalized) at the given confidence level.
Capital Capital requirement at the given confidence level.

Portfolio analysis Performance analysis per asset, per algorithm, and per component. Only components with
trades are listed. The figures are taken from the last oversampling cycle; when
the ALLCYCLES flag is set, they are taken from all cycles.
OptF OptimalF factors for portfolio strategies (see Money Management). When the factor is 0, the
component was unprofitable in the test.
ProF Profit factor (gross win divided by gross loss, including phantom trades). A '++++' in the column
indicates that there were only winners, '----' indicates that there were no winners.
Win/Loss Number of winning and losing trades, including phantom trades.
Wgt% Weight of the component; contribution to the overall profit of the strategy in percent. The weight
can be negative, f.i. with a losing component and a positive overall result.
Result Current profit or loss of the component in live trading.
Cycles Profit separated by WFO cycles. '/' is a winning cycle, '\' a losing cycle, 'X' is a cycle with both
winning and losing components, and '.' is a cycle without trades.

Additionally, the performance can be evaluated by user criteria or stored for further evaluation with the user-
supplied evaluate function.

Remarks

• Additional performance parameters can be added to the report with a print(TO_REORT, ...) command. For calculating
them, you can enumerate all trades (for(all_trades)) in the evaluate function, calculate the desired parameters from
the trade statistics, and print them to the report. The equity curve is available in the EquityCurve array for further
calculations. Graphical presentations of performance parameters can be produced with plotBar statements. Examples
can be found in the include\profile.c file. If you need a certain performance parameter that can not be easily generated
by script, please ask for it on the Zorro user forum.
• All performance parameters above are calculated under the assumption that the result is positive. A neutral or negative
result will invalidate most performance figures.
• In backtest and training, performance is calculated under the assumption of constant spread, commission, slippage,
rollover, and account currency exchange rate, which are taken from the asset list. Theoretically the performance could
be adapted to variations in those parameters as in the example under PipCost. This is however not recommended,
since the resulting differences are irrelevant for the strategy performance, but would add artifacts to the test and training
process.
• When the backtest runs over several sample cycles, the price curve in the chart only shows the trades of the last
cycle, but the performance report and the equity curve are calculated from all cycles. If some cycles win and others
lose, the summing up can generate results that appear inconsistent, for instance a small positive Sharpe ratio, but a
small negative Annual return.
• The Maximum Adverse Excursion (MAE) can be substantially higher than Maximum Drawdown, especially when
trades stay open a long time. Example: A single trade first goes up to $3000 profit, then falls down to -$1000 loss, then
goes up again and is closed at $500 profit. This trade has a MAE of -$4000, but only a drawdown of -$1000. You need
a capital of at least $1000 plus the trade margin for avoiding a margin call. Unlike the drawdown, the MAE is not

47
dangerous (except for your nerves) and does not affect the capital requirement. But a high difference between MAE
and drawdown can indicate a less-than-perfect trade exit algorithm.
• Maximum drawdown and MAE depend on the test period; longer periods cause higher maximum drawdown. On a
break-even system the maximum drawdown is proportional to the square root of the test period*. For this reason,
maximum drawdown dependent performance figures such as Annual return and Capital required are calculated
from a normalized drawdown, which adjusts the drawdown to a 36 months test period. This normally generates
performance figures that are largely independent on the test period.
• Drawdowns are a result of the random win/loss pattern of trades. They can change greatly with minor changes to the
strategy or test period; therefore performance parameters calculated from the original equity curve can be subject to
high random fluctuations. When using a Monte Carlo Simulation, many equity curves affect the calculation and the
performance parameters are derived from the given Monte Carlo Confidence level.
• The Capital required is a statistical parameter calculated from the simulation. It is unrelated to the Risk displayed by
Zorro for single trades, which is the maximum loss by hitting the initial stop. The Max open risk parameter and even
an individual trade Risk can be higher than the required capital. Do not take the Capital required parameter as a
guaranteed loss limit.
• The Ulcer index is traditionally calculated from the drawdown percentages in relation to their preceding equity peaks.
This gives a misleading result, as the equity peaks normally rise during the trade period, causing the ulcer index to be
affected mostly from the begin of the equity curve where the peaks are lower and the percentages higher. For this
reason, Zorro calculates a modified ulcer index that uses a percentage of the maximum equity peak instead of the
preceding equity peak.
• The Sharpe ratio takes risk into account; it is supposed to compare the strategy performance with a risk-free return
as from a savings account. A Sharpe ratio of 1.5 would be equivalent to a savings account with 50% risk-free interest.
Traditionally, 4% are subtracted from the Sharpe ratio for obtaining the difference to the usual 4% interest of risk-free
investment returns; this would make no sense for comparing trade strategies, thus nothing is subtracted here.
• The R2 coefficient is a measure of equity curve linearity. The equity curve is compared with a steady slope, i.e. a line
through its start and end points (not with its own regression line as in some applications, as this could feign a high
linearity even with a falling equity curve). R2 is the squared correlation coefficient of curve and straight slope.
Anticorrelation by a mostly falling equity curve produces a R2 coefficient of zero.
• The per-asset and per-algo figures in the portfolio analysis are a simple non-weighted average of the single
components. Thus, low-volume components contribute overproportionally and the weights won't add up to 100%.

Keep in mind that that all those performance figures are derived from historical data (even when it's out-of-sample data).
The future is unknown, so there is no guarantee to achieve the same performance in live trading. Many figures - f.i.
Sharpe ratio, Monte Carlo analysis, drawdown extrapolation, R2 coefficient - are based on mathematical models that
assume a Gaussian distribution of returns. However there is no guarantee that real returns always follow a Gaussian
distribution. For those reasons, don't interpret too much into performance figures. Even a system with excellent
theoretical performance can cause real loss of money.

* See Malik Magdon-Ismail / Amir Atiya, "Maximum Drawdown", 2004.

48
The Z systems
The Z systems are prefabricated portfolio strategies based on the workshops (not included in some Zorro versions for
US clients). To avoid misunderstandings: they should not be the main reason of using Zorro - better learn strategy
development and trade your own systems instead. Doing it yourself has many advantages. First, you can adapt your
systems exactly to your capital and risk requirements. Second, you know precisely what they do and why, which lets
you face drawdowns with greater confidence. Third, you can permanently work on and improve the algorithms. Fourth,
you can not blame us for losses. And fifth, when you develop a good system and make it available to the community,
you can get a free Zorro S license and earn more money.

Since strategy development is not trivial - otherwise there would be some more working systems around - we've included
several ready-to-run strategies, based on the workshops in the tutorial. Keep in mind that all those systems are
simple and use no money management and no complex filter, trailing, or exit algorithms. So any of them has probably
room for improvement. But you can use them for already earning some trading income until you've learned to stand on
your own feet. And they really work, other than the countless 'robots' or 'EAs' offered on countless websites.

The Z systems cover a range of trade methods, win rates, and budgets:

System Z1 Z2 Z3 Z7 Z8 Z12

Traded EUR, CHF, EUR, CHF, Silver, DJI, EUR/USD, ETF Portfolio EUR, CHF,
assets GBP, GBP, S&P500 GBP/USD, GBP,
AUD, CAD, AUD, CAD, USD/JPY AUD, CAD,
JPY, USD, JPY, USD, JPY, USD,
NAS, NAS, NAS,
S&P500, S&P500, S&P500,
DJI, DAX, UK, DJI, DAX, UK, DJI, DAX, UK,
Gold, Silver Gold, Silver Gold, Silver

Trade Trend Mean Breakout Price patterns Portfolio Anticorrelation


method following reversion rotation

Trade Spectral filters Spectral filters Cluster Machine MV Spectral filters


algorithm detection learning Optimization

Bar period 4 hours 4 hours 1 day 1 hour 1 day 4 hours

Avg trade 7 days 3 days 2 weeks 5 hours 6 months 7 days


duration

Re-train 4 months 4 months 6 months 3 months rebalance 4 months


every monthly

Worst 9 months 7 months 12 months 6 months 4 months 6 months


drawdown

Annual ~100% ~130% ~130% ~110% ~30% ~180%


return

Profit factor ~1.4 ~1.4 ~2.0 ~1.2 ~6.5 ~1.4

Required ~1000 $ ~1800 $ ~1000 $ ~900 $ ~1200 $ ~2000 $


capital

Monthly ~70 $ ~200 $ ~100 $ ~80 $ ~30 $ ~300 $


profit

Trades per ~300 ~400 ~25 ~1600 ~55 ~700


year

Win rate ~38% ~56% ~53% ~52% ~78% ~49%

49
The Z systems will not start right away. First you need to download historical data for testing them. So when you
complain on the user forum that "the system gives a missing history error when I start it", we will know that you have not
read the manual! For trading them, you might also need to rename or exclude assets that are not provided by your
broker. Training the Z systems is normally neither required nor recommended, since we provide updated parameters
with any Zorro update. Make sure to read this page from top to bottom, and read also the income chapter before using
a Z system. All systems - maybe with exception of Z8 - can cause a total loss of the invested capital. And all of them
have long drawdown periods as you can see in the table above. You're more likely to start in a drawdown period than
not. Do not invest what you can't afford to lose.

By clicking [Test], the system is walk forward analyzed for experimenting with different slider and parameter settings
and different broker accounts. For keeping the Zorro installation size small, not all needed historic price data files are
included in the setup file. Load the additional data with the Download script or from the Zorro download page. The Z
systems normally need historical price data from 2009, Z7 also needs currency data from 2002. Your test results can
differ from the figures above. Broker accounts with higher lot amounts, margins, and commissions can produce very
different results.

Not all brokers offer all assets traded by the Z systems. If a certain asset is not available, remove it from the strategy as
described below. If it is available, but under a different symbol, edit the asset list and enter its symbol name. US citizens
are normally not allowed to trade CFDs; in that case set the FXOnly flag (see below) for trading Forex only.

The performance figures and equity curves are from a walk-forward analysis on a simulated FXCM microlot account at
100:1 leverage, with all sliders at their default values. Early versions of the systems had been in live trading since 2012
(in case of Z1, since October 2011); the results so far had been consistent with the simulation. All systems except Z3
and Z8 have been trained with oversampling and are thus very robust against price curve noise. Note that all
performance parameters are broker and leverage dependent; lower leverage leads to higher capital requirement, a
reduced number of trades and lower annual returns.

The Z strategies are not available in script. They are compiled executables (*.x) only. This is not due to some secret
trade algorithm - the algorithms are described in the workshops, so any Zorro user can write such strategies himself
after finishing the tutorial. But if we published the script code, anyone could precisely predict when the strategies buy
and when they sell. It would then be possible to prey on Z system users with fore-running methods. For the same reason,
some of the strategies contain a random element that makes it hard to predict their trading behavior. This random
element has only a small effect on the strategy performance, but it ensures that many people can trade the strategies
without hampering each other. It is technically impossible to reverse engineer compiled Zorro strategies for determining
their code.

Due to the profit limit, the free Zorro license allows to trade only one system - either one of the Z strategies or a system
of your own - on a real money account. Z1, Z2, and Z8 are included in the free Zorro version. The other systems are
included as demo versions; the versions for real money trading are included in Zorro S or separately available for a
subscription fee on the download page.

The Z systems do not automatically reinvest profits. The trade size can be manually controlled with a slider and is
unrelated to the account balance. OptimalF factors are used by some systems for distributing the capital among the
components, but not for reinvesting. If you want to reinvest your profits, manually increase the Margin slider from time
to time proportionally to the square root of the profit accumulated so far.

Z1

Z1 trades a portfolio of trend following algorithms with currencies, commodities, and indices. The algorithms are variants
of the lowpass filter trend trading system from Workshop 4. They are self-adapting to the market within certain
boundaries, and can detect unprofitable market periods. OptimalF factors are used for distributing the capital among
the components. Trade exits are handled by TMFs. All trades have rather tight stop loss limits and no profit targets.
Some of the algorithms control the trade duration by moving the stop loss; this can cause the stop to move in both
directions.

50
The equity curve above is on the optimistic side due to selection bias from trading many different portfolio components.
For reducing the expiration risk of single components, an equity curve trading mechanism is implemented in Z1 - you
can deactivate it with the Phantom flag (see below). A table with the current algorithm states is displayed in the trade
status page:

EUR/USD VO:LS -198 HU:LS +68

USD/CHF VO:LS +726 LP: +549 HU:S +612 ES:LS +190 LS: +611

GBP/USD LP:LS +610

USD/CAD HU:LS -108 ES:L -73 LS:L +821

USD/JPY VO: +2400 HU: +1178

AUD/USD VO:S -82 LP:LS -0 LS:S +582

NAS100 ES: +886

SPX500 ES:L +318 LS:L +629

US30 LP: +256 ES:L +761

GER30 LP:LS +746 HU:LS +1186

XAU/USD LP:L +420 ES:S +3457 LS: +2020

XAG/USD VO:L +1797 LP:L +89 LS:S +156

UK100 LS:L -47

In the above matrix, active algorithms are displayed with their identifier (VO, LP, etc.) and L and/or S for the currently
preferred trade direction, long, short, or both. Algorithms that are currently winning are displayed on a green rectangle.
Algorithms that are currently losing and thus temporarily suspended (if equity curve trading was enabled, see below)
are displayed on a red rectangle. The displayed numbers are the total win or loss per component, including phantom
trades.

Due to their relatively long lookback period, Z1 and Z2 should only be traded with brokers that provide sufficient price
history. If price data gaps are indicated in the message window at start, it is recommended to use the Preload flag (see
below) or trade the system with a different broker. Otherwise the shortened price history can affect the performance in
the first weeks.

Z2

Z2 is a portfolio of variants of the Workshop 5 system. While its own algorithms are more or less correlated, it is
anticorrelated to Z1. The algorithms are self-adapting to the market. OptimalF factors are used for distributing the capital

51
among the components. Trade exits are handled by TMFs. All trades have rather tight stop loss limits and no profit
targets. Some of the algorithms control the trade duration by moving the stop loss; this can cause the stop to move in
both directions.

As with Z1, the equity curve above is on the optimistic side due to selection bias. Z2 also contains an equity curve
trading mechanism that can be deactivated with the Phantom flag (see below). A table with the current algorithm states
is displayed in the trade status page:

EUR/USD CT:L -62

USD/CHF CT:LS -130 HP:S +35

GBP/USD BB:LS +634 CY:LS +466

USD/CAD CT:LS -139

USD/JPY CT:L +884 CY:L +3464

AUD/USD HP:S +1076

NAS100 BB:L +797 CY:L +1768 HP: +5861

SPX500 CY:L -120 HP:L +51

US30 CT:L +1110 CY:LS +688

GER30

XAU/USD HP:LS +4231

XAG/USD HP:LS +3347

UK100 CY:LS +325 HP:L +526

Like Z1, Z2 should only be traded with brokers that provide sufficient price history.

Z12

A pimped up version of the system described in Workshop 6. It exploits the anticorrelation of the Z1 and Z2 equity
curves. By combining Z1 and Z2 to a compound system, profit is increased and drawdowns are reduced. The Z12 profit
exceeds the sum of Z1 and Z2 profits, while the capital requirement is lower than the sum of both.

The above remarks about the Z1/Z2 drawdown, equity curve trading, and lookback period also apply to Z12.

52
Z3

Z3 is a medium-term commodity and index trading system - an improved version of the simple system from the Zorro
video tutorial. It detects price clusters that precede a price breakout. Those breakouts happen frequently especially with
metals and stock indices, and constitute an exploitable inefficiency. The results are uncorrelated to Z1 and Z2, so all
systems can be traded simultaneously with Zorro S. Since Z3 is based on a single algorithm, the equity curve is
unaffected by selection bias.

The Z3 performance depends on the asset volatility. In low volatility periods the equity is more or less flat. As with Z1
and Z2, the trade duration is controlled by moving the stop loss level. This can cause the stop to move in both directions
and to tighten even when the trade is not in profit.

Z7

Z7 is a forex trading system based on a price pattern detection algorithm similar to Workshop 7 (a variant is described
on the Financial Hacker blog). It is designed for low capital requirement (less than 1000 $) and short trades (5 hours).
Trades are triggered by price patterns preceding weekend gaps and shortlived up- or downwards movements. Z7 uses
different patterns for the US, European, and Pacific sessions. The results are unrelated to trends and cycles, and
uncorrelated to the other systems.

53
All Z7 trades end after at least one market day, most after a few hours. The stop loss is relatively distant and is used for
risk limitation only. A trailing mechanism locks profits when the trade goes well in high volatility situations.

Z8

Z8 is a low-risk, long-term trading system based on a modified mean/variance optimization (MVO) algorithm. It is an
improved version of the MVO system described on Financial Hacker. Z8 opens a portfolio of ETFs, stocks, or other
assets determined by an external asset list, and re-allocates the invested capital among the portfolio components once
per month for achieving a straight positive balance curve with minimum variance. Since it trades with low leverage, the
risk of capital loss is low in comparison to the other systems.

The balance curve above is from an out-ouf-sample test with the default assets. The assets can be set up in an asset
list named AssetsZ8.csv. By default it contains a set of ETFs that were selected by their supposed long-term prospects.
But we're programmers, not traders, so you can possibly find a better combination. Z8 can also trade stocks or any
instruments with long-term positive return. For adding a new asset, just duplicate a line in AssetsZ8.csv and edit the
asset name in the first column. For temporarily out-commenting a line, add a '#' in front of the asset name. Up to 300
assets can be entered in the list. For finding assets with low correlation, use the Heatmap.c script from Financial
Hacker. Forex or CFDs are not suited for Z8 since they have no long-term positive return.

For the backtest, historical data is automatically downloaded from Yahoo, so no further price history files are necessary.

For trading, the broker API must support the GET_POSITION command, which is the case for the Interactive
Brokers TWS API. Make sure that you've subscribed market data for all traded assets (normally the "US Value Bundle")
54
and that you're permitted to trade them (check all relevant boxes on the "Trade Permissions" page). Since Z8 trades
only once per month, it needs not to run permanently, and requires no VPS and no permanent broker connection. It also
needs not be explicitely trained since this is automatically handled by the MVO process. Just start it in [Trade] mode
once every month, at the first trading day after the opening time of the New York market (9:30 ET). Z8 will first download
the current asset prices from Yahoo, then calculate the optimal portfolio. Set the Capital slider to the total margin you
want to invest. After about a minute a message box will open, like this:

"Old" is the current position in the asset, and "new" is the new position. Clicking [Yes] will automatically sell or buy the
difference of any position, and this way optimize the portfolio. Afterwards the trading session is closed. The procedure
should be repeated once per month. The maximum portfolio capital can be set up in Z.ini (see below); it is limited to
$7000 with the free Zorro version.

Setup & Trading

Every Z strategy can be configured at startup with parameters from an .ini file, and is controlled at runtime with
the Margin, Risk, and Panic sliders. First set up the strategy to your country and other requirements:
open Strategy\Z.ini with SED or any other text editor, and edit the following parameters:

Line Default Description

NFA = 0 No US Set this to 1 for for NFA compliance; required for US accounts (except for MT4
account. accounts) and for some brokers, f.i. IB.

FXOnly = 0 Trade all Set this to 1 for trading only currencies; often required for US accounts, as CFD trading
assets. is restricted in the US. This reduces the profits and increases the risk due to the
correlation of major currencies, so run a new test after modifying this line.

Phantom = 1 Equity Set this to 0 for disabling equity curve trading. When active, trades of Z1, Z2, Z3, or
curve Z12 components are temporarily suspended when the algorithm deteriorates, and
trading. resumed when it becomes profitable again. Equity curve trading can reduce the profit,
but can also prevent losses by expiration of strategy components.

Hedge = 5 Full virtual Set this to 2 for disabling virtual hedging. Required when your broker does not
hedging. support partially closing of trades, as is the case for some MT4 brokers.

MMax = 100 Margin Set this to the desired maximum value of the Margin or Capital sliders for trading with
0..100. higher volume (Zorro S only). The maximum of the Capital slider is MMax * 100.

Weekend = 2 Always Set this to 7 (see Weekend) for automatically logging off at weekends; set it to 3 for
online. staying online, but not trailing and stopping trades during the weekend.

Verbose = 2 Verbosity. Set this to 1 for fewer messages, and to 14 or 30 for diagnostics mode (see Verbose).

55
BrokerPatch Broker API Set this to 3 for displaying the strategy balance, equity, and open trade profits on the
=0 ok. Zorro panel (see SET_PATCH), rather than the account equity. Recommended for
some versions of the FXCM API that didn't calculate equity correctly.

Preload = 0 Load all Set this to 1 for loading the lookback period prices not from the broker's price server,
prices. but from Zorro's historical data files (see PRELOAD). Recommended for some MT4
brokers that offer only a very short price history. The history files from the Zorro
download page are required.

AssetList = Default Enter a different asset list (not for Z8) for simulating different accounts or for setting
"" account. up different broker symbols. All traded assets must be contained in the list.

Exclude = "" No Enter a list of comma separated asset names (f.i. "EUR/USD,NAS100" for excluding
excluded those assets in Z3 and Z7. The asset prices are still loaded, but the asset isnot traded.
assets.

Cancel = 0 Cancel no Set this to the trade ID when a position was closed externally and must not be
trade. managed anymore by Zorro. Zorro detects externally closed trades automatically, but
not when a trade is closed by opening a counter-trade on an NFA account. The
cancelled trade will be removed from the trade list at the end of the current bar. In the
case of virtual hedging, cancel the phantom trade only; this will also automatically
cancel the pool trade.

The .ini file is read at start of the strategy; subsequent changes have no effect until the strategy is restarted. If you want
to trade several of the strategies with different parameters, save Z.ini under the name of the strategy,
f.i. Strategy\Z4.ini for Z4. The strategy will then read the parameters from its corresponding .ini file.

At runtime, the Z strategies are normally controlled with three sliders:

Slider Range Default Description

Margin 0..100 50 Determines the average margin per trade, and thus the trade volume and the total
investment of the system (see Margin and MARGINLIMIT). The value set up here is
multiplied by OptimalF and internal weight factors, so individual trade margins can be
higher or lower than the amount set upo by the slider. If a trade margin is below the
minimum volume - usually $50 for mini lots, and $5 for micro lots - trades are entered
less frequently. In [Test] mode you can set up this slider for achieving the desired
annual return and required capital. For reinvesting your profits, you can increase the
slider when the capital grows, but observe the square root rule according to the formula
under Money Management.

Capital 0..7000 1000 Z8 only: Determines the total leveraged capital to be invested, i.e. the sum of the
maintenance margin of the open positions. While the portfolio composition changes
every month, the total invested margin remains constant and can be set up with this
slider. Keep this value well below the cash on the account.

Risk 0..50 10 Risk limit per trade in multiples of the Margin slider setting (see Risk). At the default
settings of 10 and 50, the risk limit is 10 * $50 = $500. The risk limit is the maximum
56
amount that the worst trade can lose, excluding slippage. If the risk of a trade would
exceed this limit, the trade size is accordingly reduced. This is not a hard limit; slippage
can increase the loss, and a trade with minimum size (1 lot) is still entered even if its
risk is above the limit.

Panic 0..100 0 Tighten the stop loss of all open trades in percent of the current price difference to the
original stop loss. At 0, stop loss control is completely up to Zorro; at 100 the stop limits
are placed just at the current asset prices. This causes trades to be sold as soon as
their prices move slightly in adverse direction, and can be used for taking profits early.
No new trades are opened as long as the position is above 90. This slider reduces the
profit, so use it only in a panic situation, for instance when you hear in the news about
a stock market crash. Put it back to 0 when you feel calm again.

Note that the Margin and Risk sliders are not the same as the Margin and Risk variables. The Margin slider
determines the average margin per trade only; the individual margin of a trade can be higher or lower, dependent on its
capital allocation by the OptimalF factor of the component. The value of the Risk slider is multiplied with the value of
the Margin slider for getting the risk limit per trade.

Before trading, make sure you've read the suggestions under Regular Income. Determine your capital requirement by
testing the strategies with different settings of the Margin slider. Check the performance report and set aside enough
capital for trading, either in your broker account or at least in your bank account.

After starting a strategy, the first trades will be entered after about 1-2 days. The frequency of trades depends on the
market; in unprofitable periods with high efficiency there will be only a few trades, especially when Phantom is set in
the Z.ini file. Most trades are hold for 2-3 days, but some can stay open for weeks when they are profitable.

Your account balance will most likely go down by several $100 in the first weeks after starting a strategy. This is not a
real drawdown, but the result of two statistical effects. Unprofitable trades are closed early, while profitable trades stay
open longer and thus contribute later to the balance. The equity fluctuations - the up and down 'ripples' in the equity
curve - will most likely exceed your initial profit if you don't have an exceptionally lucky start. Both effects will cause
periods of negative balance and equity in the first time of almost all strategies (for details see income). When the profit
accumulates, negative balance periods get shorter and eventually disappear. However if you're not yet in the profit zone
after several months and your trade returns still deviate largely from the predicted performance, something is wrong.
The strategy might be expired and you should stop trading it.

Events - such as entering and exiting trades, or trailing the stop loss - are displayed in Zorro's message window. The
strategies use trade management functions and money management, and adapt the stop limits permanently to the price
development of the asset. Additionally to the standard messages (see Trading), Z1, Z2, and Z12 inform from time to
time when the risk or possible profit of open trades change. They are displayed this way in Zorro's message window:

[AUD/USD:CY:S4400] Estimated risk: 150


[USD/JPY:CT:S4321] Estimated profit: 720

The estimated risk is the maximum loss, the estimated profit is the minimum win that Zorro currently predicts for the
trade, in units of your account currency. When the risk goes down or the profit goes up, the position is moving in
favourable direction. Note that it's only an estimate, so the real loss as well as the real win can be worse.

Tips & Tricks

Stopping & Starting: At trading start, the systems download recent price data from the broker for filling
the LookBack period. Therefore avoid starting the strategies during the weekend when recent prices are not available
and broker servers can be offline. Otherwise gaps in the price data can cause reduced performance during the following
week. Once a system is trading, stop it only in case of an emergency. Most systems do not enter trades immediately
after being started, so frequent stopping and starting will cause a system not to trade. While a system is stopped, it can
not react on the market, which bears the danger of a potential loss when a price shock happens during that time.

Testing & Re-Training: The strategies can be tested with historical price data (you can download the price data from
the Zorro download page). The meaning of the performance parameters can be found under performance report.
The most recent parameters are used for trading. The strategies must be re-trained regularly for keeping them profitable;
in out-of-sample backtests they started to deteriorate about 4..12 months after the last training. New parameter sets will

57
be released with Zorro updates, so dou don't need to train them yourself. But if you want, you can do that with Zorro S
by clicking the [Train] button, even while live trading. The factors file (Z12.fac) of Z12 is not re-trained since it changes
very slowly and needs the full simulation period. A new factors file is provided with any Zorro update.

Invest & Withdraw: All profitable strategies have long drawdowns that are listed in the table above (Worst Drawdown).
Take care to remargin your account when you're in a drawdown and your equity gets dangerously low. On the other
hand, if you don't have Zorro S, remove profits regularly from your account - the free Zorro version will stop trading when
your balance exceeds 7000 $ or the equivalent in your account currency (see profit ceiling). Look under Money
Management for reinvesting and withdrawing correctly.

Low Capital: You can trade strategies with low capital, such as a few hundred dollars, when you remove assets as
described below. Additionally, move the Margin slider to the left and run repeated tests until the value displayed
under Capital meets your needs. Note that a low margin and few assets will normally reduce the performance and
increase the risk due to the reduced number of trades.

Remove Assets: For excluding all assets except currencies, set FXOnly (see above). For excluding a certain asset or
component from Z1, Z2, or Z12, edit the Z12.fac factors file (Data\Z12.fac) and place minus signs in front of
its OptimalF factors (for details see Money Management). The algorithms with the ES, HU, LP, LS, VO identifiers
belong to Z1, BB, CT, CY, HP to Z2. An example for excluding the CT algorithm with the USD/CAD asset:

...
USD/CAD:CT -.041 1.32 263/420
USD/CAD:CT:L -.029 1.25 177/227
USD/CAD:CT:S -.058 1.38 86/93
...

Components with a negative or .000 OptimalF factor are automatically excluded. Be aware that excluding the least
profitable assets or algorithms will generate a too optimistic test result due to selection bias.

Rename assets: Edit History\AssetsFix.csv and enter the asset name used by the broker in the last column of the
spreadsheet, as described under asset list.

Multiple Zorros: You'll need Zorro S for trading all Z systems at the same time. Also make sure with your broker that
your account is set up to allow multiple sessions. Some brokers set up accounts for single sessions only by default.

Tax: In most countries you have to pay tax for trading profits. Normally the broker keeps records of your trades that you
can download from his website. Otherwise Zorro also records all trades in the trades.csv spreadsheet in the Data folder
for your tax declaration. Interest (rollover) and profit/loss are recorded separately. Usually you have to submit this
spreadsheet or the broker's records together with your tax declaration.

Updating: We'll publish updates to the Z strategies in regular intervals, so it's recommended to visit the user forum or
the Zorro website from time to time. Subscribers and Zorro S users will also be informed by email about updates.
Please look here for how to move from an old Z strategy to a new version.

Expiration: All strategies can eventually expire; for instance the Z5 system expired when the Swiss removed the CHF
cap. If we suspect that a certain system will expire, we'll announce that on the forum and will also inform subscribers
and Zorro S users by email. Normally we'll provide a successor after a system expired. As long as the markets are not
perfectly effective, there will always be infinite possibilities for profitable trade algorithms.

58
Zorro and the brokers
Zorro needs a broker for receiving online price data and buying or selling assets. Most brokers offer free demo accounts
(also called practice, paper, or game accounts) where trading can be tested without risking real money. A demo account
with a broker can usually be opened in 5 minutes on their website. In most cases you'll get an MT4 account, and can
start trading with Zorro's MT4 bridge. A real account for trading with real money takes longer, as the broker has to
confirm your identity through some ID verification process.

Selecting a broker and account

Every broker has a different trade model and different values for spread, commission, rollover, or slippage. Many brokers
offer the choice between several account types. Select the account with the smallest lot size and the highest leverage.
Maybe you've read in a trading book to avoid high leverage as it implies "high risk". That's nonsense. High leverage just
means that you're free to determine your own leverage. And the higher the leverage, the smaller the required margin
and the greater the distance to the dreaded margin call. When trading the same number of contracts with the same
budget, a low leverage account is always more likely to be wiped out than a high leverage account. Small lot sizes are
preferable as you can trade with less capital and can better adjust the trade volume. For low-budget trade strategies, a
micro lot or nano lot account is mandatory.

Here are the criteria for selecting the best account for Forex trading:

• Initial capital. Although you can immediately withdraw 90% of your capital once the account is opened, it is preferable
to select a broker with small initial capital requirement.
• Lot size. The smaller, the better. Almost all brokers offer micro lot accounts, where 1 lot is equivalent to 1000 Forex
contracts. Some offer nano lot accounts (1 lot = 100 contracts), and a few even offer a lot size of 1 contract.
• Minimum order volume. The smaller, the better. Most brokers have a minimum volume of 1 lot, but some require 10
lots or more for placing an order.
• Leverage. The higher, the better (see above). Accounts in the US have normally lower leverage than in the rest of the
world. Most brokers offer about 100:1 for Forex, and 4:1 for stocks.
• Dealing desk (DD). That normally means you trade against the broker, not against the market, so your broker wants
you to lose. Brokers with no dealing desk have no such conflict of interest and just transfer your orders to liquidity
providers. If possible, always select NDD accounts over DD accounts. Often DD accounts do not allow to trade
automated anyway.
• Spread. The smaller, the better. Brokers with a dealing desk normally offer lower spreads than brokers with no dealing
desk - but often the disadvantages of DD outweigh the advantages of smaller spreads.
• Commission. Obviously, low commission is better, as is a simple commission structure compared to a complex one.
For comparing costs, a formula for commission -> spread conversion can be found under Commission.

Connecting Zorro to a broker

Zorro is designed to directly work with all brokers that fulfill at least one of the following requirements:

• A trading API - that's a software library with direct access to the broker's price and trading servers. The free Zorro
version comes with trading API modules for Forex Capital Markets LLC (FXCM) and Oanda. More API modules are
available with Zorro S. Any other broker API can be relatively easily implemented by any user with programming
knowledge through a Broker Plugin DLL.
• Metatrader4™ (MT4) platform support. With the MT4 Bridge, Zorro can run as an Expert Advisor (EA) in MT4 and
thus trade with any broker that supports this popular trading platform. This way you have several thousand brokers to
choose from.
• A web based trading platform, or any trading program where trades can be placed with key strokes or mouse clicks.
Zorro can control other programs by sending key or mouse commands to their user interface. In this mode, Zorro can
retrieve the current price data either through the FXCM API module from an FXCM demo account, or through the MT4
bridge. Use the order function to send keys to the web based trading platform, or pop up a message when a trade
should be opened or closed.

Simulating a certain broker and account

In backtests, Zorro can simulate any broker through asset specific parameter sets that are automatically updated from
the broker API, or can alternatively be set up by script. Broker and account specific parameters, such as rollover fee,
spread, lot size, pip cost, etc. are loaded from the History\AssetsFix.csv file. This file can be edited with a text editor
for simulating different accounts and assets in the backtest. For details see Data Import.
59
Manually trading with Zorro

If your broker is not yet supported by any of the three methods above, you can trade manually by using Zorro as a signal
provider. For this, run your strategy on a FXCM demo account, and enter or exit a trade with your broker when you see
(or hear) that Zorro opens or closes a similar trade in its message window. If your strategy has not a too-short time
frame, it won't matter when there are a few minutes delay between Zorro's and your trade entry. Zorro plays the sound
file trade.wav each time when it enters a trade, and win.wav / loss.wav each time when it closes it. Those files are
located in the Zorro main folder, and you can replace them with an alarm sound if you want to get alerted every time
for entering a trade.

Please be aware that the trading restrictions also apply when you use Zorro not directly for trading, but only for
displaying trade signals.

Running Zorro on a VPS

It has several advantages to run Zorro not at home, but on a virtual private server (VPS) hosted by a commercial service
provider. You'll have a fast and reliable online connection to the broker, regardless how bad your internet is at home,
and can access Zorro from anywhere. VPS are available from many providers for a small fee, such as $20 to $30 per
month. It's recommended to select a large and reliable company, like 1&1 or Amazon, for hosting a trading VPS. Amazon
offers its EC2™ servers even free during the first year. The setup is not totally trivial to a beginner, so we're offering
a VPS installation service on the download page. If you want to do it yourself, here are the basic steps for setting up
an Amazon VPS:

• Order a cloud server with Windows Server 2012 OS. You'll usually receive a server IP, a login name (normally
"Administrator"), and a password from your provider. With Amazon you have to generate a public key pair for getting
the password. You'll be walked through the procedure.
• Launch the server from your provider's website. When it's running, connect to the server. Under Windows, use the
Remote Desktop Connection (under Accessories). Enter your access data, and a window will open where you can
see the VPS desktop. It normally looks like a very empty Windows desktop. Alternatively to the Windows Remote
Desktop Connection, you can install TeamViewer™ on the VPS - it's free for private use and more convenient to
handle, especially for uploading or downloading files. The Windows Remote Desktop, as well as TeamViewer are also
available for iPhone and Android, so you can control your VPS from anywhere.
• You can now install Zorro on the VPS. We recommend not to run the installation program, but to copy the Zorro folder
directly - it's a lot faster. Start the Windows Explorer both on your PC and on the VPS. On your PC, open
your User folder (or the Program Files folder if you have Zorro installed there), right click on the Zorro folder, and
select Copy. On the VPS, open a folder of your choice - we recommend User/Administrator - and right click on an
empty space. Select Paste. Your Zorro folder will now be copied over to the VPS.
• You can now start Zorro on the VPS, select your broker, enter your user name and password, and begin trading.
Dependent on number of CPU cores and RAM on your VPS, you can trade with several Zorro instances
simultaneously. Note that testing large portfolio strategies - such as Z12 - will not work on cheap VPS with little RAM.
Trading uses minimal memory and will work in any case.

Make sure that the time and the time zone of the VPS is set up correctly. It does not matter which time zone - you can
either use your PC's time zone, or the VPS time zone, or any other time zone - but the VPS time must be correct and
match the zone. If it is wrong, Zorro will display a wrong UTC time in the server window, and strategies based on market
open and close times - such as gap trading - won't work anymore. If in doubt, set the VPS to the time and time zone of
your PC at home.

Running multiple Zorro instances

The free Zorro version can trade with one broker and one account only. Zorro S allows to trade several Zorro instances
simultaneously with different scripts, brokers, and accounts (for details about setting up the account list, see Data
Import). How many Zorro instances can run at the same time depends on the broker connection, the Internet bandwidth,
and the PC resources (speed and memory). As an example, a Amazon EC2 Micro Instance - the lowest and cheapest
one - can support up to 4 Zorro instances with a normal broker connection, or up to 2 with a MT4 connection.

Observing your VPS trade status

It is recommended that you regularly observe Zorro's trade list, profit status, and messages on your PC or smartphone.
For this let Zorro display the trade status and the message window on an Internet page. Here's the instruction for
Amazon EC2 with Windows Server 2012:

60
• Open your VPS desktop with Windows Remote Desktop or TeamViewer, and use the Server Manager to add a new
role. From the selection of roles, select "Web Server (IIS)". Windows will walk you through the setup - the default
settings will do - and install the server.
• In the Amazon EC2 dashboard, select the "security group" of your instance and add a new inbound rule. Select
"HTTP" with port 80. This will make your website visible to the public.
• In the zorro.ini file, edit WebFolder and set it to the web folder of your server. The default web folder of a Windows
server is normally C:\inetpub\wwwroot.
• You can now visit Zorro's status pages through the public IP address of your server - for instance, by
entering 98.765.43.12\Z12.htm in your browser's address field. Note that anyone else who knows this address can
observe your trade success too, so you might want to add an authentication feature to the IIS web server.

Running Zorro as a trade signal provider

This is for Zorro S users only, as the free Zorro license does not permit providing trade signals. Some trade copy
services such as ZuluTrade™ offer a free VPS for their signal providers. The VPS is already configured, completely
with MT4, but it's a little tricky to copy Zorro onto it. Here's the procedure for ZuluTrade, step by step:

• Open a MT4 demo account with a broker of your choice (f.i. AAAFx, Zulutrade's own broker). When starting MT4, you'll
get a form for adding an account; write down the login number and the password that you'll see in the MT4 welcome
message. You can deinstall MT4 afterwards, as you only needed the login data. The MT4 demo account will not expire
unless the broker goes broke or the account is inactive for 15 days.
• Register as a trader on ZuluTrade. On the MT4 link settings, give the login number and password from the previous
step. Zulutrade will now link your trader account to the MT4 version. If that was successful, you'll get a button under
your [Settings] tab for connecting to your VPS. Note that sometimes you have to wait a couple of weeks until a free
VPS slot is available and the button appears.
• Click the [Connect] button to open the VPS. The VPS desktop will appear in your browser. Compress a copy of your
Zorro folder to a single zip archive; strip it before from files not needed for trading, such as the content of the Log folder,
or the price data files in the History folder. A stripped down Zorro archive has only a size of about 10 MB. Upload the
zip file to the VPS. With Zulutrade, you can do this with the upload manager: click on the half-transparent round icon
at the top of the VPS desktop, then click [Select Files] and navigate to the Zorro zip file on your PC.
• Once the archive is uploaded, open the folder on the VPS that receives uploaded files - on the ZuluTrade VPS it's
the ZuluHDD folder. You can now open the archive and drag the Zorro folder inside over to the MT4 Experts folder.
Zorro can run from anywhere, so it's no problem to have it in a MT4 subfolder.
• Now install the MT4 bridge on the VPS. You can now start MT4 and Zorro on the VPS, select your strategy, click
[Trade] and begin generating trade signals.

When you provide signals, please play fair in the interest of your followers. Do not use martingale or similar methods,
even though this might attract more followers at first. In the end, only the very few signal providers that survive more
than one or two years will earn long-term trust and profits.

61
Broker / Feed Plugin
Zorro already supports most major brokers either through a direct connection or via the MT4 bridge. But for connecting
to a broker API or a data feed that's not supported yet, you can write a DLL and put it in Zorro's Plugin folder. Zorro
automatically scans that folder at startup, and lists all broker DLLs in the [Broker / Account] scrollbox. The DLL uses
the broker's API for placing orders and getting price information. The interface is organized to keep the DLL as simple
as possible; only 9 functions are required for automated trading. If you know programming, you can write a broker DLL
in a few days. It can be written in any language that supports DLLs.

In the following you'll find instructions about how to set up Microsoft VC++ to write a broker DLL for the Zorro broker API
interface. The C++ source code of the FXCM Order2Go™ plugin is included in the Zorro distribution and can be found
in the Source folder. You can look into it to see how a broker plugin is written, and use it as a 'template' for your own
broker DLL.

Setting up VC++

When you create a new project (File->New Project), VC++ offers you a choice of project templates. Select Win32
Project. When the Win32 Application Wizard pops up, select DLL in the application settings, then click [Finish]. VC++
now creates a new DLL project for you, with a main cpp file that looks like this:

// broker.cpp : Defines the entry point for the DLL application.

#include "stdafx.h"

BOOL APIENTRY DllMain(


HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
return TRUE;
}

That's the main entry point of the broker DLL, and you can leave that function unchanged. The broker functions require
the DATE and TICK data types, thus you need to define DATE and include the trading.h header, like this:

typedef double DATE;


#include "..\Zorro\include\trading.h"

If you want to distribute your broker DLL to other people, we suggest that you open Properties / C/C++ / Code
Generation, and change the Runtime Library setting from Multi-threaded DLL (/MD) to Multi-threaded (/MT).
Otherwise your users won't be able to use the DLL without installing the VC++ Runtime Redistributable before - one of
the funny ideas by Microsoft to make life more interesting.

Developing a broker plugin - the TradeTest script

Implement the DLL functions in this order: BrokerOpen, BrokerLogin, BrokerTime, BrokerAsset, BrokerHistory,
BrokerAccount, BrokerOpen, BrokerBuy, BrokerTrade, BrokerSell, BrokerStop (functions are described below).
Test any function after implementation f.i. with the TradeTest script. Some of the functions are optional and need not
be fully implemented, or not at all. The minimum functions for TradeTest to run are BrokerOpen, BrokerLogin,
and BrokerTime. If a function, f.i. BrokerAsset, is not yet available in the DLL, it is simulated with default values. So
you can implement them step by step.

As soon as the BrokerAsset function is implemented, you should see the current price in the Server window.
The TradeTest script opens a panel with the following buttons for testing various broker functions:

[Auto On/Off] - Toggle button for a simulated trade session that automatically opens or closes a trade every minute.

[NFA On/Off] - Toggle the NFA flag. Required for most US accounts; must not be set for most other accounts.

[Hedge] - Toggle between Hedge modes 0, 2, 4,and 5. Some brokers do not support full hedging (mode 2) or partial
closing (mode 5).

[Buy Long] - Open a long position with the Lots and Stop value set up with the sliders.
62
[Buy Short] - Open a short position. Dependent on Hedge, close any open position in opposite direction.

[Close Long] - Close the given number of lots from an open long position. Partial closing is not supported by some
brokers.

[Close Long] - Close the given number of lots from an open short position.

[Update Stop] - Sets the Stop of all open positions to the value (in Pips) set up with the slider. Stop adjustment is not
supported by some brokers. Due to StopFactor, the broker stop is more distant than the real stop.

Broker API functions


The broker DLL exports functions that are described in the following list. With VC++, exported DLL functions must be
either declared with the extern "C" __declspec(dllexport) attribute, or listed in a .def file. The DLL functions use only
a small subset of a usual broker API. In the following list, pointer arguments printed in italic can be NULL; if they are
nonzero, the function must fill them with the required data. All data is mandatory if not mentioned otherwise.

BrokerOpen (char* Name, FARPROC fpError, FARPROC fpProgress) : int


Called at startup for all broker DLLs found in the Plugin folder. Retrieves the name of the broker, and sets up two
callback functions. Should not allocate or load any resources - this should be done in the BrokerLogin function.

Parameters:
Name Output, char[32] array to be filled with the name of the broker, f.i. "FXCM". The name appears in
the Account scrollbox.

fpError Input, pointer to a int BrokerError(char* message) function, to be called for printing broker messages
(usually error messages) in Zorro's message window. Calling this function is not mandatory. If the
message string begins with an exclamation mark '!', Zorro opens an alert box for notifying the user that
his attention might be required. If it begins with a hash '#', it is printed into the diagnostics file only.

fpProgress Input, pointer to a int BrokerProgress(DWORD progress) function, to be called repeatedly when
broker operations take longer than a second, f.i. BrokerLogin or BrokerHistory. When
the progress parameter is 0, Zorro will trigger a cycle for requesting a price quote. When it is > 0, dots
are printed in the message window for indicating the progress of the operation. When progress is a
pointer and a callback function exists in the script, it is called and the pointer is passed for triggering
script functions from the broker API. Calling this function is not mandatory, but when it is called during
a broker operation and returns 0, the operation must be aborted.

Returns:
Broker interface version number; currently 2.

BrokerHTTP (FARPROC fpSend, FARPROC fpStatus, FARPROC fpResult, FARPROC fpFree)


Optional function that is called when the broker API requires HTTP access to a URL, f.i. for REST and FIX APIs. Sets
up 4 functions for exchanging data through a URL, so the plugin needs not to implement own http/https libraries.

Parameters:
fpSend, fpStatus, fpResult, Input, pointers to the http_send, http_status, http_result and http_free
fpFree functions.

63
BrokerLogin (char* User, char* Pwd, char* Type, char* Accounts): int
Login or logout to the broker's API server; called in [Trade] mode or for downloading historical price data. If the
connection to the server was lost, f.i. due to to Internet problems or server weekend maintenance, Zorro calls this
function repeatedly in regular intervals until it is logged in again. Make sure that the function internally detects the login
state and returns safely when the user was still logged in.

Parameters:
User Input, User name for logging in, or NULL for logging out.

Pwd Input, Password for logging in.

Type Input, account type for logging in; either "Real" or "Demo".

Accounts Optional output, char[1024] array to be filled with all user's account numbers as subsequent zero-
terminated strings, ending with "" for the last string. Only the first account number is used by Zorro.

Returns:
Login state: 1 when logged in, 0 otherwise.

BrokerTime (DATE *pTimeUTC): int


Returns connection status and (optionally) the server time. Repeatedly called during the trading session.

Parameters:
pTimeUTC Optional output, current server time in UTC / GMT+0 with no daylight saving. The DATE format (OLE
date/time) is a double float value, counting days since midnight 30 December 1899, while hours,
minutes, and seconds are represented as fractional days.

Returns:
0 when the connection to the server was lost, and a new login is required.
1 when the connection is ok, but the market is closed or trade orders are not accepted.
2 when the connection is ok and the market is open for trading at least one of the subscribed assets.

Remarks:

• If the UTC server time is not available in the broker API, *pTimeUTC can be left unchanged. If the market state is not
available, let the function just return 2 or 0 dependent on whether the connection is established or not.
• If the server time is returned, but does not change for several minutes, Zorro assumes that the broker server is offline.
It then displays "Offline" in the server window, and suspends trading and requesting price quotes.
• If the broker API uses a different time format, here's a C++ code example for converting DATE to/from the Linux time
format, which is the number of seconds since January 1st 1970 midnight:

DATE convertTime(__time32_t t32)


{
return (double)t32/(24.*60.*60.) + 25569.; // 25569. = DATE(1.1.1970 00:00)
}

__time32_t convertTime(DATE date)


{
return (__time32_t)((date - 25569.)*24.*60.*60.);
}

64
BrokerAsset (char* Asset, double *pPrice, double *pSpread, double *pVolume, double *pPip, double
*pPipCost, double *pLotAmount, double *pMarginCost, double *pRollLong, double *pRollShort): int
Subscribes an asset, or returns information about it. Zorro subscribes all assets at the begin of the trading session.
Price and spread are retrieved when the strategy needs them or when BrokerProgress was called; other asset
information is retrieved in regular intervals.

Parameters:
Asset Input, name of the symbol, f.i. "EUR/USD" or "NAS100". Some broker APIs, such as MT4 or IB,
don't accept a "/" slash in an asset name; either a different symbol in the asset list must be defined,
or the plugin must remove or replace the slash before sending the request to the API.

pPrice Optional output, current ask price of the asset, or NULL for subscribing the asset. An asset must be
subscribed before any information about it can be retrieved.

pSpread Optional output, the current difference of ask and bid price of the asset.

pVolume Optional output, recent trade volume of the asset per minute, or 0 when the volume is unavailable. If
no volume is returned in this function, Zorro retrieves it with a BrokerHistory2 call.

pPip Optional output, size of 1 PIP, f.i. 0.0001 for EUR/USD.

pPipCost Optional output, cost of 1 PIP profit or loss per lot, in units of the account currency. If not directly
supported, calculate it as decribed under asset list.

pLotAmount Optional output, minimum order size, i.e. number of contracts for 1 lot of the asset. For currencies
it's usually 10000 with mini accounts and 1000 with micro accounts. For CFDs it's usually 1, but can
also be a fraction of a contract, f.i. 0.1.

pMarginCost Optional output, required margin for buying 1 lot of the asset in units of the account currency.
Determines the leverage. If not directly supported, calculate it as decribed under asset list.

pRollLong Optional output, rollover fee for long trades, i.e. interest that is added to or subtracted from the
account for holding positions overnight. The returned value is the daily fee per 10,000 contracts for
currencies, and per contract for all other assets, in units of the account currency.

pRollShort Optional output, rollover fee for short trades.

Returns:
1 when the asset is available and the returned data is valid, 0 otherwise. An asset that still returns 0 after subscription
will not be traded.

Remarks:

• If any of the parameters is not available by the broker API, it can be left unchanged. Zorro will then use its default value
from the asset list. Only price and spread must always be returned when the pPrice and pSpread parameters are
nonzero.
• Dependent on the broker API, some asset parameters might require unit conversions because lots and pips can have
special meanings. In most APIs, such as the FXCM API, the parameters are directly available. A more complicated
example is the MT4™ API where the parameters must be corrected by the different scales of "Lot" and "Point" (MQ4
example):

double Price = MarketInfo(Asset,MODE_ASK);


double Spread = Price - MarketInfo(Asset,MODE_BID);
double Volume = 0;
double LotFactor = MarketInfo(Asset,MODE_MINLOT); // correction for different lot scale
double Pip = MarketInfo(Asset,MODE_POINT);
double PipCost = MarketInfo(Asset,MODE_TICKVALUE) * LotFactor;
int DigitSize = MarketInfo(Asset,MODE_DIGITS); // correction for brokers with 5 digits
if(DigitSize == 3 || DigitSize == 5) {

65
Pip *= 10.;
PipCost *= 10.;
}
double MinAmount = MarketInfo(Asset,MODE_LOTSIZE) * LotFactor;
double Margin = MarketInfo(Asset,MODE_MARGINREQUIRED) * LotFactor;
double RollLong = MarketInfo(Asset,MODE_SWAPLONG);
double RollShort = MarketInfo(Asset,MODE_SWAPSHORT);
if(MarketInfo(Asset,MODE_SWAPTYPE) == 0.) {
RollLong *= PipCost;
RollShort *= PipCost;
}

• Some brokers charge a three times higher rollover fee on Wednesday for compensating the weekend. This must be
taken into account when downloading asset parameters on a Wednesday.
• If the broker API can not subscribe an asset, it must be manually subscribed in the broker platform software.

BrokerHistory2 (char* Asset, DATE tStart, DATE tEnd, int nTickMinutes, int nTicks, T6* ticks): int
Returns the price history of an asset. Called by Zorro's assetHistory function and at the begin of a trading session for
filling the lookback period.

Parameters:
Asset Input, symbol from the asset list, f.i. "EUR/USD". Some broker APIs, such as MT4 or IB, don't
accept a "/" slash in an asset name; the plugin must remove or replace the slash in that case before
sending the request to the API.

tStart Input, UTC start date/time of the price history (see BrokerTime about the DATE format). This has
only the meaning of a seek-no-further date; the relevant date for the begin of the history is tEnd.

tEnd Input, UTC end date/time of the price history. If the price history is not available in UTC time, but in
the brokers's local time, the plugin must convert it to UTC.

nTickMinutes Input, time period of a tick in minutes. Usual values are 0 for single price ticks (T1 data;
optional), 1 for one-minute (M1) historical data, or a larger value for more quickly filling
the LookBack period before starting a strategy.

nTicks Input, maximum number of ticks to be filled; guaranteed to be 300 or less.

ticks Output, array of up to 300 T6 structs (defined in include\trading.h) to be filled with the ask price
history and additional optional data if available, such as historical spread and volume.
The ticks array is filled in reverse order from tEnd on until either the tick time reaches tStart or the
number of ticks reaches nTicks, whichever happens first. The most recent tick, closest to tEnd, is
at the start of the array. In the case of T1 data, or when only a single price is available, all prices in
the TICK struct can be set to the same value.

Returns:
Number of ticks returned, or 0 when no ticks could be returned, f.i. when the server was offline, the asset was not
subscribed, or price history was not available for the given date/time.

BrokerAccount (char* Account, double *pBalance, double *pTradeVal, double *pMarginVal): int
Optional function. Returns the current account status, or changes the account if multiple accounts are supported.
Called repeatedly during the trading session. If the BrokerAccount function is not provided, f.i. when using a FIX API,
Zorro calculates balance, equity, and total margin itself.

Parameters:
Account Input, new account number or NULL for using the current account.

pBalance Optional output, current balance on the account.

66
pTradeVal Optional output, current value of all open trades; the difference between account equity and balance.
If not available, it can be replaced by a Zorro estimate with the SET_PATCH broker command.

pMarginVal Optional output, current total margin bound by all open trades.

Returns:
1 when the account is available and the returned data is valid, 0 when a wrong account was given or the account was
not found.

BrokerBuy (char* Asset, int nAmount, double dStopDist, double *pPrice): int
Enters a long or short trade at market. Also used for NFA compliant accounts to close a trade by opening a position
in the opposite direction.

Parameters:
Asset Input, name of the asset, f.i. "EUR/USD". Some broker APIs don't accept a "/" slash in an asset name;
the plugin must remove the slash in that case.

nAmount Input, number of contracts, positive for a long trade and negative for a short trade. The number of
contracts is the number of lots multiplied with the LotAmount. If LotAmount is < 1 (f.i. for a CFD with
0.1 contracts lost size), the number of lots is given here instead of the number of contracts.

dStopDist Input, 'safety net' stop loss distance to the opening price, or 0 for no stop. This is not the real stop loss,
which is handled by the trade engine. Placing the stop is not mandatory. NFA compliant orders do not
support a stop loss; in that case dStopDist is 0 for opening a trade and -1 for closing a trade by opening
a position in opposite direction.

pPrice Optional output, the current asset price at which the trade was opened.

Returns:
Trade ID number when opening a trade, or 1 when buying in opposite direction for closing a trade the NFA compliant
way, or 0 when the trade could not be opened or closed. If the broker API does not deliver a trade ID number (for
instance with NFA brokers that do not store individual trades), the plugin can just return an arbitrary unique number f.i.
from a counter.

BrokerTrade (int nTradeID, double *pOpen, double *pClose, double *pRoll, double *pProfit): int
Optional function. Returns the status of an open or recently closed trade. Can be omitted when the API does not store
individual trades.

Parameters:
nTradeID Input, trade ID number as returned by BrokerBuy.

pOpen Optional output, enter price of the asset including spread. Not available for NFA compliant accounts.

pClose Optional output, current price of the asset including spread.

pRoll Optional output, total rollover fee (swap fee) of the trade so far. Not available for NFA
compliant accounts.

pProfit Optional output, profit or loss of the trade so far. Not available for NFA compliant accounts. Possible
wrong values due to API bugs can be replaced by Zorro estimates with the SET_PATCH broker
command.

Returns:
67
Number of contracts of the given trade ID number, or 0 when no trade with this ID could be found, or a negative
number when the trade was recently closed. When the returned value is nonzero, the output pointers must be filled.

BrokerStop (int nTradeID, double dStop): int


Optional function. Adjusts the stop loss of an open trade if it had an original stop (dStopDist != 0). If this function is
not provided, the original stop loss is never updated. Only for not NFA compliant accounts.

Parameters:
nTradeID Input, trade ID number as returned by BrokerBuy.

dStop The new stop loss price. Must be by a sufficient distace (broker dependent) below the current price for a
long trade, and above the current price for a short trade.

Returns:
0 when no open trade with this ID could be found, otherwise nonzero.

BrokerSell (int nTradeID, int nAmount): int


Optional function; closes a trade - completely or partially - at market. If partial closing is not supported by the broker,
the trade is completely closed. Only for not NFA compliant accounts.

Parameters:
nTradeID Input, trade ID as returned by BrokerBuy.

nAmount Input, number of contracts resp. lots to be closed, positive for a long trade and negative for a short trade
(see BrokerBuy). If less than the original size of the trade, the trade is partially closed.

Returns:
Trade ID number of the remaining 'reduced' trade when it was partially closed, original trade ID number when it was
completely closed, or 0 when the trade could not be closed.

BrokerCommand (int nCommand, DWORD dwParameter): var


Optional function. Sets various plugin parameters or returns asset specific extra data. This function is not mandatory,
as it is not used by Zorro's trade engine; but it can be called in scripts through the brokerCommand function for
special purposes.

Parameters:
nCommand Input, command from the brokerCommand list.

dwParameter Input, parameter or data to the command.

Returns:
0 when the command is not supported by this broker plugin, otherwise the data to be retrieved.

Remarks:

• A broker DLL must contain at least the BrokerOpen function for being recognized by Zorro.

68
• The broker DLL may contain other broker-specific functions that are not directly used by Zorro, but can be called from
strategy scripts using the DefineApi function.
• For passing struct pointers via BrokerProgress from the API to the Zorro script, make sure that struct member
alignment is set to 1 byte. Lite-C has no struct member alignment.

Example: see source\FXCMplugin.cpp

69
IB Bridge / TWS Bridge
The Interactive Brokers™ bridge allows direct trading with IB, either through IB's Trader Workstation (TWS), or with
the IB Gateway. IB's main advantages are access to many exchanges, a huge number of supported assets, and
relatively low trading costs. On the downside, their API is functionally limited in some ways.

For long-term automated trading the IB Gateway is preferable, since the TWS, a bloated java program, breaks down
once per day and interrupts the connection. After starting the Gateway, select IB API (not FIX CTCI).
Open Configuration/API/Settings and make sure that the socket port is set to 4002.

For trading with the TWS, open File/Global Configuration/API/Settings (see screenshot below) and select Enable
ActiveX and Socket Clients. Make sure that the socket port is set to 7496.

Deselect Read-Only API when you want to send orders. Up to 8 Zorros can connect to the same Gateway or to the
same TWS. Enter an individual number in Zorro's User field. Numbers 1..8 connect to the Gateway via socket port 4002,
numbers 101..108 connect to the TWS via socket port 7496. The Password field can be left empty.

Trading with IB

The included plugin supports IB paper trading accounts that begin with the letter "D". A plugin for all accounts is included
in Zorro S or available for subscription on the Zorro download page. For opening an account with IB, the normal choice
is a RegT Margin account. After opening the account, make sure to withdraw enough from your initial deposit as to not
exceed the account size limit of the free Zorro version. If you own Zorro S, consider a Portfolio Margin account with
its lower margin costs. IB paper trading accounts are only available after opening a real account.

Due to the high margin and lot size requirements, most Z systems are not really suited for IB, as they would require a
large Margin setting for not skipping too many trades, and even then achieve only a fraction of the annual return due to
the low leverage. Only exception is Z8 that is especially tailored for IB. The main advantage of IB is that most public
financial assets can be traded. This allows many new trade systems that exploit specific inefficiencies, f.i. volatility, or
seasonal effects of particular stocks or treasuries.

Historical price data for backtests can not be downloaded from IB, but they are available from many other Internet
sources, f.i. from Yahoo™ or Quandl™ by using the Download script or the assetHistory or dataDownload function.

70
For trading an asset, some prerequisites must be fulfilled:

• Subscribe market data on the IB website; otherwise you'll get a "market data not subscribed" error message at
session start. Forex market data is free, most other data requires a monthly fee. For subscribing market data, enter
your IB account management page, and select Trade Configuration / Market Data. For the usual US stocks and
ETFs, subscribe the "U.S. Securities Snapshot and Futures Value Bundle" and the "US Equity and Options Add-On
Streaming Bundle". For demo trading, activate Share Market Subscription for sharing your subscriptions with the
paper trading account.
• Obtain trading permission from IB for all asset types you want to trade, under Trade Configuration /
Permissions (see image below). You must fill in a form about your trading experience and your financial situation. It
takes about 24 hours to get approval for trading all asset types. Penny stocks require a special approval.
• Remove trading restrictions. Open File / Global Configuration / API / Precautions / Bypass Order Precautions for
API Orders and enable Suppress market cap check. Otherwise orders of more than 5 contracts will not be executed
and you'll get an "over limit" error message. Alternatively, you can increase order precaution limits individually per
asset type.
• Make sure that all traded assets are listed in the TWS Monitor and their prices are visible before connecting to IB (even
when using the Gateway). A system can not be started when its assets prices or history are currently not available due
to holidays or outside market hours.

Not all assets can be traded all the time. Index (IND) assets are read only, but you can trade index ETFs, Futures, or
CFDs. Be aware that you have to pay interest for CFDs, so they are no equivalent to ETFs. Some assets, such as CFDs
and gold/silver commodities, are not available to US citizens. For getting permission to trade them you have to open an
UK or other account outside the US. The default minimum sizes for currency pairs are in the 20,000 contracts range,
but entering a lower minimum size in the asset list is also possible. Orders are then marked as 'odd lot' and cause higher

71
transaction costs. Some stocks (STK) and some other assets can be not shortable temporarily (dependent on market
liquidity) or permanently. Short trades for closing a long position are normally accepted.

IB symbols

Any asset you want to trade must be represented by a line in the asset list. Asset symbols (Symbol column) are
converted to IB symbols in the following way:

• Any symbol name containing a '/' slash at the 4th position (f.i. EUR/USD) is assumed a currency pair. Is is split in
symbol (first three characters) and counter currency (last three characters), gets the asset type CASH and is routed
to the IDEALPRO exchange. Currency pairs are displayed with a dot (f.i. EUR.USD) in the TWS, and the purchased
amount is listed separately per currency in the TWS account window.
• Any symbol name containing '-' hyphen characters (f.i. AAPL-STK-NYSE-USD, or XAGUSD-CMDTY) is split in strings
separated by hyphens. String 1 is the asset, string 2 the type (STK, OPT, FUT, FUTX, IND, FOP, WAR, CASH, CFD,
STKCFD, FUND, EFP, BAG, BOND, CMDTY), string 3 normally the exchange and string 4 the currency. If the currency
is omitted, USD is assumed. If the exchange is omitted, smart routing is used with no primary exchange. The following
exchange codes are supported: SMART, AMEX, ARCA, BELFOX, BOX, BRUT, BTRADE, CBOE, CBOT, CFE, CME,
DTB, E-CBOT, ECBOT, EUREX US, FOREX, FTA, GLOBEX, HKFE, IBIS, ICE, IDEM, IDEALPRO, ISE, ISLAND,
LIFFE, LSE, MATIF, ME, MEFFRV, MONEP, NYBOT, NYMEX, NYSE, ONE, OSE.JPN PHLX, PSE, SNFE, SOFFEX,
SUPERMONTAGE, SWX, TSE, TSE.JPN, TSX, VIRTX, XETRA. Details about smart routing and the supported
exchanges can be found (or not) in the IB documentation.
• Two special symbol types are supported by the bridge. The type STKCFD trades as a CFD, but gets the price quotes
and historical prices of the underlying stock. This is useful for stock CFDs (f.i. AAPL-STKCFD) which can be traded,
but get no price quotes from IB. The type FUTX can be used for downloading historical data of expired futures
contracts.
• Options and futures options have symbol names in the format Underlying-OPT/FOP-Expiry-Strike-P/C-Exchange,
f.i. ES-FOP-20161218-1350.0-C-GLOBEX. Futures have symbol names in the format Underlying-FUT-Expiry-
TradingClass-Exchange-Currency, f.i. SPY-FUT-20161218-SPY1C-GLOBEX-USD. If the currency is omitted, USD is
assumed. The symbol names of options and futures are normally automatically generated by calling
the contract function.
• If the name contains neither a slash nor a hyphen, is it assumed a stock or ETF in USD. SMART routing with primary
exchange ISLAND is used. These parameters supposedly work for all US stocks and ETFs.

A example file AssetsIB.csv with currencies, CFDs, and stocks is included; we do however not guarantee the accuracy
and correctness, so use it at your own risk.

Extra data

The IB bridge supports the following additional data streams:

• marketVal: Not supported.


• marketVol: Historical data only, and not for all assets.

Extra broker commands

The IB plugin supports the brokerCommand function with the following additional commands:

• brokerCommand(SET_PATCH, patch)
• brokerCommand(GET_DELAY, 0)
• brokerCommand(GET_WAIT, 0)
• brokerCommand(SET_DELAY, delay)
• brokerCommand(SET_WAIT, wait)
• brokerCommand(GET_POSITION, Symbol) (Positions for forex pairs are not supported, but for single currencies, f.i.
"EUR").
• brokerCommand(GET_ACCOUNT, String)
• brokerCommand(SET_SYMBOL, Symbol)
• brokerCommand(SET_MULTIPLIER, Multiplier)
• brokerCommand(SET_CLASS, Class)
• brokerCommand(SET_LIMIT, Price*10000)
• brokerCommand(GET_OPTIONS, CONTRACT*)
• brokerCommand(GET_FUTURES, CONTRACT*)
72
• brokerCommand(GET_UNDERLYING, 0)
• brokerCommand(DO_EXERCISE, Lots)

More broker commands, f.i. for market depth data or news messages, can be implemented on user request.

Known IB API issues

In comparison to other broker APIs, the IB API has several limitations that must be worked around in the script, and can
make an IB trading script a bit more complicated:

• Limited history. IB allows only 60 requests of historical data every 10 minutes. This limitation restricts automated
trading strategies to few assets and short lookback periods. More assets and long lookback periods, as with some Z
systems, cause a "pacing violation" error message at session start. In that case use the PRELOAD flag and get
recent historical data from other sources. Update 2017: According to their new API specifications, IB has removed the
limit for M1 or low-resolution data (the current limitations can be found under http://interactivebrokers.github.io/tws-
api/historical_limitations.html#gsc.tab=0). The assetHistory function can be used for downloading up to one year
of historical M1 data for most available assets from IB.
• Trade control. The IB API can not control individual trades. So all trades are strictly virtual and controlled by Zorro
only. The account portfolio only reflects the net sum of all open positions by all connected Zorros. The NFA flag must
be set for all IB accounts, even outside the US. Stops are handled by Zorro and not sent to IB. Closing positions (f.i.
by manual intervention) is not reported through the API and thus won't automatically close the corresponding trades
in the Zorro session. The brokerCommand(GET_POSITION) function and the LotsPool variable can be used for
comparing open positions between Zorro and IB as long as it's not a currency pair. Use cancelTrade for removing
manually closed positions and for keeping the session in sync with the account.
• Market hours. Outside market hours - normally 9:30 .. 15:00 ET for US-traded assets - orders can often not be
executed and price quotes are often not available. If PRELOAD is set and the API does not respond to a price request,
Zorro will attempt to retrieve the last known historical price. This is indicated by a "frozen price" message. If an order
can not be immediately executed, it is cancelled, so there won't be any pending orders except for Zorro-controlled
entry conditions. Sometimes orders can be executed outside market hours, but with higher transaction costs. Therefore
trading outside market hours should generally be avoided.
• Asset parameters. Except for price and bid/ask spread, asset parameters are not available through the IB API.
Leverage, commission, pip cost, and lot size of traded assets must be located on the IB website and entered manually
in the asset list spreadsheet. Typical leverage on a RegT account is 33 for most Forex pairs, 4 for stocks and ETFs,
and 20 for CFDs. The parameters need not be 100% accurate, but should not be too far off in the interest of realistic
backtests. You can find examples (with no guarantee of accuracy and correctness) of major currencies, CFDs, and
stocks in the included file AssetsIB.csv. Commissions are not included.
• Account status. The API provides a large number of account parameters, but not the balance and the equity.
Therefore proxies are used: Equity is set to the Equity with Loan Value, Balance is set to the Cash Value,
and MarginVal is set to the Current Maintenance Margin. When Forex positions are open, the account balance
includes all currency positions and thus does not reflect the true balance. Non-US accounts sometimes display account
parameters in wrong currency immediately after connection. This is corrected after about a minute.
• Limited result accuracy. Profits displayed in the TWS by closing a trade can be very different from the real trade
profit, since the API returns only the profit based on the average of all open positions. Total profit will also differ slighty
because transactions costs and leverage can not be retrieved through the API, therefore approximate values are used
on the Zorro side.
• Connection reliability. The IB API will produce warnings during operation, some harmless, some more serious. In
the Gateway log you'll see ominous messages such as "Invalid incoming request type 0". They are by design and can
be safely ignored. Warnings of orders outside business hours can also be ignored as long as the order is still executed.
A more serious matter is that the Gateway tends to occasionally log out and in by itself, displaying messages like
"Connectivity between IB and Trader Workstation has been lost". Even without logging out, the connection to price
servers is sometimes briefly interrupted with the message "Data Farm connection lost". Normally all this does not
require user intervention, since Zorro will automatically re-connect and continue the session. However during log out
time Zorro can not react on price events. So an IB script should detect offline periods by comparing bar times, and
check asset positions (see above) for making sure that no trades were closed during that time.
• Single session only. You can not connect with your IB user name to the TWS and the Gateway at the same time -
not even for only checking your account. When you log in with the TWS, the Gateway connection breaks down. After
terminating your TWS session you must therefore manually re-login from the Gateway. Zorro will automatically resume
the session after an interruption, but while not connected it can not handle entries, stops, or profit targets. For working
around this problem, you can register a second user for your IB account, and open the TWS with this second user
name. This workaround however does not work for IB paper accounts, which are single-user only.
• No hibernation. The PC must not be reset, restarted, switched off, hibernate, or go in suspend mode while connected
to the IB API. Otherwise the API may crash and require a Zorro restart.

73
On the other hand, the IB API offers many interesting features that are not available in Forex broker APIs, such as
volume data, market depth data, options and futures trading, and news messages. Especially market depth is of interest
for HFT strategies since it gives insight in short-term trends. If you need additional features for your automated trading
strategy, please contact us. But be aware that those additional data are mostly unavailable in historical data and thus
can not be easily used for simulations and backtests.

74
Oanda Plugin
You can trade with Oanda™ either through the MT4 bridge, or with a direct REST API connection using the Oanda
plugin. The REST API connection is preferable due to higher speed, lower spreads, and the ability to trade currencies
with minimum volume, such as a single contract. Oanda is a 'market maker' broker with a large selection of index and
commodity CFDs, free historical price data, a free API, no commission, no minimum monthly investment, and a simple
and transparent margin and fee structure. Especially trading with minimum volume opens interesting possibilities, for
instance running strategy tests on real Oanda fxTrader accounts instead of demo accounts.

For opening an Oanda demo account for API access, visit http://www.oanda.com, and select an fxTrader practice
account. You will need an Access Token for trading through the API. For this, sign in on the Oanda website with your
user name, then select Other Action / Manage API Access for getting your token (some clients reported that they had
to contact Oanda support for getting access to Manage API Access).

The token is a long hexadecimal string that you paste in Zorro's Password field. Put your Oanda Account Id in
the User field, or leave it empty for using the default primary account. Then you're all set for automated trading with
Oanda. Make sure to store the access token for later use. You will need to revoke your token and generate a new one
when you create a sub-account.

!! Oanda is currently in the process of developing a new API V2.0 version. It is not yet fully finished and thus can not
yet be used for API trading. However they already give out account numbers and access tokens for the new API that
don't work anymore for the REST API. If your access token does not work, or if you got an account identifier consisting
of multiple numbers with hyphens (like 123-456-78999-007), please contact Oanda support and request a "legacy
account" for REST API V1. When you create sub-accounts for your account, contact them also for a legacy sub-account.
Accounts that work for API access have a single number identifier (like 1234711). The API access token for the main
account also works for API-enabled subaccounts.

Oanda asset symbols

Currency names can be directly used for Oanda symbols, but most CFD names must be converted to a specific symbol
in the asset list. An asset list AssetsOanda.csv with the main CFD symbols is included for this purpose. A list of
available assets can be found at https://www.oanda.com/forex-trading/markets/live. Click on the asset to get its
symbol name. Note that some assets are not available in all countries, f.i. no CFDs in the US.

Extra data

The Oanda plugin supports the following additional data streams:

• marketVal: Not supported.


• marketVol: Tick frequency in live data and in historical data.

Supported broker commands

The Oanda plugin supports the brokerCommand function with the following commands:

• brokerCommand(SET_PATCH, patch)

More commands, f.i. for retrieving order book data from Oanda's Forex Labs, can be implemented on user request.

Known Oanda API issues

The Oanda plugin uses the Oanda REST API. Compared with other broker APIs, the REST API is well structured, easy
to implement, supports full trade management and allows unrestricted price history access. Known issues of the Oanda
API are:

• Historical price data. Compared with FXCM, the Oanda price history is of slightly lower quality due to the reduced
number of ticks (max. 24 per minute) and more small gaps. For both reasons T1 data from Oanda has a smaller file
size than T1 data from FXCM. On the other hand, Oanda's price history loads faster than FXCM's, and goes further
back. Oanda also supports more currency pairs than FXCM.

75
• Rate limit violation. Oanda is limited in the number of simultaneous connections and in the price retrieval rate. Do
not trade more than one Zorro instance on any Oanda account. For trading several systems at the same time, use
sub-accounts.
• No hedging. Oanda accounts are NFA compliant. But Oanda works around most NFA issues on their side of the API,
so the NFA flag needs not be set. Hedge however must be either at 0, 4, or 5 since long and short positions can not
be open at the same time. Stops and partial closing are supported by the API.
• Shorthand Tickets. Oanda uses 64-bit trade tickets, which can be theoretically a 20-digit number. Zorro deals with
them internally, but displays only the last 10 digits on the trade status page.
• Instrument trading halted. Trades can not be opened and closed outside trading hours. The trading hours of CFDs
and currencies can be found on the Oanda website.
• Internal server error. Oanda servers occasionally go offline; during that time all API functions return a message that
Oanda engineers are on their way to fix the problem.
• No hibernation. The PC must not be reset, restarted, switched off, hibernate, or go in suspend mode while connected
to the Oanda API. Otherwise the API may crash and require a Zorro restart.

76
FXCM Plugin
The included FXCM plugin allows direct trading with Forex Capital Markets LLC on demo and real accounts, without the
need to install the MT4 platform. FXCM offers some advantages, such as index and commodity CFDs, free tick-based,
good-quality historical price data, a free API, and no minimum monthly investments.

For opening a FXCM demo account, visit http://www.fxcm.co.uk and click on Free FXCM Practice Account. When
you enter a different FXCM page, be aware that there are several demo account types - the best is a normal practice
account with currencies and CFDs (if available for your country). If possible, activate hedging for your account. If you
live in the US, they will normally only offer you a no-hedging, no-CFD account; you have to activate NFA compliance in
that case for trading with the FXCM API. The demo account will normally expire after a month of no trading, but can be
renewed indefinitely by opening a new demo account.

• Some FXCM real account types (f.i. a "mini" account) do not support API trading. When opening an account with
FXCM, make sure that the FXCM API is supported. FXCM might require a higher initial deposit for API trading.
• Do not open several sub-accounts with the same user name. The free Zorro version only supports one account per
login, so for different accounts also use different user names for logging in.
• In some countries, FXCM accounts are pre-set to connect only to a single session. For trading with several Zorros on
the same account, contact FXCM and let them switch your account to multiple sessions.

FXCM symbols

Asset names are directly used for FXCM symbols. No conversion is needed.

Additional data

The FXCM plugin supports the following data streams:

• marketVal: Not supported.


• marketVol: Tick frequency in live data and in historical data.

Supported broker commands

The FXCM plugin supports the brokerCommand function with the following commands:

• brokerCommand(SET_PATCH, patch)

Known FXCM API issues

You can trade with FXCM either through the MT4 bridge, or with a direct API connection through the FXCM plugin.
Direct API connection is preferable due to higher speed and lower spreads. Compared with other APIs, the FXCM
"FxConnect" API is relatively fast and allows unlimited price history access. The main issues of the FXCM API (version
1.31) are:

• Wrong equity value. Due to known bugs in the FxConnect API, the account equity value returned by the API will get
out of sync with the account and become wrong after a few days. This has no effect on trading as long as the equity
is still positive, but it affects the equity display on the Zorro GUI. As a workaround, let Zorro calculate the equity with
the BrokerPatch setting in Z.ini or with the SET_PATCH command in your own scripts. Zorro's equity estimate can
also deviate from the real account value due to accumulation of small differences, but will be far more accurate than
the value returned by the API.
• Orphaned trades. The FXCM API has internal lists of opened and of rejected trades. A trade becomes orphaned
when Zorro opens it, but it does not appear in either of the two lists due to an API or server glitch. So Zorro does not
know if the trade was opened or rejected, and does not have its ID. Without trade ID, Zorro can not handle the trade,
and can not close it. Normally this happens rarely, not more often than once a year. Zorro will assume in such a case
that the trade was not opened, but will warn about a possible orphaned trade in its message window and on the trade
status page. So check Zorro's trade status regularly with your smartphone or in a browser, especially when running
Zorro on a VPS. If you see the message "Possibly orphaned trade", open the FXCM platform (Trade Station or MT4),
and compare Zorro's list of open trades with the platform's list. Check if there is really an orphaned trade. If a trade
appears in the platform, but not in Zorro's trade list, close it manually.

77
• No commission parameter. The commission is not available through the API and thus must be manually entered in
the Assets List file for any new added asset. Commissions for all assets can be found on the FXCM website. For
estimating trading costs, the rule of thumb is that FXCM commission adds about 0.7 pips to the EUR/USD spread.
• Stop limit. FXCM has an upper and lower limit to the stop loss. Too small or too large stop distances cause a trade to
be rejected, so the distances are limited by the plugin. This can produce different stops on the FXCM platform and the
Zorro platform. If this is undesired, set StopFactor to 0.
• Server outages. The FXCM servers are usually switched off during the weekend, so their API connection breaks down
every Friday night. This is normally no reason to worry since Zorro automatically resumes a trading session as soon
as the server is online again. However historical price or asset data can not be downloaded during the weekend. Since
the latest FxConnect API is known to crash when the server goes down, the FXCM plugin uses an older and more
stable version of the API.
• No hibernation. The PC must not be reset, restart, switched off, hibernate, or go in suspend mode while connected
to the FXCM API. Otherwise the API will crash and require a Zorro restart.

78
Metatrader4™ Bridge Plugin - version 1.4
Metatrader4™ (MT4) is a popular trading platform, supported by most brokers. It consists of two parts: The MT4 client
terminal runs on the trader's PC and communicates with MT4 server via Internet. The MT4 server program runs on the
broker's server; it generates prices and slippage as set up by the broker, and manages the trades. With the MT4 bridge,
Zorro can control the MT4 client terminal as an Expert Advisor (EA) and this way trade with all brokers that provide the
MT4 platform for their clients.

Through its DLL functions, the MT4 bridge can also be used for controlling Metatrader4 with third party software.

MT4 client with attached Zorro

The MT4 bridge DLL has two sides. On the MT4 side it appears as a library DLL that can be called from any EA. On
the Zorro side it appears as a Broker Plugin. This way the MT4 bridge can theoretically be used not only by Zorro,
but also by any other software for reading prices and sending orders via MT4.

Installation and startup

If you want to run Zorro on a VPS together with the MT4 bridge and have limited PC experience, you can subscribe
a VPS installation service on the download page. Otherwise, here are the 4 steps for connecting Zorro to MT4 build
600 and above:

• Install Zorro. Install the broker's MT4 platform if you haven't already. Connect to to your MT4 account with the login
data from your broker. Locate the ZorroMT4.zip archive in your Zorro folder. In the MT4 terminal, select File / Open
Data Folder for locating the MQL4\Experts and MQL4\Libraries folders. Unzip the archive. It contains 3
files: Zorro.mq4 goes into the Experts folder, and ZorroMT4.ex4 and ZorroMT4.dll both go into the libraries
folder. Zorro.mq4 is the EA, ZorroMT4.ex4 is its library for connecting MT4 to Zorro, and ZorroMT4.dll handles the
communication between the two programs. When your MT4 version is not running in "portable mode", Experts and
Libraries are located in your user directory
(under C:\Users\YourName\AppData\Roaming\MetaQuotes\Terminal\SomeStrangeCodeSequence\MQL4).
There is also a MQL4 subfolder directly in the MT4 directory - that's NOT the folder that you need and copying files
there will have no effect!
• In the MT4 terminal, check the Expert Options under Tools / Options / Expert Advisors, and make sure that [Allow
Automated Trading] and [Allow DLL Imports] are enabled. In the MT4 Navigator under Expert Advisors you should
now find the entry [Zorro] (if not, right click on Expert Advisors and select Refresh). Open a chart window with the
asset that you want to trade; for multiple assets use an EUR/USD chart. The time frame of the chart does not matter.
Drag the Zorro EA onto it. Confirm in the pop-up dialog that DLL imports and live trading are enabled. You should now
see the comment "Controlled by Zorro Bridge" with a version number in the upper left corner of the chart (current
bridge version is 1.9), and a smiling face next to "Zorro" in the upper right corner. If MT4 does not start the EA, look
for the reason under the Experts tab: you might have forgotten to copy a file or to check an option. Some MT4 setups
do not compile the EA automatically: In that case right click on the Zorro EA, select Modify, then click Compile in the

79
editor window.

• Check the assets that you want to trade, and make sure that they are available in the MT4 market watch window (a
right click and Show All enables all assets). Make also sure that the assets have similar names as the assets traded
by your strategy. Forex pairs that differ in a missing slash and an added suffix (f.i. "EURUSDpro" instead of
"EUR/USD") are corrected automatically. But if you want to trade other assets that have different names (f.i. "GOLD"
instead of "XAU/USD", or "DE30EUR" instead of "GER30"), convert the names as described below under MT4
symbols. Also make sure that the time and time zone on your PC or VPS are set up correctly - Zorro needs the correct
time while trading.
• Now start Zorro. Select [MT4] or [MT4 (demo)] in Zorro's Account scrollbox. Enter your MT4 account number in
Zorro's [User] field (the content of the [Password] field does not matter). You can see the account number in
MT4 Navigator / Accounts. Make sure that [Demo] / [Real] is set to the same account type as in the MT4 title bar.
Clicking [Trade] will now attach Zorro to MT4 and handle price quotes and orders through the MT4 server. You should
see the broker's name in Zorro's message window on login, and a"Zorro connected" comment in the upper left corner
of the MT4 chart window. The asset prices are now downloaded from the MT4 server. When accessing an asset the
very first time, MT4 will need a minute or longer for downloading it from the server (equivalent to the "Waiting For
Update" message when opening a MT4 chart the first time). In that case you'll see an error message in the Zorro
window since the data is not yet available; just connect again after a minute until all assets load. If you get other error
messages, look under Known issues

MT4 symbols

Most MT4 versions use special asset names with broker specific suffixes. For instance, the "EUR/USD" currency pair
is called "EURUSD" in the MT4 version by FXCM™, "EURUSDm" in the IBFX™ version, "EURUSDi" in the version
by Traders Way™, and "EURUSD.G" in the Global Prime™ version. The Zorro.mq4 EA automatically adapts the
names by removing the slash and adding the broker specific suffix from EURUSD symbol name of the EA chart window.
So no adaption is required on your side.

For non-currency assets with names longer than 6 characters, or for names that can not be automatically adapted this
way - for instance, "GOLD" instead of "XAU/USD" - the MT4 symbols must be entered directly in the Symbol column
of the AssetsFix.csv spreadsheet. Otherwise your selected assets are not found in the MT4 symbol list, and you'll
get "asset unavailable" error messages when starting your script.

All assets you want to trade must be visible in the MT4 Market Watch window. If you still get error messages about
unavailable assets, read the MT4 Experts log. It normally tells what the problem is.

80
Known MT4 Issues:

• Some MT4 clients don't have all assets enabled by default. Disabled assets are not visible in the "Market
Watch" window and not available for trading. For enabling them, right click on "Market Watch", select "Symbols",
select the symbol(s) you want to trade, and click "Show". But do not select more than you actually trade, since any
additional asset in the Market Watch requires bandwidth, memory, and CPU resources.
• Some MT4 brokers - for instance, LMAX - provide different symbols for the same asset. Only one of the symbols is
really traded, so make sure that you have the right symbol on the chart window with the Zorro EA.
• Some MT4 servers need a long time for the initial access of the price history of previously unused assets. This can
cause a timeout. The strategy will then not start, and a Error 054 or Error 055 message is displayed. In that case just
restart the strategy by clicking [Trade] again. Inbetween the server had enough time to upload the missing asset. You
might need to repeat that step until all assets load properly.
• Some MT4 servers have no sufficient price history for systems with a long lookback period, such as Z1, Z2, or Z12 -
especially when starting after a holiday. The strategy will then not start, and a Error 047 message is displayed. In that
case use the PRELOAD flag or the Preload .ini setup for using Zorro's own history instead of the MT4 price server.
• Most MT4 servers can download only a limited live price history, such as 2000 hours. Therefore
the assetHistory function and the Download script normally does not work with MT4; downloading large historical
price data requires direct access to your broker's API.
• Some MT4 versions are set up to work only with encoded EAs. In this case the Zorro EA will appear grey in the
Navigator and can not be attached to a chart. Open it with the MetaQuotes Language Editor and click [Compile] - this
will encode the source code and store it as an .ex4 file, which can then be attached to a chart.
• Some old MT4 versions physically delete .ex4 files that have been generated with a newer MT4 version. The reason
for that bizarre behavior is unknown, but you'll find that the ZorroMT4.ex4 file suddenly disappears from
the libraries folder upon lauching MT4. Consequently the Zorro EA will generate an error message because the
ZorroMT4 library is not found. In that case just update MT4 to the current version, and copy ZorroMT4.ex4 again into
the libraries folder.
• When getting a practice account number and password directly from a MT4 broker, it's sometimes displayed as "Real
Account" in the MT4 title bar. Since the 'demo only' version of the MT4 bridge can not trade with real accounts, you
need to open a true demo account. You can open any number of demo accounts directly in MT4 by selecting File /
Open an Account.
• Trades via MT4 are often executed on a virtual market handled by the MT4 server. In that case you must NOT set
the NFA flag, as the virtual trades are not NFA compliant.
• Some MT4 brokers especially in the US comply with NFA FIFO rules (first in, first out). In that case you can close
trades, but you must close old trades before new trades. Otherwise the trade won't open or close, and you'll see an
error message like "Close failed - prohibited by FIFO rules" in the MT4 experts log. Use Hedge = 4 in such a case.
• Some MT4 brokers prohibit hedging. In this case you can not open long and short positions at the same time. This
problem can be overcome by setting Hedge to 4 or 5.
• Some MT4 brokers do not support partially closing trades. You can see this from an error message like "invalid trade
volume" in the MT4 experts log. In this case set Hedge to 4 or below for disabling virtual hedging and/or partial
closing.
• Some MT4 brokers reject trades ("Can't open...") vor various reasons, for instance a too distant stop, a too close
stop, or too large slippage. The reason is normally visible in the MT4 Experts log.
• The MT4 bridge was tested with all currently available MT4 client builds, and is tested with any new upgrade. Since
new MT4 builds are often not fully compatible to their predecessors, do not upgrade a working system. If MT4 upgrades
behind your back and you experience problems afterwards, please report on the user forum or to Zorro Support.
• Communication between Zorro and MT4 on the same PC is very fast, so trading with the MT4 bridge does not add
noticeable latency compared to directly trading with an MT4 Expert Advisor. Still, if a broker offers both API and MT4
access, using the API is normally preferable. The API is faster than the MT4 server, and MT4 accounts are often
slightly more expensive due to additional spread.

Remarks:

• The MT4 bridge of the free Zorro version supports demo accounts only. A MT4 bridge for real accounts is automatically
included with Zorro S or can alternatively be subscribed on the Zorro Download Page. The MT4 client terminal and
Zorro must run on the same PC in the same Windows environment, and must be started by the same user.
• With Zorro S, several Zorro instances can connect simultaneously to a single MT4 client terminal. Otherwise, only one
Zorro instance can connect. Do not attach the Zorro EA to more than one chart window in a single MT4 instance. For
running several MT4 instances simultaneously for special purposes - for instance, signal providing - install several
MT4 clients in separate folders.
• MT4 is resource and bandwidth hungry, which can cause long response times of the MT4 bridge when several
processes run in parallel. Depending on the Internet bandwidth, you should normally not run more than one MT4 client

81
on a low-performance VPS (such as the free Amazon VPS), and not more than 3 clients on a high-performance VPS.
Make sure that only the really traded assets are visible in the Market Watch window.
• While Zorro is connected to MT4 in [Trade] mode, do not close or reset the MT4 chart with the Zorro EA. Closing the
chart will stop the EA and disconnect Zorro (visible by a red square next to the Server window, and a frozen server
time). It must then be stopped and restarted.
• The MT4 client terminal displays prices as Bid prices, while Zorro always uses Ask prices. So you'll see different
prices on the MT4 chart and in the Zorro window. This has no effect on trading, as Zorro internally converts all prices
to Bid or Ask dependent on the trade direction.

Extra data

The MT4 bridge supports the following data streams:

• marketVal: Not supported.


• marketVol: Not supported in historical data, tick frequency in live data.

Supported broker commands

The MT4 bridge supports the brokerCommand function with the following commands:

• brokerCommand(SET_PATCH, patch)

• brokerCommand(GET_TIME, 0)

• brokerCommand(SET_SLIPPAGE, slippage)

• brokerCommand(SET_MAGIC, magic)

• brokerCommand(SET_ORDERTEXT, string Text) (Zorro 1.48 and above)

• brokerCommand(SET_COMMENT, string Text)

• brokerCommand(PLOT_HLINE, var* p): id

• brokerCommand(PLOT_TEXT, var* p): id

• brokerCommand(PLOT_MOVE, var* p)

• brokerCommand(PLOT_REMOVE, id)

• brokerCommand(PLOT_REMOVEALL, 0)

• brokerCommand(PLOT_STRING, string)

More commands can be implemented by the user by editing the Zorro EA.
Any brokerCommand(Command,Parameter) call in a Zorro script triggers the CMD_BCOMMAND part of
the Zorro.mq4 run loop. The parameter is transferred in arr[1] and can be used for calling arbitrary functions.
Use arr[0] for transferring the return value back to Zorro. Be aware that sent parameters are stored on the MT4 side
and thus affect all Zorro instances connected - not only the instance that sent the parameter.

Zorro EA functions
Zorro can directly trade with MT4 through the Zorro.mq4 Expert Advisor. This EA is included in MQL4 source code, so
it can be used as a template for writing own EAs that evaluate trade signals from Zorro. For this the following four
functions are available in the ZorroMT4.ex4 library:

82
ZorroInit () : int
Open the Zorro plugin. Normally called in the MT4 init() function.

Returns:
0 when the initialization failed, otherwise nonzero.

ZorroExit ()
Release the Zorro plugin. Normally called in the MT4 deinit() function.

ZorroRequest (double& array[]): int


Receive a request from the DLL, f.i. for opening a trade. Normally called in a loop in the MT4 start() function.

Parameters:
array Array of up to 10 double variables that are filled with the request parameters.

Returns:
0 if no request is pending, otherwise one of the following commands: CMD_COMMENT, CMD_PRINT, CMD_ASSET,
CMD_HISTORY, CMD_BALANCE, CMD_BUY, CMD_TRADE, CMD_SELL,
CMD_STOP, CMD_BCOMMAND (see Zorro.mq4). The zstring() function returns a string containing the requested
asset name or a text message. Asset names need possibly be converted to the specific symbol names of the MT4
version (see remarks).

ZorroRespond (int cmd, double& array[])


Respond to a request from the DLL. Normally called to transfer the result after receiving a request.

Parameters:
cmd One of the following commands: CMD_ASSET, CMD_TICK, CMD_HISTORY, CMD_BALANCE,
CMD_BUY, CMD_TRADE, CMD_SELL,CMD_STOP, CMD_BCOMMAND (see Zorro.mq4).

array Array of up to 10 double variables containing the response parameters.

Example: see source\Zorro.mq4

83
R Bridge
The R bridge connects Zorro scripts to the R environment and allows R computations from your lite-C code.

R is an interactive script language for data analysis and charting, developed in 1996 by Ross Ihaka and Robert
Gentleman of Oakland University as successor to the S language. The latter was developed by John M. Chambers and
his colleagues in the Bell Labs company in 1976. Today, R is still being improved by the R Development Core Team
including John Chambers. Although R is an interpreted language and thus slow compared to 'real' programming
languages such as C or lite-C, it has many advantages:

• It is the global standard for data analysis and machine learning algorithms.
• It is interactive and easy to use once you're familiar with its command syntax. R functions are short and effective.
• It is 'Zorro-like' - it has a minimalistic user interface and can be easily automatized for experiments and research tasks.
• It has ready-to-use extensions - "packages" - for all imaginable mathematical and statistical tasks, including support
vector machines, genetic optimization, several kinds of neural networks, and deep learning algorithms. Meanwhile
there are more than 4000 R packages.
• It is continuously developed and supported by the global scientific community. The time between the publication of an
idea and its implementation in a R package is usually very short. In average, 15 new R packages come out every
single day.
• It is free. As Linus Torvalds said: "Software is like sex - it's better when it's free".

This makes R an excellent research tool for analyzing and predicting financial time series, using the newest published
methods. However, R is not a clean programming language such as C - it is full of traps to the beginner, so using it
effectively needs experience. It's also no environment suited for trading (although some packages provide rudimentary
backtest, optimization, and even broker connection functions!). The solution to this is the R bridge that allows triggering
R computations in a Zorro strategy and using their results for trade signals. The lite-C script starts the R session, sends
price data or indicator values to it, and calls training and predicting functions from a R machine learning package. The
bridge uses the mt4R.dll by Bernd Kreuss. Its original distribution with source code is contained in
the Zorro\Source folder.

Many problems can be solved in lite-C as well as in R, but R often has already a dedicated command implemented for
that. For instance, downloading historical price data from Yahoo requires 11 code lines in lite-C (here), but only a single
line in R (read.csv("http://ichart.finance.yahoo.com/table.csv?...")).

Installation and test

• Install R from http://cran.r-project.org.


• Open Zorro.ini and make sure that the entry RTermPath contains the correct path to the R terminal RTerm.exe.
• Start Zorro and select the script RTest. Click [Test] for running RTest. If everything went well, you should see the
result in your Zorro window:

• For using the R bridge in your scripts, include the r.h header at the begin of the script.

84
R bridge functions
The r.h header defines several functions to directly assign and read data types without the need for manually formatting
and executing snippets of R code for these purposes. Not all possible data types are directly supported, the main
emphasis is on vectors and matrices containing floating point values. Once you have transferred the bulk of your data
as vectors or matrices into the R session, you can execute snippets of R code to combine them or convert them into
something more complex if needed.

Normally you want to get huge amounts of numeric data into the R session quickly, but not the other way. The
assumption is that you want to feed it with price data on order to crunch numbers, and only need to get back a single
value or a vector as a result.

All vector functions have a parameter for the number of elements. Be careful that the array you supply has the correct
size, or it will crash the R console. Expressions or code snippets have a maximum size of 1000 characters per command;
larger pieces of code must be sent with several commands or - much better - be called as a function in the R
environment. See RTest.c for example usage.

Rstart(string source, int debuglevel): int


Start a new R session, and load R functions and variables from the file source. This must be called in
the INITRUN before any R computations can be done. Returns 0 when the R session could not be started, otherwise
nonzero.

Parameters:
source Name of a .r file in the Strategy folder containing all needed R functions and variables
(f.i. "MySource.r"), or "" (default) for not sourcing a file.
debuglevel 0 - only output fatal errors (default)
1 - output warnings and notes
2 - output every R message
The R output goes to the system debug monitor. Use the free DebugView tool from Microsoft, or
call Rx(..., 3) to view R output).
Rrun(): int
Return 1 if the R session is ready for input, 2 if the session is busy with a computation, and 0 if the session is terminated.
R will terminate on any fatal error in the code. You should check this regularly. The last command prior to the termination
will be found in the debug output. If R is not running anymore, the R bridge won't emit any more messages and will
silently ignore all commands.

Rx(string code, int mode): int


Execute a line of R code. Similar to the Ri / Rd / Rv functions below, as evaluating an expression is also just executing
code; the difference is that Rx() can send commands asynchronously, allows R and Zorro to do computations in parallel,
and prints R output to the Zorro window.

Parameters:
code R code line to be executed (1000 characters max).

mode 0 - synchronous. Wait until the computation was finished (default).


1 - asynchronous. Return immediately. Rrun() can be used to check when the next R command can be
sent.
2 - asynchronous, with a wait loop. Return 1 when finished, or quit with returning 0 when the session was
aborted due hitting [Stop] or due to a fatal R error.
3 - asynchronous with a wait loop, and direct R print output to the Zorro window. The debuglevel must be
set accordingly.

85
Rset(string name, int n)
Rset(string name, var d)
Rset(string name, var *v, int elements)
Rset(string name, var *m, int rows, int cols)
Store an int, var, series or matrix in the R variable name.

Parameters:
name Name of the R variable.

i int value to be assigned to a R integer variable.

d var value to be assigned to a R floating point variable.

v Pointer of the var array or series to be assigned to a R vector.

m Pointer of the var array to be assigned to a R matrix, by row order.

elements Length of the vector or series. Make sure to give the exact number of elements.

rows, cols Number of rows and columns of the matrix.

Ri(string expression): int


Rd(string expression): var
Rv(string expression, var *v, int elements)
Evaluate the given R expression, and return the result as an int, double, or vector. The expression can be a single
variable, a R function, or any R code that will evaluate to the required variable type.

Parameters:
expression R expression to be evaluated (1000 characters max).

v Pointer of the var array to be filled with the R vector.

elements Number of elements of the vector; must be identical in the R code and in the lite-C script.

Remarks:

• Test your R commands and functions carefully on the R console before executing them in a lite-C script. A command
error - wrong syntax, a wrong path, a missing R package, or a bad parameter for a R function - will cause the session
to abort and all subsequent R commands to fail. You'll normally see the error reason printed in the Zorro window. Use
the Rrun() function at the end of every bar of a strategy to determine if the session is still running. When live trading,
stop the strategy and raise the alarm when Rrun() fails.
• While the R bridge is busy, Zorro can't react on user actions such as clicking the [Stop] button. For avoiding this on
long R computations, use Rx() with mode=2. Alternatively, call Rx() with mode=1 and check Rrun() in a wait() loop
(f.i. while(Rrun() == 2) wait(100) ).
• Make sure to send complete commands. If a R command is incomplete f.i. due to a missing bracket at the end, the R
session will freeze and consequently Zorro will also freeze.
• If you have the choice, perform script-based computations on the lite-C side, as lite-C is up to 200 times faster than R.
Loops in R are awfully slow. However R is preferable for vector and matrix operations.
• R functions usually expect time series in straight order with newest elements last. Use the rev function for reversing
the order of a series.
• When passing file names to R, use forward slashes for directories, f.i. "C:/Project/Trading/R/Data.csv". You can use
the slash helper function in r.h for converting backslashes to forward slashes (f.i. slash(ZorroFolder)).

86
• Use [Train] mode and WFO for training machine learning algorithms, f.i. a SVM or neural network. Send training data
to R at the end of every WFO cycle, and store the trained models in files for later use in the [Test] or [Trade] session.
The advise(NEURAL) function contains a framework for that.
• Install all needed R packages before starting the script. Often-used packages for financial computations are caret, zoo,
xts, ttr, quantmod, rminer. There are also several popular packages for machine learning. The e1071 package contains
a support vector machine, and the deepnet package contains several deep learning network algorithms, such as
sparse autoencoders and Boltzman machines. If the package of a called R function is not installed, the R session will
abort with an error message.
• For conveniently working with R, we recommend the free programming environment Rstudio
(http://www.rstudio.org) that contains a simple debugger.
• The R lectures by Harry Georgakopoulos have been included in this manual. There are also several e-books about R
(f.i. R in a nutshell). Scanning through a R book or tutorial will pan out, as it takes a little getting used to the R
language. Variables are generated by assignment, and need not be declared. The most common variables are
a numeric (= var, int, bool), a vector (= series, array), a matrix (= two-dimensional array), a data frame (= array of
structs), and a list (= arbitrary collection of data). A series element can be accessed by appending [n] just as in lite-C,
but in R the index begins with 1, not with 0. List elements are indexed with double brackets [[n]]. Although R also
recognizes the '=' assignment operator, R assignments are usually made with '<-' or '->' depending on the assignment
direction. Functions can write into global variables with '<<-'. Boolean operators are similar to C, but mind the difference
between '&' and '&&': the former operates on all vector elements, the latter only on the first - a common trap for C
programmers who write their first R code.

Examples (see also advise):


// do some R calculations
#include <default.c>
#include <r.h>

function main()
{
Rstart("",2); // enable output

var vecIn[5],vecOut[5];
int i;
for(i=0; i<5; i++)
vecIn[i] = i;

Rset("rin",vecIn,5); // set up a vector


Rx("rout <- rin * 10"); // perform some arithmetics
Rx("print(rout)",3); // print rout to the Zorro window
Rv("rout",vecOut,5); // read it back

if(!Rrun())
printf("Error - R session aborted!");
else
for(i=0; i<5; i++)
printf("%.0f ",vecOut[i]);
}
//Example for computing trade signals in R
#include <r.h>

int Size = 200; // number of candles needed by the R algorithm

bool RCheck()
{
if(!Rrun()) {
quit("R session aborted!");
return false;
}
return true;
}

function run()
{
BarPeriod = 60;
LookBack = Size;
asset("EUR/USD");

if(is(INITRUN)) {
Rstart("MySignals.r",1);
// load all required R objects from a file in the Zorro Data folder
Rx(strf("load('%sData/MyObjects.bin')",slash(ZorroFolder)));
// make sure everything loaded ok
if(!RCheck()) return;
}

// generate reverse price series (latest price comes last)


87
vars O = rev(series(priceOpen())),
H = rev(series(priceHigh())),
L = rev(series(priceLow())),
C = rev(series(priceClose()));

if(!is(LOOKBACK)) {
// send the last 200 candles to R
Rset("Open",O,Size);
Rset("High",H,Size);
Rset("Low",L,Size);
Rset("Close",C,Size);

// let R compute the signal


var Signal = Rd("Compute(Open,High,Low,Close)");
if(!RCheck()) return;

if(Signal > 0 && !NumOpenLong)


enterLong();
if(Signal < 0 && !NumOpenShort)
enterShort();
}
}
// export historical price data to R
string name = "Data\\Export.csv";

function run()
{
BarPeriod = 60;
StartDate = 20140101;

if(is(INITRUN)) // write the header


file_write(name,"Date, Open, High, Low, Close",0);
else
file_append(name,strf(
"\n%04i-%02i-%02i %02i:%02i, %.5f, %.5f, %.5f, %.5f",
year(),month(),day(),hour(),minute(),
priceOpen(),priceHigh(),priceLow(),priceClose()));
}

/* in R:
> Data <- read.csv('C:/Projects/Zorro/Data/export.csv') # mind the forward slashes
> plot(Data$Close)
> ...
*/

88
Exporting and importing historical data; adding assets and accounts
Historical price data and data streams

Historical price data is used in [Test] and [Train] mode; in [Trade] mode the prices required for the LookBack period
are normally automatically downloaded from the broker at start of the strategy. The used data period can be determined
with StartDate and EndDate. If historical prices from the desired period are missing, they can be downloaded with
the assetHistory function or the Download script (see below). All downloaded prices are automatically aligned to UTC
time. The broker's time zone does not matter. Therefore it's normally no problem to use price data from different sources
for the simulation and for trading.

Zorro stores historical price data in the History subfolder in files that can contain tick, minute, or day based ask prices.
Each file name is composed from the asset (with special characters such as '/' removed), the year number, and the
extension ".bar" for simple candles, ".t6" for extended candles with volume data, or ".t1" for plain price quotes (see
also M1 vs. T1). For instance, "EURUSD_2010.t6" contains all prices of the EUR/USD asset in 2010. The price data
files included with Zorro are minute based, but any other time base can also be used as long as it's equal or less than
the bar period.

Price data in different formats can not be mixed. You can use either .t1, .t6, or .bar price data in the backtest of a
strategy, but not a combination, for instance .t6 from 2016 and .bar from 2015.

The .bar price data format is replaced by the newer .t6 format, but is still supported. It is a simple list of TICK structs.
The TICK struct is defined in include\trading.h:

typedef struct TICK


{
float fOpen, fClose;
float fHigh, fLow;
DATE time; // time of the tick in UTC time, OLE date/time format
} TICK;

Accordingly, a .t1 or .t6 price data file is a simple list of T1 or T6 structs. They are also defined in include\trading.h:

typedef struct T1
{
DATE time; // time of the tick in UTC time, OLE date/time format
float fVal; // price data
} T1;

typedef struct T6
{
DATE time;
float fHigh, fLow;
float fOpen, fClose;
float fVal, fVol; // additional data, like ask-bid spread, volume etc.
} T6;

For downloading and reading .t1 files, Zorro S is required. For converting price data from an external source to the
Zorro format, separate it into years, convert it to a list of T6 or T1 structs, and store it in a binary file with the name
described above. The structs are stored in reverse order, i.e. the most recent tick comes first. The OLE DATE format is
a double float value, counting days since midnight 30 December 1899; hours, minutes, and seconds are represented
as fractions of days. Zorro generally uses UTC time: if you have price data based on local time, convert it to UTC (you
can find some example code here). If the price data contains bid prices, you may convert them to ask prices by adding
your broker's spread. Using ask or bid price history has however normally no effect on backtest results.

The fOpen, fClose, fHigh and fLow data streams can be separately accessed in the script. The fVal and fVol data can
be used to store and access additional data, such as spread, quote frequency, trade volume, or rollover. Data
in .t1 format can be used for additional information, such as external indicators or statistics such as the VIX (in that case
set Detrend at 16 for disabling the automatic fixing of price data). All data streams are automatically synchronized to
the streams of the first asset call in the script; so it does not matter if some data is only available in daily or weekly
'ticks'. The asset function can be used to select between different data files, while the price functions select between
the four data streams inside the file.

Historical data in arbitrary CSV formats can be converted to .t1 or .t6 with the dataParse function. Exporting or
converting historical data to and from other formats is also easy due to the simple format. In the Strategy folder you can

89
find several small scripts for this purpose. Download.c (see below) downloads historical data in .t6 or .t1 format from
Yahoo, IB, FXCM, or Oanda. CSVtoHistory converts data from arbitrary .csv formats, f.i. from HistData, Quandl, or
Yahoo, to the Zorro .t6 format. CSVExport.c exports the complete price history of the selected asset to
a .csv spreadsheet, and CSVFromHistory converts a particular .t6 file to a .csv spreadsheet. Example code for
automatically downloading .csv price data from the Internet or from a provider API can be found on the http page.
Here's a small script that converts .t1 price history to a .t6 file:

#define NN 400000 // > 252*24*60


T6 Buffer[NN];
int Ticks = 0;

void Tick() { Ticks++; }

void run()
{
NumYears = 1;
BarPeriod = 1;
LookBack = 0;
History = ".t1";
set(TICKS);
asset(Asset);

// store candles in reverse order


static T6 *Candle;
if(is(INITRUN)) Candle = Buffer + NN;
Candle--;
Candle->fOpen = priceOpen();
Candle->fHigh = priceHigh();
Candle->fLow = priceLow();
Candle->fClose = priceClose();
Candle->fVal = 0;
Candle->fVol = max(1,Ticks);
Candle->time = wdate();
Ticks = 0;

if(is(EXITRUN)) {
string Name = strf("History\\%s_%d.t6",Asset,year());
int Size = (int)(Buffer+NN)-(int)Candle;
file_write(Name,Candle,Size);
printf("\n%d bars written!",Size/sizeof(T6));
}
}

The Download script

Download.c is a small script for downloading price history from Yahoo or from a broker or signal service. It offers also
a convenient way for retrieving broker-specific asset data, such as lot size, margin, and rollover, directly from the broker
API. The script opens a control panel with buttons (blue) and entry fields (yellow):

Download or update historical price data for the current year

• Select your broker and account (Demo or Real). Start the Download script in [Trade] mode.
• Enter the name or symbol of the asset in the top yellow field. You can enter any name supported by the data source,
it needs not necessarily be a name from the Asset scrollbox. For downloading options or futures price data, enter
the IB symbol of an existing contract (such as ES-FUT-20170915-ES-GLOBEX-USD)
• Click [Load M1 Data].

90
Adding a new asset to the Asset scrollbox

• Make sure that your broker offers that asset in your country (f.i. CFDs are often not offered in the US). Make also sure
that it's a workday and no Wednesday (rollover costs are often different on Wednesdays). Select your broker and
account (Demo or Real). Start the Download script in [Trade] mode. Zorro will log in to your broker and open the
control panel.
• Double click the yellow field next to [Single Asset], and enter the name of the new asset, f.i. "Bund" or "Copper".
• Double click the yellow field next to [Save Assets to], and enter "AssetsFix". This is the asset list used for the
scrollbox (see asset list below).
• Click [Save Assets to]. The new asset will now be added to History\AssetsFix.csv.
• Open History\AssetsFix.csv with a spreadsheet program or text editor. Check if some parameters - for instance,
broker commission - are missing in the row beginning with the asset name. If so, they are not available through the
broker API. Edit the line and enter the missing values manually as described above. Save AssetsFix.csv.
• Restart Zorro. The new asset will now appear in the [Asset] scrollbox and is available for trading and backtesting.

Downloading historical M1 price data for a new asset

• Check if historical price data is offered by the broker. MT4 can normally not be used for downloading historical data,
as their price history is limited. Make sure that the broker's price server is online. Start the Download script as above.
• Double click the yellow field next to [Load M1 Data], and set up the start and end year separated by a minus sign,
f.i. "2013-2015". Note that FXCM has no currency prices from before 2002 and no CFD prices from before 2007. Some
broker APIs can become nonresponsive or even crash when downloading historical price data of a nonexisting asset
or an unsupported time period.
• Click [Load M1 Data]. The price data is automatically downloaded. If price data files of this asset are already found in
the History folder, no prices are downloaded except for the current year, which is appended to the existing file. Do not
append together data from different price sources. For re-downloading old prices, rename or delete the old price data
files before.

Downloading historical tick-based price data

• As above, just use [Load Tick Data] instead. Please note that tick data is not available from most brokers (FXCM
however provides tick data) and that you need Zorro S. Depending on the speed of the FXCM price server, loading a
year of tick data can take a long time.

Downloading historical daily data from Yahoo™

• Enter the name of the stock, index, or other asset in the field next to [Single Asset] (make sure on the Yahoo website
that the spelling is correct!). Click [Load Yahoo Data]. The data is automatically downloaded and stored in a .t6 file in
the History folder. For a proper backtest the asset will also require an entry in the asset list.

Simulating a different broker account

• Make sure that it's a workday and no Wednesday. Select the broker and account to be simulated (Demo or Real).
Start the Download script as above.
• Click [Single Asset] and switch it to [All Assets].
• Click [Save Assets to]. The parameters of all assets from the scrollbox are updated to AssetsNew.csv (or any other
file name that you give).
• If you want to simulate several accounts, use different Assets*.csv files and set AssetsList in the script to the file
name for simulating that particular account.

Updating the recent price history of all assets

• Check if historical price data is offered by the broker. MT4 or IB can normally not be used for downloading historical
data, as their price histories are limited. Make sure that the broker's price server is online. Start the Download script
as above.
• Click [Single Asset] and switch it to [All Assets].
• Click [Load M1 Data]. The price data of all assets are automatically updated to the current date.

91
The asset list

Any used asset must have an entry in an asset list in the History folder. The default asset list, AssetsFix.csv, also
determines the assets that appear in the scroll box. Since any broker has his individual asset parameters, different asset
lists can be used for simulating different brokers and accounts. If no particular asset list is given in a
script, AssetsFix.csv is also used in the strategy script. The parameters in the asset list affect training and testing. In
live trading, asset parameters are normally not read from the list, but loaded from the broker API in real time. But when
the broker API does not provide certain parameters, their values from the asset list are used.

Different asset lists for backtesting and training can be selected either by script through the assetList command, or
automatically with an account list (see below). Asset lists are simple comma separated spreadsheet files that can be
edited with Excel or with a text editor for adding new assets, or for modifying parameters of the asset and the broker
account. The parameters are stored in this format (example):

The first line must be the header line. Assets can be temporarily commented out: A line is ignored when it begins
with "#". Names and symbols must contain no blanks, commas, or semicolons. Zorro also accepts semicolon
separated csv files with commas instead of decimal points, but not a mix of both in the same file. Excel uses your local
separation character, so make sure in your PC regional settings that it's a comma and the decimal is a point, otherwise
Excel can not read standard csv files.

Every asset is represented by a line in the csv file. New assets can be added either manually - by editing the file and
entering a new line - or automatically as described below. The asset parameters have the following meanings:

Name Name of the asset, f.i. "EUR/USD" (up to 15 characters, with no blanks and no special characters
except for slash '/' and underline '_'). This name is used in the script and in the Asset scrollbox.
Price Ask price of one contract, in counter currency units. Accessible with the InitialPrice variable. For
non-Forex assets the counter currency is usually the currency of the exchange place, such as USD
for US stocks, or EUR for the DAX (GER30). For information only; not used in the backtest.
Spread The difference of ask and bid price of the asset, in counter currency units. Accessible with
the Spread variable.
RollLong Daily rollover fee ("swap") for long resp. short trades per contract, resp. per 10000 contracts for
RollShort currencies. This is the interest that is added to or subtracted from the account for holding trades
overnight. Account currency units; accessible with the RollLong/Short variables. On Wednesdays it
is often three times higher for compensating the weekend when no rollover fee is charged. When
manually entering them, make sure to convert them to 10,000 contracts for currency pairs.
PIP Size of 1 pip in counter currency units; accessible with the PIP variable. About ~1/10000 of the asset
price. The pip size is normally 0.0001 for assets (such as currency pairs) with a single digit
price, 0.01 for assets with a price between 10 and 200 (such as USD/JPY and most stocks), and 1 for
assets with a 4- or 5-digit price. For consistency, use the same pip sizes for all your asset lists.
PipCost Value of 1 pip profit or loss per lot, in units of the account currency. Accessible with
the PipCost variable and internally used for calculating the trade profit. When the asset price rises
or falls by x, the equivalent profit or loss of a trade in account currency is x * Lots * PIPCost / PIP.
For assets with pip size 1 and one contract per lot, the pip cost is just the conversion factor from
counter currency to account currency. For calculating it manually,multiply LotAmount with PIP and
divide by the price of the account currency in the asset's counter currency. Example 1: AUD/USD on
a micro lot EUR account has PipCost of 1000 * 0.0001 / 1.11 (current EUR/USD price) = 0.09 EUR.
92
Example 2: AAPL stock on a USD account has PipCost of 1 * 0.01 / 1.0 = 0.01 USD = 1 cent.
Example 3: S&P500 E-Mini futures on a USD account have PipCost of 50 USD (1 point price change
of the underlying is equivalent to $50 profit/loss of an S&P500 E-Mini contract).
MarginCost Initial margin for purchasing 1 lot of the asset in units of the account currency. Depends on account
leverage, account currency, and counter currency; accessible with the MarginCost variable.
Internally used for the conversion from trade Margin to Lot amount: the number of lots that can be
purchased with a given trade margin is Margin / MarginCost. Also affects the Required Capital and
the Annual Return in the performance report. Can be left at 0 when Leverage (see below) is used
for determining the margin.
Leverage Account leverage for the asset, f.i. 100 for 100:1 leverage. Accessible with the Leverage variable.
MarginCost and Leverage are different methods for determining a margin for a given purchase
volume. If the price is known, they can be converted into each other: MarginCost = Asset price /
Leverage * PipCost / PIP. When the broker uses Leverage, the margin per purchased lot depends
on the current price of the asset. When the broker uses MarginCost, the margin is independent of
the asset price, therefore the broker will adapt MarginCost from time to time when the price has
moved far enough. When only Leverage is entered in the asset list, the MarginCost variable is
calculated from the Leverage value and the current price. When MarginCost is nonzero, Leverage
is ignored and the Leverage variable is calculated from MarginCost and the initial price.
LotAmount Number of contracts for 1 lot of the asset; accessible with the LotAmount variable. It's the smallest
amount that you can buy or sell without getting the order rejected or a "odd lot size" warning. For
currencies the lot size is normally 1000 on a micro lot account, 10000 on a mini lot account, and
100000 on standard lot accounts. Some CFDs can have a lot size less than one contract, such as
0.1 contracts. For most other assets it's normally 1 contract per lot.
Commission Roundturn commission for opening and closing one contract, resp. 10000 contracts for currencies.
Accessible with the Commission variable. When manually entering the commission, double it if it's
single turn. For currency pairs make sure to convert it to 10,000 contracts.
Symbol Broker symbol of the asset (up to 31 characters, no blanks, but special characters such as '.' or '-' are
allowed). Additional information such as the asset type and exchange name can be coded in the
symbol, dependent on the Broker API (f.i. AAPL-STK-NYSE-USD, see IB API). For assets to be
downloaded from Quandl or Yahoo EOD history, enter QUANDL: or YAHOO: followed by the code
or symbol (f.i. QUANDL:CHRIS/CME_EC1). When the field is empty, the asset name is used for the
symbol.

Some broker APIs, such as IB or FIX, do not provide asset parameters. So they must be calculated and entered
manually. The values can normally be taken from the broker's website. If the broker uses a very complex structure of
fees, margin, and commission, enter estimated or average values. They need not be 100% accurate, but they should
not be too far off in the interest of realistic backtests. An alternative way to get to the data is opening a minimum position
of the asset in a demo account. The commission and margin is then often displayed in the broker's trade platform.

Since the price of the account currency is not constant, PipCost and MarginCost are only valid for the time at which
the asset list was created or downloaded. In live trading they are automatically updated from the broker API. In the
backtest they could be updated by script. But this is normally not required, since the deviations in trade profit and margin
value are negligible in comparison to bias and randomness that affect strategy performance results.

Up to 8 additional asset-specific parameters - either numbers or strings - can be entered in the asset list behind
the Symbol column. They can be accessed in the script through the AssetVar/AssetStr variables, and can be used for
storing additional asset or strategy specific information, for instance the minimum and maximum portfolio weights, or
the trading class for options. Asset-specific strings must not have more than 7 characters.

For backtesting or trading a certain asset, make sure that historical price data files for the tested period are available,
and the asset it contained in the selected asset list. US citizens are restricted in trading, as high leverage and CFDs are
usually not available to them. FXCM US accounts have often only 10:1 leverage and offer only currencies; the other
assets should be removed from the list when simulating US accounts. For simulating many different accounts, place
several Asset...csv files in the History folder and call assetList with the desired asset file name in the script for
simulating the corresponding account.

There is a special asset list named Assets.csv in the Log folder. This list is updated on every [Trade] session with the
current parameters of all assets contained in the script. It's a convenient way to simulate your current broker account:
just connect to the broker with a script that selects all needed assets (f.i. the Download script), then copy Assets.csv to
the History folder under the name of the asset list that you use for testing. It's preferable to do this a Monday, Tuesday,
Thursday, or Friday, as on weekends most assets have an unrealistic spread and no rollover fee, and on Wednesdays

93
often the rollover is three times as high for compensating the weekend. For permanently simulating a certain
asset/account state, copy the corresponding asset line from Assets.csv into the History\AssetsFix.csv file. Zorro
must be restarted when AssetsFix.csv was modified. Below you'll find examples for adding assets and downloading
price data with the Download script.

The included asset list AssetsCur.csv contains about 35 currency pairs, including all pairs of the 7 major currencies
EUR, USD, AUD, GBP, CHF, NZD, CAD. It can be used for trading with a multitude of currencies. For making this list
permanent, copy AssetsCur.csv to AssetsFix.csv. The included asset lists AssetsIB.csv and AssetsOanda.csv
contain selected assets for particular brokers.

For simulating direct market access (DMA) with no broker interference, set the parameters in the following
way: Spread to a realistic bid/ask spread; Commission, RollLong, RollShort, and MarginCost to 0; Leverage to 1;
LotAmount to 1; and PipCost to PIP multiplied with the price of the assets resp. the counter currency in account
currency units.

The account list

By default, only a Demo and a Real account can be selected with the [Account] scrollbox. With Zorro S a list of
additional accounts with extra parameters can be added through a simple spreadsheet file named Accounts.csv in
the History folder. This file is a convenient way to manage many broker accounts with different logins, passwords, asset
parameters, and special modes. The Accounts.csv file can be edited with Excel or a simple text editor. It contains the
account info in plain comma separated spreadsheet format (example):

An example file AccountsExample.csv is contained in the History folder as a template for creating your
own Accounts.csv file. Every account in the scrollbox corresponds to a line in the file with the following parameters,
separated with commas:

Name Account name (no blanks) that appears in the account scrollbox.
Broker Name of the broker (with no blanks).
Account Account ID, or 0 if only one account belongs to the login data.
User User name for the login, or 0 for manually entering the user name.
Pass Password for the login, or 0 for manually entering the password.
Assets Default name of the AssetList file used for simulations with this account (see above). Only affects the
script, not the Asset scrollbox.
CCY Name of the account currency, f.i. EUR or USD.
Real Real account (1), real account with no trading (3) or demo account (0). When at 3, all trades are opened
in Phantom Mode and not sent to the broker.
NFA Compliance of the account. Affects default state of NFA flag and Hedge mode: 0 for no
restrictions, 2 for Hedge = 0 (no hedging), 14 or 15 for NFA = on (full NFA compliance).
Plugin Name of the broker plugin (without .dll extension).

The first line in the csv file must be the header line. Names and strings must contain no blanks and no special characters
except for '-' and '_'. Zorro also accepts semicolon separated csv files with commas instead of decimal point, but not a
mix of both in the same file. User names and passwords are stored in unencrypted text in the spreadsheet, so leave
those parameters at 0 when other people have access to your PC. Zorro must be restarted when Accounts.csv was
modified.

The trade log

94
Zorro exports events in two files in the Log folder when the LOGFILE flag is set: a *.log event log and a *.csv trade
spreadsheet. The *.log file records the profit at every bar, and all events such as opening or closing a trade, adjusting
a trailing stop, or a printf message by the script. If VERBOSE is set, it also records the daily state of all open trades
and the daily profit or loss. Event logs have individual names composed from the strategy name and the selected asset.
They are normal text files and can be opened with any text editor; their content looks like this (the trade messages are
explained under Trading):

--- Monday 03.08. - daily profit +363$ ---


[GER30:EA:L7407] +684.97 / +685.0 pips
[GER30:HU:L7408] +684.97 / +685.0 pips
[UK100:EA:L7409] +580.55 / +464.4 pips
[USD/CAD:LP:S7612] +136.29 / +851.8 pips
[USD/CAD:LS:S7613] +68.15 / +851.8 pips
[AUD/USD:LP:L8412] +115.92 / +483.0 pips
[AUD/USD:MA:L1511] +42.50 / +265.6 pips
[USD/CAD:HU:S5412] +14.63 / +182.9 pips
[AUD/USD:HU:L5413] +28.05 / +175.3 pips
[GER30:EA:L7407] Trail 1@4746 Stop 4907

[5770: 04.08. 04:00] 6: 9289p 143/299


[GER30:EA:L7407] Trail 1@4746 Stop 4914
[AUD/USD:MA:L1511] Exit after 56 bars
[AUD/USD:MA:L1511] Exit 2@0.8406: +38.71 07:36

[5771: 04.08. 08:00] 6: 9773p 143/299


[GER30:EA:L7407] Trail 1@4746 Stop 4920

[5772: 04.08. 12:00] 6: 9773p 143/299


[GER30:EA:L7407] Trail 1@4746 Stop 4927
[USD/JPY:LP:S7308] Short 1@94.71 Risk 8
[USD/JPY:LS:S7309] Short 2@94.71 Risk 29
[USD/JPY:HU:S7310] Short 1@94.71 Risk 13

New log files always overwrite old log files of the same name. If you want to compare log files or charts generated with
different parameters from the same script, rename them or copy them to a backup folder. For preventing that
the Log folder gets cluttered with thousands of files, Zorro automatically deletes log files that are older than 1 month (it
will ask you before).

Trade spreadsheets

Three exported spreadsheet files - testtrades.csv, demotrades.csv, trades.csv - contain a description of every trade
in comma separated format for import in Excel™ or other spreadsheet or database programs. They can be used for
evaluating trade statistics or for the tax declaration. testtrades.csv is exported in [Test] mode when LOGFILE is
set. demotrades.csv and trades.csv are exported in in [Trade] mode, dependent on whether a demo or real account
was selected. The latter two are perpetual files, meaning that they are never overwritten, but their content is preserved
and any new trade sent to the broker is just added at the end. So they will grow longer and longer until they are deleted
manually or moved to a different folder. Depending on the Comma setting, numbers are exported with either a decimal
comma or point, and separated with either a semicolon or a comma; this is because German spreadsheet programs
require CSV data to be separated with semicolon. A trade spreadsheet (generated with Comma) looks like this:

95
Meaning of the fields:

Name Algo identifier (see algo), or the script name when no identifier is used.
Type Trade type, Long or Short.
Asset Traded asset.
ID Trade identifier number, also used in the broker's records.
Lots Number of lots. Multiply this with the asset's lot size (see above) to get the number of contracts.
Open Date and time when the trade was opened, in the format Day.Month.Year Hour:Minute.
Close Date and time when the trade was closed., in the format Day.Month.Year Hour:Minute.
Entry Trade entry price (Bid or Ask, dependent on the trade type). Dependent on broker plugin
and SET_PATCH parameters, the price is either the open price of the trade, or the market price at trade
entry, which can slightly differ.
Exit Trade exit price (Bid or Ask, dependent on the trade type). Dependent on broker plugin
and SET_PATCH parameters, the price is either the close price of the trade, or the market price at trade
exit, which can slightly differ.
Profit Profit or loss in units of the account currency, as returned by the broker API. Includes spread, commission,
and slippage.
Rollover Interest received from or paid to the broker for keeping the trade open overnight, in units of the account
currency.
ExitType Sold (by exitTrade), Reverse (by enterTrade), Stop (stop loss), Target (profit target),
Time (ExitTime), Exit (by a TMF that returned 1) or Closed (externally closed in the broker platform).

Importing trade lists and other data from CSV

Sometimes you want to import further data from a spreadsheet or data file, or export price data or backtest results for
further evaluation, or convert a data file into a different format. All this is possible using the file and string functions.
The sscanf function can read comma separated data from a .csv file, while the strf or sprintf functions can write
comma separated data into a .csv file.

For reading back the CSV file format above, an example function can be found in the Simulate.c script:

string readTrade(string csv,


string* tAsset,
string* tType,
int* tLots,
DATE* tOpen,
DATE* tClose,
var* tProfit)
{
string nextline = strstr(csv,"\n");
if(nextline) nextline++;

string separator = ",";

*tLots = 0; // no valid trade


string s = strtok(csv,separator);
if(!s) return nextline;
if(s != "Long" && s != "Short") // s = Algo name?
s = strtok(0,separator);
if(s != "Long" && s != "Short") // invalid line?
return nextline;

*tType = s;
*tAsset = strtok(0,separator);
strtok(0,separator); // ID
sscanf(strtok(0,separator),"%u",tLots);

int Year,Month,Day,Hour,Minute;
sscanf(strtok(0,separator),"%2u.%2u.%2u %2u:%2u",
&Day,&Month,&Year,&Hour,&Minute);
*tOpen = ConvertTime(Year,Month,Day,Hour,Minute);

sscanf(strtok(0,separator),"%2u.%2u.%2u %2u:%2u",
&Day,&Month,&Year,&Hour,&Minute);
*tClose = ConvertTime(Year,Month,Day,Hour,Minute);

strtok(NULL,separator); // Entry
strtok(NULL,separator); // Exit
96
s = strtok(NULL,separator); // Profit
*tProfit = strvar(s,0,0);

return nextline;
}

Under Tips & Tricks more examples can be found for exporting data to .csv files or for importing data from an external
text file, for instance to set up strategy parameters.

Exporting P&L curves


When the LOGFILE flag is set in [Test] or [Trade] mode, the equity or balance curve is exported to a ..test.csv file in
the Log folder. If the BALANCE flag is set, the balance curve is exported, otherwise the equity curve. Some Zorro
versions also generate a .dbl file that simply consists of a double array containing the daily balance or equity values.

When LOGFILE and a Curves file name is set in [Train] mode, the P&L curves of all optimize parameter steps are
exported. The exported curves can then be evaluated for research purposes, f.i. for a White's Reality Check. All curves
are attached to the end of the Curves file, so training runs from different scripts can add curves to the same file. The
file can be deleted or renamed for getting rid of old curves. Any curve is stored in the following format in the file:

1. string Name, the identifier of the curve in the form "Script_Asset_Algo_ParameterNumber_StepNumber".


Example: "Workshop6_EUR/USD_TRND_2_10".
2. int Size, the size of the subsequent Values array in bytes.
3. double Values[], array containing the daily balance or equity values. The number of elements is Size/8.

Here's a code snippet that reads all curves from a file and prints their identifiers and end values:

byte *Content = file_content("Log\\Balance.curves");


while(*Content)
{
string Name = Content;
Content += strlen(Name)+1; // skip the name
int *Size = Content;
Content += 4; // skip the size
var *Values = Content;
Content += *Size; // skip the balance array
int Num = *Size/8; // number of values
var Profit = Values[Num-1]; // end balance
printf("\n%s: %.2f",Name,Profit);
}

97
Zorro command line
Zorro can be started directly with a script from an external program, a shortcut, or a Windows command shell, like this:

"c:\program files\Zorro\Zorro.exe" [options] [scriptname]

If a script name is given, Zorro will open and start it, and exit afterwards. scriptname (without blanks, '/' characters, or
extension ".c" or ".x") must be an existing script in the Strategy folder. While the script is running,
the COMMAND status flag is set to indicate that it's run from a commandline. Several Zorro functions start other Zorro
processes through the command line this way, for instance multicore training, retraining, or retesting.

You can give a command line option either directly in the Windows command prompt, or with the Windows [Run]
function, or by editing the properties of a Zorro shortcut. For this, right click the shortcut icon and select Properties (for
icons in the Win7 task bar you need to hold the [Shift] key). Under the shortcut tab you'll see the Target field containing
the exact location of Zorro.exe within quotation marks. Add -diag after the last quotation mark, and save the modified
shortcut with [Apply].

Besides the script name, the following command line options can be given (Zorro S only):

-run
Run the script in [Test] mode (default).

-train
Run the script in [Train] mode.

-trade
Run the script in [Trade] mode.

-h
In combination with -run, -train, or -trade: run with minimized Zorro window.

-a assetname
Select the given asset from the [Assets] scrollbox.

-c accountname
Selects the account with the given name from a user defined account list.

-d definename
Passes a #define statement with the given name to the script (Zorro S only). This way, a script started with the
command line can behave differently. Only a single #define can be set through the command line.

-i number
Passes an integer number to the script (Zorro S only) that can be read through the Command variable. Up to 4
numbers can be transferred to the script, each preceded by "-i". This way, a script started with the command line can
behave in different ways.

-diag
Run Zorro in diagnostics mode. A message "diagnostics mode" will appear in the message window at
startup. Verbose is automatically set to 14. A file ending with "..diag.txt" is generated in the Log folder and serves as
a 'black box' recorder - it contains a list with the last events and printf commands, and can be used to determine the
reason of a crash or other problem that leads to the termination of the script. For details see troubleshooting. Black
box recording strongly reduces the training and test speed, so do not use this feature unnecessarily.

-quiet
Don't open a message box when Zorro encounters a fatal error; print the error in the message window instead.
98
Examples

"c:\program files\Zorro\Zorro.exe" -train Z3

Starts a re-training run with the Z3 strategy.

"c:\program files\Zorro\Zorro.exe" -run pricedownload -a USD/CAD

Runs the script pricedownload.c with the selected asset "USD/CAD" in test mode.

"c:\program files\Zorro\Zorro.exe" -diag

Starts Zorro in diagnostics mode. A file diag.txt is generated in the Log folder.

99
Trading 101
For participating in the financial markets, you need a computer and Internet access, and normally a Broker to handle
your trades (unless you have direct access to the electronic markets). Your broker connects you to the networks, places
your orders, and even lends you money for trading. Most brokers offer Demo Accounts (also called Paper Accounts or
Game Accounts) where trading can be practiced without risking real money.

The point of trading is making a profit from the difference between the buying price and the selling price. Day trading is
the buying and selling of financial assets in short time intervals. There are several different styles of day trading, ranging
from extreme short term trading such as Scalping where positions are only held for a few minutes, to longer term Swing
trading where a position may be held for many days or even weeks. If you keep stocks for years, you're not a trader
but an Investor..

A financial Asset - also sometimes called Instrument or Symbol - is the object you trade with, such as a currency,
commodity, stock, future, or other derivative. In Currency trading (or 'Forex trading', Forex = Foreign Exchange) you
buy or sell one currency and pay in another currency. For example, when you buy the asset "EUR/USD", you buy in fact
the Euro and pay in US Dollar, and when you sell "USD/JPY" you sell the US Dollar and get paid in Japanese Yen. You
always trade the first currency against the second currency. It becomes funny when you're European and buy EUR/USD,
which means you buy EUR and are charged in US Dollar for which you pay with EUR.

The Ask Price of an asset is the price at which you can buy; the Bid Price is the price at which you can sell. The Ask
price is normally higher than the Bid price. The difference between Ask and Bid is the spread: Ask = Bid + Spread. For
currencies, the spread is usually in the range of 0.01..0.05 percent of the price (or about 1..5 pips, see below). That's a
lot less than the ~2% that you usually get charged by banks or exchange offices, also called tourist rip-offs! If you buy
an asset and sell it immediately, you lose the spread.

When you think the price will move up, you Buy Long (or just 'buy'). That means that you buy a certain amount of the
asset at the Ask price, and have to sell it later at a higher Bid price for making a profit. The price must rise by at least
the spread for making profit at all.

When you think the price will move down, you Buy Short (or - somehow confusing - 'Sell Short', or just 'short'). That
means you sell an asset at its current Bid price without owning it. Therefore you have to Cover it - i.e. buy it back at its
Ask price - at some point later. The asset price should have been moved down inbetween by at least the spread for
making profit. Buying short is a little counterintuitive, but is in fact the same procedure as buying long, only with the Ask
and Bid prices reversed. Many human traders prefer to buy long rather than short, for psychological reasons or because
they didn't grasp the concept. This is visible in the charts, especially with stocks - the price movements are not
symmetrical in time, but show a sort of sawtooth pattern. You can exploit this in your strategy.

Entering or opening a long/short position, and Exiting or closing a position are just other words for buying and selling
an asset.

A CFD (Contract For Difference) is an agreement to pay the difference of the buy price and sell price of an asset. It's a
sort of bet on a rising or falling price. Many brokers don't offer real stocks, they offer CFDs instead. When you buy a
CFD, you are not buying the underlying asset, although the movement of the CFD is directly linked to the asset price.
You just have the right to pocket the gain (if any), and have to cough up the loss (if any). Since you do not own the
asset, you do not need to pay for it - instead you place a deposit with your broker, called Margin. This deposit is for
covering the possible loss, as the broker doesn't want to have to go after you for collecting his money. The margin can
be as low as 0.5% of the real price of the asset. This means you can trade a volume of up to 200 times your capital.
This factor of 200 is called Leverage, and is a very convenient way to realize huge profits, or to lose all money in no
time.

The Balance is the current money in your broker account. The Equity is the money you had if you closed all currently
open trades. Due to leverage, the value of the trades can be negative, thus the equity can be less than the balance.
The equity changes all the time together with the prices of your assets, while the balance only changes when you buy
or sell something. When the trades don't go well and your equity drops below your total open margin - the deposit for
open trades, see above - your broker will close all your trades, like it or not. This is the notorious Margin Call. It does
not necessarily mean loss of all capital: you normally keep the margin. But if your trades cannot be closed immediately,
for instance due to low market volatility, you can indeed lose everything and even end up with a negative balance.

A PIP (Point In Percentage) is a certain fraction of the price of an asset. Prices normally only move a little tiny bit during
a day, so it's more convenient to say "The EUR/USD is down 15 pips" than "it's down 0.0015 Dollars". One pip is a unit

100
of the last digit of the price. In most currencies the price is normally displayed with 4 digits after the decimal - like 1.2345
- so one pip is 0.0001 in units of the second currency. One exception is USD/JPY, which has only 2 digits after the
decimal - 123.45 - and thus one USD/JPY pip is equal to 0.01 Japanese Yen. There are different pip values for stock,
index, or commodity CFDs. Many brokers display PIP Costs for their assets. That is not the price of one pip, but your
profit or loss when you buy one lot (see below) of that asset and the price moves up or down by one pip.

A Lot of an asset is the smallest amount of units you can buy. Very rarely do brokers allow you to buy currencies in
multiples of 1 unit; usually brokers offer chunks of 100K (100000) currency units. So you can buy 100000, 200000,
300000 EUR/USD contracts, but not 1, 100, or 4711. For other assets the lots are different, but they are normally all in
the range that one pip loss or gain is about 10 dollars. The margin required for buying one standard lot is in the
$500...$1000 range. Most brokers also offer accounts with mini lots that are 10K currrency units, or micro lots that are
1K currency units. For starting trading, a micro lot account is highly recommended. The lot/pip system is an abstraction
layer that puts all prices, losses, gains and margins in about the same range, and makes assets easily exchangable.

Trading Methods

If you have your TV running 24/7 on a news channel, read 12 newspapers per day and base your trade decisions on
news from CNN or the Wall Street Journal, you're a Fundamental Trader. If your trade decisions are based on price
curves, you're a Technical Trader. Technical trading does not require that you know anything about the asset that you
buy or sell; you don't care if it's pork bellies or the Euro, you're only interested in its price curve.

A Strategy is a systematic method to enter or exit trades when certain conditions are fulfilled, such as trade signals
by Technical Indicators. There are many different strategies, like Trend trading, Counter-Trend trading,
or Cycle trading. Trend trades are in the direction of the previous price movement (like buying when the price was
moving up), and counter-trend trades are against the previous trend direction (like selling when the price is high). Cycle
trades are trades that go periodically up and down, and are used when the price is moving Sideways, i.e. jittering about
without taking a certain direction. You can find more about this in the Strategy and the Indicators chapters.

A strategy can be executed manually by Discretionary Traders - that's the poor guys staring on their screens all day
and waiting for the moment to hit the buy button. Or it can be Automated through a Script - that's the enter and exit
conditions written in a programming language and executed by a computer. A special kind of automated strategy is High
Frequency Trading (HFT), where minimal price differences between markets are exploited.

Some brokers offer Binary Options for trading. A binary option is basically a bet on whether the price will be above or
below the current price at a certain time in the future. Winning such a bet does not win the full stake, but only about 70%
or 80% - this way the broker is on the safe side to never lose money with binary options. Which means that you're also
on the safe side of always losing your money. Compensating an average >5% loss is almost impossible, even with the
best trading system. There's one advantage to binary trading, though: it is equivalent to gambling and thus tax free in
some countries. Not that it would matter...

Trading institutions employ both automated systems and human traders. Studies show that employed professional
traders can achieve an average annual profit of 3% above the market*. Interestingly, talent or experience has no
influence on trading success; no institutional trader could consistently trade better than his colleagues. There are no
studies about the success of private discretionary trading, but all information suggests that there is no such. Private
traders are usually not as disciplined, but more susceptible to gut feelings, emotions**, and superstition. This makes
long term trading success very unlikely, but it does not mean that private traders lose money all the time. As shown
in workshop 8, even totally random trading can produce profits for months and even years, giving the lucky traders the
impression that they know what they are doing.

That was most of the trading terms and methods in a nutshell. A free ebook with a comprehensive introduction - Forex
for Beginners - and free trading introductory courses can be found on the links page. Of course there are thousands
of other books, courses, seminars, and trading tools readily available - most are not free, though. Will you need to spend
money in order to really learn trading? Read on.

101
Bars and Candles
A bar is a nice place for trading spirituous liquids. A bar is also the basic time period for trade strategies; it is set up with
Zorro's Period slider. In the chart below, of the EUR/USD price in summer 2009, every bar is represented by a red or
green candle. The horizontal width of the candle is the bar period (in the chart below, one day), and its vertical height
is the price movement of the asset during that period. On green candles the price went up during the bar period; on red
candles it went down. Often up or down bars are also represented with white and black candles. The thin lines above
and below the candles - the 'wicks' - represent the highest and the lowest price during the bar period.

For example, when you look at the candle of June 1 (at the vertical line below marked Jun), you see that the day started
at a price of 1.4125. During the day the price went as high as 1.4225, and as low as 1.4110. The day ended with the
price at 1.4135.

Zorro's trading is normally based on bar periods. The strategy script is run at the end of every bar, and buy or sell
commands are executed at the price of the begin of the next bar. By extending the candle time frame so that it covers
several bars, trades can be placed in the middle of a candle. The bar period plays no role in trade management
functions that evaluate entry or exit conditions and run on any tick. A tick is the arrival of a new asset price quote,
which can happen several times per second. A bar period contains many, many ticks.

In past times, a bar was really equal to a day. The closing price was the price of the asset at 4 PM when the market
closed, and the opening price was the first offer next morning at 9:30 after the traders had contemplated their strategies
all night. So the width of a candle was 6 hours and 30 minutes, followed by a gap of 17 hours and 30 minutes during
which no price quotes arrived and no trading took place. In modern times, assets are traded online 24 hours a day, so
there is no real opening and closing. There are no gaps (except for the weekend), only sessions when more or less
people are trading. The trading session starts at Monday morning in one part of the world, and ends on Friday afternoon
in another part of the world. That covers about 5 days per week. Because the big stock exchange places are in New
York, London, and Tokyo, the days are broken up into the U.S. session, the European session, and the Asian session.
Each session is roughly from 9am to 5pm on the local time. These sessions do overlap a little, and at those overlap
times you have the most people trading. Generally, trading begins Sunday 22:00 GMT when it's 8 AM in Sydney, and
ends Friday 22:00 GMT when it's 4 PM in New York.

Due to the trading around the clock, you can see that in the above chart the closing price of one bar is usually almost
identical with the opening price of the next bar. This was different in the past, and is still different today when assets that
are mainly traded at a certain stock exchange and have little trading volume outside its business hours. In that case,
102
daily candles can have a gap between the close of a bar and the open of the next bar. By the convention used by Zorro,
the time associated to a bar is its close time independent of a local time zone. Thus, a 12:00 bar is the bar that ends at
12:00 UTC.

Minute and second bars

For a day trader, a bar is just an artificial time period with no deeper meaning, and of course you're not limited to 24
hours / 1440 minutes bars. You can theoretically set the bar period down to a few minutes for 'scalping', or - using a
plugin - even lower down to 100 ms. It is indeed tempting to aim for profits within minutes, but it's not really
recommended. Although price curves look fractal - you normally can't tell apart a 1-minute-chart and a chart with 1-day
candles - there are subtle differences between time frames. The smaller the bar period, the more 'random' the price
curve. All patterns and regularities that could be exploited in a strategy disappear on short time frames. Trading costs,
such as the spread, will explode and eat up any remaining profit. Price candles below 30 minutes are also very
dependent on the broker, so a strategy that worked with one broker will fail with another one. And finally, strategies with
very short trade periods are hard to backtest because random effects, such as the delay between buy/sell order and
execution, the difference between current and order fill price - the slippage - and the random time at which price quotes
arrive have a large influence on the results.

Another problem is the high frequency trading by investment banks and trading firms that equalizes all short term market
inefficiencies. Therefore, it is not likely that a private trader can make consistent profit with scalping - in fact we've never
heard of any hard evidence that this ever happened. Normal strategies are only profitable on time frames between one
hour and one day. We found that especially for currencies, the price curve of 4-hour bars contains the most inefficiencies
that can be exploited in trade strategies. Read more about that in the Strategies chapter.

Special bars

Some traders believe that bars covering not a fixed time period, but a fixed price period, give them a better insight into
the market. With a focus on price movement, long periods of consolidation are condensed into just a few bars, thus
highlighting "real" price trends. There are many special bar types: Renko Bars, Range Bars, Momentum Bars, Point-
and-Figure Bars, or Haiken Ashi Bars. They all make the price curve look somewhat smooth and more predictable.
But this is mostly an illusion, as it's achieved by losing relevant information, such as the speed of the price movement.
Therefore curves with price-movement bars are normally harder to predict, not easier. But some market inefficiencies
may be more clearly visible when getting rid of speed and time information. Zorro allows using any imaginable
combination of price and time for constructing user-defined bars with the bar function.

Glossary of terms

Bars, candles, ticks or quotes are often confused, and the meanings of those terms can also vary from platform to
platform. Throughout this manual the following terms are used:

Quote - online offer by a market participant to sell or buy an asset at a certain price. The most recent price quote
determines the current bid or ask price of an asset.

Tick - event when a new price arrives and is evaluated by the trading software. Either triggered by receiving a price
quote in live trading, or by reading the next price out of historical data in the simulation. Ticks in historical data files are
stored with a time stamp and a price info. For keeping the files at a reasonable size, price quotes are normally combined
to one tick per minute (M1 data), but for special purposes data containing all real price ticks (T1 data) can also be used.

Bar - basic time interval; determines the width of a chart candle, the time resolution of the price curve, and the execution
interval of a trading strategy. The time scale on a chart is divided into bars that cover at least one tick, but normally
many ticks. The bar time interval can vary when special bar types, such as price-movement bars, are used. Time
periods without ticks - such as weekends - are usually skipped on the chart time scale.

Time frame - basic time unit of algorithms and indicators in a trading strategy. It is normally identical to a bar, but can
also cover multiple bars in multi-timeframe strategies.

Candle - price info covering a time interval with an open, close, high, and low price. The candle of a tick normally
represents the first, last, highest, and lowest price quote since the previous tick. T1 data contains only a single price
quote per tick, therefore its open, close, high and low is the same price. The candle of a bar represents the first, last,
highest, and lowest price of all ticks inside the bar.

103
How does a trading system work?
A trading system - also called a strategy - exploits market inefficiencies for predicting price trends of financial assets.
In a perfect, efficient market, prices would only be affected by real events, such as the publication of company results.
All traders are 'informed', decide rationally and act immediately. The price curves in such a hypothetical market would
be pure random-walk curves that contain no information for predicting future prices.

Fortunately for trading strategies, real markets are far from this theoretical ideal. Market inefficiencies are everywhere.
They are caused by slow reaction on news, incomplete information, rumors, and irrational trading behavior (such as
following advices by trading gurus). Any inefficiency can allow a strategy to predict a small part of the price curve with
some degree of accuracy. However, there's a problem: Aside from the most obvious inefficiencies, such effects in price
curves are not visible to the human eye.

Look at the chart below:

One of the two lines in the above chart is the EUR price in US$, the other one is a meaningless curve from random
numbers (you can produce such curves with the Zorro script RandomPrice). Can you tell which price curve is real? In
studies, even 'expert traders' and analysts* were unable to distinguish between real prices and random numbers. But a
simple computer program has no problems with that. Prices don't walk randomly; their inefficiencies can be easily
detected by analyzing price curves under many different aspects.

A famous deviation from the random walk is visible in the price movement analysis below:

In the chart, the height of the bars is equivalent to the number of series of rising or falling prices. The red bars are from
the real EUR/USD price curve above, the blue bars from a curve of random numbers. The numbers on the x axis are
the duration of a price movement in hours, at the right side for rising, and at the left side for falling prices. For instance,
104
the "3" at the right means that the price was rising for 3 hours in sequence. The height of a bar indicates how
frequently such a rising or falling series occurs in the curve. If prices would move totally random, the red bars had the
same heights as the blue bars. We can see that this is not the case: rising/falling series of 3, 4, 5, or more hours occur
more often in the red price curve than in the blue random data. 1-hour series - a price rising in the first and falling in
the second hour, or vice versa - occur less often. Prices tend to keep their direction: that's the famous "fat tail" of price
distributions. This effect exists with almost all assets and time frames; it can be used for detecting if a price curve
contains real prices or just random numbers. You can generate such price movement distribution charts with the
simple script RandomWalk - just experiment with different assets and bar periods!

Another effect - a real inefficiency that can be exploited in strategies - is visible in the following spectral analysis chart:

It displays the amplitudes of regular cycles found in the S&P 500 price curve in January 2013. The x axis is the cycle
duration in hours. As you can see, the highest peak is a cycle of about 24 hours, some minor peaks are at 36, 54, and
64 hours, and a broad peak at 120 hours, equivalent to a weekly cycle (a week is 5 days x 24 hours, as weekends are
skipped in price curves). Cycles arise from synchronized trading behavior and are not necessarily aligned to days or
weeks. Those cycles are not found in random data, at least not when the data sample has sufficient size. They are also
normally not visible to the naked eye in price curves, but they can be detected with spectral analysis (you can use
the Spectrum script for this) and exploited for generating profit in automated strategies. You can find an example for
cycle based trading in the tutorial. A similar trade method is used in Zorro's Z2 strategy.

Often, temporary price patterns establish and can be used by intelligent algorithms for predicting short-term trends. The
following curve is produced by a 'deep learning' neural net structure:

105
The net was fed with EUR/USD price changes of the last 60-minutes bars, and predicted the price direction of the next
hour. The blue curve shows the accumulated correct predictions minus the wrong predictions from a walk-forward
analysis. The price direction was predicted with 57% average accuracy. The net was re-trained every 4 weeks, as the
patterns began to lose their predictive power after that time. We can see that some short-term predictive patterns pop
up almost all the time in the market. Such patterns can not be found in random data - a prediction curve had no clear
direction in such a case.

Another interesting inefficiency can be seen in the following price distribution:

In the chart, the height of a bar is equivalent to how often a certain price appears in the price curve. The red bars are
based on the EUR price in Swiss Francs (EUR/CHF) from October 2011 up to January 2015; the green bars are the
EUR price in US Dollars (EUR/USD) in the same time period. You can generate such a chart with the Distribution script.
We can see that the red EUR/CHF price distribution is quite narrow and ends abruptly on the left, as if cut with a knife.
In contrast, the green EUR/USD price distribution is much wider with a broad peak in the middle. It resembles a bell
curve (more or less), which indicates a random price distribution. The EUR/CHF distribution however is no bell curve. It
reveals a market inefficiency that can generate profit in automated trading. In this case, the inefficiency is the result of
the CHF price ceiling that was established by the Swiss National Bank from September 2011 until January 2015. It
prevented the EUR from dropping below 1.20 CHF. This kind of market inefficiencies can be very successfully exploited
by grid trading strategies.

---------------------------------
* The difference between an expert trader and a beginner is that the former has lost more money.

Designing trade strategies

Highly profitable strategies are often astoundingly simple, but properly developing them is not this easy - otherwise
anyone would be doing it. At first you should have some basic knowledge of the financial assets that you want to trade.
Second, you must be able to describe trade entry and exit conditions in a precise language. Without being able to read
and write script code, you can never seriously develop and test a trading system. And third, you must be aware of all
the subtle statistical effects that can cause different outcomes of test and real trading, and know how to deal with them.
All this is described in this manual and in the tutorial, and after learning it you have the basic knowledge for developing
own strategies.

Once you got the basics, you can develop your own systems. The general process of designing a trade strategy is
described here: Build better strategies: The development process. For going deeper into the matter, check out
the links.

One thing is certain: the future is unknown. When you develop a strategy, you use historical price data for testing and
optimizing. But when you trade it, prices are real. The market and its inefficiencies can undergo changes of any kind.
What worked in the past is not guaranteed to work in the future. Therefore, even when you're using proven systems
such as the Z strategies, you always deal with an element of uncertainty. For not relying completely on your luck, learn
as much as you can and develop as many different strategies as possible.

106
What is a technical indicator?
Technical Analysis (TA in short) is a method of finding trade opportunities by analyzing past prices. It is based on the
belief that a number of well-known technical indicators gives insight into the market situation and can generate trade
signals for buying and selling at the right moments.

Normally you want to know if the price will rise or fall. For this prediction you use the prices of past bars - say the last
100 bars. So you have 100 price values that you want to reduce to a single number that predicts the future price trend,
and thus gives you a signal whether to buy or to sell. Mathematically, you want to get rid of 99 degrees of freedom,
using a transformation function from a 100-dimensional space into a 1-dimensional space. Such a function is called
a technical indicator*.

Price 1

Price 2
Indicator Buy/Sell
Function Signal
Price 3

...

Price 100

Calculating with 100 dimensions sounds complicated, but most indicators are primitive. For instance, the Simple
Moving Average (SMA) indicator just adds all 100 prices and divides the sum by 100. The result is the average price
of the last 100 bars. If the current price rises above the average, some traders believe that prices will further rise, and
take this as a buy signal. It the price falls below the average, they believe that prices will further fall and they must sell.
At least that's the theory.

Of course, instead of the last 100 bars you can use any different number of bars, differentiate between the high, low,
open, and close prices of a candle, or use other data such as the market volume. And instead of averaging the prices,
other indicators calculate their variance, their rate of change, their breakout from a given range, their maxima and minima
in a given time period, and so on. All indicators can generate buy or sell signals when reaching a threshold or crossing
each other. Because they are not based on a mathematical foundation or solid theory, anyone can anytime invent new
indicators, and anyone does. About 600 different TA indicator functions are meanwhile published in books and trader's
magazines.For any arbitrary point in any price curve there are many indicators that recommend buying and many others
that recommend selling. This might give you the impression that something must be wrong with TA: If any one of the
600 indicators would really work, there would be obviously no need for the other 599. So you might come to the
conclusion that technical indicators are just garbage. In fact it's a little more complicated.

Are indicators predictive?

Traders obviously think that they are, otherwise they wouldn't use them. People with some math background normally
think that they aren't, and disdain the uneducated traders. However, predictivity is not a property of an indicator, but also
of a price curve. Purely random curves cannot be predicted, no matter with which indicator. Heavily trending curves are
predictable with almost all indicators. So the real question is whether real price curves are predictable with the usual
technical indicators.

The answer is "most likely no". Technical indicators were first seriously tested in 2007 by Prof. D. Aronson of Baruch
college**. His study involved thousands of trade rules with all classical indicator types and price, volume, and interest
data series from 1980 to 2005. The results were adjusted by a bootstrap algorithm (described in his book) for eliminating
data mining bias. In this study, none of the tested classical indicators came out with any predictive value. They fared no
better than flipping a coin. However, the rules were only used for trading the S&P 500 index, so the question is still open
if indicators can be more predictive with other markets, or if better, more complex indicators can be predictive even with
the S&P 500.

107
This does not mean that classic technical indicators are worthless. They can be temporarily successful when a predictive
pattern develops within a limited market and time period. An example was the famous Turtle Trading System that used
the Donchian Channel (DC) for trade signals in the 1980's. Unfortunately this system ceased to be profitable after
about 10 years. Some indicators can deliver useful nonpredictive information - for instance, the Average True Range
(ATR) indicator determines the price volatility and is often used for stop loss limits. New studies*** found that indicators
become predictive when their parameters are regularly adapted to the market situation, either by a Walk Forward
Optimizationwith retraining in real time, or by using market properties - such as the dominant price cycle - for adjusting
the time periods of indicators. Our own tests by feeding indicators to machine learning algorithms also suggest a weak
predictive power in complex combinations of certain indicators. Therefore, Zorro supports most classical
indicators found in the literature. Only the very whacky, such as financial astrology, Elliott waves, Gann lines, etc. are
not included - but you can add even them if you're on the esoteric side.

It is very easy to define your own indicators. Most of them require only a few lines of code. The
file include\indicators.c contains the source codes of almost all nonstandard indicators, so you can use it as learning
material for adding new indicators.

Aside of the traditional indicators with their dubious value for generating trade signals, what else can we use? Zorro
comes with new tools that rely on sound mathematics and can detect any sort of predictability or inefficiency in a price
curve. There are many advanced functions for statistical analysis of price series. Polynomial regression can often
anticipate a small part of a price curve. Spectral analysis can remove noise and detect cycles or seasonality. For
finding price curve patterns, a pattern detector is implemented, based on a similar algorithm as in PDA handwriting
recognition. Perceptrons and decisions trees can generate trade rules from raw price data. You'll learn in the
workshops how to use those advanced tools.

For having a look at some typical indicators, select the script Indicatortest, and click [Test]. You should see a chart like
this:

The blue envelope is a Bollinger Band, a classical indicator. You can [Edit] the script for experimenting with other
indicators or analysis functions. Just add more plot commands. It's quite simple:

// Indicatortest ///////////////////

function run()
{
BarPeriod = slider(0,24*60,0,0,0,0);
set(PLOTNOW|PLOTPRICE);

// plot Bollinger bands


BBands(series(price()),30,2,2,MAType_SMA);
plot("Bollinger1",rRealUpperBand,BAND1,0x000000CC);
plot("Bollinger2",rRealLowerBand,BAND2,0x800000FF);

// plot some other indicators

108
plot("ATR (PIP)",ATR(10)/PIP,NEW,RED);
plot("Doji",CDLDoji(),NEW,BLUE);
plot("Fractal",FractalDimension(Price,20),NEW,RED);
}

* For a comprehensive list of classical indicators, see Perry J. Kaufman, New Trading Systems and Methods (2008)
** Described and published in David R. Aronson, Evidence-Based Technical Analysis (2007)
*** Study with 40,000 trade rules by P.H. Hsu and C.M. Kuan, published in Journal of Financial Econometrics 3, No.
4 (2007)

109
Regular income with automated trading
Even when you got a profitable strategy, like one of Zorro's Z systems, it's not trivial to squeeze a regular income out
of it. The markets go through periods of different efficiency and cause a high fluctuation of trade results. Systems that
generate a relatively safe income are mostly based on mid-term or long-term strategies, but can still suffer long
drawdown periods of several months, even up to a year. Drawdowns are no strategy flaw, but a logical and mathematical
consequence of profitable long-term trading. Admittedly, a 12-month drawdown does not sound well suited for a regular
income. But there's a simple trick to overcome drawdown periods: Do not rely on one single trading algorithm and one
single asset. With many uncorrelated algorithms and assets, a drawdown on one combination is cancelled by a win
series on another - at least in theory. In reality, you'll find that many assets are more or less correlated, either positively
or negatively. Still, a combination of different assets and trade algorithms gives you the relatively steady profit curves
that you can see from Zorro's included Z strategies.

Seven rules for earning money with automated trading

• Have enough capital. The minimum capital for a modest regular income is in the $5000 range. The free Zorro version
limits your account size to $7000 (see restrictions), but you need not all your capital in the broker account - you can
keep it in a savings account and remargin when your equity drops dangerously low. Have at least twice the required
capital from the performance report at your disposal. Most brokers support fast depositing funds by credit card in a
few minutes.
• Know your broker's trading platform. You should be able to observe and manually close trades anytime in case of
an emergency, ideally from your mobile phone. If a trade was not correctly opened or closed due to a software glitch -
this happens rarely, but it can happen - know how to deal with the problem. If it was a glitch of the broker's server or
software, know how to contact the broker and request a refund. If it was a bug in Zorro or in your script, you'll get our
compassion, but no refund.
• Know your strategy. You should understand how it basically works and what risk and performance you can expect.
Test it under different spread and margin settings to avoid nasty surprises. Never trade with systems based on
algorithms that you don't know or understand.
• Pull out in time. Examine the trading results regularly and compare them with the test performance. If you start
losing too much money, don't hesitate and stop the strategy. There is likely something wrong. The strategy might be
expired due to a change of the market, or it was not profitable at all due to a biased backtest.
How much money is "too much"? The simplest way is comparing the loss with the required capital - when you lose
more, you've exceeded the normalized drawdown and should stop. A more precise estimate is the following formula: E
= C + P*t/y - D*sqrt((t+l)/y). E is the expected minimum equity, C is your initial capital (= the Required Capital of the
simulation), P is the test profit, t is the trade time, y the test period, D the test drawdown, and l the drawdown length.
Example: the test shows $8,000 profit after 5 years with a $2000 1-year drawdown, and $1500 recommended capital.
You invest it and are down to $1000 after 6 months. E = 1500 + 8000*0.5/5 - 2000*sqrt(1.5/5) = $1204. You're below
minimum profit. Pull the plug. A more precise algorithm for pulling out or not is described under "The Cold Blood Index"
on the Financial Hacker blog.
• Do not pull out early. The equity curves of all systems are subject to fluctuations that are normally larger than the
upwards trend (see chart below). That's why the Total down time of successful strategies can be in the 90% range,
and that's why at some point after starting a strategy you'll be almost always below your initial capital - otherwise you
would need no capital at all!. If you stop the strategy just at the first drawdown, the money is gone. This is a frequent
mistake of beginners to automated trading, and results in losing money even with highly profitable strategies. Be
prepared for fluctuations and drawdowns as bad and long as in the backtest: they will really happen, and they can
happen anytime and right at the begin. A streak of 20 successive losses with real money is admittedly a special
experience; grit your teeth and sit it out - at least when the backtest suggests that such a streak can happen.
• Do not tamper with the strategy. It's hard to see profits dwindle in a drawdown, but the strategy is already optimized.
Any manual intervention will only make it worse. For the same reason, don't often stop and re-start strategies: this
closes open trades, initializes any intermediate parameters the strategy might use, sets back the lookback period, and
thus more or less reduces the performance dependent on the strategy. Any manual closing of trades can cost several
hundred pips and can convert a winning strategy in a losing one - no matter if those trades were in the profit zone or
not. For 'soft stopping' a strategy without loss, set the margin to zero so that no new trades are opened, then wait until
the strategy itself has closed all open trades.
• Do not get greedy. When your profits accumulate, you'll be tempted to increase the margin. This will of course also
increase drawdowns. But the average drawdown size and duration increases anyway with the square root of the trading
time. So you'll need to put aside a part of the profit for compensating this effect, and can not reinvest it. Raising the
margin too fast is the second-most frequent mistake of beginners to automated trading, and can wipe out any account
regardless of the strategy performance. Avoid increasing the margin proportionally to your equity, as most beginners
to trading tend to do. For every doubling of your equity, raise the margin only by a factor 1.4, not by a factor 2. For the
same reason, do not withdraw all your profits, but keep a part on your account - look under money management for
details.

110
Typical live equity curve of an automated trading system (Zorro Z5). Note the equity fluctuations and the negative peaks during the first weeks due to the
increasing number of open trades. Although the system is steadily winning, pulling out at any initial equity downpeak would end up with a loss. It takes about 8
weeks until enough profit is accumulated for keeping the equity curve safely above zero. Other systems have even higher equity fluctuations and longer
drawdown periods.

111
Strategy Script Introduction
A script describes the trade rules in a sort of simplified language, called lite-C (a "light" variant of the C programming
language). Theoretically, there could be other ways to define a strategy, for instance by setting up a list of indicator
filters and thresholds, or by entering formulas in a spreadsheet program. However, those methods would severely limit
any strategy to mostly predefined rules and indicators. It is very difficult, if not impossible to define a profitable strategy
for today's markets this way. All known successful automated strategies are script based.

The disadvantage of a script is obviously that you have to learn the script language. Most products using strategy scripts
require that you dive deeply into programming. No so with Zorro: lite-C is arguably the world's easiest serious
programming language. It 'hides' almost all the programming stuff and allows you to concentrate on plain strategy. You
can learn the lite-C essentials in about one day.

A simple strategy script in lite-C looks as shown below. This strategy uses two simple moving averages (SMA) with 30
and 100 bars time period, places a stop at 10 pip distance, and buys a long or short position when the faster average
crosses over or under the slower average:

function run()
{
vars Close = series(priceClose());
vars SMA100 = series(SMA(Close,100));
vars SMA30 = series(SMA(Close,30));
Stop = 10*PIP;

if(crossOver(SMA30,SMA100))
enterLong();
else if(crossUnder(SMA30,SMA100))
enterShort();
}

This is a primitive (and not really profitable) trend following strategy. A similar, but more profitable strategy can be found
in lesson 4 of the Automated Trading Course. The course is highly recommended for learning lite-C, no matter if you
have never programmed before or have already some programming knowledge.

For writing your own systems, it can save you a lot of time when you read this manual fully. Zorro has many script
functions that you do not need to program yourself. Often-used code snippets for your own scripts and strategies can
be found on the tips & tricks page. If you worked with a different trade platform before, read the conversion page
about how to convert your old scripts or EAs to lite-C.

112
Strategy Coding Tutorial
Welcome to the coding tutorial! Walk through 7 workshops for becoming a trading system programmer - even if you've
never programmed before.

The language we'll be using is named "C". It has the syntax in which most professional software today is written and
which most trade platforms - such as MetaTrader 4™, Ninja Trader™, and of course Zorro - use for trading scripts.
The first 3 workshops give you an introduction into formulating tasks in C code - also called 'coding'. The next 4
workshops are about developing and optimizing trading strategies. For testing the examples, you can use the
free Zorro platform. If you don't have Zorro yet, just download it from zorro-project.com.

Zorro's programming language, lite-C, is a 'lite' version of the C/C++ programming language. The 'lite' means here it
contains a framework that hides complex stuff - like pointers, structs, etc. - that is difficult to beginners. lite-C was
originally developed for the now defunct computer game company Atari, for controlling the artificial intelligence of actors
in computer games. As you can imagine, a language that controls ten thousands of actors in a virtual world, and at the
same time is easy enough to be used by computer gamers for the behavior of their players, is an excellent choice for
trading strategies. After finishing the tutorial, you can use your programming knowledge not only for C-based languages
like lite-C, C#, C++, or Java. Most languages have a similar structure. So after finishing the first workshops, you will also
be able to at least understand code in Python, Pascal, or Tradestation's EasyLanguage, even though the latter declares
a variable not with var x = 7, but with Vars: x(7).

Here's our curriculum:

• Workshop 1: Variables. How to use numbers and text in a programming langage.


• Workshop 2: Functions. How to tell the computer what to do with your data.
• Workshop 3: Branches and Loops. How a program can make decisions.
• Workshop 4: Trend Trading. Your first strategy - an unusual trend trading system.
• Workshop 5: Strategy Optimizing. Counter trend trading - more difficult and more lucrative.
• Workshop 6: Portfolio Strategies. A robust compound system with multiple time frames and money management.
• Workshop 7: Machine Learning. Using a machine learning algorithm for price action trading.

Although the strategies in this tutorial are profitable in the tests, they are meant for educational purposes only and are
designed for simplicity, not for maximum profit. For trading with real money, better develop your own strategies.

Workshop 1: Variables
A script (or program) is a list of instructions in plain text format that tell the computer what to do under which
circumstances. Any script consists of two types of objects, variables and functions. In this first workshop let me explain
variables. A variable is a place in your computer's memory (just like a container) that is used to store numbers, text, or
other information. Because you don't want to have to remember where in the computer which variable is stored, a
variable has a name that's used in the script. A few example script lines that define variables:

var Price;
var PercentPerMonth = 1.5; // the monthly interest
int Days = 7;
string Wealth = "I am rich!";
bool Winning = true;

We can see from those examples:

► A variable must be defined (programmers also say 'declared') before being used. The only exception are pre-defined
variables such as Spread, Stop etc. that Zorro already knows. We have different variable types: var for a number with
decimals, like prices or rates; vars for a series of many numbers; int for a number that has no decimals, such as for
counting something; string for text; and bool for a sort of 'toggle switch' that is either true or false. There are more
basic variable types in lite-C, but in trading scripts you'll normally encounter only these five.

► Any variable can receive an initial value at start, but we aren't forced to do that when the initial value does not matter.
Example:

113
int num_bars = 7;
var limit = 3.5;

► We can add our own comments to the code. Every time it encounters two slashes //, Zorro will ignore the words that
follow it, up to the end of the line. This way we can add useful information to our code:

int bar_width; // the width of one bar in minutes

or we can temporarily disable a line in the script by placing two slashes in front of it. This is called "commenting out" a
line, and while programming it is used so frequently that the script editor has two extra buttons for commenting out and
commenting in.

► Every definition or any command in C needs to end with a semicolon. Many beginners forget to add ";" at the end of
their lines of code, and this leads to an error message - not in the line with the missing semicolon, but in the following
line!

► Every variable name must start with either a letter or with an underscore _. Some valid names:

var AlohA;
var _me_too;
var go42;
var _12345;

Some bad examples:

var #ofList;
var 1_for_all;

► Variable names are case sensitive. MyTradePositions is a different variable than mytradepositions.

► You can define several variables of the same type in one line. This saves lines and keeps your code short. Example:

var momentum, strength, score;


int width = 7, height = 14, depth = 20;

► Variables should have significant names. Otherwise you will have problems trying to remember what these variables
do if you look at your script a few weeks later.

The first script

Start Zorro, select Workshop1 in the Script dropdown menu, then press [Edit]. The strategy script editor opens up
and shows you this script:

// Tutorial Workshop 1: Variables


////////////////////////////////////////

function main()
{
var a = 1;
var b = 2;
var c;

c = a + b;
printf("Result = %.f",c);
}

The script starts with a comment (the lines beginning with //) that tells us what it is. Then we have
a function named main - everything that happens in a program is inside a function. But we'll come to functions in the
next workshop. Here we concentrate on variables, and in the next lines we can see that the 3 variables a, b, c are
defined, just as described above.

Now, press [Test] on Zorro's panel, and watch what happens in its message window:

114
You can now edit the script in the editor, replace the a and b variable definitions with different numbers, like this:

var a = 5;
var b = 12;

Save the edited script (File / Save or [Ctrl-S]), then press [Test] again: it looks like c is the sum of a and b. This
happens in the following line:

c = a + b;

This is a command (also called instruction) to the computer to add the content of the variables a and b, and store the
result in the variable c. Commands are lines in the code that usually do something with variables. The last line is also a
command, used for displaying the content of c in the message window:

printf("Result = %.f",c);

You have now done the first steps with lite-C: Zorro has added two numbers.

115
Workshop 2: Functions
This is a function:

function add_numbers( )
{
var a, b, c;
a = 3;
b = 5;
c = a + b;
}

► Functions are defined using the word function followed by the name of the function and a pair of parentheses ( ).
The parentheses are used to pass additional variables to the function, if required.

► The body of the function (its list of commands) is written inside a pair of winged brackets { }. The body consists of
one or more lines of lite-C code that end with a semicolon. Programmers usually indent the code in the function body
by some spaces or a tab, for making clear that it is inside something.

► The names used for the functions follow the same naming convention as for variables. Don't use the same name for
a variable and a function.

Let's try to write a function that computes the number of days spent by me (or you) on Earth. I write the
keyword function and then the name of the function; let's name it compute_days:

function compute_days()
{

We will use some variables, so we'd better define them now:

var my_age = 33; // your age (in years) goes here


var days_a_year = 365.25;

Nothing new so far, right? We have defined two var variables and they have received initial values, because I know my
age in years and I also know that every year has about 365.25 days. Now comes the scary part: how will I be able to
tell the computer to compute the number of days? How would I do it with a pocket calculator? I would enter something
like this:

33 * 365.25 =

Now let's take a look at our variables; if I replace 33 with my_age and 365.25 with days_a_year, I will get something
like this:

number_of_days = my_age * days_a_year;

Ok, so our function should end like this:

var number_of_days = my_age * days_a_year;


printf("I am %.f days old!",number_of_days);
}

I have remembered to add the second curly bracket, so now the body of the function is enclosed by the two required
curly brackets. I am really curious to see if this function works, so let's test it. Fire up Zorro, and then select [New Script]
in the Script list. Wait until the editor opens. Then copy and paste the lines below into the editor window. Select the
entire script below with your mouse, right click and choose Copy (or hit [Ctrl-C]), switch to the editor, right click into the
empty window named "script1", then choose Paste:

function compute_days()
{
var my_age = 33;
var days_a_year = 365.25;
116
var number_of_days = my_age * days_a_year;
printf("I am %.f days old!",number_of_days);
}

The code looks pretty simple, doesn't it? We already know how to work with those variables, we know how to add
comments... So let's save it (File / Save As) into the Strategy folder of the Zorro installation, under a name
like myscript2.c. Don't forget the ".c" at the end - it means that this file contains C code.

If you did everything right, you should now find myscript2 in the Script list. Select it. Time to [Test] our script. But what
is that? Instead of the expected result we'll get an error message "No main or run function!".

Does this error message mean that a script always needs a main() or run() function? Yes, main is a predefined function
name. If a function is named main, it will automatically start when we start our script. The function named run is special
to Zorro; it contains our trade strategy and is automatically run once for every bar period. If a script has neither
a main nor a run function, Zorro assumes that you made a mistake and will give you this error message.

Now, let's enter a main function at the end of the script:

function main()
{
compute_days();
}

The way I see it, this code 'calls' (meaning it starts) our compute_days function. Ok, now that we are here let's see how
we call a function: we write its name followed by a pair of parenthesis and then we end the line of code with a semicolon.
Sounds logical, doesn't it?

Important tip: write the lines of code for your functions first and call them later. The computer reads the code the same
way you read a book: it starts with the top of the script page and goes down to the bottom, reading the code line by line.
If I would write my script the other way around, like this:

function main()
{
compute_days();
}

function compute_days()
{
...
}

the computer will say: oh, that's function main. I know function main; I need to run it every time. What does it say
now? compute_days(). What's with this function? I don't know it yet! I don't know what it wants from me. I'm going to
display an error message and I will take the rest of the day off.

117
Don't forget to define your function first, otherwise the computer will complain when you try to use it. You will encounter
such compiler errors frequently when you write scripts - even experienced programmers make such mistakes all the
time. Sometimes it's a forgotten definition, sometimes a missing semicolon or bracket. Get used to compiler errors and
don't be helpless when you see one. The computer (usually) tells you what's wrong and at which line in the script, so
you won't need rocket science for fixing it.

A short summary of the new things we've learned:

► We define simple functions by writing "function name(...) { ... }".

► If a function has the name main or run, it is automatically executed. All other functions must be called from an
already-running function to be executed.

► Don't panic when you see a compiler error - with a little logical thinking they are easy to fix. It is uncool to ask on a
forum for help with compiler errors!

Passing variables to and from functions

A function can also get variables or values from the calling function, use them for its calculation, and give the resulting
value back in return. Let's see an example of a function that gets and returns variables:

var compute_days(var age)


{
return age * 356.25;
}

The var age between the parentheses is for passing a value to the function. This value is stored in the variable age,
which can be used inside the function like any other variable.

For returning a value from a function, just write it behind a return command. It can be a number, or a variable, or any
arithmetic expression that calculates the returned value. The function then gives that value back to from where it was
called.

You did notice that we defined this function not with the keyword "function", but with "var"? But is var not a variable
definition? Yes, but when a function is expected to return something, it must be defined with the type of the returned
variable. So if a function returns a variable of type int, define it with int; if it returns a var, define it with var. Still, the
compiler knows from the (..) parentheses that this is a function definition and no variable definition.

If a function expects variables, put their definition - type and name - in the function definition between the parentheses.
The space between the parentheses is named the parameter list of the function. If there are several variables in the
parmeter list, separate them with commas. When you then call that function, just put the values of the variables you
want to pass to the function between the parentheses.

If a function returns something, you can just place a call to that function in stead of the value that it returns. This sounds
sort of complicated? Let's try it right away with our new compute_days function. This is our new script:

var compute_days(var age)


{
return age * 365.25;

118
}

function main()
{
var my_age = 33;
var number_of_days = compute_days(my_age);
printf("I am %.f days old!",number_of_days);
}

We've just set our number_of_days variable direct from the returned value by compute_days(my_age). This makes
our code shorter and more elegant! Still, the result is the same:

Note that my_age is a variable inside the main function, while age is a variable inside the compute_days function.
Both variables don't know of each other: a variable defined in a funciton only exists within that function. But both have
the same value, as age gets its value from my_age when the function is called.

The printf function

Now what is this mysterious printf(..)? It has parentheses attached, so it's obviously also a function that we call for
displaying our result. However we have nowhere defined this function; it is a function that is already "built-in" in C. Just
as the built-in variables that we mentioned in the last workshop, there are also many functions already built in the script
language.

The "I am %.f days old!",number_of_days are the two variables that we pass to the printf function. The first variable
is a string, used for displaying some text: "I am %.f days old!". The second variable is a var: number_of_days.
Variables passed to functions are separated by commas. You can read details about the printf function in the manual;
for the moment we just need to know that the strange "%.f" in the string is a placeholder. It means: the function inserts
here the value - with no decimals - of the var that is passed as second variable to the function. So,
if number_of_days has the value 12045, our printf function will print "I am 12045 days old!".

We can make our code even shorter. Remember, if a function returns a var, we can just place a call to this function in
stead of the var itself - even inside the parentheses of another function. We'll save one variable and one line of script
this way. Programmers do such shortcuts all the time because they are lazy and prefer to type as less code as possible:

function main()
{
var my_age = 33;
printf("I am %.f days old!",compute_days(my_age));
}

!! Here's an important tip when you call functions in your code that return something. Do not forget the parentheses -
especially when the parameter list is empty! In C, an function name without parentheses means the address of that
function in the computer memory. add_numbers and add_numbers() are both valid and won't give an error message
in the code! But they are something entirely different. For avoiding errors, all Zorro's 'built-in' functions begin with a
lowercase character (such as enterLong()) and all built-in variables begin with an uppercase character (such
as BarPeriod).

Enough for today. The next workshop will teach us how a script can make decisions (called 'branches' in computerish).
Being able to decide something is important for trading. So we're now going in huge steps towards writing our own trade
strategy.

119
Workshop 3: Branches and Loops
If my bills grow bigger than $3000, I need to find a new job, else I will keep my existing job.

Yes, my brain is still working ok, thank you for asking. That was just an example of if - else branching; its associated
code would look like this:

if(my_bills > 3000)


find_new_job();
else
keep_old_job();

You will use "if" statements when you want your script to make decisions - meaning that it behaves differently depending
on some conditions, like user input, a random number, the result of a mathematical operation, a crossing of two
indicators, etc. Here's the basic form of the "if" statement:

if(some condition is true)


do_something(); // execute this command (a single command!)

or

if(some condition is true)


{
// execute one or several commands that are placed inside the curly brackets
}

A more complex form of the "if" instruction is listed below:

if(some condition is true)


{
// execute one or several commands that are placed inside the curly brackets
} else {
// execute one or several commands that are placed inside these curly brackets
}

The instructions placed inside the "else" part are executed only if "some condition" is not true. Here's a practical example:

if(my_age > 65)


income = 2000;
else // age less than or equal to 65?
income = 3000;

It is pretty clear that income can be either 2000 or 3000 because only one of the branches will be executed (income =
2000 or income = 3000, not both). The conditional parts of the code are called "branches" because several
nested if instructions can look like a tree with the root at the first "if" and many branches of which only one is executed.

By the way, have you notices how we indented the lines after the "if" and between the winged brackets? The C language
does not care how you indent lines or if you write a command on the same or on a new line, but we do that for clarity.
The code reads easier if dependent lines are indented. However, if we want we can write it also like this:

if(my_age > 65) income = 2000;


else income = 3000; // age less than or equal to 65

or even

if(my_age > 65) income = 2000; else income = 3000;

Let's draw some conclusions:

120
► "if" branching statements start with the if keyword followed by a pair of parentheses;

► the parentheses contain a comparison, or any other expression ("some condition") that can be true or false;

► if the expression is true, the following instruction or the set of instructions placed inside the first pair of curly
brackets is executed;

► if the expression is false and we don't use "else", the set of instructions placed between the curly brackets is
skipped (it isn't executed);

► if the expression is false and we are using the "else" branch as well, the set of instructions placed inside the first
pair of curly brackets is skipped, and the set of instructions placed inside the second pair of curly brackets is executed.

► indentation and lines are not needed for the language, but make code easier readable.

You'll be mastering these techniques in no time, trust me! Let's write the following script :

function main()
{
var profit = 50;
if(profit > 100)
printf("Enough!");
else
printf("Not enough!");
}

The code doesn't look that complicated; we have defined a var named profit which receives an initial value of 50, and
an if statement. If profit is greater than 100, we have enough, otherwise not. We can omit the if / else pairs of curly
brackets mentioned above when their content consists of a single line of code.

Create a new script - you have learned in the last workshops how to do that - paste the content, save it as "myscript3.c"
in the Strategy folder, select and [Test] it:

Now let's try something else. Modify the code by editing the red marked line:

function main()
{
var profit = slider(3,50,0,200,"Profit",0);
if(profit > 100)
printf("Enough!");
else
printf("Not enough!");
}

When you now click [Test] to run the script again, you'll notice that the bottom slider gets the label "Profit". Move it all
the way to the right, so that 200 appears in the small window, and click [Test] again:

121
What happened? The slider() function put its return value - which is the value from the bottom slider - into
the profit variable, and thus the if(..) condition became true as the value was now bigger than 100. We can make an
educated guess how the slider function works: It gets six variables - the slider number (3), the initial value (50), the right
and left borders (0 and 200), the name of the slider ("Profit"), and a tooltip (here 0, i.e. not used). Put the slider to the
right again and verify that the program now prints "Not enough!" when you click [Test] with the slider value at 100 or
below. You can now imagine how we can use the sliders for adjusting variables for our strategies.

Now let's assume that you want to do something only when two different conditions are fulfilled. Try the following
program:

function main()
{
var profit = slider(3,50,0,200,"Profit",0);
var loss = slider(2,25,0,200,"Loss",0);
if((profit > 50) and (loss == 0))
printf("Enough!");
else
printf("Not enough!");
}

Now two sliders are involved. How do you need to set them for the "Enough!" condition? We leave this as a little puzzle
to the reader... but we've learned that we can combine two conditions using the "and" keyword. That means both
conditions must be true for the combination to be true. There is also an "or" keyword when only one of the conditions
needs be true.**

Now, I'm giving you three important tips for avoiding coding mistakes in expressions and comparisons.

► Have you noted the use of parentheses around (profit > 50) and (loss == 0)? We know from school mathematics
that in a mathematical equation, the expressions in the parentheses are solved first. (1+2)*3 is not the same as 1 + (2*3)
- this is true in mathematics and also in a programming language. Always use parentheses to make sure that the
program calculates in the same order that we want... and make sure that you have as many opening as closing
parentheses! A missing parentheses at the end of a line is one of the most frequent reasons of compiler error messages.
The computer will usually complain about an error in the following line because it's there looking for the missing
parenthesis. That was the first tip.

► What's with that "loss == 0 " in the first new line of code? Is the double equals sign a typing error? No, it isn't.
Whenever you compare two expressions (loss and 0 in the example above) you have to use "==" instead of "=",
because a line of code that looks like this:

if(loss = 0)
{
// do some stuff
}

122
will set loss to zero instead of comparing loss with zero! This is one of the most frequent mistakes; even an experienced
programmer might set a variable to a certain value by mistake, instead of comparing it with that value. Using one instead
of two equality signs for comparing two expressions is a very frequent mistake. Don't forget this!

if(a == 3) // correct
{
// do_some_stuff
}

if(a = 3) // wrong!
{
// do_some_stuff
}

You can avoid this mistake is you make it a habit to put the constant value on the left side of a comparison and the
variable on the right side. The loss comparison statement would look this way:

if(0 == loss) // correct


...

If you then accidentally put a single '=' instead of '==', the computer will report an error because it knows that 0 can't be
set to a different value. That was the second tip.

► The third tip is that you should use the '==' comparison with care when comparing var variables. If a computer
calculates a mathematical expression, the result is usually inaccurate due to the limited floating point precision. Instead
of 2, the square root of 4 might be 2.000001 or 1.999997. If you then compare it with 2, the comparison will come out
false. Generally when comparing values with many decimals. such as prices, it's highly unlikely that two such values
will be ever exactly equal. Normally you'll only use 'greater' or 'smaller' comparisons (< or >) with var variables. This
problem does not affect int variables, as they have no decimals and can indeed be exactly equal.

Expressions

We've talked a lot about expressions, but what is an expression? An (arithmetic) expression is a sequence of variables,
numbers, operators, etc that are linked together. You can have simple expressions that look like the one below:

if(indicator > 50) // simple expression inside the parenthesis


{
// do something
}

Or you can have arbitrary complicated expressions that are a combination of sub-expressions:

if((factor1*indicator1 > 50) and (indicator2 < 35) and (volatility < 10)) // more complicated expression
{
// do something
}

You can combine as many expressions as you want, using parentheses, just like in the example above. The same rules
from your math classes apply here, too. Sadly, you have to know some basic math if you want to be a good trader.

Now that you know everything about if statements, let me just talk a little about a statement that seems quite similar
to if, but introduces a new concept in the programming language: Loops.

While loops

While I have less than a million, I must continue trading.

That was just an example of a "while loop". In code it would look like this:

123
while (my_money < 1000000)
trade(my_money);

The while statement has the same syntax as the if statement. There is some expression - in this case the condition that
the variable my_money is less than a million - and a command that is executed when the expression is true. However,
in the case of an if statement, the program whould then go on with executing the next command afterwards. In the case
of a while statement, the program "jumps back" to the while condition and tests it again. If it's still true, the command
is executed again. And the while condition is then tested again. And so on. This process is repeated until
the while expression eventually comes out false, or until eternity, whichever happens first.

A "loop" is called so because the program runs in a circle. Here's the basic form of the while loop:

while (some condition is true)


do_something(); // execute this command repeatedly until the condition becomes false

or

while (some condition is true)


{
// execute all commands inside the curly brackets repeatedly until the condition becomes false
}

Note that whatever the commands do, they must somehow affect the while condition, because when the condition never
changes, we have an "infinite loop" that never ends!

Here's a practical example of a while loop. Run this in Zorro:

function main()
{
int n = 0;
while(n < 10) {
n = n + 1;
printf("%i ",n);
}
}

This little program adds 1 to the variable n, prints the variable to the message window, and repeats this until n is 10.

Loops are very useful when something has to be done repeatedly. For instance, when we want to execute the same
trade algorithm several times, each time with a different asset. This way we can create a strategy that trades with a
portfolio of assets.

And there's more...

We are now at the end of the three basic workshops that teach general programming. In the next workshops we'll begin
with developing trade strategies; you've already learned all programming stuff that you'll need for those lessons. But
there's far more to programming - we haven't touched yet concepts such as macros, pointers, arrays, structs, classes,
or the Windows API. lite-C also supports some elements from C++, the 'big brother' of C - such as methods or function
overloading.

124
If you want, you can learn 'real' programming beyond the scope of trade strategies. Buy a C book or go through a free
online C tutorial, such as Sam's Teach Yourself C in 24 Hours. You can also join the Gamestudio community that
uses Zorro's lite-C language for programming small or large computer games. Check out how a 'real' C program looks
like: open and run the script Mandelbrot. It has nothing to do with trading and you will probably not understand yet
much of the code inside. It is a normal Windows graphics program. Don't worry, trading strategies won't be this
complicated - it just shows that programming has a lot more depth and can be a lot more fun even without earning
money with it.

** C/C++ programmers might wonder why they have to use and and or instead of the familiar operators && and ||.
Don't worry, all C operators work as before, we're just using easier-to-memorize operators for beginner's sake.

125
Workshop 4: Trend Trading. Price Series. Backtests.
Prediction is difficult. Especially about the future.
- Niels Bohr

The point of trading is knowing the moment when it's good to buy, good to sell, or good to do nothing. A trade
strategy uses market inefficiencies - deviations of the price curves from random data - for predicting future prices and
finding the right buying and selling points. That will be the topic of the next workshops. Two things to keep in mind:

► All strategies presented here are meant for educational purposes. They all are designed for simplicity, not for
maximum profit or robustness. For really trading such a strategy, you would normally add more entry filter rules, and
you would also use a more complex trade exit method than a simple stop. But we'll keep it easy in the workshops.

► The backtest results included here can be different to the results you'll get when testing the scripts yourself. That's
because you're most likely using a more recent simulation time period, and different spread, commission, and rollover
parameters which are updated when connecting to a broker. If not otherwise mentioned, the included scripts are set to
a simulation period from 2010-2015; for a different time period modify or remove the StartDate and EndDate lines in
the script.

The story of Alice and Bob

The most obvious way to make profits is going with the trend. Let's have a little stage play in which trader Bob tries to
explain his trade strategy to programmer Alice. Bob has just hired her to automatize his system:

Bob: I go with the trend. I buy long when prices start going up and I go short when they start going down.
Alice: And this works?
Bob: Sometimes. Depends on the market.
Alice: So you just buy long when today's price is higher than yesterday's price?
Bob: Nah, one single higher price alone won't do. Prices wiggle a lot. I look for the long term trend, like the trend of
the last month. I do that with a moving average. That means I just sum up the prices of the last 4 weeks and take their
average.
Alice: Good. That shouldn't be a problem to automatize.
Bob: Well, actually there is a problem. You see, a moving average is not very good for trend finding. For getting a
smooth trend curve the moving average period must be made long. But making the average long also lets it lag a lot
behind the prices. The signals are just not timely for a good trade. The trend is already over when my moving average
finally bends up or down. You need to sort of look ahead of the moving average curve, if you get my meaning.
Alice: So you want to know when a 4 weeks trend changes, but you need to know it in far less time than 4 weeks?
Bob: You got it.
Alice: I could use a lowpass filter for getting the trend curve. Second order lowpass filters have almost no lag. Will that
be ok for you?
Bob: I dunno what a second order lowpass filter is. But I trust you.
Alice: Good. So I buy when the trend curve changes its direction? For instance, when it starts to move up from a
valley, or down from a peak?
Bob: You got it.
Alice: How do you exit trades?
Bob: When it's the right time. Depends on the market.
Alice: I can exit a long position when entering a short one and vice versa. Does this make sense?
Bob: Yeah, that's what I normally do when I'm not stopped out earlier.
Alice: Stopped out?
Bob: Sure. A trade must be stopped when it's losing too much. We need a stop loss. Or do you want my whole
account wiped from some bad trade?
Alice: Certainly not before I got paid. At which price do you place the stop loss?
Bob: Not too far and not too tight. I don't want to lose too much, but I also don't want my trades stopped out all the
time.
Alice: So let me guess: it depends on the market?
Bob: You got it.

126
Following the conversation, Alice wrote this trade strategy script for Bob (Workshop4_1 script; agreed fee: $5,000):

function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,500));

Stop = 4*ATR(100);
if(valley(Trend))
enterLong();
else if(peak(Trend))
enterShort();
}

(If you're not yet familiar with scripts, start with Workshop 1.) We can see that the function is now named "run" and not
"main". "run" is also a special function name, but while a main function runs only once, a run function is called after
every bar with the period and asset selected with the scrollbars. By default, the bar period is 60 minutes. So this function
runs once per hour when Zorro trades.

Series

We're now going to analyze the code line by line. At the begin we notice two strange lines that look similar
to var definitions:

vars Price = series(price());


vars Trend = series(LowPass(Price,500));

However these special variables are defined with 'vars' and initialized with a series() function call. We define not a
single var here, but a var series - a var 'with a history'. (C++ programmers who peeked into the lite-C headers might
have noticed that vars is in fact a double* pointer, but that needs not bother us here). A series begins with the current
value of the variable, followed by the value the variable had one bar period before, followed by the value from two bar
periods before and so on. Series are mostly used for price curves and other data curves. For instance, we could use a
series to take the current price of an asset, compare it with the price from the previous bar, and do some other things
dependent on past prices.

For better readability of the code, Alice uses a simple naming convention. Her variable names always begin with an
uppercase letter and function names with a lowercase letter. This seems not very important, but such a convention helps
a lot reading code. An even better idea - sadly not realized by Alice here - would be to let array or series names always
end with a 's' (such as Prices, or Trends) for distinguishing them from single-value variables. Confusing single variables
and series is one of the top beginner's mistakes with scripts.

For getting the current value of a series, add [0] to the series name; for the value from one bar before add [1], for two
bars before add [2] and so on. In Alice's code above, Price[0] would be the current value of the Price series,
and Price[1] the value from 60 minutes ago. All better trading script languages support series in some way, and
all indicator, statistics, and other financial functions use series for calculating their results. We'll encounter price series
very often in trade scripts, so we'll become familiar with them.

The series() function converts a single variable to a series. The variable or value for filling the series is normally passed
as parameter to that function. However, we're not using a variable here, but the return value of another function call
instead. vars Price = series(price()); means: define a var series with the name "Price" and fill it with the return value
of the price() function. As we've learned in the last workshop, you can this way 'nest' as many function calls as you
want, using the return values of functions as parameters to other functions.

The price() function returns the mean price of the selected asset at the current bar. There are
also priceOpen(), priceClose(), priceHigh() and priceLow() functions that return the opening, closing, maximum and
minimum price of the bar; however, the mean price is usually the best for trade strategies. It's averaged over all prices
inside the bar and thus less susceptible to random noise.

vars Trend = series(LowPass(Price,500));

This line defines a series named "Trend" and fills it with the return value from the LowPass function. As you probably
guessed, this function is Alice's second order lowpass filter. Its parameters are the previously defined Price series and
a time period, which Alice has set to 500 bars. 500 bars are about 4 weeks (1 week = 24*5 = 120 hours). The lowpass
filter attenuates all the wiggles and jaggies of the Price series that are shorter than 4 weekss, but it does not affect the
127
trend or long.term cycles above two months. You can see the frequency characteristic of the LowPass filter in the image
on the Filter page.

In the image below, the black line is the original EUR/USD price and the red line is the result from the LowPass function:

A lowpass filter has a similar smoothing effect as a Moving Average function (see Indicators), but has the advantages
of a better reproduction of the price curve, and less lag. This means the return value of a lowpass filter function isn't as
delayed as the return value of a Moving Average function that is normally used for trend trading. The script can react
faster on price changes, and thus generate better profit.

Buying and selling

The next line places a stop loss limit:

Stop = 4*ATR(100);

Stop is a predefined variable that Zorro knows already, so we don't have to define it. It's the maximum allowed loss of
the trade. The position is sold immediately when it lost more than the given value. The limit here is given by 4*ATR(100).
The ATR function is a standard indicator. It returns the Average True Range - meaning the average height of
a candle - within a certain number of bars, here the last 100 bars. So the position is sold when the loss exceeds the
size of four candles. By setting Stop not at a fixed value, but at a value dependent on the fluctuation of the price, Alice
adapts the stop loss to the market situation. When the price fluctuates a lot, higher losses are allowed. Otherwise trades
would be stopped out too early when the price jumps down just for a moment.

A stop loss should normally be used in all trade strategies. It not only limits losses, it also allows the trade engine to
better calculate the risk per trade and generate a more accurate performance analysis.

The following lines are the core of Alice's strategy:

if(valley(Trend))
enterLong();
else if(peak(Trend))
enterShort();

The valley function is a boolean function; it returns either false (i.e. 0) or true (a value different to 0). Here it
returns true when the series just had a downwards peak. The peak function returns true when the series just had an
upwards peak. The if(..) condition then is then fullfilled, and a long or short trade with the selected asset is entered with
a enterLong or enterShort command. If a trade was already open in the opposite direction, it is automatically closed
by this command. Note how we combined the else of the first if with a second if; the second if() statement is only
executed when the first one was not.

Let's have a look into an example trade triggered by this command:

128
The red line in the chart above is the Trend series - the lowpass filtered price. You can see that it has a peak at the end
of September, so the peak(Trend) function returned true and the enterShort function was called. The tiny green dot is
the moment where the short trade was entered. The Trend series continues down all the way until November 23, when
a valley was reached. A long trade (not shown in this chart) was now entered and the short trade was automatically
closed. The green line connects the entry and exit points of the trade. It was open almost 2 months, and made a profit
of ~ 13 cents per unit, or 1300 pips.

Backtesting

Now let's just test how buying and selling works in that strategy. Start up Zorro, select the [Workshop4_1] script and
the [EUR/USD] asset, leave the [Period] slider at 60 minutes, then click [Test]:

Remember that you'll most likely get a different result when testing this strategy in a different time period or when your
simulated account has different spread, rollover costs, commission, or slippage. By default, the simulation runs over the
last 6 years, f.i. from 2010 until 2015. The strategy achieves an annual profit of ~600 pips, equivalent to about 50%
annual return on capital - that's the average profit per year divided by the sum of drawdown and margin. The average
monthly income (MI in the window) is 5 $. That's quite modest, but Zorro simulated a microlot account and needs only
~100 $ capital for keeping this strategy at a safe distance from a margin call. So you have about 5% return on capital
per month. By the way, the '$' sign in Zorro's messages does not necessarily mean US-Dollars, it represents the account
currency.

Although 50% look certainly better than the usual 2% of a savings account, Alice is not too excited about the result. The
low Sharpe Ratio (SR) tells her that this result comes at a risk. The Sharpe ratio is the mean profit divided by its standard

129
deviation. Sharpe ratios below 1 indicate that the gains fluctuate a lot - there might be years when the strategy achieves
a lower profit, or even a loss. This is confirmed by the profit curve that Alice gets by clicking [Result]:

In the image that pops up in the chart viewer, you can see a black curve and some green lines and red dots attached to
the curve. In the background there's a jaggy blue area and a red area below. The black curve is the price of the selected
asset - the EUR/USD, i.e. the price of 1 EUR in US Dollars. The price scale is on the left side of the chart. The green
and red dots are winning and losing trades. The green lines connect the entry and exit point of a winning trade. You can
see immediately that there are far more red than green dots - about 80% of the trades are lost. However, the long-term
trades all have green lines. So we have a lot of small losses, but several large wins. This is the typical result of a trend
following strategy.

The most interesting part of the chart is the blue area that represents the equity curve. We can see that it's below zero
in the first years, then rises to about 200 $ in 2014. The red area below is it's evil counterpart, the "underwater equity"
or drawdown curve that indicates losses on our account. The more blue and the less red, the better is the strategy. This
one gives a mixed result. The winning years are 2010 and 2011; in the other years we had a loss or a tie. This shaky
behavior is reflected in the low Sharpe Ratio and the high Ulcer Index (UI) of the strategy.

Analyzing the trades

If Alice had wanted to check the single trades of this strategy in detail, she had added the following line to
the run function:

set(LOGFILE);

LOGFILE is a flag - something like a "switch" that can be on or off. Such switches are turned on with the set() function.
If the switch is on, the next click on [Test] stores a log of all events in the Log subfolder. Add the line, click [Test], then
open Log\Workshop4_EURUSDtest.log with the script editor or any other plain text editor. It begins with a list similar
to this one:

BackTest: Workshop4 EUR/USD 2008..2013

[139: Thu 10.01. 07:00] 1.46810


[140: Thu 10.01. 08:00] 1.46852
[141: Thu 10.01. 09:00] 1.46736
[142: Thu 10.01. 10:00] 1.46721
[EUR/USD::S4300] Short 1@1.4676 Risk 6 at 10:00

[143: Thu 10.01. 11:00] 0p 0/1


[EUR/USD::S4300] Reverse 1@1.4694: -1.52 at 11:00
[EUR/USD::L4400] Long 1@1.4694 Risk 6 at 11:00

[144: Thu 10.01. 12:00] -20p 0/2


[145: Thu 10.01. 13:00] -20p 0/2
[146: Thu 10.01. 14:00] -20p 0/2
[EUR/USD::L4400] Reverse 1@1.4649: -3.54 at 14:00
[EUR/USD::S4700] Short 1@1.4649 Risk 6 at 14:00

[147: Thu 10.01. 15:00] -67p 0/3


[EUR/USD::S4700] Stop 1@1.4729: -6.22 at 15:00

130
[148: Thu 10.01. 16:00] -148p 0/3
[EUR/USD::L4900] Long 1@1.4744 Risk 6 at 16:00

The meaning of the messages is explained in the trading chapter. Let's go through the log above. The first short trade
is entered after bar number 142, starting January 10 10:00 am. Zorro bought 1 lot EUR/USD at a price of $1.4676. The
trade risk was about $6, determined by the stop loss distance (due to slippage the risk can be estimated only). After one
bar the short trade was closed at a loss of $1.52, the position was reversed, and a long trade was opened at a price of
$1.4694. It was closed 3 bars later at a $3.54 loss.

You can see in the log that most trades are lost. Zorro seems to deliberately enter trades in the wrong direction; trading
at random would only lose a little more than 50%, not 80%. Surely no human trader in his right mind would enter trades
this way! But there's a method behind this madness. The algorithm wants to be in a favorable position when a long-term
trend begins, and then keeps the position for a long time. That's why it wins in the long run despite losing most trades.

The trade distribution

For some more insight into the distribution of trades, Alice wants to plot the trade distribution. For this she adds the
following line to the very begin of the script (before the run function):

#include <profile.c>

This is a command to the compiler to insert another script file from the include folder. profile.c is a script that contains
functions for plotting price and trade statistics and seasonal analysis charts. For the trade distribution, Alice calls the
following function from inside the run function:

plotTradeProfile(-50);

This generates a trade distribution chart in steps of 50 pips at the end of the test run. The generated chart looks similar
to this one:

For generating the chart, all trades are sorted into buckets, depending on their profit. Every bucket is represented by a
red and a blue bar. Trades with a loss between -200..-100 pips go into the first bucket at the left side, marked -200 at
the x axis. The next buckets are for trades with loss or profit from -100..0 pips, 0..100 pips, 100..200 pips, and so on.
The height of the blue bar is the number of trades ending up in that bucket (right y axis), the height of the red bar is the
sum of all profits in the bucket (left y axis). We can see that most trades end with a loss between 0 and -50 pips. The
total profit of the system comes from relatively few profitable trades, some even with about 1000 pips profit.

This is a profit distribution that is less than optimal. With only few winning trades, the system is too dependent on chance.
It also requires strong nerves to trade it even automatically, as it will most likely begin with a loss streak and an equity
drawdown. And it suffers anyway from drawdowns all the time. Can Alice come up with some idea to improve the system
a little?

131
Filtering trades

Alice had noticed that the losing trades are all clustered around sideways price movements, while the wins occur during
long up or down trending periods. This effect can be used for filtering trades - suppressing trades as long as some
indicator suggests that the market is moving sideways.

Many indicators have been invented for detecting non-trending market situations, with more or less success. Alice uses
the MMI, the Market Meanness Index, an indiator that performs a statistical analysis of the data (Workshop4_2):

function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,500));
vars Signals = series(0);

Stop = 4*ATR(100);

vars MMI_Raw = series(MMI(Price,300));


vars MMI_Smooth = series(LowPass(MMI_Raw,500));

if(falling(MMI_Smooth)) {
if(valley(Trend))
enterLong();
else if(peak(Trend))
enterShort();
}
}

Alice has calculated the MMI for the last 300 bars and smoothed it with the LowPass filter:

vars MMI_Raw = series(MMI(Price,300));


vars MMI_Smooth = series(LowPass(MMI_Raw,500));

A falling MMI indicates that the market enters a trending situation. Only then trades are allowed:

if(falling(MMI_Smooth)) {
if(valley(Trend))
enterLong();
else if(peak(Trend))
enterShort();
}

This is the equity curve of the improved strategy:

The annual return is now higher, and most performance parameters - profit factor, Sharpe ratio, ulcer index - have
improved. But you can see that the strategy is still not perfect: 2013 and 2014 are still not winning years. Anyway,
Alice thinks that this strategy is now good enough for that lousy $5,000 programming fee, and Bob got what he
wanted. We'll see in the next workshop how to improve such strategies further.

132
What have we learned in this workshop?

• A strategy script contains a run function that is called once per bar.
• A series is a variable with a history.
• It is strongly recommended to use a naming convention (f.i. variables begin with uppercase, functions with lowercase,
series end with 's').
• A lowpass filter removes the jaggies from a price curve without much lag penalty. It is superior to traditional moving
averages.
• The valley and peak functions can be used to buy or sell at the turning points of a curve.
• A stop loss limits the trade risk.
• Use the LOGFILE switch for checking the strategy behavior in detail.
• The #include statement includes another script.
• The plotTradeProfile function gives insight into the trade profit distribution.
• Filtering trades improves the strategy performance.

133
Workshop 5: Counter-Trend Trading. Walk Forward Analysis.
Bob: Last month I ran into Warren Buffett and asked him for a trading advice. That's what he said: 'Be greedy when
others are fearful'.
Alice: Interesting. And what does it mean?
Bob: He didn't tell, but went away. But I think he wants me to go against the trend.
Alice: Isn't that just the opposite of your last strategy?
Bob: You got it. I need you to automatize this. I now buy long when prices moved down very much, and go short
when they moved up very much.
Alice: How much is very much?
Bob: Depends on the market.
Alice: I should have known.
Bob: Well, prices often move up and down in cycles. You must check if the price is close to the bottom or top of a
cycle. This is the right moment to buy or sell.
Alice: I can use a bandpass filter for cutting off the trend and the noise, and getting a clean price cycle.
Bob: Sounds good.
Alice: For finding how close the cycle is to its peaks, I can normalize it to a fixed range. And then apply a Fisher
transformation for giving the it a Gaussian distribution. This makes the peaks of the cycle relatively sharp and well
defined, so we won't get too many false signals.
Bob: I have no idea what you're talking about. But when it's sharp and well defined, I like it.
Alice: Of course it's more complicated than trend trading. This comes at a higher fee.
Bob: I like that less.
Alice: I can run a Walk Forward Optimization as an extra.
Bob: Huh? Will that get me more profit?
Alice: Not necessarily. But it will make your profit more certain. I want to be sure that you can afford me.

The counter trend algorithm

This is the first version of Alice's counter trend trading script (Workshop5_1 script; agreed fee: $12,000):

function run()
{
BarPeriod = 240; // 4 hour bars

// calculate the buy/sell signal


vars Price = series(price());
vars Filtered = series(BandPass(Price,30,0.5));
vars Signal = series(FisherN(Filtered,500));
var Threshold = 1.0;

// buy and sell


Stop = 4*ATR(100);
Trail = 4*ATR(100);
if(crossUnder(Signal,-Threshold))
enterLong();
else if(crossOver(Signal,Threshold))
enterShort();

// plot signals and thresholds


plot("Filtered",Filtered,NEW,BLUE);
plot("Signal",Signal,NEW,RED);
plot("Threshold1",Threshold,0,BLACK);
plot("Threshold2",-Threshold,0,BLACK);
PlotWidth = 600;
PlotHeight1 = 300;
}

Counter trend trading is affected by market cycles and more sensitive to the bar period than trend trading. Bob has told
Alice that bar periods that are in sync with the worldwide markets - such as 4 or 8 hours - are especially profitable with
this type of trading. Therefore she has set the bar period to a fixed value of 4 hours, or 240 minutes:

BarPeriod = 240;

The counter trend trade rules are contained in the following lines that calculate the buy/sell signal. The first line sets up
a price series just as in the trend trading strategy:

vars Price = series(price());

134
In the next line, a bandpass filter is fed with the price curve:

vars Filtered = series(BandPass(Price,30,0.5));

This bandpass filter has a center period of 30 bars and a width of 0.5. The BandPass function is similar to
the LowPass function except that it also dampens high frequencies, i.e. short cycles. Its frequency curve can be
examined in the Filters chapter. This way the trend (a cycle with a very long period) and the noise (short period cycles)
are removed from the price curve. The result is a clean curve that consists mostly of the medium-period peaks and
valleys. It's stored in a new series named Filtered.

Just like the original prices, the values of the Filtered price curve are still all over the place. For generating a trade signal
they must be normalized - meaning they are 'compressed' in a defined range so that they can be compared with a
threshold. In traditional technical analysis, an indicator called "Stochastic" is used for normalizing a curve. Alice prefers
the Fisher Transformation. This is an operation that transforms a curve into a Gaussian distribution - that's the famous
'bell curve' distribution where most values are in the center and only few values are outside the +1...-1 range.
Normalization and Fisher transformation are done with the FisherN function. It converts the Filtered series into the
normalized and Gaussian distributed Signal series, using the last 500 bars for the normalization.

vars Signal = series(FisherN(Filtered, 500));

The Signal series can now finally be compared with an upper and lower threshold for generating trade signals.
The Threshold is defined in the next line:

var Threshold = 1.0;

This line defines a new variable Threshold with a value of 1.0. Alice's intention is to let any Signal value that leaves
the +1.0 ... -1.0 range trigger a trade.

This happens in the following part of the code. But before we can start trading, Alice places a stop loss at an adaptive
distance from the price, just as in the trend trading script. The ATR function is again used to determine the stop loss
distance at the average height of 4 candles:

Stop = 4*ATR(100);

Additionally to the stop loss, Alice has also placed a trail limit 4 average candles away from the current price:

Trail = 4*ATR(100);

If the trade now goes in favorable direction by more than 4 average candles, the stop loss will follow the price at a
distance of 8 candles. This ensures that all trades that reach an 8 candle profit are guaranteed to end with a win,
regardless how the price further behaves. Trailing often - not always - improves the profit of a strategy, but is almost
always better than placing a profit target.

if(crossUnder(Signal, -Threshold))
enterLong();
else if(crossOver(Signal, Threshold))
enterShort();

When the Signal curve crosses the negative threshold from above - meaning when Signal falls below -1 - the price is
supposedly close to the bottom of the main cycle, so we expect the price to rise, and buy long. When the threshold is
crossed from below - meaning Signal rises above 1 - the price is close to a peak and we buy short. This is just the
opposite of what we did in trend trading. For identifying the threshold crossing we're using the crossOver()
and crossUnder() functions.

Plotting signals

Obviously, these trade rules are somewhat more complicated than the simple lowpass function of the previous lesson.
So Alice needs to see how the various series look lke, for checking if everything works as supposed. This happens in
the last lines at the end of the script.

135
plot("Filtered",Filtered,NEW,BLUE);

This line generates a plot of the Filtered series. It's plotted in a NEW chart window with color BLUE. We can use
the plot function to plot anything into the chart, either in the main chart with the price and equity curve, or below the
main chart in a new window.

The Signal curve and the upper and lower Threshold are plotted in another new chart window:

plot("Signal", Signal, NEW, RED);


plot("Threshold1", Threshold, 0, BLACK);
plot("Threshold2", -Threshold, 0, BLACK);

The first statement plots the Signal series as a red curve. The next two statements plot the positive and
negative Threshold with two black lines in the same chart window.

PlotWidth = 600;
PlotHeight1 = 300;

This just sets the width and height of the chart window. Below is the resulting chart. Load the script Workshop5_1 and
make sure that EUR/USD is selected. Click [Test], then click [Result]:

The blue curve in the middle window is the plot of the Filtered series. It shows the price fluctuation in the range of about
+/-0.005, equivalent to about 50 pips. The bottom window displays the Signal series. The black lines are the thresholds
that trigger buy and sell signals when Signal crosses over or under them. Plotting variables and series in the chart
greatly helps to understand and improve the trade rules. For examining a part of the chart in details,
the PlotDate and PlotBars variables can be used to 'zoom into' a part of the chart.

We can see that the script generates a positive return, although we again have profit bursts followed by long unprofitable
periods. But we also note something else: the blue Filtered curve got smaller fluctuations from 2012 on, indicating a
136
lower volatility of the EUR/USD. Apparently the market has changed in 2012. At the same time, the system didn't make
profits anymore. Is this just chance or can it be used to improve the strategy by filtering out unprofitable periods? This
may be subject to some further examination at a later time. We also notice that the red Signal curve does not have this
fluctuation variance because it's normalized.

The first question is whether the system is profitable at all. Had it achieved the same performance in live trading, or is
the result just due to a lucky choice of parameters? For getting some information how a system would behave in real
trading, a simple backtest is not enough. Alice needs to train the strategy.

Training

Training serves two purposes. At first, it improves the 'robustness' of a strategy. During the training run, strategy
parameters are optimized and adapted to the market until the strategy returns stable profits with minimum deviation.
The second purpose is finding out how sensitive the system is to small parameter changes. The more sensitive, the less
likely is it that backtest results are reproduced in live trading.

Which parameters are adapted in in which way is determined in the script; thus parameter adaption is no separate
process, but an integral part of the strategy. For this Alice has added some new commands to the strategy
(select Workshop5_2):

function run()
{
set(PARAMETERS); // generate and use optimized parameters
BarPeriod = 240; // 4 hour bars
LookBack = 500; // maximum time period

// calculate the buy/sell signal with optimized parameters


vars Price = series(price());
vars Filtered = series(BandPass(Price,optimize(30,20,40),0.5));
vars Signal = series(FisherN(Filtered,500));
var Threshold = optimize(1,0.5,1.5,0.1);

// buy and sell


Stop = optimize(4,2,10) * ATR(100);
Trail = 4*ATR(100);

if(crossUnder(Signal,-Threshold))
enterLong();
else if(crossOver(Signal,Threshold))
enterShort();
}

Parameter optimization requires some additional settings at the begin of the script:

set(PARAMETERS);
BarPeriod = 240;
LookBack = 500;

PARAMETERS is a flag - similar to the LOGFILE flag that we know from the last workshop - that tells Zorro to generate
and use optimized parameters. LookBack must be set to the 'worst case' lookback time of the strategy. The lookback
time is required by the strategy for calculating its initial values before it can start trading. It's usually identical to the
maximum time period of functions such as HighPass() or Fisher(). If the lookback time depends on an optimized
parameter, Zorro can not know it in advance; so we should make it a habit to set the LookBack variable directly when
we optimize a strategy. In this case we set it to the 500 bars required for the FisherN function to be on the safe side.

The signal calculation algorithm now also looks a little different (changes in red):

vars Filtered = series(BandPass(Price, optimize(30, 20, 40)));


vars Signal = series(FisherN(Filtered, 500));
var Threshold = optimize(1, 0.5, 1.5, 0.1);
...
Stop = optimize(4, 2, 10) * ATR(100);

Three parameters of the strategy are now optimized. The time period of the BandPass filter is set from the return value
of an optimize function. We notice that optimize is called with 3 numbers. The first is the parameter default value, which
is 30 - just the previous time period of the BandPass filter. The next two numbers, 20 and 40, are the parameter range,

137
i.e. the lower and upper limit of the time period. So the BandPass time period will now run from 20 to 40. During the
optimization process, Zorro will try to find the most robust time period within this range.

The next parameter to be optimized is Threshold. This time optimize is called with 4 numbers. The 4th number, which
is optional, is the step value to increase the parameter for every optimization run. So Threshold can now have any
value from 0.5 to 1.5 in steps of 0.1. Thresholds for triggerings signals should generally be optimized in fixed steps. If
the step width is omitted, Zorro increases the parameter value by 10% for every optimization step.

Alice also optimizes the factor for the stop loss distance between 2 and 10. Note that the parameter order in the script
matters when optimizing. Trade entry parameters - in this case, the time period and Threshold - should be optimized
first, exit parameters - such as Stop - afterwards. Theoretically, there could be even more parameters to optimize - for
instance the number of bars for the ATR function, or the Trail distance. But the more parameters we have, and the
larger their range is, the higher is the danger of overfitting the strategy. Overfitted strategies perform well in the
simulation, but poor in real trading. We'll look into this soon. Until then, just keep in mind that only few essential
parameters should be optimized, and only within reasonable parameter ranges.

For training the strategy, click [Train] and observe what the optimize calls do. During the training phase, which can take
about one minute depending on the PC speed, you'll see some charts pop up, like this:

Parameter 1 (time period)

Parameter 2 (Threshold)

138
Parameter 3 (Stop factor)

The parameter charts show how the parameter values affect the performance of the strategy. The red bars are
the return ratio of the training period - that's basically the total win divided by the total loss, multiplied by a penalty factor
for less trades. The dark blue bars are the number of losing trades and the light blue bars are the number of winning
trades. We can see that the time period produces slightly increasing returns up to about 35, then the returns go
down. Threshold has the best performance at about 1.0. The stop factor - the third parameter - slightly goes up and
has a maximum at about 7. We can also see here that a distant stop, although it increases the risk and eventually
reduces the profit, achieves a higher number of profitable trades and thus a better 'accuracy' of the strategy.

All red bars ending above 1.0 indicate a profitable parameter combination. In this case they stay above 1.0 over the
whole range, which means that the strategy performance is quite robust and not very sensitive to parameter changes.
The optimized parameters are stored in the file Data/Workshop5_2_EURUSD.par (the file name would be different for
other assets).

A click on [Test] reveals that training has improved the strategy. But if Alice would trust this result, she'd make a
severe mistake.

139
Walk Forward Optimization

Alice has used the price data from the last 6 years for optimizing the parameters, and has then used the same price
data for testing the result. This establishes a sort of self-fulfilling prophecy and generates too optimistic performance
figures (see the discussion of curve fitting bias under Testing). It also has a second problem. In 6 years, markets
change and trading strategies must adapt. It is not recommended to trade a trained strategy unchanged for many years;
normally the strategy parameters should be re-optimized in regular intervals for adapting them to the market situation.
Zorro can do that automatically while live trading, but how can we simulate this in a test and get some realistic prediction
of the real trading behavior?

The answer is Walk-Forward Optimization (WFO). Contrary to its name, it's not merely an optimization. It's an analysis
method that tests the strategy together with its parameter ranges and optimization method. If a strategy fails in a walk
forward analysis, it will also fail in real trading, even if it collected huge profits in backtests. For this reason, walk forward
optimization is the most important process when developing a strategy - and this workshop is the most important one
for learning strategy development.

All this requires only adding two lines to the script (Workshop5_3):

StartDate = 2005;
NumWFOCycles = 10;

This activates WFO with a data frame that is shifted in 10 cycles over the simulation period. The frame consists of a
training period and a subsequent test, as in the figure below:

Cycle Simulation period


1 LookBack Training Test
2 LookBack Training Test
3 LookBack Training Test
4 LookBack Training Test
5 LookBack Training

The lookback periods at the begin are needed to collect initial data for the functions. The training periods generate the
parameters that are then tested in the subsequent test periods. This ensures that every test uses "unseen" price data
that were not used for optimizing its parameters - just as in real trading. The data frame is then shifted over the simulation
period for verifiying how the strategy would fare when started at different times.

Because the test period is now much smaller than the whole simulation period, Alice has set StartDate to 2005. This
way enough data can be collected for getting still the same test period from 2010 to 2015.

After loading the Workshop5_3 script, click [Train]. Training now takes a few minutes because the simulation period is
now about 10 years, and a full optimization is performed for any of the 10 cycles. The optimized parameters are stored
in a separate parameter file for every WFO cycle. They are used when you click [Test] after the optimization process.
Click [Result] for getting the equity curve:

140
We can see that the strategy still stays profitable with walk forward analysis, but the equity curve does not look smooth
and the return in 2013 and 2015 was negative. In the next workshop we'll learn how to make strategy returns more
steady and reliable so that Bob can really derive a regular income from them.

Real time optimizing a WFO strategy

When trading a walk forward optimized strategy, it must be regularly re-trained and adapted to the current market
situation, just as in the WFO process. For this, Alice has added the following lines to the script:

if(ReTrain) {
UpdateDays = -1;
SelectWFO = -1;
}

ReTrain is nonzero when the [Train] button is clicked during live trading. UpdateDays is the time period for
automatically downloading new price data for the current asset from the broker's server, which is then used for the
subsequent training cycle. If set to -1, the price data is updated to the current date. Alternatively, price data from a Zorro
update could be manually copied into the History folder. SelectWFO tells Zorro not to optimize the whole simulation
period, but only a certain WFO cycle; in this case, the last cycle (-1) that contains the new price data.

Clicking [Train] every couple of months (at least every 35 weeks, as indicated by "WFO test cycles" in the performance
report) will continue the WFO process during trading, and make the strategy independent of external parameter settings.
This way we have essentially a 'parameter-free' strategy.

What have we learned in this workshop?

• A BandPass filter emphasizes a cycle and removes trend and noise from a price curve.
• The Fisher transform compresses a curve to a Gaussian distributed range.
• You can use the output of any filter or indicator function as input to another function.
• The plot function displays the curves of signals and indicators.
• The crossOver and crossUnder functions detect the crossing of a curve with a threshold or with another curve.
• Optimize parameters for making a strategy more robust.
• Walk Forward Optimization simulates trading under realistic conditions.
• WFO trained strategies should be re-trained in regular intervals.

141
Workshop 5: Counter-Trend Trading. Walk Forward Analysis.
Bob: Last month I ran into Warren Buffett and asked him for a trading advice. That's what he said: 'Be greedy when
others are fearful'.
Alice: Interesting. And what does it mean?
Bob: He didn't tell, but went away. But I think he wants me to go against the trend.
Alice: Isn't that just the opposite of your last strategy?
Bob: You got it. I need you to automatize this. I now buy long when prices moved down very much, and go short
when they moved up very much.
Alice: How much is very much?
Bob: Depends on the market.
Alice: I should have known.
Bob: Well, prices often move up and down in cycles. You must check if the price is close to the bottom or top of a
cycle. This is the right moment to buy or sell.
Alice: I can use a bandpass filter for cutting off the trend and the noise, and getting a clean price cycle.
Bob: Sounds good.
Alice: For finding how close the cycle is to its peaks, I can normalize it to a fixed range. And then apply a Fisher
transformation for giving the it a Gaussian distribution. This makes the peaks of the cycle relatively sharp and well
defined, so we won't get too many false signals.
Bob: I have no idea what you're talking about. But when it's sharp and well defined, I like it.
Alice: Of course it's more complicated than trend trading. This comes at a higher fee.
Bob: I like that less.
Alice: I can run a Walk Forward Optimization as an extra.
Bob: Huh? Will that get me more profit?
Alice: Not necessarily. But it will make your profit more certain. I want to be sure that you can afford me.

The counter trend algorithm

This is the first version of Alice's counter trend trading script (Workshop5_1 script; agreed fee: $12,000):

function run()
{
BarPeriod = 240; // 4 hour bars

// calculate the buy/sell signal


vars Price = series(price());
vars Filtered = series(BandPass(Price,30,0.5));
vars Signal = series(FisherN(Filtered,500));
var Threshold = 1.0;

// buy and sell


Stop = 4*ATR(100);
Trail = 4*ATR(100);
if(crossUnder(Signal,-Threshold))
enterLong();
else if(crossOver(Signal,Threshold))
enterShort();

// plot signals and thresholds


plot("Filtered",Filtered,NEW,BLUE);
plot("Signal",Signal,NEW,RED);
plot("Threshold1",Threshold,0,BLACK);
plot("Threshold2",-Threshold,0,BLACK);
PlotWidth = 600;
PlotHeight1 = 300;
}

Counter trend trading is affected by market cycles and more sensitive to the bar period than trend trading. Bob has told
Alice that bar periods that are in sync with the worldwide markets - such as 4 or 8 hours - are especially profitable with
this type of trading. Therefore she has set the bar period to a fixed value of 4 hours, or 240 minutes:

BarPeriod = 240;

The counter trend trade rules are contained in the following lines that calculate the buy/sell signal. The first line sets up
a price series just as in the trend trading strategy:

vars Price = series(price());

142
In the next line, a bandpass filter is fed with the price curve:

vars Filtered = series(BandPass(Price,30,0.5));

This bandpass filter has a center period of 30 bars and a width of 0.5. The BandPass function is similar to
the LowPass function except that it also dampens high frequencies, i.e. short cycles. Its frequency curve can be
examined in the Filters chapter. This way the trend (a cycle with a very long period) and the noise (short period cycles)
are removed from the price curve. The result is a clean curve that consists mostly of the medium-period peaks and
valleys. It's stored in a new series named Filtered.

Just like the original prices, the values of the Filtered price curve are still all over the place. For generating a trade signal
they must be normalized - meaning they are 'compressed' in a defined range so that they can be compared with a
threshold. In traditional technical analysis, an indicator called "Stochastic" is used for normalizing a curve. Alice prefers
the Fisher Transformation. This is an operation that transforms a curve into a Gaussian distribution - that's the famous
'bell curve' distribution where most values are in the center and only few values are outside the +1...-1 range.
Normalization and Fisher transformation are done with the FisherN function. It converts the Filtered series into the
normalized and Gaussian distributed Signal series, using the last 500 bars for the normalization.

vars Signal = series(FisherN(Filtered, 500));

The Signal series can now finally be compared with an upper and lower threshold for generating trade signals.
The Threshold is defined in the next line:

var Threshold = 1.0;

This line defines a new variable Threshold with a value of 1.0. Alice's intention is to let any Signal value that leaves
the +1.0 ... -1.0 range trigger a trade.

This happens in the following part of the code. But before we can start trading, Alice places a stop loss at an adaptive
distance from the price, just as in the trend trading script. The ATR function is again used to determine the stop loss
distance at the average height of 4 candles:

Stop = 4*ATR(100);

Additionally to the stop loss, Alice has also placed a trail limit 4 average candles away from the current price:

Trail = 4*ATR(100);

If the trade now goes in favorable direction by more than 4 average candles, the stop loss will follow the price at a
distance of 8 candles. This ensures that all trades that reach an 8 candle profit are guaranteed to end with a win,
regardless how the price further behaves. Trailing often - not always - improves the profit of a strategy, but is almost
always better than placing a profit target.

if(crossUnder(Signal, -Threshold))
enterLong();
else if(crossOver(Signal, Threshold))
enterShort();

When the Signal curve crosses the negative threshold from above - meaning when Signal falls below -1 - the price is
supposedly close to the bottom of the main cycle, so we expect the price to rise, and buy long. When the threshold is
crossed from below - meaning Signal rises above 1 - the price is close to a peak and we buy short. This is just the
opposite of what we did in trend trading. For identifying the threshold crossing we're using the crossOver()
and crossUnder() functions.

Plotting signals

Obviously, these trade rules are somewhat more complicated than the simple lowpass function of the previous lesson.
So Alice needs to see how the various series look lke, for checking if everything works as supposed. This happens in
the last lines at the end of the script.

143
plot("Filtered",Filtered,NEW,BLUE);

This line generates a plot of the Filtered series. It's plotted in a NEW chart window with color BLUE. We can use
the plot function to plot anything into the chart, either in the main chart with the price and equity curve, or below the
main chart in a new window.

The Signal curve and the upper and lower Threshold are plotted in another new chart window:

plot("Signal", Signal, NEW, RED);


plot("Threshold1", Threshold, 0, BLACK);
plot("Threshold2", -Threshold, 0, BLACK);

The first statement plots the Signal series as a red curve. The next two statements plot the positive and
negative Threshold with two black lines in the same chart window.

PlotWidth = 600;
PlotHeight1 = 300;

This just sets the width and height of the chart window. Below is the resulting chart. Load the script Workshop5_1 and
make sure that EUR/USD is selected. Click [Test], then click [Result]:

The blue curve in the middle window is the plot of the Filtered series. It shows the price fluctuation in the range of about
+/-0.005, equivalent to about 50 pips. The bottom window displays the Signal series. The black lines are the thresholds
that trigger buy and sell signals when Signal crosses over or under them. Plotting variables and series in the chart
greatly helps to understand and improve the trade rules. For examining a part of the chart in details,
the PlotDate and PlotBars variables can be used to 'zoom into' a part of the chart.

We can see that the script generates a positive return, although we again have profit bursts followed by long unprofitable
periods. But we also note something else: the blue Filtered curve got smaller fluctuations from 2012 on, indicating a
144
lower volatility of the EUR/USD. Apparently the market has changed in 2012. At the same time, the system didn't make
profits anymore. Is this just chance or can it be used to improve the strategy by filtering out unprofitable periods? This
may be subject to some further examination at a later time. We also notice that the red Signal curve does not have this
fluctuation variance because it's normalized.

The first question is whether the system is profitable at all. Had it achieved the same performance in live trading, or is
the result just due to a lucky choice of parameters? For getting some information how a system would behave in real
trading, a simple backtest is not enough. Alice needs to train the strategy.

Training

Training serves two purposes. At first, it improves the 'robustness' of a strategy. During the training run, strategy
parameters are optimized and adapted to the market until the strategy returns stable profits with minimum deviation.
The second purpose is finding out how sensitive the system is to small parameter changes. The more sensitive, the less
likely is it that backtest results are reproduced in live trading.

Which parameters are adapted in in which way is determined in the script; thus parameter adaption is no separate
process, but an integral part of the strategy. For this Alice has added some new commands to the strategy
(select Workshop5_2):

function run()
{
set(PARAMETERS); // generate and use optimized parameters
BarPeriod = 240; // 4 hour bars
LookBack = 500; // maximum time period

// calculate the buy/sell signal with optimized parameters


vars Price = series(price());
vars Filtered = series(BandPass(Price,optimize(30,20,40),0.5));
vars Signal = series(FisherN(Filtered,500));
var Threshold = optimize(1,0.5,1.5,0.1);

// buy and sell


Stop = optimize(4,2,10) * ATR(100);
Trail = 4*ATR(100);

if(crossUnder(Signal,-Threshold))
enterLong();
else if(crossOver(Signal,Threshold))
enterShort();
}

Parameter optimization requires some additional settings at the begin of the script:

set(PARAMETERS);
BarPeriod = 240;
LookBack = 500;

PARAMETERS is a flag - similar to the LOGFILE flag that we know from the last workshop - that tells Zorro to generate
and use optimized parameters. LookBack must be set to the 'worst case' lookback time of the strategy. The lookback
time is required by the strategy for calculating its initial values before it can start trading. It's usually identical to the
maximum time period of functions such as HighPass() or Fisher(). If the lookback time depends on an optimized
parameter, Zorro can not know it in advance; so we should make it a habit to set the LookBack variable directly when
we optimize a strategy. In this case we set it to the 500 bars required for the FisherN function to be on the safe side.

The signal calculation algorithm now also looks a little different (changes in red):

vars Filtered = series(BandPass(Price, optimize(30, 20, 40)));


vars Signal = series(FisherN(Filtered, 500));
var Threshold = optimize(1, 0.5, 1.5, 0.1);
...
Stop = optimize(4, 2, 10) * ATR(100);

Three parameters of the strategy are now optimized. The time period of the BandPass filter is set from the return value
of an optimize function. We notice that optimize is called with 3 numbers. The first is the parameter default value, which
is 30 - just the previous time period of the BandPass filter. The next two numbers, 20 and 40, are the parameter range,

145
i.e. the lower and upper limit of the time period. So the BandPass time period will now run from 20 to 40. During the
optimization process, Zorro will try to find the most robust time period within this range.

The next parameter to be optimized is Threshold. This time optimize is called with 4 numbers. The 4th number, which
is optional, is the step value to increase the parameter for every optimization run. So Threshold can now have any
value from 0.5 to 1.5 in steps of 0.1. Thresholds for triggerings signals should generally be optimized in fixed steps. If
the step width is omitted, Zorro increases the parameter value by 10% for every optimization step.

Alice also optimizes the factor for the stop loss distance between 2 and 10. Note that the parameter order in the script
matters when optimizing. Trade entry parameters - in this case, the time period and Threshold - should be optimized
first, exit parameters - such as Stop - afterwards. Theoretically, there could be even more parameters to optimize - for
instance the number of bars for the ATR function, or the Trail distance. But the more parameters we have, and the
larger their range is, the higher is the danger of overfitting the strategy. Overfitted strategies perform well in the
simulation, but poor in real trading. We'll look into this soon. Until then, just keep in mind that only few essential
parameters should be optimized, and only within reasonable parameter ranges.

For training the strategy, click [Train] and observe what the optimize calls do. During the training phase, which can take
about one minute depending on the PC speed, you'll see some charts pop up, like this:

Parameter 1 (time period)

Parameter 2 (Threshold)

146
Parameter 3 (Stop factor)

The parameter charts show how the parameter values affect the performance of the strategy. The red bars are
the return ratio of the training period - that's basically the total win divided by the total loss, multiplied by a penalty factor
for less trades. The dark blue bars are the number of losing trades and the light blue bars are the number of winning
trades. We can see that the time period produces slightly increasing returns up to about 35, then the returns go
down. Threshold has the best performance at about 1.0. The stop factor - the third parameter - slightly goes up and
has a maximum at about 7. We can also see here that a distant stop, although it increases the risk and eventually
reduces the profit, achieves a higher number of profitable trades and thus a better 'accuracy' of the strategy.

All red bars ending above 1.0 indicate a profitable parameter combination. In this case they stay above 1.0 over the
whole range, which means that the strategy performance is quite robust and not very sensitive to parameter changes.
The optimized parameters are stored in the file Data/Workshop5_2_EURUSD.par (the file name would be different for
other assets).

A click on [Test] reveals that training has improved the strategy. But if Alice would trust this result, she'd make a
severe mistake.

Walk Forward Optimization

Alice has used the price data from the last 6 years for optimizing the parameters, and has then used the same price
data for testing the result. This establishes a sort of self-fulfilling prophecy and generates too optimistic performance
figures (see the discussion of curve fitting bias under Testing). It also has a second problem. In 6 years, markets
change and trading strategies must adapt. It is not recommended to trade a trained strategy unchanged for many years;
normally the strategy parameters should be re-optimized in regular intervals for adapting them to the market situation.
Zorro can do that automatically while live trading, but how can we simulate this in a test and get some realistic prediction
of the real trading behavior?

The answer is Walk-Forward Optimization (WFO). Contrary to its name, it's not merely an optimization. It's an analysis
method that tests the strategy together with its parameter ranges and optimization method. If a strategy fails in a walk
forward analysis, it will also fail in real trading, even if it collected huge profits in backtests. For this reason, walk forward
optimization is the most important process when developing a strategy - and this workshop is the most important one
for learning strategy development.

All this requires only adding two lines to the script (Workshop5_3):

StartDate = 2005;
NumWFOCycles = 10;

This activates WFO with a data frame that is shifted in 10 cycles over the simulation period. The frame consists of a
training period and a subsequent test, as in the figure below:

147
Cycle Simulation period
1 LookBack Training Test
2 LookBack Training Test
3 LookBack Training Test
4 LookBack Training Test
5 LookBack Training

The lookback periods at the begin are needed to collect initial data for the functions. The training periods generate the
parameters that are then tested in the subsequent test periods. This ensures that every test uses "unseen" price data
that were not used for optimizing its parameters - just as in real trading. The data frame is then shifted over the simulation
period for verifiying how the strategy would fare when started at different times.

Because the test period is now much smaller than the whole simulation period, Alice has set StartDate to 2005. This
way enough data can be collected for getting still the same test period from 2010 to 2015.

After loading the Workshop5_3 script, click [Train]. Training now takes a few minutes because the simulation period is
now about 10 years, and a full optimization is performed for any of the 10 cycles. The optimized parameters are stored
in a separate parameter file for every WFO cycle. They are used when you click [Test] after the optimization process.
Click [Result] for getting the equity curve:

We can see that the strategy still stays profitable with walk forward analysis, but the equity curve does not look smooth
and the return in 2013 and 2015 was negative. In the next workshop we'll learn how to make strategy returns more
steady and reliable so that Bob can really derive a regular income from them.

Real time optimizing a WFO strategy

When trading a walk forward optimized strategy, it must be regularly re-trained and adapted to the current market
situation, just as in the WFO process. For this, Alice has added the following lines to the script:

if(ReTrain) {
UpdateDays = -1;
SelectWFO = -1;
}

ReTrain is nonzero when the [Train] button is clicked during live trading. UpdateDays is the time period for
automatically downloading new price data for the current asset from the broker's server, which is then used for the
subsequent training cycle. If set to -1, the price data is updated to the current date. Alternatively, price data from a Zorro
148
update could be manually copied into the History folder. SelectWFO tells Zorro not to optimize the whole simulation
period, but only a certain WFO cycle; in this case, the last cycle (-1) that contains the new price data.

Clicking [Train] every couple of months (at least every 35 weeks, as indicated by "WFO test cycles" in the performance
report) will continue the WFO process during trading, and make the strategy independent of external parameter settings.
This way we have essentially a 'parameter-free' strategy.

What have we learned in this workshop?

• A BandPass filter emphasizes a cycle and removes trend and noise from a price curve.
• The Fisher transform compresses a curve to a Gaussian distributed range.
• You can use the output of any filter or indicator function as input to another function.
• The plot function displays the curves of signals and indicators.
• The crossOver and crossUnder functions detect the crossing of a curve with a threshold or with another curve.
• Optimize parameters for making a strategy more robust.
• Walk Forward Optimization simulates trading under realistic conditions.
• WFO trained strategies should be re-trained in regular intervals.

149
Workshop 6: Portfolio strategies. Money management.
Bob: I got it! This is it!
Alice: Bob! Why are you running naked through the streets?
Bob: Huh? Oh, I was just sitting in my bathtub when I suddenly got this brilliant idea. I need you to program it
immediately. This will solve all my trading problems!
Alice: Problems? I thought you were already getting rich by automated trading?
Bob: It started well, but then it went all wrong. You know, from all the systems you programmed for me, the counter
trend system worked best.
Alice: I figured that.
Bob: And from all assets, I found it had the most profit with the EUR/USD. So I invested all my money in that system
trading with EUR/USD.
Alice: Oh no!
Bob: Yes! And it worked like a charm! My account went up from $100,000 to $350,000 in six months. I naturally
began to think about what do do with all that money. I had already ordered a Porsche and a golden bathtub.
Alice: Naturally.
Bob: But then all of the sudden, your system started losing money. My account went down and down and didn't stop
going down.
Alice: How much?
Bob: It lost $200,000 in a single month. I'm now back at $150,000, and it's staying there since weeks.
Alice: Wins and losses follow a random sequence. Any system will eventually meet a loss streak of any length and
depth - that's mathematically certain.
Bob: I know, I know, it's a drawdown and I must just sit it out. But I don't have the nerve for that. Every morning I'm
looking at the PC screen and my account is either going down, or not going up!
Alice: Put a blanket over your PC.
Bob: I got a better idea. That's why I was on the way to you. What if the system traded with many assets at the same
time?
Alice: Indeed, that could improve the Sharpe Ratio when the assets uncorrelated.
Bob: I have no idea what you mean with that. But when EUR/USD is in a drawdown, chances are that another pair,
like USD/JPY, is still profitable. So I lose with one but win with the other. And I can do this with a portfolio of many
assets.
Alice: Certainly.
Bob: And can't you also combine your systems into one? I mean a super system that goes with the trend and against
the trend at the same time? So I win when assets are trending and I also win when assets are cycling? Meaning I win
all the time?
Alice: You probably won't win all the time because I know that different price curves are still somewhat correlated. But
it can reduce your drawdowns.
Bob: Well, then program it. I'll sell my Porsche and pay you well.
Alice: Good. I'll start at once. Now better go home and put some clothes on.

The portfolio script

Bob has given Alice the task to write a script that trades with several assets simultaneously, and uses both trend and
counter-trend strategy algorithms. This is her first version (Workshop6_1 script, agreed fee: $28,000):

// Counter trend trading function from Workshop 5


function tradeCounterTrend()
{
TimeFrame = 4; // 4 hour time frame
vars Price = series(price());
vars Filtered = series(BandPass(Price,optimize(30,25,35),0.5));
vars Signal = series(Fisher(Filtered,500));
var Threshold = optimize(1,0.5,2,0.1);

Stop = optimize(4,2,10) * ATR(100);


Trail = 4*ATR(100);

if(crossUnder(Signal,-Threshold))
enterLong();
else if(crossOver(Signal,Threshold))
enterShort();
}

// Trend trading function from Workshop 4


function tradeTrend()
{
TimeFrame = 1; // 1 hour time frame
vars Price = series(price());
150
vars Trend = series(LowPass(Price,optimize(500,300,700)));

Stop = optimize(4,2,10) * ATR(100);


Trail = 0;

vars MMI_Raw = series(MMI(Price,300));


vars MMI_Smooth = series(LowPass(MMI_Raw,500));

if(falling(MMI_Smooth)) {
if(valley(Trend))
enterLong();
else if(peak(Trend))
enterShort();
}
}

function run()
{
set(PARAMETERS); // use optimized parameters
BarPeriod = 60; // 1 hour bars
LookBack = 2000; // needed for Fisher()
StartDate = 2005; // > 10 years
NumWFOCycles = 10; // activate WFO, 10 cycles

if(ReTrain) {
UpdateDays = -1;
SelectWFO = -1;
}

// double portfolio loop


while(asset(loop("EUR/USD","USD/JPY")))
while(algo(loop("TRND","CNTR")))
{
if(Algo == "TRND")
tradeTrend();
else if(Algo == "CNTR")
tradeCounterTrend();
}
}

The strategy is now divided into 3 different functions: tradeTrend for trend trading, tradeCounterTrend for counter
trend trading, and the run function that sets up the parameters, selects the assets, and calls the two trade functions.

The tradeCounterTrend function is almost the same as in the last workshop. At the begin of the function Alice has
added the line

TimeFrame = 4;

What does this mean? The trend trading strategy from workshop 4 used 60-minutes bars, while counter trend trading
was based on 240-minutes bars. When we trade both together in the same script, we'll need both types of bars.
The BarPeriod variable must not be changed at runtime, as it determines the sampling of the price curves. But
the TimeFrame variable does the job. Alice has set BarPeriod to 60 minutes, so it needs a TimeFrame of 4 bars for
getting the 240 minutes period for the counter trend strategy. TimeFrame affects all subsequent price and series calls,
all indicators using those series, and the ATR function.

The tradeTrend function uses the same algorithm as in workshop 4. The TimeFrame variable is set to 1, so this
strategy is still based on a 60-minutes bar period. The LowPass time period and the stop loss value are now optimized
with the method explained in the last workshop. Alice has also explicitly set the Trail variable to 0:

Trail = 0;

Trend trading works best when profitable trades last a long time; trailing would stop them too early. However,
the Trail variable was already set in the tradeCounterTrend function. If Alice had not reset Trail to 0 (meaning no
trailing), it would keep its last value, tradeTrend would trail too, and its profits would go down. When predefined
variables are used anywhere in the script, make sure that they have the right value at any place where they are needed.
This is a typical source of mistakes, so always look over the trade log as described in workshop 4 to be sure that trades
behave as they should.

The first lines of the run function are similar to the workshop 5 script, only BarPeriod is now at 60 and
consequently LookBack at 2000 for getting the same 500 4-hour periods.

151
The core of the strategy looks very unfamiliar:

while(asset(loop("EUR/USD","USD/JPY")))
while(algo(loop("TRND","CNTR")))
{
if(Algo == "TRND")
tradeTrend();
else if(Algo == "CNTR")
tradeCounterTrend();
}

We have two 'nested' while loops, each with two nested functions. Let's untangle them from inside out:

loop("EUR/USD","USD/JPY")

The loop function takes a number of arguments, and returns one of them every time it is called. On the first call it returns
the first parameter, which is the string "EUR/USD". We have learned in Workshop 1 that a string is a variable that
contains text instead of numbers. Strings can be passed to functions and returned from functions just as numerical
variables. If the loop function in the above line is called the next time, it returns "USD/JPY". And on all further calls it
returns 0, as there are no further arguments.

asset(loop("EUR/USD","USD/JPY"))

The string returned by the loop function is now used as argument to the asset function. This function selects the traded
asset, just as if it had been choosen with the [Asset] scrollbox. The string passed to asset must correspond to an asset
subscribed with the broker. If asset is called with 0 instead of a string, it does nothing and returns 0. Otherwise it returns
a nonzero value. This is used in the outer while loop:

while(asset(loop("EUR/USD","USD/JPY")))

This loop is repeated as long as the while argument, which is the return value of the asset function, is nonzero (a
nonzero comparison result is equivalent to 'true'). And asset returns nonzero (= true) when the loop function returns
nonzero. Thus, the while loop is repeated exactly two times, once with "EUR/USD" and once with "USD/JPY" as the
selected asset. If Alice had wanted to trade the same strategy with more assets - for instance, also with commodities or
stock indices - she only needed to add more arguments to the loop function, like this:

while(asset(loop("EUR/USD","USD/CHF","USD/JPY","AUD/USD","XAU/USD","USOil","SPX500","NAS100",...)))

and the rest of the code would remain the same. However, Alice does not only want to trade with several assets, she
also wants to trade different algorithms. For this, nested inside the first while loop is another while loop:

while(algo(loop("TRND","CNTR")))

The algo function basically copies the argument string into the predefined Algo string variable that is used for identifying
a trade algorithm in the performance statistics. It also returns 0 (= false) when it gets 0, so it can be used to control
the second while loop, just as the asset function in the first while loop. Thus, for every repetition of the first loop, the
second loop repeats 2 times, first with "TRND" and then with "CNTR". As these strings are now stored in the Algo
variable, Alice uses them for calling the trade function in the inner code of the double loop:

if(Algo == "TRND")
tradeTrend();
else if(Algo == "CNTR"))
tradeCounterTrend();

The if condition checks if the Algo string is identical to a given string constant (== "TRND") and returns nonzero, i.e.
true, in that case. So when Algo was set to "TRND" by the algo function, tradeTrend is called;
otherwise tradeCounterTrend is called. Due to the two nested loops, this inner code is now run four times per run() call:

• First with "EUR/USD" and "TRND"


• Then with "EUR/USD" and "CNTR"
• Then with "USD/JPY" and "TRND"
152
• And finally with "USD/JPY" and "CNTR"

Those are the 4 asset/algorithm combinations - the components - of the strategy. If Alice had instead entered 10 assets
and 5 algorithms in the loops, we had 50 (10*5) components and the inner code would be run fifty times every bar. Keep
in mind that all parts of the script that are affected by the asset - for instance, retrieving prices or calling indicators - must
be inside the asset loop. This is fulfilled here because anything asset related happens inside the two called trading
functions.

Training and result

Now it's time to [Train] the strategy. Because we have now 4 components, 4x more bars, and twice as many cycles, the
training process will take much longer than in the last workshop. As soon as it's finished, let's examine the resulting
parameters. Use the SED editor to open the Workshop6_1.par file in the Data folder. This file contains the optimized
parameters from the most recent WFO cycle. It could look like this:

EUR/USD:TRND +106 6.91 => 1.144


EUR/USD:CNTR 0.976 1.10 6.93 => 3.056
USD/JPY:TRND 386 6.93 => 3.089
USD/JPY:CNTR 1.73 1.09 5.75 => 1.899

As we can see, parameters for every asset/algo combination are optimized separately. Each line begins with the
identifier for the component, consisting of asset and algorithm separated by a colon. Then follows the list of parameters.
This allows Zorro to load the correct parameters at every asset() and algo() call. We can see that
the tradeTrend() function uses two parameters and the tradeCounterTrend() function three, and their optimal values
are slightly different for the "EUR/USD" and the "USD/JPY" assets. If a '+' sign appears in front of a parameter, its most
robust value has been found at the start or end of its range. The last number behind the "=>" is not a parameter, but
the result of the objective function of the final optimization run; it is for your information only and not used in the strategy.

Finally, let's have a look at the performance of this portfolio strategy. Click [Test], then click [Result].

You can see in the chart below that now two different algorithms trade simulaneously. The long green lines are from
trend trading where positions are hold a relatively long time, the shorter lines are from counter-trend trading. The
combined strategy does both at the same time. It generated about 80% annual profit with $400 capital requirement in
the walk forward test. The equity curve contains all typical characteristics encountered with most profitable automated
systems:

• Flat periods. There are long time periods with little profit in 2012, 2013 and 2014.
• Profit bursts. In 2011, 2014 and 2015 some lucky trades produced large gains within a relatively short period.
• Drawdowns. 2013 and 2015 had long periods with permanent losses.

153
Portfolio analysis

Now look at the end of the performance report that popped up when clicking [Result] (it is stored
under Workshop6_1.txt in the Log folder). You can see how the separate asset/algo combinations - the components -
performed during the test:

Portfolio analysis OptF ProF Win/Loss Wgt% Cycles

EUR/USD avg .045 1.67 203/280 82.7 XXXXXXXXX


USD/JPY avg .059 1.25 158/357 17.3 XXX\XXXXX

CNTR avg .092 1.73 254/157 89.7 /XXXXXXXX


TRND avg .034 1.15 107/480 10.3 XXXXXX\XX

EUR/USD:CNTR .099 1.89 159/85 73.3 /X//X\XXX


EUR/USD:CNTR:L .067 1.56 72/45 24.5 /\//\\/\X
EUR/USD:CNTR:S .124 2.25 87/40 48.8 /////\\/\
EUR/USD:TRND .037 1.23 44/195 9.4 XXXXX/\XX
EUR/USD:TRND:L .000 0.89 19/96 -2.1 /\/\\/\\/
EUR/USD:TRND:S .081 1.56 25/99 11.5 \/\///\/X
USD/JPY:CNTR .094 1.40 95/72 16.4 /\X\/XXXX
USD/JPY:CNTR:L .123 1.60 51/37 12.7 /\\\/////
USD/JPY:CNTR:S .055 1.19 44/35 3.6 /\/\/\\\X
USD/JPY:TRND .007 1.03 63/285 0.9 \/X\XX\X\
USD/JPY:TRND:L .057 1.30 27/143 4.6 \/\\//\/\
USD/JPY:TRND:S .000 0.74 36/142 -3.7 \//\\\\\\

The performance is listed for every component and separately for long (":L") and short (":S") trades (your own list might
look different when you tested a different time period). We can see that the highest contribution to the profit
(Wgt% column) came from the EUR/USD counter trend trading. Trend trading USD/JPY however did not perform as
well and made only a small contribution. Different assets apparently often require different trade methods.

The performance report raises an obvious question. Wouldn't it be wise to invest more money in the more profitable
components, and no money in the less profitable, such as USD/JPY trend trading? And how about reinvesting our
profits? We can see in the performance report that the system produces about $1500 profit from about $400 required
initial capital. How much more can we get, using the same strategy and the same capital, with a clever money
management?

Four money management methods

Money management is an algorithmic method of distributing your capital among a portfolio of assets and trade
algorithms in a way that your profit is maximized and your risk is minimized. These are contradictory conditions.
Consequently, many different money management methods and philosophies exist. The simplest method,
recommended in trade books, is: "Invest 1% of your account balance per trade". This is a bad advice: 1% is often either
too low for a decent profit, or too high for an acceptable risk. In fact, all strategies will inevitably some day run into a
margin call with the 1% method (read here why). The only question is how soon that happens.

Zorro follows a money management method developed by Ralph Vince. He published a computer algorithm that
evaluates every component's balance curve for calculating the optimal percentage of the gained capital to be reinvested.
This percentage is called the OptimalF factor (you can see it in the OptF column in the above performance sheet).
Multiply OptimalF with the capital earned so far, and you'll get the maximum margin to be allocated to a certain trade.
Normally, people try to stay well below this maximum margin. OptimalF is calculated from historical performance, thus
there's no guarantee that this performance will continue in the future. Exceeding the maximum margin is far worse than
staying below it, therefore hedge fund managers normally only invest about 50% of OptimalF.

Alice modified the run function for reinvesting profits (Workshop6_2):

function run()
{
set(PARAMETERS+FACTORS); // use optimized parameters and reinvestment factors
BarPeriod = 60; // 1 hour bars
LookBack = 2000; // needed for Fisher()
StartDate = 2005;
NumWFOCycles = 10; // activate WFO
Capital = 10000; // initial capital

154
if(ReTrain) {
UpdateDays = -1;
SelectWFO = -1;
reset(FACTORS); // don't re-train factors
}

// portfolio loop
while(asset(loop("EUR/USD","USD/JPY")))
while(algo(loop("TRND","CNTR")))
{
// set up the margin
Margin = 0.5 * OptimalF * Capital * sqrt(1 + ProfitClosed/Capital);

if(Algo == "TRND")
tradeTrend();
else if(Algo == "CNTR")
tradeCounterTrend();
}
}

There are three changes. At the begin of the script, Alice has now also set the FACTORS flag:

set(PARAMETERS+FACTORS);

One set() call can set several flags at the same time by just adding them with '+'. FACTORS lets Zorro
calculate OptimalF factors. [Train] generates now not only parameters, but also factors separately for every strategy
components. The factors are generated in the last optimization run and stored in Data\Workshop6_2.fac. It's a simple
text file, so all factors can be examined and edited.

Re-training the strategy only uses the last WFO cycle; this is not suited for generating new OptimalF factors. Therefore
Alice resets the FACTORS flag when the system is retrained:

if(ReTrain) {
UpdateDays = -1;
SelectWFO = -1;
reset(FACTORS);
}

Alice has now also invested $10000 initial capital:

Capital = 10000;

We could see in the last performance report that the strategy without reinvestment needed a minimum capital of about
$300. With $10000 we're certainly on the safe side. The OptimalF factors are used in the inner loop for determining the
trade volume:

Margin = 0.5 OptimalF * Capital * sqrt(1 + ProfitClosed/Capital);

Margin is one of the methods for determining the amount invested per trade. Other methods were giving the number of
lots or the risked amount. Margin is only a fixed percentage of the real trade volume - for instance 1% at 1:100 leverage
- that the broker keeps as a deposit. If Margin is left at its default value, Zorro always buys 1 lot, the minimum allowed
trade size. The higher the margin, the higher the number of lots and the higher the profit or loss. For determining the
optimal margin for the strategy component, our initial Capital is multiplied with the 50% of the OptimalF factor. The
result is then multiplied with a term that is supposed to start at 1 and to grow with the square root of the profit:

sqrt(1 + ProfitClosed/Capital)

ProfitClosed is the sum of the profits of all closed trades of the portfolio component. 1 + ProfitClosed/Capital is our
capital growth factor. Let's examine the strange formula with an example. Assume we started with $10000 capital and
the component made $20000 profit. The inner term inside the parentheses is then 1 + $20000/$10000 = 3. This is the
growth of the capital: we started with $10000 and have now $3000 on our account, so it grew by factor 3. The square
root of 3 is ~1.7, and this is the factor used for reinvesting our profits.

Why the square root - why don't we reinvest the whole profit? Because this would be very dangerous and can cause a
margin call sooner or later. The reason is that the drawdown depth of almost all trade systems grows with time; details

155
can be read here. This effect can be overcome by reinvesting only the square root of the profit. For checking how our
reinvesting method affects the final return of the system, train it first for generating the OptimalF factors, then click
[Test]:

Instead of the Annual Return, Zorro now displays the Compound Annual Growth Rate (CAGR). This is the average
annual growth of the invested capital during the test period. For instance, with 30% annual growth our $10,000 initial
capital would have grown to $13,000 at the end of the first year, to $16,900 at the end of the second year, and so on.
Consequently the money management system now generates a multiple of the previous profit:

Now let's try the hedge fund manager method, and invest 50% of OptimalF - with no square root:

Margin = 0.5 * OptimalF * (Capital + ProfitClosed);

156
This method achieves indeed the highest profit, $130,000. But this comes at the cost of deep and dangerous drawdowns
- most of the accumulated profit was lost in 2014. Trading this way requires strong nerves. It might also require having
a packed suitcase ready for fleeing the country when your clients' money vaporized in a margin call.

Finally, let's try the amateur trader method, and invest 1% of the account balance as recommended in most trading
books. Our margin is then calculated this way:

Margin = 0.01 * (Capital + ProfitClosed);

And this is the sad result:

Although the profit is much smaller than with any OptimalF investment method, the drawdowns are still significant. We
can imagine that a slightly higher investment, like 2% or 3%, would bring us close to a margin call. In fact, if we continued
to trade this way, even with only 1% it's mathematically certain that a drawdown will some day wipe out the account. By
the way, the same would happen when you don't reinvest your profits, but regularly withdraw them - but then at least
the withdrawn money is safe. Be always careful with reinvesting and withdrawing!

157
What have we learned in this workshop?

• The TimeFrame variable allows different time frames for algorithms or indicators within the same strategy.
• The loop function can be used to go through a list of assets or algorithms.
• The asset and algo functions select a certain asset and set up an algorithm identifier.
• The OptimalF factors give the maximum capital allocation to a certain strategy component.
• Set up the Capital variable for calculating the strategy performance with reinvestment.
• Reinvest only the square root of your profit.

158
Workshop 7: Machine Learning. Price Action Trading
Bob: Quick, close the door! Maybe I've been shadowed.
Alice: What happened?
Bob: Someone just revealed the ultimate trading secret to me.
Alice: Really?
Bob: Yes. It's a price action trading method. I need you to program this immediately.
Alice: Price action? What's that?
Bob: You don't use any indicators. You trade just when the price candle pattern is right. You compare the open, high,
low and close of the last candle with the open, high, low and close of the previous candles - that's the pattern. It's all
you need.
Alice: Didn't the Japanese use such candle patterns for rice trading, with funny names like "Three Black Crows"?
Bob: Yeah, 300 years ago. I don't think that Japanese rice candle patterns are still good for trading today. But I know
a guy named Bert at McDuck Capital. He found new candle patterns that work for the Forex market. He said it's like a
slot machine. The pattern appears and cash comes out. Bert got a mad bonus and McDuck is since then trading price
action with his patterns.
Alice: So you want me to write a script that looks for those patterns and then triggers a trade signal? Should not be a
problem.
Bob: Well, there is a problem. I don't know the patterns. I only know that they are made of three daily candles.
Alice: You don't know of which candles?
Bob: No, Bert said he had to kill me when he told me that. McDuck is very serious in that matter.
Alice: Hmm.
Bob: Can't you find out the patterns yourself?
Alice: If a guy at McDuck found them, I suppose I can find them too. But why do they work at all? I mean, why should
a price move be preceded by a certain candle pattern?
Bob: No idea. But this method worked for the Japanese rice market. Maybe there are some big traders that wake up
in the morning, look at the last 3 day candles, and if they like what they see they buy or sell.
Alice: If this establishes a pattern, I can apply a machine learning function. It goes through historic prices and checks
which candle patterns usually precede a price movement up or down.
Bob: Will that be expensive?
Alice: The search of candle patterns? No. Still, I'm afraid I'll have to charge more than last time.
Bob: Why is that?
Alice: Risk fee. I might get killed when programming this script.

The price action script

Alice uses Zorro's advise function for finding a system in candle patterns, and using the most profitable patterns for a
trade signal. This is her script (Workshop7, fee incl. risk: $30,000):

function run()
{
StartDate = 2005; // use > 10 years data
BarPeriod = 1440; // 1 day
BarZone = WET; // West European midnight
Weekend = 1; // separate Friday and Sunday bars
LookBack = 3; // only 3 bars needed
NumWFOCycles = 10; // mandatory for AI functions

set(RULES+TESTNOW); // generate rules, test after training


if(Train) Hedge = 2; // allow long + short
ExitTime = 6; // = one week

if(adviseLong(PATTERN+2,0,
priceHigh(2),priceLow(2),priceClose(2),
priceHigh(1),priceLow(1),priceClose(1),
priceHigh(1),priceLow(1),priceClose(1),
priceHigh(0),priceLow(0),priceClose(0)) > 40)
reverseLong(1);
if(adviseShort() > 40)
reverseShort(1);
}

Many lines in this code should be already familiar, but there are also some new concepts. Let's start with the most
important one, the machine learning algorithm.

The advise function

159
This looks like a strange entry condition for a long trade:

if (adviseLong(PATTERN+2,0,
priceHigh(2),priceLow(2),priceClose(2),
priceHigh(1),priceLow(1),priceClose(1),
priceHigh(1),priceLow(1),priceClose(1),
priceHigh(0),priceLow(0),priceClose(0) ) > 40)
reverseLong(1);

Alice calls adviseLong with the PATTERN classification method and the High, Low, and Close prices of the last 3
candles. If adviseLong returns a value above 50, a long trade is entered (reverseLong is a 'special version'
of enterLong; we'll come to that soon) . But when does this happen?

In training mode, the adviseLong function always returns 100. So a trade is always entered. The function stores a
'snapshot' of its signal parameters - in this case, 12 signals from the High, Low, and Close prices of the last 3 candles -
in an internal list. It then waits for the result of the trade, and stores the profit or loss of the trade together with the signal
snapshot. Thus, after the training run Zorro got a large internal list containing all signal snapshots and their
corresponding trade profits or losses.

The signals are then classified into patterns. Alice has put a +2 to the PATTERN parameter. This tells Zorro's pattern
analyzer to split the signals into two equal groups. Each got 6 of the 12 signals. The first group contains the prices of
the first two candles of the 3-candle sequence:

priceHigh(2),priceLow(2),priceClose(2),
priceHigh(1),priceLow(1),priceClose(1)

And the second group contains the prices of the last two candles:

priceHigh(1),priceLow(1),priceClose(1),
priceHigh(0),priceLow(0),priceClose(0)

Note that the middle candle, with offset 1, appears in both groups. The Open price is not used in the signals because
currencies are traded 24 hours a day, so the Close of a daily bar is normally identical to the Open of the next bar. Using
the Open price would emphasize outliers and weekend patterns, which is not desired.

Within every signal group, Zorro now compares every signal with every other signal. This generates a huge set of
greater, smaller, or equal results. This set of comparison results is a pattern. It does not matter if priceHigh(2) is far
smaller or only a little smaller than priceHigh(1) - the resulting pattern is the same. The patterns of the two groups are
now glued together to form a single pattern. It contains all information about all price comparisons within the first and
the second and within the second and the third candle, but the pattern does not contain any information about how the
first candle compares with the third. Bert had told Bob that it's best for price action trading to compare only adjacent
candles - therefore the two independent pattern groups. If Alice had looked for 4-candle-patterns, she'd used three
groups.

Aside from grouping, Zorro makes no assumptions of the signals and their relations. Therefore, in stead of candle
patterns any other set of signals or indicators could also be used for the advise function. After the pattern was classified,
Zorro checks how often it appears in training data set, and sums up all its profits or losses. If a pattern appears often
and with a profit, it is considered a profitable pattern. Zorro removes all unprofitable or insignificant patterns from the list
- patterns that don't have a positive profit sum or appear less than 4 times. From the remaining patterns, a pattern finding
function is generated and stored in the workshop7_EURUSD.c script in the Data folder. Such a machine generated
pattern finding function can look similar to this:

int EURUSD_L(float* sig)


{
if(sig[1]<sig[2] && eqF(sig[2]-sig[4]) && sig[4]<sig[0] && sig[0]<sig[5] && sig[5]<sig[3]
&& sig[10]<sig[11] && sig[11]<sig[7] && sig[7]<sig[8] && sig[8]<sig[9] && sig[9]<sig[6])
return 19;
if(sig[4]<sig[1] && sig[1]<sig[2] && sig[2]<sig[5] && sig[5]<sig[3] && sig[3]<sig[0] && sig[7]<sig[8]
&& eqF(sig[8]-sig[10]) && sig[10]<sig[6] && sig[6]<sig[11] && sig[11]<sig[9])
return 70;
if(sig[1]<sig[4] && eqF(sig[4]-sig[5]) && sig[5]<sig[2] && sig[2]<sig[3] && sig[3]<sig[0]
&& sig[10]<sig[7] && eqF(sig[7]-sig[8]) && sig[8]<sig[6] && sig[6]<sig[11] && sig[11]<sig[9])
return 74;
if(sig[1]<sig[4] && sig[4]<sig[5] && sig[5]<sig[2] && sig[2]<sig[0] && sig[0]<sig[3] && sig[7]<sig[8]
&& eqF(sig[8]-sig[10]) && sig[10]<sig[11] && sig[11]<sig[9] && sig[9]<sig[6])
160
return 43;
if(sig[1]<sig[2] && eqF(sig[2]-sig[4]) && sig[4]<sig[5] && sig[5]<sig[3] && sig[3]<sig[0]
&& sig[10]<sig[7] && sig[7]<sig[8] && sig[8]<sig[6] && sig[6]<sig[11] && sig[11]<sig[9])
return 68;
....
return 0;
}

Machine generated code is automatically called by adviseLong/Short in test or trade mode, or when training other
parameters. The functions in the code get their names from their asset, their algo, and whether they are used for long
or short trades. This way many different functions can be stored in the same .c file. The function here has the
name EURUSD_L. The list of signals is passed to the function as a float array named sig. float is a variable type
similar to var, but has lower precision and thus consumes less memory. sig[0] is the first signal passed to
the advise function - in this case priceHigh(2). sig[1] is the second signal (priceLow(2)) and so on. The signals are
accessed inside the function just like the elements of a series.

We can see that the function contains many if() conditions with many comparisons of signals with other signals.
Any if() condition represents a pattern. The comparisons are linked with && (that's the same as the and operator), so
the if() condition is true only when all its comparisons are true. In this case a certain value, like 19 in the
first if() condition in the above example, is returned by the function. If none of the if() conditions is true, 0 is returned,
meaning that no pattern is found. The returned value is the pattern's score - its information ratio multiplied with 100. The
higher the information ratio, the more predictive is the pattern.

Alice compared the returned value with 40, meaning that a long trade is entered for any pattern match with a score
above 40. Short trading just works the same way:

if(adviseShort() > 40)


reverseShort(1);

The adviseShort call has no parameters. In that case the function uses the same method and signals as the
last advise call, which was the preceding adviseLong. This way lengthy signal lists don't have to be written twice.

Limiting the number of trades

For reducing drawdown and risk, Alice wants to open only one trade at a time. If a profitable pattern is detected and a
trade in the pattern direction is already open, no more trades should be entered. One way to achieve this is evaluating
the NumOpenLong or NumOpenShort variables. So Alice would enter a trade only when the number of open trades
is less than the limit. This would require an extra conditon in the trade signal, like this:

if (adviseShort() > 40 and NumOpenShort < 1)


enterShort();

However, this method would not close trades in opposite direction when the condition is not fulfilled. It also just keeps
the previous trades open, but it would be better to update them with the stop loss or take profit limit from the new trade,
as if they were replaced by the new one. And finally, it would also limit the trades in training mode, which means training
would miss many patterns. Taking care of all this requires more code.

Because this is a frequent task, a helper function reverseLong / reverseShort has been written for this. This function
can be found in the include\default.c file. This way it is automatically included and can be called in all strategies. It first
establishes a pending exit for trades in the same direction, this way effectively updating the stop loss and take profit
limits of open trades. It also updates the trade exit time. Then it checks if the trade number limit is reached. If not, it
opens a new trade, otherwise it simulates a reversal by explicitly closing all trades in opposite direction. For using it, just
replace enterLong() with reverseLong(1) and enterShort() with reverseShort(1).

The only parameter to the functions is the maximum allowed number of open trades. In this case, Alice allows only one
trade at the same time, so the parameter is 1.

Price action set-up

There are still some prerequisites for pattern analysis that haven't been discussed yet. Let's look at the rest of the code:

161
StartDate = 2004;
BarPeriod = 1440;
NumWFOCycles = 10;

NumWFOCycles or some other out-of-sample test method is mandatory for this type of strategy. All machine learning
systems tend to overfitting, so any in-sample result from price patterns, decision trees, or preceptrons would be far too
optimistic and thus meaningless. The number 10 is a compromise: higher numbers produce more WFO cycles, ergo
less bars for any cycle to train, so less patterns are found and the results become more random. Lower numbers produce
more bars per cycle and more patterns are found, but they are from a longer time period - above one year - within which
the market can have substantially changed. So the results can become more random, too.

The number of bars from the same time period could theoretically be increased with oversampling. Unfortunately,
oversampling is useless for daily bars because the High, Low, and Close prices depend on a certain bar start and end
time. Resampled bars would produce very different patterns. So Alice has to use more than 10 years for the simulation
period to get enough data for training. (If price data from a certain year is not included in the Zorro program, it can be
downloaded either automatically from the broker server, or with the historic price package from the Zorro download
page).

BarZone = WET;

Normally, daily bars begin and end at UTC midnight. But for price patterns the time zone of the bars is critical. Alice has
to catch a time with low volatility. A good time for low EUR/USD volatility is midnight in Western
Europe. BarZone determines the time zone of a daily bar; WET is the Western European Time, the time zone of
London, considering daylight saving time.

Weekend = 1;

The Weekend variable determines how the simulator deals with weekend bars. Normally, no bar is allowed to start or
end within a weekend. This means that for daily bars, the bar starting Friday 00:00 midnight would end Monday 00:00
midnight. This is not desired here because this bar would then contain prices from Friday as well as from Sunday
evening, and spoil the candle pattern. Thus, Weekend = 1 enforces the Friday bar to end Saturday 00:00 midnight,
although due to the weekend no trades can be entered on that bar. The week then consists of 6 instead of 5 bars.

LookBack = 3;

Because the strategy needs only the last 3 candles for trade decisions, we can set the lookback period from its default
80 down to 3 bars. This gives us three months more for training and testing.

set(RULES+TESTNOW);

The RULES flag is required for generating price patterns with the advise function. TESTNOW runs a test automatically
after training - this saves a button click when experimenting with different pattern finding methods.

The next code part behaves different in training and in test or trade mode:

if(Train) Hedge = 2;

Train is true in [Train] mode. In this mode we want to determine the profitability of a trade that follows a certain
pattern. Hedge is set to 2, which allows long and short positions at the same time. This is required for training the
patterns, otherwise the short trade after adviseShort would immediately close the long positions that was just opened
after adviseLong, and thus assign a wrong profit/loss value to its candle pattern. Hedge is not set in test and trade
mode where it makes sense that positions are closed when opposite patterns appear.

ExitTime = 6;

ExitTime sets the duration of a trade to 5 bars, equivalent to one week. If a trade is not closed by an opposite pattern,
it is closed after a week. The trade results after one week are also used for training the candle patterns and generating
the trade rules.

162
The result

Click [Train]. Depending on the PC speed, Zorro will need a few seconds for running through the five WFO cycles and
finding about 50 profitable long or short patterns in every cycle. Click [Result] for the equity curve:

The machine learning algorithm with daily candle patterns seems to give us a relatively steadily rising equity curve and
symmetric results in long and short trading. This was apparently the top secret result that Bert presented to his employer.
But was his mad bonus justified? Or was his employer just fooled by randomness? Find out by running the test many
times with a randomized price curve (Detrend = SHUFFLE), plotting a histogram of the results, and comparing it with
the result from Alice's test. How to do such an histogram will be explained in the next workshop.

What have we learned in this workshop?

• Daily candle patterns can have predictive power under certain circumstances.
• The advise function generates trade rules with machine learning algorithms.
• Out of sample testing is mandatory for AI based strategies.
• The Weekend variable determines the sampling of weekend bars.
• if(Train) can be used for different setups depending on the mode.
• ExitTime limits the duration of a trade.
• The reverseLong / reverseShort functions keep a limit to the number of open trades.

163
Workshop 8: Anatomy of a Scam Robot.
Bob: May I ask you a personal question?
Alice: What question?.
Bob: Are you an over honest person?
Alice: If I recall our contracts, I charged you so far $75,000 for about 100 lines of code. Does that answer your
question?
Bob: Well, let's forget about the past contracts. The thing is, I need your help now to get rich.
Alice: You should already get some good income with my scripts. What was with the price action script that I
programmed for you, with the McDuck method?
Bob: McDuck went broke last week. And I'm tired of those experiments. I don't want just some income, I want to get
rich. I mean really, obscenely rich. I want my own island in the Caribbean, and I feel that for this I'll need other
methods. I thought about offering expensive trading seminars, but I'm not a good speaker. I could write expensive
trading books, but I'm not a writer. So, my idea is to sell an expensive trading robot. I already have a name: "Forex
Turbo Growth Pilot". How does this sound?
Alice: You want me to program a scam robot? For ripping people off?
Bob: Ripping off... that's an ugly word. I mean, what can someone expect when he buys a robot? I just want you to
program a robot that looks better than all the other robots.
Alice: And it does not matter if it really generates profit or not?
Bob: It's a robot after all. It must of course appear as if it would spit out money like mad, but it needs not really do that.
Otherwise I would naturally use it myself and would not sell it.
Alice: That makes programming it sort of easier.
Bob: I have already ordered the advertising: 95% win rate! 20,000 pips annual profit! Confirmed with real trading!
Your robot must produce those figures. The equity curve must go straight up to the sky, and it must come from a year
live trading. A backtest won't do. Sadly, today people won't buy a robot that has no excellent trading history verified by
myfxbook.
Alice: So I have to program a robot that is not profitable, but nevertheless has 95% win rate and produces 20,000
pips per year on a real account?
Bob: You got it. Can you do that?
Alice: Sure. Piece of cake.
Bob: But I guess you'll still charge an outrageous fee?
Alice: I have to. This job will give me a guilty conscience. This must be financially compensated.

The robot script

A robot script works in a very different way than a normal trading strategy. The least important part of the script is the
trade signal algorithm. Robots normally just use some simple indicators, similar to the systems posted by beginners on
trader forums. Robot developers are aware that such systems are unlikely to generate profit, but this doesn't matter for
reasons that will soon become clear. Alice decided for the simplest possible approach - this is her first robot script
version (fee: $44,000):

function run()
{
if(random() > 0)
enterLong();
else
enterShort();
}

The strategy enters a random trade on any bar. The random function will in 50% of all cases return a number that is
greater than 0, therefore triggering a long trade; otherwise it will trigger a short trade. If trading had no cost, this strategy
had an expectancy of zero. Selecting EUR/USD and clicking [Test] however reveals an average loss of about 3 pips
per trade. 3 pips are just the simulated broker's spread for EUR/USD, the Ask-Bid price difference that is always lost.
So no surprise here.

This random trading script obviously won't sell to anyone. Alice has to pimp it up. The first step is setting up some system
parameters and fulfilling Bob's demand of the 95% win rate:

function run()
{
BarPeriod = 1440;
StartDate = 2015;
NumYears = 1;
LookBack = 0;

164
Stop = 200*PIP;
TakeProfit = 10*PIP;

if(NumOpenTotal == 0) {
if(random() > 0)
enterLong();
else
enterShort();
}
}

The robot shall trade once per day, so Alice needs a 1440 minutes bar period. Backtest is restricted to simulate the year
2015 - the robot must work for one year only, so a longer backtest is not necessary. It also uses no lookback period, as
there's no indicator or other function that would need any price history. Therefore, this is the parameter setup:

BarPeriod = 1440;
StartDate = 2015;
NumYears = 1;
LookBack = 0;

The next lines set a stop loss at 200 pips distance from the current price, and a profit target at 10 pips distance:

Stop = 200*PIP;
TakeProfit = 10*PIP;

This establishes a risk/reward ratio of 20. Each trade risks 200 pips for a potential 10 pips reward. But this also means
that the profit target will be hit 20 times earlier than the stop loss, and thus 20 times more often. From 20 trades, 19 will
be won and only one will be lost - this is the 95% accuracy that the robot needs for matching Bob's advertisement.

However, Alice must make sure that any trade ends with hitting either the stop loss or the profit target. Any other exit
would spoil the 95%. Another exit happens when entering a trade in reverse direction, which automatically closes the
current trade. One method to prevent this would be hedging. Hedging however is not allowed to US traders, who are
the main buyers of robots. For not losing the US market, Alice prevents trade reversal by only entering a new trade
when no trade is open:

if(NumOpenTotal == 0) {
if(random() > 0)
enterLong();
else
enterShort();
}

The predefined variable NumOpenTotal is the current number of open trades.

A click on [Test] reveals that the current script version has indeed about 95% win rate (workshop8_1). Of course this
does not improve its performance. 19 out of 20 trades are won, but the loss from the 20th eats all the profits from the
19 winners before. The only effect of the high win rate is now a strange sawtooth pattern in the equity curve:

165
We can see that sequences of winning 1-day trades cause parts of the equity curve to raise linearly up to the point
where a trade is not hitting the profit target at the same day. This trade will stay open for a longer time, possibly hit its
stop loss, and spoil the equity curve. Profit-wise the system is no better than the version before.

But Bob wants an annual return of about 20,000 pips - enough to arouse expectation of great wealth and sell lots of
robots. How can Alice adjust the script to generate this profit - real money, from live trading - and this with an obviously
unprofitable strategy?

For this she has to use magic - the magic of statistics.

Distributing profits

The average loss of a random trade is the spread or commission. Thus, one trade per day and 3 pips spread will produce
a 750 pips average loss per year. This does not mean that every random trader will suffer 750 pips loss by the end of
the year. Some might end up with a larger loss, others even with a profit. Let's give 3000 traders some initial capital and
the task to enter random trades, one trade per day, for one year. At the end of the year we'll put together their profits or
losses in a histogram. It will look like this:

166
This profit distribution chart can be generated with the workshop8_2 script. It runs 3000 1-year simulation cycles of
Alice's random trading strategy and thus might take a minute to complete. The x axis of the chart is the profit or loss in
pips at the end of the year. The y axis shows the number of traders that got that particular profit or loss. We can see
that the largest group - about 130 traders - had a loss in the 500 pips range after a year. There are also some unfortunate
traders with more than 7000 pips loss, but on the other hand, far on the right side of the chart, a lucky few made more
than 7000 pips profit! No doubt, those guys consider themselves genius traders and brag with their success on trader
forums...

This profit distribution is a Gaussian Normal Distribution - the famous "Bell Curve". It looks a little jaggy because 3000
samples are not enough for a perfect bell. When running the simulation with more traders, the curve will become more
regular, but the script will need more time to run. The peak of the bell curve is at -750 - the average loss to be expected
with 3 pips spread per trade.

Let's have a look into the workshop8_2 script, in order to understand how this chart is generated:

function strategy1()
{
if(random() > 0)
enterLong();
else
enterShort();
}

function run()
{
BarPeriod = 1440;
StartDate = 2015;
NumYears = 1;
LookBack = 0;

// run this simulation 3000 times


NumTotalCycles = 3000;
strategy1();

// plot the result of every run in a bar graph


if(is(EXITRUN)) {
int Step = 250;
int Result = floor(ProfitClosed/PIPCost/Step);
plotBar("Profit",Result,Step*Result,1,SUM+BARS+LBL2,RED);
}
}

Alice's strategy is now called as an external function strategy1 - that's not really necessary, but makes it easier to
experiment with different strategies. Some commands in the script are new. We're setting a variable for running the one-
year simulation many times:

NumTotalCycles = 3000;

This just repeats the simulation 3000 times, one simulation cycle per trader.

if(is(EXITRUN)) { ...

EXITRUN is a status flag that is set on the last run of every cycle, at the end of the year. is(EXITRUN) then
becomes true and the following lines are executed:

int Step = 250;


int Result = floor(ProfitClosed / PIPCost / Step);

ProfitClosed - the same as WinLong+WinShort-LossLong-LossShort - is the result of the current simulation cycle.
We divide the result by PIPCost for converting it to pips. We divide it further by 250 (the Step variable) for distributing
the results among 250 pips wide bars. If a result is 1 pip or 249 pips does not matter - both contribute to the same bar.
The floor function converts the resulting value to an integer that we can plot in a chart. For this the plotBar function is
used:

plotBar("Profit",Result,Step*Result,1,SUM+BARS+LBL2,RED);

167
This draws a bar in a graph named "Profit" at a chart position given by Result. The x axis value belonging to that bar
is Step*Result. We had divided the result by 250 for the distribution among bars, so this multiplication lets the bar's pip
value appear on the x axis below the bar. The 1 is the height of the bar. The height is summed up (SUM), so the bar
height increased by 1 for every cycle whose result matches the bar's pip value. BARS tells the plotBar function to plot
bars instead of a line, and LBL2 tells it to print only every second value on the x axis - otherwise it would be hard to
read. The last parameter, RED, gives the color of the bar.

Trading myths and trading gurus

You can see that the resulting chart above also debunks a widespread myth in the trader scene. It is a "known fact" that
95% of all private traders lose all their money in the first 6 months. Not true - at least not with completely random trading
and one trade per day. You can see from the profit distribution that only about 55% lose money in the first year at all
(the sum of the red bars with negative profit), while 45% end their first year with a profit. Of course, most of those lucky
45% will then lose everything in one of the following years when they continue trading. But depending on the initial
capital, it can take 5 years or more until really 95% of random traders have lost their money and quit the game. The 5%
survivers enjoyed continual profits from their trading. Of course they won't attribute their success to the bell curve, but
to their ingenious trading skills.

The bell curve does indeed not fully reflect the reality. Traders do not throw a coin. They normally follow some system
or gut feeling. This does not change the survival rate, and it does not increase the win chance, but it changes the form
of the curve. The profit distribution of real traders is not a Gaussian, but a Lévy distribution. It has a smaller peak and
fatter tails. That means the losers lose more, and the winners take more than in a random-trading situation.

Are trading profits just luck? Or are there really some trading gurus who trade better than the rest? All studies so far
suggest otherwise. No professional trader was found who produced permanently and significantly more profit than any
other trader in the same institution. Of course, there are famous examples of winning streaks that generated extreme
profits and huge bonuses for the lucky guy. Nevertheless the same trader had still the same chance of losing big in the
next year. If there really is a super trader, he's keeping his trading so secret that no one has found him so far.

Squeezing the bell curve

Back to our profit distribution. Let's see what happens when Alice uses her stop and profit targets for getting the 95%
win rate. Edit workshop8_2.c and replace the strategy1() call with strategy2(), which is the stop/takeprofit version:

function strategy2()
{
Stop = 200*PIP;
TakeProfit = 10*PIP;

if(NumOpenTotal == 0) {
if(random() < 0)
enterShort();
else
enterLong();
}
}
The profit distribution now looks quite different:

168
The bell peak is still at -750 pips, but the distribution is now much narrower and a little distorted towards the left side.
Restricting trades with stop and profit targets eliminates large wins and large losses. This puts upper and lower limits to
the annual result, thus 'squeezing' the bell from both sides. With 10 pips profit target, no trader can earn more than 2000
pips per year even in the unlikely case that all trades are won.

However, Alice needs an annual result of at least 20,000 pips. She can do nothing about the average 750 pips loss. But
she can manipulate the profit distribution curve in a way that a large number of traders end up with 20,000 pips. For
this, Alice just adds two more lines to her strategy (workshop8_3):

var ProfitGoal = 100*Bar*PIPCost;


Lots = clamp((ProfitGoal-ProfitClosed) / (7*PIPCost), 1, 200);

This is a martingale system. Such systems are used, more or less hidden, in most robots. At first Alice determines a
profit goal. She needs 100 pips per day for ending the year with more than 20,000 pips profit. A day is equivalent to a
bar, so at any bar the accumulated profit should be 100 pips times the bar number. This is multiplied with PIPCost for
getting the result in account currency instead of pips, and stored in the ProfitGoal variable.

The next line is the martingale. The lot size is set dependent on how much the current profit (ProfitClosed) deviates
from the profit goal. If we're far below our goal, we need a huge lot size to catch up. The number of Lots is calculated
just so that the next winning trade reaches the profit goal. For this, the profit difference is divided by the expected profit
per lot. The profit per lot of a winning trade is 10 pips profit target minus 3 pips spread. The result, 7 pips, is again
multiplied with PIPCost for converting it to account currency.

The clamp function limits Lots between 1 and 200. We need at least 1 lot per trade, and we don't want to exceed 200
lots for not being too obvious or risking crazy losses. When analyzing robot strategies, one can notice such a martingale
system from telltale peaks in the lot size. For this reason, robots or signal providers often increase not the number of
lots, but the number of trades, which is less suspicious.

Select workshop8_3 and click [Test] repeatedly. Every click will now generate a different equity curve. Most look like
this:

169
But some - surprisingly many - look like this:

This is the perfect equity curve that Bob wanted for his robot. It's even a little too perfect - its straight slope comes from
the ProfitGoal variable that just linearly increases with the bar number. For really selling the robot, Alice would have to
modify the profit goal formula for letting the curve appear more bumpy and realistic. We leave that as an exercise to the
reader.

Let's now use workshop8_4 for determining the profit distribution:

170
This distribution does not resemble a bell curve anymore. Although the average loss is still at -750 pips, the distribution
got an extremely long left tail (most of it is not visible because we clipped the chart at -100,000 pips) and a sharp peak
at the right in the 20,000 pips profit area. From our 3000 traders, about 2000 earned more than 20,000 pips with this
robot! Sadly, about 1000 traders will suffer losses, even extreme losses in excess of 100,000 pips. But we hope a
merciful margin call saves them early.

The profit distribution chart is a little misleading. In fact the year won't end with 2000 lucky traders. Many of them will
bite the dust before, because their equity curves, although reaching the 20,000 pips goal at the end, went through a
100% drawdown inbetween and wiped out their account. Let's see how many traders will encounter no margin call and
reach the end goal smoothly. For this, let's edit workshop8_4 and simulate a margin call in the strategy3 function:

function strategy3()
{
Stop = 200*PIP;
TakeProfit = 10*PIP;

var ProfitGoal = 100*PIPCost*Bar;


Lots = clamp((ProfitGoal-ProfitClosed)/(7*PIPCost),1,200);

if(ProfitOpen+ProfitClosed < -250) { // margin call


exitLong();
exitShort();
return;
}

if(NumOpenTotal == 0) {
if(random() < 0)
enterShort();
else
enterLong();
}
}

Every trader will now close his positions and refrain from further trading when his equity loss (ProfitOpen+ProfitClosed)
exceeds $ 250 capital at the end of a day. This changes the profit distribution remarkably:

171
Most traders have now given up before the end of the year. But about 500 still reached the 20,000 pips end goal without
a fatal drawdown - and this with totally random trading!

So Alice has a script that indeed generates more than 20,000 pips per year. There's a slight problem though: it works
only for one out of 6 traders (500 of the 3000). Most of the rest will also earn large profits in the first months due to the
martingale system and the high win rate, but they all will have been hit by a margin call before the end of the year.

Bob mercifully won't mention this little problem in his robot advertisement - but he'll need something else instead. For
selling the robot, at least one of those 250 profitable equity curves has to be verified on a real account by a trade
recording service. For this purpose Bob will invest $10,000. Not, as you might think, for bribing the service to confirm a
fake equity curve. No, they are certainly honest guys and won't accept bribes. The $10,000 are used in a different way
- for real trading.

Real profit from a losing system

Bob's next steps:

• Get the script from Alice. Pay her fee.


• Open 20 trading accounts, each with $250 deposit.
• Register the accounts with a trade recording service such as myfxbook™.
• Start his scam robot script on all accounts.

For financing the 20 accounts, Bob invests half of his $10,000. The money is not lost. A part of it can be recovered later.
But first Bob needs strong nerves, as $250 deposit leave not much room for drawdowns. Many of the 20 accounts will
sooner or later go down with a margin call. No problem: for any wiped account, Bob just opens a new one with $250,
until the rest of the $10,000 is spent.

Now Bob has to wait a year.

$10,000 allow running about 40 accounts simultaneously. If the equity curve of any account does not look good, even if
the account is in profit, Bob just closes it and opens a new one. After a year, Bob has only 4 accounts left, but any one
with about 20,000 pips profit. Because the accounts had been started with only $250 deposit, myfxbook has given them
all impressive annual gain rates of more than 1000%.

Bob keeps the account with the smoothest equity curve and closes the rest. A part of his $10,000 is thus recovered and
goes back on Bob's bank account.

Now Bob has the verified equity curve that he wanted, and can start advertising and selling his robot. And the money
will come rolling in, and he will get obscenely rich. And he will retreat to his Caribbean island and live from his robot

172
sales happily forever after. And when his robot won't sell anymore because too many people lost their money with it,
he'll have already 10 new robots in preparation.

Real robot equity curve (myfxbook screenshot)

The above equity curve was generated by a typical commercial robot that was very popular in the trading scene for
some time, and got enthusiastic reviews due to its "myfxbook-verified trading record". We won't tell the name of this
robot, and we're not suggesting that it is scam. It can be a unique example of a honest robot. Only a little curious is the
fact that the account was started with only $250 deposit. Or that the lot size - the green bars - grew large peaks whenever
the equity went down, just like a martingale system. Or that the equity curve went straight upwards in its first part, but
suddenly - just after the robot started selling - dropped like a stone, losing all previous 'profits'. Really strange. Shame
upon him who thinks evil upon it!

What have we learned in this workshop?

• The random function makes your script feel lucky.


• Setting Stop and Takeprofit is enough to generate any arbitrary win rate, up to (almost) 100%.
• The plotBar function generates distribution charts.
• NumTotalCycles runs the simulation many times.
• Random trading generates a bell shaped profit distribution.
• Genius traders are no geniusses at all.
• Setting the trade size according to a profit goal results in a martingale system.
• It is relatively easy to place faked equity curves on myfxbook™ or similar services.
• Selling trade robots can be far more profitable than actually trading.

The purpose of the workshop was not to learn ripping off other traders. It just should make you aware of a typical
phenomenon in the trading scene. Are all commercial robots 100% scam? This is hard to tell. A honest robot is
theoretically possible, but it would not sell well: the expectations of robot buyers require high win rates and straight
equity curves. This can not be achieved with a real trading system. Therefore vendors have no interest in selling robots
that really work, even if they knew how to program them. They get much better reviews and more sales with scam. If
you know how such a robot works, you'll be able to see the hints in the performance figures. Few robots are programmed
so good that they can not be identified as scam at a first glance on their trading curves.

Aside from that, programming a scam robot is a good exercise in statistics, profit distributions, and risk and money
management. So the knowledge of scam robot scripting helps with programming real strategies.

We're now at the end of the tutorial. From this point you're on your own. The workshops offered an introduction into
automated trading with different algorithms, and this one gave a view into scam methods and profit statistics. All
workshop strategies have been quite simple and are coded with just a few lines of lite-C. Zorro offers a lot more functions
for trading, and you're invited to experiment with all of them. Don't buy robots, and don't follow signal providers. Use
your own ideas. Don't rely on only a single trading method, but attack the financial markets in as many different ways
as possible. Have a look into the link & book list for getting strategy ideas. If you feel something missing or want to
have something new implemented in future Zorro versions, just notify the developers on the Zorro forum. If your
suggestion is good, it will make it into Zorro sooner or later.
173
8 x R lectures:

Lecture 1 – An introduction to R
The R lectures are part of the FINC 621 (Financial Mathematics and Modeling) graduate level class at Loyola University
in Chicago. The lectures give an introduction into R for the non-programmer, with a focus on quantitative finance and
statistics. If you've done the Strategy Development Tutorial, you should have no problem to follow the lectures and
become familiar with R.

Harry Georgakopoulos, who teaches the FINC 621 class, graciously gave permission to include his lectures in the Zorro
tutorial. Harry Georgakopoulos is a quantitative trader for a proprietary trading firm in Chicago. He holds a master’s
degree in Electrical Engineering and a master’s degree in Financial Mathematics from the University of Chicago. He is
also the author of Quantitative Trading with R and runs the R for Traders website.

Any errors and omissions in the following lectures are not the responsibility of the author. The lectures should only be
used for educational purposes and not for live trading.

Let's get started!

What is R?

R is a language and environment for statistical computing and graphics. It includes an effective data handling and
storage facility that provides a suite of operators for calculations on arrays, matrices, data-frames and lists. The base
installation of R comes with a large collection of tools for data analysis and data visualization. The language itself
supports conditional statements, loops, functions, classes and most of the other constructs that VBA and C++ users are
familiar with. R supports the object-oriented, imperative and functional programming styles. The plethora of contributed
packages, a solid user-base and a strong open-source community are some of the other key strengths of the R
framework.

The R system is divided into 2 parts:

1. The base package which is downloadable from CRAN.


2. Everything else.

The base R package contains, among other things, the necessary code which is required to run R. Many useful functions
and libraries are also part of this base installation. Some of these include: utils, stats, datasets, graphics, grDevices and
methods. What this means for you, is that you can get a lot done with the plain vanilla R installation!

History of R

The S language (R is a dialect of the S-language) was developed by John Chambers and others at Bell Labs in 1976.
It started off as a collection of Fortran libraries and was used for internal Bell Lab statistical analysis. The early versions
of the language did not contain functions for statistical modeling. In 1988 the system was rewritten in C (version 3 of the
language). In 1998, version 4 of the language was released. In 1993 Bell Labs gave StatSci (now Insightful Corp.) an
exclusive license to develop and sell the S language. The S language itself has not changed dramatically since 1998.
In 1991 Ross Ihaka and Robert Gentleman developed the R language. The first announcement of R to the public
occurred in 1993. In 1995, Martin Machler convinced Ross and Robert to use the GNU General Public License to make
R free software. In 1996 public mailing lists were created (R-help and R-devel). In 1997 the R Core Group was formed
(containing some people associated with the S-PLUS framework). The core group currently controls the source code
for R. The first version R 1.0.0 was released in 2000.

Installing R

The installation of the R environment on a Windows, Linux or Mac machine is fairly simple. Here are the steps to follow
for the Windows version:

1. Navigate to http://cran.r-project.org/
2. Click on the appropriate link for your system.
3. For a Windows machine, select and download the base installation.

174
4. Select all the default options.
5. A desktop icon will appear once the installation is successful.

The following display appears when you click on the R icon.

R Console

Interacting with the Console

There are at least three ways to enter commands in R. The code can either be typed directly into the console, sourced
from a .R file or pasted verbatim from a text file.

Customization

There are a number of customizations that can be performed on the R console itself. For font, color and other
cosmetic changes, navigate to the GUI Preferences menu:
Edit -> GUI Preferences

Another useful modification is to enable the sdi option for child windows. Whenever you create a plot or bring up another
window within the existing R console, the child-window is confined within the main-window. In order to disable this
behavior, do the following:

1. Right-click on the R shortcut icon on the desk- top and select Properties
2. Change the Target directory text from “…\R-2.15.1\bin\Rgui.exe” to “…\R-2.15.1\bin\Rgui.exe” –sdi

The Basics

One can think of R as a glorified calculator. All the usual mathematical operations can be directly entered into the
console. Operations such as addition, subtraction, multiplication, division and exponentiation are referenced by the usual
175
symbols +, -, /, * and ^. More advanced mathematical operations can be performed by invoking specific functions within
R.

Basic Math
1+1
sqrt(2)
20 + (26.8 * 23.4)/2 + exp(1.34) * cos(1)
sin(1)
5^4
sqrt(-1 + 0i)

Advanced Math
integrand <- function(x) 1 / ((x+1) * sqrt(x))
integrate(integrand, lower = 0, upper = Inf)

Variable Assignment

The assignment of a value to a variable in R is accomplished via the <- symbol

x <- 3
x <- x + 1
z <- x ^ 2
z <- "hello XR"
y <- "a"
Z <- sqrt(2)

A few things to notice from the previous example:

• Variables in R are case-sensitive. z is not the same thing as Z. Spaces or special characters are not allowed within
variable names. The dot is an exception. Variable names cannot start with a numeric character.
• Variables in R do not have to be declared as int, double or string as in other languages. R dynamically figures out what
the type of the variable is.
• Variables in R can be modified and copied into other variables.
• The third example from above does not actually modify the value of x. Rather, x is squared and the result is assigned
to the new variable z.
• Other languages use the = operator in place of the <- operator to denote assignment. R is capable of supporting both
conventions. Stick with <- in order to minimize confusion.

Containers and Operations Containers

In order to properly work with any raw data, we need to first place that data into a suitable container. The important data
containers in R are:

• vector
• matrix
• data frame
• list

Once we have successfully placed our data into suitable data structures, we can proceed to manipulate the data in
various ways.

Vector

A vector can be thought of as a 1-dimensional array. Vectors can hold data of similar type. Only numbers, or only
characters can be placed inside a vector. The following example creates three vectors of type numeric and character.

firstVector <- c(1,2,3,4,5,6)


secondVector <- c("a", "b", "hello")
thirdVector <- c("a", 2, 23)

The concatenation operator c() is used to create a vector of numbers or strings. The third example mixes numbers with
characters. R will convert the type of any numeric values into string characters. Typing the variable name into the R
console reveals the contents of our newly created vectors.

176
firstVector
thirdVector

The concatenation operator c() can also be used on existing vectors.

newVector <- c(firstVector, 7, 8, 9)

The extraction of elements from within a vector can be accomplished through a call to the [] operator.

Operations on Vectors

The following examples illustrate various operations that can be performed on vectors. The first example specifies a
single index to use for extracting the data. The second example specifies two indexes. Notice how the c() operator is
used to create a vector of indexes. These indexes are subsequently used to extract the elements from the initial vector.
This method of “extracting” data elements from containers is very important and will be used over and over again.

#extract the 4th element of a vector


example1 <- newVector[4]
#extract the 5th and the 8th elements
example2 <- newVector[ c(5, 8)]

The next few examples illustrate mathematical operations that can be performed on vectors.

x <- c(1, 5, 10, 15, 20)


x2 <- 2 * x
x3 <- x ^ 2
x4 <- x / x2
x5 <- round(x * (x / x2) ^ 3.5 + sqrt(x4), 3)
x6 <- round(c(c(x2[2:4], x3[1:2]), x5[4]), 2)

Here are a few conclusions we can draw from these examples:

• Operations can be vectorized. In other words, we do not have to loop through all the elements of the vector in order to
perform an operation on each data element. Rather, the operation of interest is performed on all the elements at once.
• If we only wanted to perform an operation on the 4th and 6th elements of our vector, we would have to “index” into the
vector and extract the elements of interest.

y <- x[4] + x[6]

• The last example, x6 combines some of the operations discussed earlier in this tutorial. We are extracting specific
elements from vectors x2, x3 and x5 and then concatenating them into a single vector. The result of the operation is
then truncated to 2 decimal places.

Matrix

A matrix can be thought of as a 2-dimensional vector. Matrices also hold data of similar type. The following code defines
a matrix with 2-rows and 3-columns. In R, matrices are stored in columnar format.

myMatrix <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, ncol = 3)

The default matrix() command assumes that the input data will be arranged in columnar format. In order to arrange the
data in row format, we need to modify our previous example slightly:

myMatrix <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, ncol = 3, byrow = TRUE)

In subsequent sections we will cover attributes of containers and other R-objects. In a nutshell, an attribute is an extra
layer of information that we can assign to our objects. For matrices, a useful attribute might be a list of names for the
rows and columns. If we do not specify the names, R will assign default row and column numbers.

177
Operations on Matrices

The extraction of elements from a matrix can be accomplished via the use of the [,] operator. In order to extract the
element located in row 1 and column 3 of a matrix, we need to issue the following command:

ans <- myMatrix[1, 3]

Operations of matrices can also be vectorized

newMatrix1 <- myMatrix * myMatrix


newMatrix2 <- sqrt(myMatrix)

Here are some examples that utilize vectorization and single-element operations.

mat1 <- matrix(rnorm(1000), nrow = 100)


mat2 <- mat1[1:25, ] ^ 2
round(mat1[1:5, 2:6], 3)
head(round(mat2,0), 9)[, 1:7]

Data Frame

Think of a data frame object as a single spreadsheet. A data frame is a hybrid 2-dimensional container that can include
both numeric, character and factor types. Whenever data is read into R from an external environment, it is likely that the
resulting object in R will be a data frame. The following code creates a data frame.

df <- data.frame(price = c(89.2, 23.2, 21.2), symbol = c("MOT", "AAPL", "IBM"), action = c("Buy", "Sell",
"Buy"))

A data frame accepts columns of data as input. Notice that these can be either numeric or of type character. Also notice
that a name can be assigned to each column of data. In a data frame, as in a matrix, it is important to ensure that the
number of rows is the same for all columns. The data need to be in “rectangular” format. If this is not the case, R will
issue an error message.

df2 <- data.frame(col1 = c(1, 2, 3), col2 = c(1, 2, 3, 4))

Error in data.frame(col1 = c(1, 2, 3), col2 = c(1, 2, 3, 4)) : arguments imply differing number of rows: 3, 4

Operations on data frames

Data frames can also be indexed via the [,] operator.

price1 <- df[1, 1]

The $ operator can be used to extract data columns by “name”.

symbols <- df$symbol


class(symbols)

The “Levels” descriptor for the symbols variable signifies that the type of the variable is a factor. We can find out what
type any object in R is by using the class keyword.

Factors are a convenient data-type that can assist in the categorization and analysis of data. We will not cover factors
in this class. In order to disable the conversion of any character vector into a factor, simply use the stringsAsFactors =
FALSE argument in the data.frame() call.

df3 <-data.frame(price = c(89.2, 23.2, 21.2), symbol = c("MOT", "AAPL", "IBM"), action = c("Buy", "Sell",
"Buy"), stringsAsFactors = FALSE)
class(df3$symbol)

178
Some things to take away from the previous examples:

• Functions can take multiple input arguments. In order to figure out exactly what extra arguments are available for any
predefined R function, use the ? operator in front of the function name. i.e. ?data.frame
• Objects can be passed directly into other functions. Functions are objects. In fact, everything is an object in R.

List

A list structure is probably the most general container. It can be used to store objects of different types and size. The
following code creates a list object and populates it with three separate objects of varying size.

myList <- list(a = c(1, 2, 3, 4, 5), b = matrix(1:10, nrow = 2, ncol = 5), c = data.frame(price = c(89.3,
98.2, 21.2), stock = c("MOT", "IBM", "CSCO")))
myList

The first component of the list is named “a” and it holds a numeric vector of length 5. The second component is a matrix
and the third one, a data frame. Many functions in R use this list structure as a general container to hold the results of
computations.

Operations on lists

Lists can be indexed either numerically or by the component name through the double bracket operator [[]].

firstComp <- myList[[1]]


class(firstComp)

An alternate extraction method is the following:

secondComp <- myList[["b"]]


class(secondComp)

By using the [[]] operator, we extract the object that is located within the list at the appropriate location. By using the
single bracket operator [] we extract part of the list.

partOfList <- myList[ c(1, 3)]


class(partOfList)

The size of the list can be determined with the length() keyword.

sizeOfList <- length(myList)

Useful Functions

The base package of R includes all sorts of useful functions. Novice R users are sometimes faced with the problem of
not knowing what functions are available for performing a specific task. The following examples contain a few functions
that are worth memorizing.

Simple Operations
#1. Greate 1000 normally distributed random numbers of mean 0 and standard dev 1
x <- rnorm(1000, mean = 0, sd = 1)

#2. Find the length of the vector x.


xL <- length(x)

#3. Compute the mean and standard deviation of those numbers


xM <- mean(x)
xS <- sd(x)

#4. Find the sum of all the numbers in x vector x.


xSum <- sum(x)

#5. Do a cumulative sum of the values in x


xCSum <- cumsum(x)

179
#6. Look at the first 3 elements a vector
head(xCSum, 3)

#7. Look at the last 3 elements of a vector


tail(xCSum, 3)

#8. Look at summary statistics


summary(x)

#9. Sort x from smallest to largest and from largest to smallest.


xSIn <- sort(x)
xSDec <- sort(x, decreasing = TRUE)

#10. Compute the median value of the vector


xMed <- median(x)

#11. Compute the range of a variable


xR <- range(x)

#12. Compute the difference between elements


y <- c(100.1, 100.2, 100, 99, 99.9)
yDiff <- diff(y)

#13. Create a sequence from 1 to 10


s <- 1:10

#14. A sequence from 1 to 10 in steps of 0.1


z <- seq(1, 10, 0.1)

Simple Graphing

R has the ability to produce some intricate graphics. Most of the advanced graphing functionality is exposed in other
add-on libraries such as lattice, ggplot2, rgl, and quantmod. For the time being, the plot() command is adequate to
address our graphing needs. This is what the default output for plot() looks like.

#Create a vector of numbers x and plot it


x <- c(1, 2, 3.2, 4, 3, 2.1, 9, 19)
plot(x)

Simple plot in R

180
The following code converts the dot plot into a line plot.
plot(x, type = "l")

Line plot in R

One can think of a plot as a canvas. We first create the canvas (by calling the first plot() command) and then proceed
to paste other lines, points and graphs on top of the existing canvas. The following example will demo the creation of a
simple plot with a main title, axis labels, a basic grid and an appropriate color scheme. We will then superimpose a few
vertical and horizontal lines onto the first plot.
plot(rnorm(1000), col = "blue", main = "Yo!", xlab = "Time", ylab = "Returns")
grid()
abline(v = 400, lwd = 2, col = "red")
abline(h = 2, lwd = 3, col = "cadetblue")

181
Scatter plot in R

Another useful command is the par() function. This command can be used to query or set global graphical parameters.
The following code-snippet splits the viewing window into a rectangular format with 2 rows and 2 columns. A plot()
command can be issued for each one of the child-windows. We can superimpose multiple line plots, other graphs and
text on each unique plot.

#Create a 2-row/2-column format


par(mfrow = c(2, 2))
plot(rnorm(100), col = "red", main = "Graph 1")
plot(rnorm(100), col = "blue", main = "Graph 2", type = "l")
plot(rnorm(100), col = "brown", main = "Graph 3", type = "s")
abline(v = 50, col = "red", lwd = 4)
plot(rnorm(100) , col = "orange", main = "Graph 4")
abline(a = -1, b = 0.1, lwd = 2, col = "purple")

#Reset the plotting window


par(mfrow = c(1, 1)

182
Multiple R plots

References

• Venables and Smith. An Introduction to R. http://www.r-project.org/about.html Accessed 11 Nov 2012


• Roger D. Peng. Overview and History of R. http://www.biostat.jhsph.edu/~rpeng/biostat776/lecture1.pdf Accessed 11
Nov 2011

183
Lecture 2 – Functions in R
The first class served as an introduction to the R environment. The fundamental data containers c(),
matrix(), data.frame(), list() were introduced and some useful functions were presented. This second class is going to
cover user-defined functions. When dealing with any sort of data analysis project, it is important to be able to create
simple functions that perform specific tasks. Functions are programming constructs that accept zero or more inputs and
produce zero or more outputs. Before we jump into functions, we need to address the concepts of: conditional
statements, loops and extraction of elements from containers via boolean operators.

Conditional Statements and Boolean Expressions

A conditional statement can be thought of as a feature of a programming language which performs different
computations or actions depending on whether a programmer-specified boolean condition evaluates to true or false.
We will be using conditional statements quite a bit in most of our functions in order to create logic that switches between
blocks of code.

If-Else Statement

The if-else conditional construct is found in R just as in most of the other popular programming languages (VBA, C++,
C#, etc).

value <- 0
if(value == 0) {
value <- 4
}

The statement within the parenthesis after the if keyword is a boolean expression. It can either be TRUE or FALSE. If
the value is TRUE, the code within the curly braces will be evaluated. If the statemenent is FALSE, the code within the
curly braces will not be evaluated. If we want to provide an alternate evaluation branch, we can use the else keyword.

value <- 0
if(value == 0) {
value <- 4
} else {
value <- 9999
}

If the boolean expression within the if() is FALSE, then the code after the else will be evaluated. We can combine
multiple if-else statements in order to create arbitrarily complex branching mechanisms.

myIQ <- 86
if(myIQ <= 10) {
cat("Wow! Need improvement!")
} else if(myIQ > 10 && myIQ <= 85) {
cat("Now we're talking!")
} else {
cat("You're hired!")
}

A couple of points to take away from the previous example:

• The cat() command simply prints everything passed to it to the screen.


• The operator && is the boolean AND and the operator || is the boolean OR. Do not confuse these with
the & and |operators. These are called the bitwise boolean operators.
• An arbitrarily complex boolean expression can be passed to the if() statement.

Booleans

Let’s take a look at some boolean expressions:

x <-5
y <-6
bool1 <- x == y
184
bool2 <- x != y
bool3 <- x < y
bool4 <- x > y
bool5 <- ((x + y) > (y - x)) || (x < y)
bool6 <- (bool5 && bool2) || (x/y != 3)

Statements such as if() take booleans as input. If the boolean expression is a simple one, it is a good idea to place it
within the parentesis of the if() or while() directly. If the boolean expression is more involved, it is probably a good idea
to pre-compute the expression, assign it to a variable, and then pass the variable to the appropriate statement.

#simple case x <-5


y <-4
if(x > y) {
cat("Success")
}

#complicated case
x <-5
y <-4
boolN <- ((x > y) && (sqrt(y) < x)) ||
((x + y == 9) && (sqrt(y) < x))
if(boolN) {
cat("Good times...")
} else {
cat("Bad times...")
}

Let’s take a look at a vectorized boolean comparison.

x <-5
w <- c(1, 2, 3, 4, 5, 6)
z <- c(1, 3, 3, 3, 5, 3)
boolV <- w == z
boolV <- x > w

In order to evaluate a boolean expression between 2 variables or expressions, we should use the && and || operators.
If we want to evaluate a collection of variables against a collection of a different set of variables, we should use
the & and | operators. Here is a simple example:

#using && and ||


w <-1
z <-2
boolS <- (w < z) && ( z < 5)

#using & and |


x <-3
w <- c(1, 2, 3, 4, 5, 6)
z <- c(1, 2, 3, 7, 8, 2)
boolV <- (w > x) & (x < z)
boolV <- (w > x) | (x < z)
Loops

The for() and while() structures are typically utilized when the user wants to perform a specific operation many times.

for()

Here’s how to fill a numeric vector with integers from 1 to 20 using a for() loop.

myNumbers <- c()


for(i in 1:20) {
myNumbers[i] <- i
}

In the previous example the iterator i took values between 1 and 20. Any variable name can be used as an iterator. This
way of populating a vector is certainly possible in R. However, it is not the recommended method for populating a
container with data. The following vectorized example accomplishes the same task and avoids the for() loop altogether.
Having said this, keep in mind that vectorization might be difficult to implement for certain types of problems. For most
of the examples we are going to encounter in this class vectorization works just fine.
185
myNumbers <- 1:20

The iterator within the for() loop does not have to be sequential. A vector of possible iterators can be passed directly to
the loop.

seqIter <- c(2,4,6)


myArray <- 1:10
for(j in seqIter) {
myArray[j] <- 999
}
while()

Another popular looping structure is the while() loop. The loop will perform a certain calculation until the boolean
expression provided to it returns a FALSE value.

x <- 5
while(x < 5) {
x <- x + 1
}

There is no need to keep track of a counter within a while loop.

Memory Pre-Allocation

It is advisable to pre-allocate a data container before filling it up with values within any loop. The following example fills
up a numeric vector with numbers between 1 to 100,000 without pre-allocating the size. The system.time() function is
used to measure the elapsed time.

emptyArray <- c()


system.time(
for(i in 1:100000) {
emptyArray[i] <- i
})

#Output
user system elapsed
10.36 0.00 10.47

It takes roughly 10 seconds for this operation to complete!


The next example pre-allocates the container up to the maximum-size prior to populating the array.

fullArray <- c(NA)


length(fullArray) <- 100000
system.time(
for(i in 1:100000) {
fullArray[i] <- i
})

#Output
user system elapsed
0.25 0.00 0.25

The reason for the time discrepancy is due to the copying of the vector elements into a new vector that is large enough
to hold the next entry in the for() loop. If the vector is pre-allocated, no copying of elements needs to occur and the time
for vector insertions decreases substantially.

Memory allocation in R

186
Indexing with Booleans

In the previous class we saw how to extract elements of a data-container by passing in the numeric index of interest.
Another way of accomplishing the same task is to pass in a boolean vector as the index. A TRUE value will return the
contents at that particular location, whereas a FALSE value will not return the contents at that index.

v1 <- c(1,2,3,4,5,6,7,8)
boolI <- c(TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE, FALSE)
v2 <- v1[boolI]

If the length of the boolean index is smaller than the array being indexed, the boolean index will be internally replicated
as many times as necessary in order to match the array length.

v1 <- c(1,2,3,4,5,6,7,8)
boolI <- c(TRUE,FALSE)
v2 <- v1[boolI]
Writing Functions

There exist hundreds of functions in the base-installation of R. Thousands more exist within 3rd party packages/libraries.
In many cases, it is convenient to create our own custom functions to perform certain tasks. Here is an example of a
simple function that accepts one input and produces one output:

MyFirstFunction <- function(a) {


return(a)
}

In this simplistic example, the variable a is simply returned by the function. In order to create a function, we need to use
the function() keyword. The values within the parenthesis are called the arguments of the function. Everything within
the curly braces { } is the fuction body. All variables that are declared within the body of the function are only known to
the function. The only exception occurs if the variables are declared as global. It is good practice to avoid the use of
global variables. The return() keyword returns the computed result to the user. The next few examples explore functions
in more depth.

Example 1
SumF <- function(a, b) {
return(a+b)
}

#Usage
ans <- SumF(1,3)

Example 2
SqCol <- function(d) {
return(sum(d[,2]^2))
}

#Usage
dF <- data.frame(a = c(1, 3, 3, 2),
b = c(1, 3, 5, 6),
c = c(1, 2, 3, 4))
ans <- SqCol(dF)

Example 3
MatF <- function(mat) {
outList <- list()
sizeMat <- dim(mat)
for(i in 1:sizeMat[2]) {
ind <- which(mat[,i] < 15)
if(length(ind) > 0) {
outList[[i]] <- mat[ind,i]
} else {
outList[[i]] <- "Empty"
}
}
return(outList)
}

#Usage
m1 <- matrix(sample(1:100, replace = TRUE),
nrow = 10, ncol = 10)
187
ans <- MatF(m1)

Example 3 needs some clarification. The input to the function is a matrix called mat. Within the function, we first create
a list container to hold our final answers. The dim() function figures out what the size of mat is. The output of dim() is
itself a 2-dimensional vector. We just need the number of columns. That is what the [2] argument in the for loop
accomplishes. The iterator variable i loops through all the columns of mat and the which() function checks to see which
row in the matrix satisfies a given condition. In this case mat[, i] < 15. The ind variable creates a vector of booleans that
is subsequently passed back to mat in order to extract only those elements. The final results are stored in the outList
variable.

References

• Wikipedia, Subroutine, http://en.wikipedia.org/wiki/Subroutine


• Wikipedia, Conditional(programming), http://en.wikipedia.org/wiki/Conditional_(programming)

188
Lecture 3 – Matrices and Libraries
Matrices in R

A matrix is a very useful mathematical construct. Matrices provide a mechanism for easily manipulating large collections
of data. Matrix Mathematics is a vast topic and there exist numerous papers and publications that talk about all the
possible uses of matrices. Suffice it to say that this class is only going to use a small subset of these theorems. In R, a
matrix can be created in the following manner:

#specify an empty marix with 3 rows and 3 columns


emptyMat <- matrix(nrow = 3, ncol = 3)

Matrices are created column first. If you want to create the rows first, make sure to use the byrow = TRUE attribute.

mat1 <- matrix(c(1,2,3,4,5,6), nrow = 2, ncol = 3, byrow = TRUE)

as opposed to:

mat2 <- matrix(c(1,2,3,4,5,6), nrow = 2, ncol = 3)

Naming Convention for Matrices

Since a matrix is an object within R, one can change the name attribute of the matrix. Names are assigned to the rows
and to the columns of a matrix. The following three snippets of code accomplish this.

#Method 1
mat3 <- matrix(rnorm(16,0,1), nrow = 4, ncol = 4)
dimnames(mat3) <- list(c("Row1", "Row2", "Row3", "Row4"),
c("Col1", "Col2", "Col3", "Col4"))
#Method 2
mat4 <- matrix(rnorm(16,0,1), nrow = 4, ncol = 4, dimnames =
list(c("Row1", "Row2", "Row3", "Row4"),
c("Col1", "Col2", "Col3", "Col4")))
#Method 3
myRowNames <- c("r1", "r2", "r3", "r4")
myColNames <- c("c1", "c2", "c3", "c4")
matrixNames <- list(myRowNames, myColNames)
mat5 <- matrix(rnorm(16,0,1),nrow = 4, ncol = 4, dimnames = matrixNames)

Fun with Matrices

The following basic operations can be performed on matrices:

Addition

Provided that the number of rows and columns are the same for the matrices being added, once can do the following:

m1 <- matrix(c(1,2,3,4), nrow = 2, ncol = 2)


m2 <- matrix(c(5,4,3,2), nrow = 2, ncol = 2)
m3 <- m1 + m2

Subtraction
m1 <- matrix(c(7.8,2.4,3.3,4.0), nrow = 2, ncol = 2)
m2 <- matrix(c(5,4,3,2), nrow = 2, ncol = 2)
m3 <- m1 - m2

Multiplication

When multiplying together two matrices, make sure that the inner dimensions match. For example, it is fine to multiply
a 2×3 with a 3×4 matrix. It is not ok to multiply together a 2×3 with a 4×4 matrix.

m1 <- matrix(c(7.8,2.4,3.3,4.0), nrow = 2, ncol = 2)


m2 <- matrix(c(5,4,3,2), nrow = 2, ncol = 2)
m3 <- m1 %*% m2

189
Other

Matrix division is not defined. Rather, one can think of matrix division as multiplication by a matrix times the inverse of
the second matrix. Remember also that, AB is not equal to BA in matrix land. Another operation that can be defined with
matrices is that of exponentiation. This is a more involved topic and will not be covered in this class.

Determinant and Inverse

The determinant of a matrix A can be written as det(A) or |A|. The inverse of a matrix A can be written as inv(A) or A^-
1. The determinant and the inverse of a matrix in R can be computed with the following functions: det() and solve().

Sourcing Files

R code can be composed entirely within a simple text file. For more advanced editing capability, check out the following
links:

• Tinn-R
• Notepad++
• Sublime 2
• RStudio

As mentioned in Class 1, there are 3 ways to get code into R. 1. Write code directly into the R console 2. Write code
into a text file and copy/paste it into the R console 3. Write code into a text file, save the text file as a .R file and then
invoke the source() command to load the contents of that file into memory.

#specify the path of the .R file


fileName <- "c:/myCode.R"
#load the code into memory
source(fileName)

Finding Packages

One of the benefits of the R environment is the abundance of open-source code in the form of external
libraries/packages. The vast majority of these add-ons can be found here: http://cran.r-project.org. Libraries are
typically organized by subject matter. For a useful breakdown, click on Packages -> CRAN Task Views.

Installing Packages

There are two ways to install packages in R. The first way is via the GUI, and the second way is by issuing the appropriate
command in the console.

190
Installing via the GUI

Package Loading in R

1. Click on Packages -> Install package(s)


2. Select a CRAN mirror site from the drop-down window.
3. Select the appropriate package from the drop-down window and click OK.
4. A diagnostic message will appear on the screen and the package will be loaded into the appropriate library
folder.

There is a difference between installing a package and loading a package. The installation procedure will expose the
new library/package to the R environment. This task only needs to occur once. In order to use the functions and classes
within the newly installed package, the library() or require() commands need to be specified. This needs to occur every
time the R workspace is re-loaded. The following command loads the newly installed package into memory.

library(xts)

Here, we have made the assumption that the xts package was installed. If all goes well, nothing will appear on the
screen. If the package has not been previously installed, R will issue an error message.

Installing via the Command Prompt

To install a package from the command prompt, simply issue the following command.

install.packages("xts")

Like most functions, the install.packages() function takes multiple arguments. Various repositories and alternate file
locations can be specified.

191
Useful Financial Packages

This class will briefly cover 2 packages. These are xts and quantmod. The xts package is a timeseries package and
comes in very handy when dealing with ordered observations. The quantmod package allows for some extended
graphing functionality and works well with xts.

xts()

Over the years, various practitioners and academics have written functions in R that deal with financial timeseries data.
Given that the bulk of xts is written in C, it is ideal for fast lookups and indexing. An xts timeseries obect is composed of
an index and coredata. The index contains the time information and the coredata contains the raw data. The following
examples illustrate the creation and manipulation of xts objects. The first example is taken directly from the ?xts help
file.

data(sample_matrix)
myXts <- as.xts(sample_matrix, descr='my new xts object')
class(myXts)
str(myXts)

#attribute 'descr' hidden from view


head(myXts)
attr(myXts,'descr')

#sub-setting all of 2007


myXts['2007']

#March 2007 to the end of the data set


myXts['2007-03::']

#March 2007 to the end of 2007


myXts['2007-03::2007'] #the whole data set
myXts['::']

#the beginning of the data through 2007


myXts['::2007']

#just the 3rd of January 2007


myXts['2007-01-03']

The first line of the previous example invokes the data() command. Typically, external packages include both functions
and supporting data. The included data is meant to assist the user in understanding the functionality of the package.
The as.xts() command casts the matrix into an xts object. In this example, the row-names of the matrix are converted
into an index object and the rest of the data into the coredata. The :: operator is used to extract specific data from the
xts object. The next example extracts the index and the coredata from myXts.

timeInfo <- index(myXts)


dataInfo <- coredata(myXts)

The timeInfo object should now only contain the time-information. The command class(timeInfo) reveals that we are
dealing with a POSIXct object. It is good practice to convert any timestamps into POSIXct from now on. Before we move
on to more intricate timeseries examples, we need to address the conversion of strings into POSIXct objects. Typically,
timestamps are formatted as strings initially when read in from Excel or other databases. Before we can convert the
strings into POSIXct, we need to let R know what format we are dealing with. The next example illustrates this.

#read in file from C: drive


x <- read.csv("C:/Users/yourname/Desktop/pricesFile.txt", stringsAsFactors = FALSE)
head(x)

#convert the first column from a character into a POSIXct object so that we can use it
#to create an xts object.

timeI <- x$Date


class(timeI)
xtsIndex <- as.POSIXct(timeI, format = "%m/%d/%Y")
xtsPrices <- xts(x[,-1], xtsIndex)

After converting a regular timeseries into an xts object, it becomes fairly easy to perform sub-setting, indexing and
merging operations.

192
#indexing
xtsPrices['2006-07-11::2007-05-10']

#create a dummy xts series


xtsDummy <- 1.2 * xtsPrices[1:10,1] - xtsPrices[1:10,3]

#merging
xtsMerged <- merge(xtsPrices[,1], xtsPrices[,2])

quantmod()

After installing and loading quantmod, we can use the following functions to visualize financial timeseries data. The
following link provides some useful information about quantmod. http://www.quantmod.com. The examples that follow
are taken directly from the quantmod website.

#install package
install.packages("quantmod")

#load package
library(quantmod)

#Goldman OHLC from yahoo


getSymbols("GS")
chartSeries(GS)
barChart(GS,theme='white.mono', bar.type='hlc')

#how about some candles, this time with color


candleChart(GS,multi.col=TRUE,theme='white')

#and a line, with the default color scheme


lineChart(GS,line.type='h',TA=NULL)

#(December '07 to last observation in '08)


candleChart(GS,subset='2007-12::2008')

#slightly different syntax - after the fact.


#also changing the x-axis labeling
candleChart(GS,theme='white',type='candles')
reChart(major.ticks='months',subset='first 16 weeks')

#The TA argument is one way to specify the


#indicators to be applied to the chart. NULL means don't draw any.
chartSeries(GS, TA=NULL)

#Now with some indicators applied


chartSeries(GS, theme="white", TA="addVo(); addBBands(); addCCI()")

#The same result could be accomplished a bit more interactively:


chartSeries(GS, theme="white")
addVo() #add volume
addBBands() #add Bollinger Bands
addCCI() #add Commodity Channel Index

#Yahoo! OHLC from yahoo


getSymbols("YHOO")
chartSeries(YHOO, TA=NULL)
addTA(OpCl(YHOO),col='blue', type='h')

#With newTA it is possible to create a #generic TA function. Let's call it addOpCl


addOpCl <- newTA(OpCl,col='green',type='h')
addOpCl()

References

• CRAN, CRAN Packages, http://cran.r-project.org


• Rmetrics, Rmetrics, http://www.rmetrics.org
• Quantmod, Package by Jeff Ryan, URL http://www.quantmod.com
• Sample stock prices

193
Lecture 4 – Regression and Pairs Trading
Regression Analysis

Regression is a very important topic. It is a widely used statistical tool in economics, finance and trading. R provides
pre-written functions that perform linear regressions in a very straightforward manner. There exist multiple add-on
packages that allow for more advanced functionality. In this class, we will only utilize the lm() function which is available
in the base installation of R. The following example demonstrates the use of this function:

x <- rnorm(1000)
y <- (x - 2) + rnorm(1000)
outR <- lm(y ~ x)
summary(outR)

What we did above was creat an independent variable x and a dependent variable y. The call to the
function lm() performed an OLS (Ordinary Least Square’s) fit to the function: y = b0 + b1x + e, where e was distributed
as N(mu, sigma^2)

The ~ is used to separate the independent from the dependent variables. The expression y ~ x is a formula that specifies
the linear model with one independent variable and an intercept. If we wanted to fit the same model, but without the
intercept, we would specify the formula as y ~ x – 1. This tells R to omit the intercept (force it to zero). The following
graph illustrates the best fit lines for a model with and without an intercept. If you know that your model should contain
an intercept term, then include it. Otherwise, do not include the intercept. For the subsequent pairs trading example, we
are going to assume that the intercept term is equal to 0.

Whenever a regression is performed, it is very important to analyze the residuals (e) of the fitted model. If everything
goes according to plan, the residuals will be normally distributed with no visible pattern in the data, no auto-correlation
and no heteroskedasticity. The residuals can be extracted from the regression object by using the residuals keyword.

194
res <- outR$residuals
par(mfrow = c(2,1))
plot(res, col = "blue", main = "Residual Plot")
acf(res)

Here is a graph of both the residuals themselves and their auto-correlation.

The summary keyword can be used to obtain the results of the linear regression model fit.

summary(outR)

The p-values and t-statistics can be used to evaluate the statistical significance of the coefficients. The smaller the p-
value, the more certain we are that the coefficient estimate is close to the actual population coefficient. Notice how both
the intercept and the independent variable coefficient is significant in this example. The extraction of the coefficients can
be accomplished via the coefficients keyword.

outR$coefficients
Pairs Trading

What follows is a really simple version of a pairs trade between two equities. The main motivation for the following
example is to expose you to obtaining data via the quantmod package and in using the lm() function that we covered
above.

We will explore a simple two-legged spread between AAPL and QQQ. Once we download and transform the timeseries
for both stocks, we will define a simple trading rule and explore the trades that our signal generates. Various trade
statistics and graphs will be presented.

Step 1: Obtain data via quantmod


#Utilize quantmod to load the security symbols

195
require(quantmod)
symbols <- c("AAPL", "QQQ")
getSymbols(symbols)

Now that our data frames for AAPL and QQQ are loaded into memory, let’s extract some prices.

Step 2: Extract prices and time ranges


#define training set
startT <- "2007-01-01"
endT <- "2009-01-01"
rangeT <- paste(startT,"::",endT,sep ="")
tAAPL <- AAPL[,6][rangeT]
tQQQ <- QQQ[,6][rangeT]

#define out of sample set


startO <- "2009-02-01"
endO <- "2010-12-01"
rangeO <- paste(startO,"::",endO,sep ="")
oAAPL <- AAPL[,6][rangeO]
oQQQ <- QQQ[,6][rangeO]

Notice how we defined and in-sample and out-of-sample range. We will use the in-sample data to compute a simple
hedge ratio and then we will apply this hedge ratio to the out of sample data.

Step 3: Compute returns and find hedge ratio


#compute price differences on in-sample data
pdtAAPL <- diff(tAAPL)[-1]
pdtQQQ <- diff(tQQQ)[-1]

#build the model


model <- lm(pdtAAPL ~ pdtQQQ - 1)

#extract the hedge ratio


hr <- as.numeric(model$coefficients[1])

Step 4: Construct the spread


#spread price (in-sample)
spreadT <- tAAPL - hr * tQQQ

#compute statistics of the spread


meanT <- as.numeric(mean(spreadT,na.rm=TRUE))
sdT <- as.numeric(sd(spreadT,na.rm=TRUE))
upperThr <- meanT + 1 * sdT
lowerThr <- meanT - 1 * sdT

#visualize the in-sample spread + stats


plot(spreadT, main = "AAPL vs. QQQ spread (in-sample period)")
abline(h = meanT, col = "red", lwd =2)
abline(h = meanT + 1 * sdT, col = "blue", lwd=2)
abline(h = meanT - 1 * sdT, col = "blue", lwd=2)

196
Let’s look at the distribution of the spread.

hist(spreadT, col = "blue", breaks = 100, main = "Spread Histogram (AAPL vs. QQQ)")
abline(v = meanT, col = "red", lwd = 2)

197
Step 5: Define the trading rule

Once the spread exceeds our upper threshold, we sell AAPL and buy QQQ. Once the spread drops below our lower
threshold, we buy AAPL and sell QQQ.

indSell <- which(spreadT >= meanT + sdT)


indBuy <- which(spreadT <= meanT - sdT)

Step 6: Figure out the trades


spreadL <- length(spreadT)
pricesB <- c(rep(NA,spreadL))
pricesS <- c(rep(NA,spreadL))
sp <- as.numeric(spreadT)
tradeQty <- 100
totalP <- 0

for(i in 1:spreadL) {
spTemp <- sp[i]
if(spTemp < lowerThr) {
if(totalP <= 0){
totalP <- totalP + tradeQty
pricesB[i] <- spTemp
}
} else if(spTemp > upperThr) {
if(totalP >= 0){
totalP <- totalP - tradeQty
pricesS[i] <- spTemp
}
}
}

198
Step 7: Visualize trades
plot(spreadT, main = "AAPL vs. QQQ spread (in-sample period)")
abline(h = meanT, col = "red", lwd =2)
abline(h = meanT + 1 * sdT, col = "blue", lwd = 2)
abline(h = meanT - 1 * sdT, col = "blue", lwd = 2)
points(xts(pricesB,index(spreadT)), col="green", cex=1.9, pch=19)
points(xts(pricesS,index(spreadT)), col="red", cex=1.9, pch=19)

199
Lecture 5 – Applied Statistical Concepts
In this lecture we will discuss statistical estimators, investigate the law of large numbers, the central limit theorem and
look at implementing all these concepts within R.

Population vs. Sample Statistics

Consider the set of numbers: 102, 103.2, 102, 101.2, 499, 103.2 101.23, 99.2.
Here are some questions we might want to ask about these numbers:

1. Where do these numbers come from?


2. Are these all the numbers, or should I expect more to arrive?
3. Is there a sequence (pattern) present or are these random?

We notice something right off the bat. We need to make assumptions. If these are the only numbers in our set, then we
call this set a population. If the numbers are a sample from a larger set, then we call this a sample.

Motivation

We want to write down the height of every Loyola student and we want to ask the following questions:

1. What is the average height of all the Loyola students?


2. What is the variance from that average height?

What is the sample and what is the population in this example?

Law of Large Numbers

Use R to generate 100000 random numbers. This is our population. Compute the mean and the standard deviation of
these numbers.

set.seed(100)
X <- rnorm(1000000, 2.33, 0.5)
mu <- mean(X)
sd <- sd(X)
hist(X, col = "blue", breaks = 100)
abline(v = mu, col = "red", lwd = 3)

200
Next, let’s take samples from this population and compute the average of these samples for different sample sizes.

sample5 <- sample(X, 5, replace = TRUE)


sample10 <- sample(X, 10, replace = TRUE)
sample50 <- sample(X, 50, replace = TRUE)

mean(sample5)
mean(sample10)
mean(sample50)
mean(sample(X,1000,replace=TRUE))
mean(sample(X,10000,replace=TRUE))

Notice how the mean of the samples converges to the population mean as the number of samples increases. This is
referred to as the Law of Large Numbers.

Central Limit Theorem

This example will build on the previous example. This time, we will take repeated measurements from X, but we will
keep the sample size the same.

meanList <- list()


for(i in 1:10000) {
meanList[[i]] <- mean(sample(X, 10, replace=TRUE))
}
hist(unlist(meanList), breaks = 500, main = "Repeated measurements of the
average for n = 10", col = "cadetblue", xlab = "average of 10 samples from X")
abline(v=mu,col="red",lwd=3)

201
The distribution of the sample average converges to a normal looking distribution! This property is referred to as
the Central Limit Theorem.

To see how powerful the Central Limit Theorem is, consider a population that is highly non-normal. We only draw a zero
or a one repeatedly and look at the distribution.

population <- sample(c(0,1), 100000, replace=TRUE)


hist(population, col="blue", main="Non-normal")
abline(v=mean(population), lwd=3, col="red")

202
By taking repeated samples of size 10 from this highly non-normal distribution, we still obtain a normal looking
distribution for the sample mean!

meanList <- list()


for(i in 1:10000) {
meanList[[i]] <- mean(sample(population, 10, replace = TRUE))
}
hist(unlist(meanList), main = "Repeated measurements of the average for n=10",
col = "cadetblue", xlab = "average of 10 samples")
abline(v = 0.5, col = "red", lwd = 3)

203
204
Unbiasedness and Efficiency

In a nutshell, unbiasedness means that the expected value of the estimator equals the true value of the parameter.
Efficiency means that the variance of the estimator is as small as possible.

Covariance Matrix

Before we can start talking about what a covariance matrix is, we need to address the concepts of volatility and portfolio
risk. This discussion will only focus on equities. Similar arguments can be applied to other instrument classes (bonds,
options, futures, etc).

Volatility

Volatility is a proxy for risk. The Greek letter sigma σ is used to denote volatility. Inherently, when a stock has more risk,
we can say that the σ of the stock is higher. Volatility is closely related to the second moment of the return distribution
of a particular stock. Volatility is also the square root of the variance.

Mathematical Properties of Volatility

The following mathematical operations apply to the variances of random variables X and Y



The ρ variable is the correlation between instruments X and Y. Think of X as the percentage returns of a particular stock
named X. Similarly, think of Y as the percentage returns of stock Y. The correlation between X and Y signifies the
strength of linear relationship between the two return series. The following graphic depicts the relationship between the
two random variables X and Y. The first one is a linear relationship, whereas the second one is a non-linear relationship.
Even though there exists a strong dependence between the random variables in the second graph, the correlation is
very low.

205
Portfolio Risk

The idea of portfolio diversification has been known for quite some time. Diversification implies that one can create a
portfolio of risky instruments where the aggregate risk is smaller than the sum of the individual risks. We can use formula
2 from above to understand this idea. The diversification (risk reduction) actually comes from the covariance term of
equation 2. As the number of instruments in a particular portfolio increases, so does the benefit of diversification via the
mutual covariance. Formula 2 becomes cumbersome to write as the number of instruments increases. The formula
below represents the variance of a portfolio of 3 instruments.

The risk of the portfolio σ is the square root of this formula. For many assets, writing down such a formula becomes a
nightmare. Luckily, there is an easier way to express the portfolio risk.

Matrix Notation

The portfolio variance for three assets can be written in a quadratic form as follows:

1.

2.
3.

206
The covariance matrix Σ plays a pivotal role in modern portfolio theory. All the important volatility and correlation
information between instruments can be encapsulated within this matrix. One of the important properties of a covariance
matrix is the notion of positive definiteness. Intuitively, we would never expect the risk of a portfolio to be a negative
number. Positive definiteness is exactly the property of the matrix that guarantees this fact. A covariance matrix is
positive definite if the following holds:

1. wΣw^t > 0

A covariance matrix can be produced within R by using the cov() command.

207
Lecture 6 – Stochastic Processes and Monte Carlo
The use of probability and statistics is ubiquitous in quantitative finance. All of the observable prices, volumes, order
arrival rates, etc, are due to supply and demand imbalances. However, keeping track of all the supply and demand
imbalances becomes cumbersome as the number of variables increases. Statistical tools are vital in explaining and
modeling these effects. Stochastic processes and Monte Carlo analysis are some of the tools that are used in the fields
of finance, economics and marketing.

Random Variables

The term random variable is somewhat of a misnomer. A random variable, in effect, is neither random nor a variable. It
is a function that maps a particular sample space Ω onto the real number line R. We can express the above statement
in mathematical notation as follows: X : S → R. For every event in S, X is a function that maps s to a real number.

The following examples will help illustrate the difference between the a) experiment, b) sample space and c) mapping
to the real line.

Flipping 2 coins

Consider flipping a fair coin 2 times. In this case, the experiment is the act of tossing the fair coin 2 times. The sample
space is all the possible outcomes of the experiment. In this simple example, the sample space consists of {Head,
Head}, {Head, Tail}, {Tail, Head} and {Tail, Tail}. Each {} item is an event in the sample space S. A random variable X
is simply the function that takes each {} and maps it into a number.

Tossing 1 die

In this experiment, you toss 1 fair die and count the number of dots that appear face up. The sample space is S =
{1,2,3,4,5,6}. We can define a random variable X that assigns the number of dots to the real numbers {1,2,3,4,5,6}

208
Stochastic Process

Another name for Stochastic Process is Random Process. Roughly speaking, one can think of such a process as a set
of random variables indexed by time. Consider the case of a repeated coin toss. This process is called a Bernoulli
Process. In effect, we have a sequence of random variables that can take on either the values 0 or 1. In this case, we
are implicitly defining a random variable that maps the outcome of a head to 0 and the outcome of a tail to 1. More
formally: A Bernoulli Process is a finite or infinite sequence of independent random variables X1, X2, X3, X4, … , such
that:

1. For each i,the value of Xi is either 0 or 1.


2. For all values of i, the probability that Xi = 1 is the same number p.

Brownian Motion

Our end goal will be to write down a stochastic process for a particular stock or equity index. In order to do so, we need
to explore the notion of a Wiener Process; otherwise known as Brownian Motion. This process forms the basic building
block of more advanced models. The three properties of Brownian Motion are:

1. W0 = 0.
2. Wt is almost surely continuous.
3. Wt has independent increments with Wt−Ws = N(0, t−s) for 0 < s < t.

Brownian Motion Example

Let’s take a look at an example of Brownian Motion.

bm <- cumsum(rnorm(1000,0,1))
bm <- bm - bm[1]
plot(bm, main = "Brownian Motion", col = "blue", type = "l")

209
Next, let’s take a look whether any dependence exists between consecutive observations.

acf(diff(bm), main = "Autocorrelation of Wt")

210
We can even investigate whether the differences are normally distributed.

par(mfrow = c(2,1))
hist(diff(bm), col = "orange", breaks = 100, main = "Wt-s Distribution")
qqnorm(diff(bm))
qqline(diff(bm))

211
Monte Carlo Analysis

Monte Carlo analysis is a practical technique that has a long history and a ton of theory behind it. Fermi, Ulam and Von
Neumann used statistical sampling ideas back in the 1930’s and 1940’s. The origins of statistical sampling date back to
Laplace in the early 1800’s. The name Monte Carlo Analysis was suggested by Metropolis in 1946. Monte Carlo was
used on the ENIAC computer to do neutron transport calculations in th mid 1940’s. Today, Monte Carlo analysis is
utilized in all fields of research. The main assumption of this approach is that a randomly chosen sample tends to exhibit
the same properties as the population from which it as drawn. Before we apply this technique to modeling stock prices,
let’s take a look at a simple example.

How many runs of 4 do we expect in a sequence of 1000 coin tosses?

In other words, we toss a coin 1000 times. How many times should we expect to see 4 heads or 4 tails in a row? This
is a problem that can easily be solved by repeated sampling from a known distribution.

run4 <- numeric(10000)


for(i in 1:10000) {
run4[i] <- sum(rle(sample(c(-1, 1), 1000, TRUE))$lengths == 4)
}
hist(run4)
mean(run4)

212
Armed with our basic building block (Brownian Motion, we can go on to construct a plausible model for the behavior of
actual stock prices. Before we proceed with constructing a model, let’s take a look at some of the stylized facts of actual
stock prices.

Prices and Returns


#load quantmod
library(quantmod)
getSymbols("AAPL")
price_AAPL <- AAPL[,6]
plot(price_AAPL, main = "The price of AAPL")

213
The first thing we notice is that this price series doesn’t appear to be stationary. In other words, there is no obvious
mean price and it doesn’t make sense to talk about the standard deviation of the price. Working with such non-stationary
timeseries is a hassle. If prices are not convenient to work with, then what should we use instead? Let’s take a look at
the percentage returns of this stock.

returns_AAPL <- diff(log(price_AAPL))


plot(returns_AAPL, main = "AAPL % returns", col = "navyblue")
hist(returns_AAPL, breaks = 100, col="brown")

214
Apart from some clustering in the returns plot, it appears that the returns are distributed somewhat like a normal
(Gaussian) distribution. This is an exciting fact since we already know how to work with normal distributions! How about
independence? Are these returns independent of each other in time? Here’s a quick way to partially answer that
question:

acf(returns_AAPL[-1], main = "Autocorrelation plot of returns")

215
Notice that there doesn’t seem to be any autocorrelation between consecutive returns. What are the mean and standard
deviation of these returns?

mR <- mean(returns_AAPL[-1])
sdR <- sd(returns_AAPL[-1])
> mR
[1] 0.001369495
> sdR
[1] 0.02572958

Leap of Faith

So, the typical argument goes as follows:

• We want to deal with stationary timeseries since we have a ton of statistical tools available at our disposal that deal
with such timeseries.
• Prices are surely non-stationary. Is there any other transformation of prices that, at least, looks like it might be
stationary?
• It seems like percentage returns fit the bill.
• It also looks like percentage returns have a stable mean and standard deviation.
• So we can make the claim that percentage returns are normally distributed with mean μ and standard deviation σ.

Now, remember what our end goal is. We want a way to simulate stock prices. In order to do so, we need to come up
with a model of how the prices behave (are distributed.) If returns are normally distributed, then how are prices
distributed? The answer to this question is straightforward. A little math shows us the answer: Rt = log(Pt/Pt−1). The
logarithm of the price is normally distributed. This means that price has a lognormal distribution. A straightforward
method to simulate a stock price is to draw a random normal number with a certain mean and standard deviation value,
and then exponentiate this number. Based on the formula from above: Pt = Pt−1*e^Rt. To summarize:

1. Draw a random number from N(μ, σ).


2. Exponentiate that number and mulitply it by Pt−1.
3. Repeat for t = 1…N.

N <- 1000
mu <- 0.0010
sigma <- 0.025
p <- c(100, rep(NA, N-1))
for(i in 2:N)
p[i] <- p[i-1] * exp(rnorm(1, mu, sigma))

216
plot(p, type = "l", col = "brown", main = "Simulated Stock Price")

Portfolio Simulation

Next, we will take a look at a simple portfolio simulation example.

require(MASS)
require(quantmod)

#load a few symbols into memory


getSymbols(c("AAPL", "QQQQ", "SPY", "GOOG", "CVX"))

#plot the prices of these stocks


par(mfrow = c(3,2))
plot(AAPL[,6], main = "AAPL")
plot(QQQQ[,6], main = "QQQQ")
plot(SPY[,6], main = "SPY")
plot(GOOG[,6], main = "GOOG")
plot(CVX[,6], main = "CVX")
par(mfrow = c(1,1))

#compute price matrix


pM <- cbind(AAPL[,6], QQQQ[,6], SPY[,6], GOOG[,6], CVX[,6])

#compute returns matrix


rM <- apply(pM,2,function(x) diff(log(x)))

#look at pairwise charts


pairs(coredata(rM))

#compute the covariance matrix


covR <- cov(rM)

#use this covariance matrix to simulate normal random numbers


#that share a similar correlation structure with the actual data

217
meanV <- apply(rM, 2, mean)
rV <- mvrnorm(n = nrow(rM), mu = meanV, Sigma = covR)

#simulate prices based on these correlated random variables

#calculate mean price


p0 <- apply(pM,2,mean)
sPL <- list()
for(i in 1:ncol(rM)){
sPL[[i]] <-round(p0[i]*exp(cumsum(rV[,i])),2)
}

#plot simulated prices


par(mfrow = c(3,2))
plot(sPL[[1]],main="AAPLsim",type="l")
plot(sPL[[2]], main = "QQQQ sim",type = "l")
plot(sPL[[3]], main = "SPY sim", type = "l")
plot(sPL[[4]], main = "GOOG sim",type = "l")
plot(sPL[[5]], main = "CVX sim", type = "l")

In the prior example, we gather daily data for 5 stocks and we compute the covariance matrix of the returns, along with
an average price for each security. Since the purpose of this exercise is to generate a realistic simulation of the portfolio,
we use the function mvrnorm() to create a matrix of random normal variables that are correlated in a similar manner as
the original data. The following graphs display the original stock prices, the pairwise plot of their returns and the
simulated stock prices. One can also look at the correlation matrix of the actual returns and the simulated returns and
verify that they are similar.

218
References

1. Stochastic Process – http://en.wikipedia.org/wiki/Stochastic_process


2. Random Variable and Process –
http://www.iitg.ernet.in/scifac/qip/public_html/cd_cell/chapters/Statistical%20Signal%20Processing.pdf
3. Bernoulli Process – http://en.wikipedia.org/wiki/Bernoulli_process
4. Wiener Process – http://en.wikipedia.org/wiki/Wiener_process
219
Lecture 7 – Visualization and Reporting with R
By now you should be familiar with most of the core functionality of the R programming language. We can perform
simulations, graph results, create summary statistics, export our results to files and accomplish almost any programming
feat by simply using the base installation of R. In this lecture I want to introduce a few tools (libraries) that extend R’s
reach. Especially, in terms of producing robust and elegant visualizations and drafting reproducible reports.

Graphing

All of our graphing needs up to this point have been met by the plot(), lines() and abline() functions. We can do better
than this.

Hadley Wickham has created a great library that takes graphing/plotting in R to a whole new level. The library is
called ggplot2 and much more information about it can be found here: ggplot2 examples.

According to the ggplot2 website: “ggplot2 is a plotting system for R, based on the grammar of graphics, which tries to
take the good parts of base and lattice graphics and none of the bad parts. It takes care of many of the fiddly details
that make plotting a hassle (like drawing legends) as well as providing a powerful model of graphics that makes it easy
to produce complex multi-layered graphics.”

Let’s take a look at a few examples. (I will closely follow the examples presented here.)

qplot()

We will be utilizing the mtcars data frame that is already accessible through R. Here’s what it looks like:

The qplot() function is similar to the plot() function in many respects.

require(ggplot2)
qplot(mpg, wt, data = mtcars)

220
# Add different colors based on the cylinder type
qplot(mpg, wt, data = mtcars, colour = cyl)

221
# Different styling based on the cylinder type
qplot(mpg, wt, data = mtcars, size = cyl)

# One or two sided faceting formula


qplot(mpg, wt, data = mtcars, facets = vs ~ am)

222
Graphics grammar syntax

Let’s take a look at a more interesting example that leverages the graphics grammar paradigm. More information on
what a graphics grammar is can be found here.

stocks <- data.frame(signal = sample(c("Buy", "Sell"), 100, replace = TRUE), returns = rnorm(100, 0, 0.6))

obj <- ggplot(stocks, aes(x=returns))


obj <- obj + geom_histogram(aes(y=..density..), binwidth=.8, colour="red", fill="grey")
obj <- obj + geom_density(alpha=.2, fill="purple")
obj

223
Reporting

No matter how sophisticated or elegant your statistical analysis happens to be, if you can’t report your results in a
meaningful and reproducible way to your management or end-users of your research, your conclusions will be lost. The
following steps outline one easy way for weaving together your analytics with your reporting and presenting the end
result in a clean and comprehensive manner.

Rstudio

Rstudio is currently one of the best IDE’s (Integrated Development Environment) available for the R language. It is 100%
free, is cross-platform (meaning that it works on windows, linux and mac machines) and enables R programmers to
become more productive by providing them with all the functionality they could possibly need within the IDE.

Rstudio can be downloaded from Rstudio.com. Rstudio bundles various third party libraries that we will be utilizing in
this lecture. One of them is knitr. This library was created by Yihui Xie and allows one to create PDF reports that include
embedded R code along with LaTeX formatting. To find more about what LaTeX is and how it can help you write better
documents, click here: LaTeX details.

224
In order to use the knitr library, LaTeX needs to be installed on your computer. If you are using a windows machine,
you can obtain LaTeX from here: LaTeX for Windows. If you are using a mac, you can get LaTeX from here: LaTeX
for Mac. Once you have downloaded and installed Rstudio, there is no need to install knitr. It is already part of the
Rstudio installation.

How to create a simple .pdf report with Rstudio

Here are some basics:

1. Open Rstudio, click on File -> New -> R Sweave

2. Let’s start off with a simple example that doesn’t include any R code yet. Type the following between the
begin{document} and end{document} tags. Hello R world. This is my first LaTeX document.
3. Now click on the Compile PDF button above and save the file to an appropriate location.
4. This is what you will see. A complete .pdf document.

225
Let’s take this a step further and add some R code and some formatting into the mix. Here’s what the complete document
contents should look like:

\documentclass{article}

\begin{document}

Hello R world. This is my first LaTeX document.

<<echo = TRUE, message = FALSE>>=

x <- rnorm(1000)
plot(x)

\end{document}

The echo = TRUE command tells knitr to output the code to the pdf file. Not only is the code echoed to the user, it is
also executed and the result prints to the pdf document.

226
Here’s a slightly more complicated example that passes a variable calculated from R back to the LaTeX text. I have also
added some formatting to the pdf document itself for adjusting the margins slightly. Notice the use of
the Sexpr command in the example below:

\documentclass{article}

\usepackage[
top = 1.00cm,
bottom = 1.00cm,
left = 2.00cm,
right = 1.50cm]{geometry}

\begin{document}

Hello R world. This is my first LaTeX document.

<<echo = TRUE, message = FALSE>>=

x <- rnorm(1000)
plot(x)
@

Now we will compute something else with R:

<<echo = TRUE, message = FALSE>>=

# Here's a coin toss simulation


N <- 100
coinToss <- sample(c(-1, 1), 100, replace = TRUE)

The number of heads that occur while tossing a fair coin \Sexpr{N} times is: \Sexpr{length(which(coinToss
== 1))}

\end{document}

227
Formatting a LaTeX document is something that we have not covered in this class. Here’s a cheat sheet that might
come in handy when working with LaTeX syntax.

228
SED Script Editor
SED is a simple editor for writing and editing strategy scripts. Scripts are just plain text, so you can use any text editor -
such as Windows Notepad™ - for them, but to help you on your way, SED provides syntax highlightning, a command
help window and other features. This is how SED looks:

At the bottom, we have the Command Help providing all the help from this help file while you writing. At the top, there
are buttons for commenting, indenting, etc. If you are not sure what a button does, keep the cursor over it and you'll
get a hint.

!! For editing strategy scripts or any other files that control an application or program on your PC, it's highly
recommended to disable the UAC (User Access Control) on Windows Vista or above. Otherwise Windows will create
shadow copies of your strategies in your User folder, and redirect all file access to that copy. This was originally
intended as a safe way of editing application data, but can lead in reality to great confusion when different copies of
the same scripts are at different places on your PC. For disabling the UAC, enter "uac" in the Windows Search box
and/or select the UAC control panel from "Settings", then set the UAC slider to its lowest position.

SED is (c) by oP group Germany and created by Gustav Nordvall.

229
SED Menu
File menu

New Do what they are supposed to do. You can open .C strategy scripts, or all other sorts of text files.
It's no problem to use SED as a replacement for the Notepad editor. If you save a file, the previous
Open... version will be kept as a backup when set in Preferences.

Save

Save As...

Save All

Close

Close All

Exit

Edit menu

Undo Undo / Redo the last operation.

Redo

Select All Selects the whole text in the file.

Cut Cuts selection to the clipboard, or cuts the current line if nothing is selected.

Copy Copies selection to the clipboard, or copies the current line if nothing is selected.

Paste Pastes the clipboard.

Find Text Opens the Find menu for searching through the file.

Find Next Jumps to the next occurrence of the Find text.

Find In Files Finds text in scripts from a folder structure. Leave the Select Folder field empty to search all .c,
.h files in the current folder and subfolders.

Replace Replaces text in a selection or in the rest of the file.

Indent All Indents the whole script, nice for cleaning up the code to get a better look at it.

Comment Line Comments all selected lines or comments the current line if nothing is selected.

Uncomment Removes the comments from all selected lines or uncomments the current line if nothing is
Line selected.

Insert Date Inserts the current date.

Goto Last Places the cursor at the last edited position in the same file.
Change

230
Bookmarks For easier navigation through your code, use bookmarks. You can also place numbered
bookmarks through Ctrl-0 .. Ctrl-9, and jump to a numbered bookmark through Alt-0 .. Alt-9.

Outlining Use outlining for making your scripts better readable. You can collapse or expand the content of
functions and structs by clicking the [-] and [+] outlining icons near to them. Use Collapse
All or Expand All to shrink or expand the content of the whole script.

Options menu

Auto Indent Indents automatically when pressing enter.

Sort Code Sorts the code jumper ascending.


Jumper

Highlight Highlights the selected line, with use of the highlight color which can be changed in
Selected Line the Customize Colors dialog.

Indentation Shows markers where the tabs are placed.


Guide

Show Line Displays the line numbers to the left. You can find the current line number in the bottom status
Numbers line.

Show Shows the outlining [-] and [+] icons to the left.
Outlining

Toolbars Displays the Toolbars, Command Help, and Variable List.

Preferences Takes you to the Preferences configuration with the rest of the options.

Customize Customizes individual keys for the menu commands.


Hotkeys

Font Changes the text font.

Windows Menu

Cascade

Tile Vertically

Tile
Horizontally

Arrange Icons

Plugins Menu

Plugins found in the sed_plugins folder will appear in this menu. Instructions on how to create
plugins for SED are available from the download page at www.3dgamestudio.com.

Help menu

Help Opens this help manual.

About Displays SED's version number and the user ID (if any).

231
Editing
Smart built-in shortcuts can help you a lot for quickly editing scripts.

Inserting Commands
If you want to add a command or engine variable, press [Ctrl+Space]. It will bring up a list of all keywords from the
commands database. Press the first letter of the command you want to add and the list will scroll. You can also go up
and down in the list by pressing [up/down]-arrow and [page up/page down].

You can also press [Ctrl+Space] even if you already started writing on the command. The list will scroll according to
the text before the cursor, and you can press [enter] to complete the command.

Commenting lines
Commenting lines is very useful when you want the compiler in the engine to skip a part of code. To comment lines,
select them and press the blue //... at the toolbar. You can also press [Ctrl+Alt+C]. This will add the comment
token // before each of the selected lines. To uncomment lines, select them and press the red //... at the toolbar button
or press [Ctrl+Shift+C]. This will remove // from each of the selected lines.

Using the Code Jumper


The code jumper can save you a lot of time. Let's say you write a function named myfunction. Then when the script
gets bigger, you decide to go back and change it. Now you will use the code jumper.

As default it is located at the right, and contains all the script's elements such as functions, var*, strings etc. Just
expand the Functions section of the code jumper and click on myfunction. The cursor position will move
to myfunction directly.

Smart Editing
While you are writing, notice the auto indent function that automatically indents when pressing [Enter]. Also when
pressing [Enter] and if a '{' is located in the line, a '}' symbol will be added automatically. A tab will be added to the
line to indent the code between the brackets. The same thing happens with the '(' symbol, when you add it,
a ')' symbol is added automatically. All those smart features can be switched on and off in Options / Preferences /
Environment.

All windows attached to SED is floating, which means they could be moved and the position will be stored. So use this
and customize colors, located in the syntax menu, to get a nice interface for your needs.

Some shortcuts to keep in mind:


- [Alt+Left] jumps to the last edit position.
- [Ctrl+F6] switches between opened scripts.
- [F1] brings up a help file.

SED Preferences
Open Options / Preferences for changing the settings below:

Environment

Tab Size The size of the tabs in the code, default is 3.

Auto Indent Set indents automatically when pressing [Enter].

Automatic Adding Adds a closing bracket when an opening bracket is entered (default: on).
Brackets

232
Dynamic Brace When closing a bracket, the opening bracket is highlighted.
Matching

Load included files Loads the included script files when opening a script, off by default.

Show hints while Shows hints while writing instructions, on by default


writing

Start with a blank Starts with a blank script, on by default.


script

Backup

Enable Backup If this option is selected, a backup will be created in the sed_backup folder when opening
a file.

Time between Saves The current file will be automatically saved every X minutes.

Delete Files Older Periodically dDeletes files older than the given number of days from the sed_backup
Than folder.

Go To Folder Opens the sed_backup folder for restoring files.

Customize Colors

Customizes the colors for the SED syntax. Press Defaults to restore all colors to the
default value.

Customize Keywords

Add/Edit keywords for the SED syntax highlighting. To delete a keyword, select the
keyword and press Delete.

233
Strategy Script Introduction
A script describes the trade rules in a sort of simplified language, called lite-C (a "light" variant of the C programming
language). Theoretically, there could be other ways to define a strategy, for instance by setting up a list of indicator
filters and thresholds, or by entering formulas in a spreadsheet program. However, those methods would severely limit
any strategy to mostly predefined rules and indicators. It is very difficult, if not impossible to define a profitable strategy
for today's markets this way. All known successful automated strategies are script based.

The disadvantage of a script is obviously that you have to learn the script language. Most products using strategy scripts
require that you dive deeply into programming. No so with Zorro: lite-C is arguably the world's easiest serious
programming language. It 'hides' almost all the programming stuff and allows you to concentrate on plain strategy. You
can learn the lite-C essentials in about one day.

A simple strategy script in lite-C looks as shown below. This strategy uses two simple moving averages (SMA) with 30
and 100 bars time period, places a stop at 10 pip distance, and buys a long or short position when the faster average
crosses over or under the slower average:

function run()
{
vars Close = series(priceClose());
vars SMA100 = series(SMA(Close,100));
vars SMA30 = series(SMA(Close,30));
Stop = 10*PIP;

if(crossOver(SMA30,SMA100))
enterLong();
else if(crossUnder(SMA30,SMA100))
enterShort();
}

This is a primitive (and not really profitable) trend following strategy. A similar, but more profitable strategy can be found
in lesson 4 of the Automated Trading Course. The course is highly recommended for learning lite-C, no matter if you
have never programmed before or have already some programming knowledge.

For writing your own systems, it can save you a lot of time when you read this manual fully. Zorro has many script
functions that you do not need to program yourself. Often-used code snippets for your own scripts and strategies can
be found on the tips & tricks page. If you worked with a different trade platform before, read the conversion page
about how to convert your old scripts or EAs to lite-C.

234
Variables, Arrays, Strings
Variables store numbers. For defining variables and giving them initial values, use a declaration like this:

int name; // uninitialized variable of type 'int'


int name = 123; // initialized variable

This declaration creates a variable of type int with the given name. The name can contain up to 30 characters, and
must begin with A..Z, a..z, or an underscore _. Variables must never have names that are already used for functions
and for other previously-defined variables.

Also constant expressions can be assigned to variables. Example:

int size = 100/2; // assign the value 50

!! Be careful when assigning constant expressions to variables. If the constant expression only contains integers
(numbers without decimals), the result is also an integer. For instance, the constant (99/2) is 49, but (99.0/2.0) is 49.5.
Likewise, the constant 1.0/2 is 0.5, but 1/2 is 0!

Variable types

Computers always perform their calculations with finite accuracy, so all normal variable types are limited in precision
and range. Lite-C supports the following variable types for different purposes:

Type Size (bytes) Range Step Digits Used for


double, var 8 -1.8·10 308
to 1.8·10 308
2.2·10 -308
~14 prices
float 4 -3.4·10 to 3.4·10
38 38
1.2·10 -38
~6 compact prices
fixed 4 -1048577.999 to 1048576.999 0.001 ~9 compact prices
int, long 4 -2147483648 to 2147483647 1 ~10 counting, flags
short 2 0 to 65536 1 ~4 saving space
char, byte 1 0 to 256 1 ~2 text characters
bool 4 true, false - - decisions
char*, string characters+1 "..." - - text
var*, vars elements*8 -1.8·10 308
to 1.8·10 308
2.2·10 -308
~14 arrays, series
mat rows*cols*8+12 -1.8·10 308
to 1.8·10 308
2.2·10 -308
~14 vectors, matrices
DATE 8 12-30-1899 00:00 to doom ~0.1 ms - time, date

Directly entered numbers in the script - such as character constants ('A'), integer numeric constants (12345) or
hexadecimal constants (0xabcd) are treated as int. Constants containing a decimal point (123.456) are treated
as var. Script constants have a precision of only 6 digits after the decimal, even when you type more digits.

Cast operators
Variables are normally automatically converted to another type. So you can assign var to int variables or vice versa
without having to care about type conversion. However, sometimes you'll need to convert expressions or variables
when the conversion is not obvious - for instance when calling an overloaded function that accepts several variable
types. In this case you can enforce a type conversion with a cast operator. A cast (or typecast) operator is the target
variable type in parentheses placed before the expression or variable to be converted. Example:
var dx = 123.456;
printf("The value of dx is: %i",(int)dx); // %i expects the "int" variable type

Global, local, and static variables


Variables can be defined outside functions or inside functions. When defined outside, they are called global
variables and are accessible by all functions. They keep their values until the script is compiled the next time.
Variable definitions inside functions produce local variables. Local variables only exist within the scope of the
function. They are unknown outside and lose their values when the function returns. If a global variable with the same
name as a local variable exists, the local variable has priority inside it's function.

235
A local variable can be declared as static. It is then treated as a global variable even if it is declared within a function,
and keeps its content when the function terminates or when another instance of the function is called. This means that
it is initialized only the first time the function is called. Example:

function foo()
{
static bool initialized = false;
if (!initialized) { initialize(); } // initialize only once
initialized = true;
...
}
!! Because static and global variables keep their values until the script is compiled, make sure to initialize them at
script start - if(is(INITRUN)) - to their initial values if required.

Arrays
If you group several variables together, you have an array:
var name[n]; // uninitialized array
var name[n] = { 1, 2, ... n }; // initialized global array
This creates a variable that contains n numbers, and optionally gives them default values as in the second line. Note
the winged brackets { } that must surround the set of default values. Such a multi-number variable is called an array.
The number n is called the length or the dimension of the array. Example:
int my_array[5] = { 0, 10, 20, 30, 40 };

This creates an array of 5 numbers that can be accessed in expressions through my_array[0]...my_array[4]. In the
expression the number in the [ ] brackets - the index - tells which one of the 5 numbers of the array is meant. Note that
there is no my_array[5], as the index starts with 0. The advantage of using an array, compared to defining single
variables, is that any numeric expression also can be given as index. Example:

int i;
for (i=0; i<5; i++)
my_array[i] = i; // sets the array to 0,1,2,3,... etc.

Care must be taken that the index never exceeds its maximum value, 4 in this example. Otherwise the script will crash.

!! Initializing in the definition works only for simple arrays; multidimensional arrays, arrays that contain something else
than numbers, or several arrays defined together in a single logical line must be initialized by explicitly setting their
elements. Initializing local arrays makes them static (see below), meaning that they keep their previous values when
the function is called the next time. For initializing them every time the function is called, explicitly set them to their initial
values, like this:

function foo()
{
var my_vector[3];
my_vector[0] = 10;
my_vector[1] = 20;
my_vector[2] = 30;
...
static var my_static[3] = { 10, 20, 30 }; // initializing local arrays makes them static
...
}

!! All local variables are stored on a special memory area called the stack. In lite-C the stack has a limited size that is
sufficient for many variables, but not for huge arrays. Exceeding the stack size causes a Error 111 message at runtime,
so when you need large arrays of ten thousands of variables, declare them outside the function. When you want to
determine the array size dynamically, use the malloc / free method.

Arrays can contain not only variables, but also complex data types such as structs. For sorting or searching arrays, the
standard C functions qsort and bsearch can be used. They are documented in any C book and available in the stdio.h
include file.

Multidimensional arrays can be defined by using several indices:

var heightmap[10][20];
...
heightmap[j][i] = 10; // j = 0..9, i = 0..19

typedef
236
You can define your own variable types with typedef statements. Example;
typedef long DWORD;
typedef char byte;

...

DWORD dwFlags;
byte mypixel;
typedef can be used to redefine variables and structs; it can not be used for arrays.

Strings

Strings are arrays of char variables that contain alphanumerical characters - letters, numbers or symbols - which can
be used for messages or names. They are defined this way:

string Name = "characters";

This defines a string with the given name and initializes it to the content characters between the quotation marks.
Special characters within a string must be preceded by a '\': \n = Line feed; \\ = Backslash; \" = Quotation mark.
The string type is a char pointer (char*) that points to the first character. The last character is followed by a 0 byte to
indicate the end of the string.

Any static char array can serve as a string with a fixed maximum length and variable content:

char Name[40];
...
strcpy(Name,"My String!");

After filling this array with characters, it can be used as a string. Care must be taken not to exceed 39 characters, as
the last one must be the end mark 0. String functions can be used for setting and manipulating string content.

The content of a string can be compared with a string constant using the '==' operator:

string MyAsset = "AAPL";


...
if(MyAsset == "AAPL")
...
!! Comparing strings with string constants is not the same as comparing two strings (f.i. if(MyAsset1 == MyAsset2)
...). The latter expression is only true when the two string pointers are really identical. For comparing the contents of
two non-constant strings, use standard C library functions such as strstr or strcmp.
!! Some string functions return temporary strings that are allocated on the CPU stack. Temporary strings can not
be used for permanently storing content. They lose their content as soon as another function call returns a temporary
string.

Series

Series are a special array of var variables, normally used for price data, indicators, or data curves. If not given
otherwise, the length of a series is determined by the global variable LookBack. Series data is stored in reverse order,
i.e. the latest data is at the begin of the array.

Pointers
Pointers store references to variables. They point to the location in memory where the variable stores it's content.
Pointers allow, for instance, the same function to do the same with different global variables, depending on what variable
is currently referenced through a certain pointer. Pointers can be used like synonyms or alternative names for single
variables or for arrays of variables.

A pointer is defined by adding a '*' to the name, like this:

var *mypointer; // defines a pointer of type var with the name mypointer
237
The '*' is also used for multiplication, but the compiler knows from the context if a pointer or a multiplication is meant.

You can get a pointer to any variable by adding a '&' to the variable name:

var myvar = 77;


mypointer = &myvar; // now mypointer points to myvar

You can see the '&' as the opposite of '*'. For accessing a variable that is the target of the pointer, add a '*' to the pointer
name, just as in the pointer definition. This way the variable can be directly read or set:

*mypointer = 66; // now myvar contains 66

Pointers can also point to variable arrays, and can access their elements just by adding the usual [0], [1], ... etc. to the
pointer name. In fact pointers and arrays are the same internal type. When mypointer is a pointer to an
array, mypointer+n is a pointer to the n-th element of that array. Therefore for accessing elements of the
array, *mypointer points to the same as element as mypointer[0] and *(mypointer+n) points to the same element
as mypointer[n].

Variable pointers in functions


There can be some situation where variable pointers might be useful. Normally if you pass a variable to a function, the
function works merely with a copy of that variable. Changing the variable within the function only affects the copy.
However if you pass the pointer to a variable, the function can change the original variable. For getting a pointer to a
variable, just place a '&' before the variable name. Example:
function change_variable(var myvar)
{
myvar += 1;
}

function change_variable_p(var *myvar)


{
*myvar += 1;
}
...
var x = 10;
change_variable(x); // now x is still 10
change_variable_p(&x); // now x is 11

Lite-C automatically detects if a function expects a variable or a pointer to a variable, so you can usually omit the '&'
and just write:

change_variable_p(x); // now x is 1

Function pointers
A function pointer is defined just as a function prototype with return and parameter types. Example:
float myfunction(int a, float b); // define a function pointer named "myfunction"

float fTest(int a, float b) { return (a*b); }


...
myfunction = fTest;
x = myfunction(y,z);
For storing arrays of function pointers in lite-C, void* arrays can be used. Example:
float myfunction(int a, float b); // define a function pointer

void* function_array[100]; // define a pointer array

float fTest(int a, float b) { return (a*b); }


...
function_array[n] = fTest;
myfunction = function_array[n];
x = myfunction(y,z);

238
Structs
A struct is an assembled object that contains variables, pointers, or further structs. Members of a struct are individually
accessed using the struct name, followed by a '.' and the member name. Example:

typedef struct {
int x;
int y;
string name;
} SPOT; // defines a struct type named "SPOT"
...
SPOT myspot; // creates an uninitalized SPOT struct named "myspot"
SPOT* pspot; // creates an uninitalized pointer to a SPOT struct
...
myspot.x = 10;
myspot.name = "test!";

A struct can contain pointers to previously defined structs, and even pointers on itself, but no pointers to later defined
structs:

typedef struct SPOT {


int x;
int y;
string name;
struct SPOT* next; // pointer to a SPOT struct
} SPOT; // defines a struct type named "SPOT"

In lite-C, struct pointers can be initialized to a static struct. Example:

SPOT* myspot = { x = 1; y = 2; name = "my struct"; }


// creates a new SPOT struct with initial values that is then referenced through the myspot pointer

In standard C / C++, members of structs are accessed by a dot '.' and members of struct pointers are accessed by an
arrow '->'. In lite-C, the dot '.' can normally be used for both because the compiler automatically detects whether a simple
object is a pointer or not. You only need to give the '->' in ambiguous cases, for instance when the struct pointer is a
member of another struct pointer.

SPOT* myspot = { x = 1; y = 2; name = "my struct"; }


...
myspot->x = 1; // standard C/C++; works also in lite-c
myspot.y = 2; // lite-C

Working with structs is explained in any C/C++ book, so we won't cover it here in detail. For creating structs or arrays
of structs at run time, the standard C library functions malloc and free can be used. For initializing or copying structs,
use the C library functions memset() and memcpy():

function foo()
{
SPOT* myspot = malloc(sizeof(SPOT)); // creates a new SPOT struct at runtime
memset(myspot,0,sizeof(SPOT)); // set the struct content to zero (it's undefined after malloc)
myspot->x = 1;
SPOT* spot_array = malloc(100*sizeof(SPOT)); // creates an array of 100 SPOT structs
memcpy(&spot_array[0],myspot,sizeof(SPOT)); // copy the myspot struct to the first member of the array
...
free(myspot); // removes the created structs
free(myspot_array);
}

!! There is plenty information about C, C++, or Windows library functions on the Internet or in online C courses. It is
highly recommended for adcanced lite-C programming to work through such a course or book and learn
about malloc, memcpy and all the other library functions that you can use.

sizeof(struct)
The sizeof() macro that is used in the example above gives the size of a variable or a struct in bytes. This can be
used to initialize structs:
#define zero(struct) memset((void*)&struct,0,sizeof(struct))
...

239
SPOT speed;
zero(speed); // initializes the SPOT struct "speed" to zero

!! Arrays are internally treated as a pointer to a memory area. So, sizeof(any_array)


and sizeof(any_pointer) always equals to 4 because that is the size of a pointer.

240
Functions
A function is simply a list of commands. When the function is called, the commands will be executed one after another.
Such a command can assign a value to a variable, or call another user-defined or pre-defined function. A function is
defined this way:

functiontype name (parameter1, parameter2, ...) { ... code ... }

functiontype is the variable type (such as var, int...) that the function returns to the caller. If the function returns nothing,
write just function or void for the type.

name is the name of the function. It must follow the same rules as variable names.

(parameter1, parameter2, ...) is a list of variables that the function expects from the caller. For each variable that is
passed to the function, the parameter list must contain one entry. This entry specifies the variable type and the name
with which the variable is referred to inside the function. The parameter list is enclosed in parentheses. If the function
expects no variables, leave the parentheses empty.

{ ... code ... } is the function body, enclosed in braces. It's a list of commands that do the real work. When a function is
called, execution begins at the start of the function body and terminates (returns to the calling program) when
a returnstatement is encountered or when execution reaches the closing brace.

A simple function definition without parameters looks like this:

function alert()
{
printf("Warning!");
}

This function is executed whenever the program calls the command alert(). The next example is a function that takes
a var as parameter:

function alert2(var value)


{
if (value > 50)
printf("Warning - value above 50!");
}

If a global variable or another object of the same name as the parameter exists, all references within the function refer
to the 'local' parameter and not to the 'global' variable. The function may change any parameter, as 'value' in the
example, but the original variable in the calling function remains unchanged.

Return values
A function can return a value to its caller, through the return statement. Example:
var average(var a, var b)
{
return (a+b)/2;
}

// this can be called this way:


...
x = average(123,456); // calculate the average of 123 and 456, and assign it to x

Function prototypes and pointers


Like all other objects, functions must be defined before they can be called by another function. Sometimes this is
inconvenient, for instance if two functions call each other. In that case, a function can be predefined through
a prototype. The prototype consists of the function title with the parameters, followed by a semicolon, but without the
command list in {...}. In the argument list of a prototype, the variable names can be omitted, only the variable types are
necessary.Example:
var greater_of(var,var); // prototype of function greater_of

241
var greater_of(var a, var b) // real function greater_of
{
if (a > b) return a;
else return b;
}

In lite-C a function prototype can also be used as a function pointer. Example:

long MessageBox(HWND,char *,char *,long); // prototype of function MessageBox


...
MessageBox = DefineApi("user32!MessageBoxA"); // set the MessageBox pointer to a function in the user32.dll

Recursive functions
A function can call itself - this is named a recursive function. Recursive functions are useful for solving certain
mathematical or AI problems. Example:
int factorial(int x)
{
if (x <= 1) return 1;
if (x >= 10) return(0); // number becomes too big...
return x*factorial(x-1);
}

Recursive functions must be used with care. If you make a mistake, like a function that infinitely calls itself, you can
easily produce a 'stack overflow' which crashes the computer. The stack size allows a recursion depth of around 1000
nested function calls, dependent on number and size of local variables inside the function.

Overloaded functions
In lite-C, functions can be overloaded, which means you can define several functions with the same name, but different
parameter lists. The compiler knows which function to use depending on the types and numbers of parameters passed.
Example:
var square(var x)
{
return(x*x);
}
int square(int x)
{
return(x*x);
}
...
var x = square(3.0); // calls the first square function
int i = square(3); // calls the second square function

Variable number of parameters


Functions can be called with less or more than given number of parameters when the parameter list has a "..." for the
last parameter. The function can then be called with less parameters; the missing parameters at the end are replaced
with 0. Functions in an external DLL or library, such as printf, can also be called with more parameters. C specific
macros such as va_start and va_end then retrieve the additional parameters. Example:
int test(int a,int b,int c,...)
{
return a+b+c;
}

void main()
{
printf("\n%d",test(1,2,3)); // 6
printf("\n%d",test(1,2)); // 3
printf("\n%d",test(1)); // 1
printf("\n%d",test()); // 0
}

Calling modifiers
In lite-C, C,or C++ the __stdcall calling modifier is used to call Win32 API functions. The called function cleans the
stack. The __cdecl calling modifier is the default calling convention in C-Script, lite-C, C and C++ programs. Because
the stack is cleaned up by the caller, it can do variable argument (vararg) functions, like printf(...).

242
Special functions
If a function named main() exists in the script (see above example), it is automatically called at the start of the program.
The program terminates when the main() function returns. This is for using a script for other purposes than for trading.

It a function named run() exists in the script, it is automatically called at the start of the program and also after every
bar. The program terminates when there are no more bars, or when [Stop] is clicked. More functions that are
automatically called at certain events are listed here.

return
return (expression)
Terminates the function. In the second case the given expression is returned as result to the calling function, where it
can be evaluated.

Parameters:
expression - value to be returned, like a variable, expression or pointer.

Example:
var compute_days(var age, var days)
{
return age * days;
}

243
Expressions
An expression is an arithmetical operation that delivers a result, which can then be assigned to a variable or object
parameter. The arithmetic expression may be composed of any numbers, further variables or object parameters,
function calls, brackets, and arithmetic operators.

The following operators are available in expressions:

= Assigns the result right of the '=' to the variable left of the '='.

+-*/ The usual mathematical operators. * and / have a higher priority than + and -.

% Modulo operator, the integer remainder of a division.

| Bitwise OR, can be used to set certains bits in a variable.

^ Bitwise exclusive OR, can be used to toggle certain bits in a variable.

~ Bitwise invert, toggles all bits of a variable.

& Bitwise AND, can be used to reset certains bits in a variable.

>> Bitwise right shift, can be used to divide a positive integer value by 2.

<< Bitwise left shift, can be used to multiply a positive integer value by 2.

() Parentheses for defining the priority of mathematical operations.

Examples:
x = (a + 1) * b / c;
z = 10;
x = x >> 2; // divides x by 4 (integer only)
x = x << 3; // multiplies x by 8 (integer only)
x = fraction(x) << 10; // copies the fractional part of x (10 bits) into the integer part

Assignment operators
The "="-character can be combined with the basic operators:

+= Adds the result right of the operator to the variable left of the operator.

-= Subtracts the result right of the operator from the variable left of the operator.

*= Multiplies the variable left of the operator by the result right of the operator.

/= Divides the variable left of the operator by the result right of the operator.

%= Sets the variable left of the operator to the remainder of the division by the result right of the operator.

|= Bitwise OR's the the result right of the operator and the variable left of the operator.

&= Bitwise AND's the the result right of the operator and the variable left of the operator.

^= Bitwise exclusive OR's the the result right of the operator and the variable left of the operator.

>>= Bitwise right shift the variable left of the operator by the result right of the operator.

244
<<= Bitwise left shift the variable left of the operator by the result right of the operator.

Increment and decrement operators


Variables can be counted up or down by attaching '++' or '--' before or after a variable.

x++ Increments x by 1; the result is the previous value of x (before incrementing).

++x Increments x by 1; the result is the current (incremented) value of x. This is slightly faster than x++.

x-- Decrements x by 1; the result is the previous value of x (before decrementing).

--x Decrements x by 1; the result is the current (decremented) value of x. This is slightly faster than x--.

Examples:
x = x + 1; // add 1 to x
x += 1; // add 1 to x
++x; // add 1 to x

Remarks:

• For setting and resetting flags through the & or | operators, use long or int variables.
• !! The precedence of comparison and expression operators follows the C/C++ standard. Use parentheses in case of
doubt. For instance, the expressions (x & y == z) and ((x & y) == z) give different results because the & operator has
lower precedence than the == operator.

Comparisons
A comparison is a special type of expression that delivers either true (nonzero) or false (zero) as a result. There are
special comparison operators for comparing variables or expressions:

== True if the expressions left and right of the operator are equal.

!= True if the expressions left and right of the operator are not equal.

> True if the expression left of the operator is greater than the expression right of the operator.

>= True if the expression left of the operator is greater than or equal to the expression right of the operator.

< True if the expression right of the operator is greater than the expression left of the operator.

<= True if the expression right of the operator is greater than or equal to the expression left of the operator.

and, && True if the expressions left and right of the operator are both true.

or, || True if any one of the expressions left and right of the operator is true.

not, ! True if the expression right of the operator is not true.

() Brackets, for defining the priority of comparisions. Always use brackets when priority matters!

Remarks:

• The "equals" comparison is done with '==', to differentiate it from the assignment instruction with '='. Wrongly using '='
instead of "==" is not noticed by the compiler because it's a valid assignment, but is one of the most frequent bugs in
scripts.
245
• For comparing the content of structs or arrays, compare their elements. The only exception is the comparison '==' that
can compare the content of string variables against a string constant.
• The precedence of comparison and expression operators follows the C/C++ standard. Use parentheses in case of
doubt. For instance, the expressions (x & y == z) and ((x & y) == z) give different results because the & operator has
lower precedence than the == operator.
• Unlike C/C++, lite-C evaluates all parts in a && comparison, even if one of it evaluates to false. Therefore, avoid
constructs like if (p && p->data == 1)..; use if (p) if (p->data == 1).. instead.

Examples:
10 < x // true if x is greater than 10
(10 <= x) and (15 => x) // true if x is between 10 and 15
!((10 <= x) and (15 => x)) // true if x is less than 10 or greater than 15 (lite-C only)

if (comparison) { instructions... }
else { instructions... }
If the comparison between the round brackets is true resp. non-zero, all commands between the first pair of winged
brackets are executed. It it's not true resp. zero, all commands between the second pair of winged brackets
following else will be executed. The else part with the second set of commands can be omitted. The winged brackets
can be omitted when only one instruction is to be executed dependent on the comparison.

Example:
if (((x+3)<9) or (y==0)) // set z to 10 if x+3 is below 9, or if y is equal to 0
z = 10;
else
z = 5;// set z to 5 in all other cases

while (comparison) { instructions... }


do { instructions... } while (comparison) ;
Repeats all instructions between the winged brackets as long as the comparison between the round brackets is true
resp. evaluates to non-zero. This repetition of instructions is called a loop. The while statement evaluates the
comparison at the begin, the do..while statement at the end of each repetition.

Remarks:

• If you want the loop to run forever, simply use the value 1 for the comparison - 1 is always true.
• Loops can be prematurely terminated by break, and prematurely repeated by continue.
• The winged brackets can be omitted when the loop contains only one instruction.

Example:
x = 0;
while(x < 100) // repeat while x is lower than 100
x += 1;

for (initialization; comparison; continuation) { ... }


Performs the initialization, then evaluates the comparison and repeats all instructions between the winged brackets as
long as the comparison is true resp. non-zero. The continuation statement will be executed after the instructions and
before the next repetition. This repetition of instructions is called a loop. Initialization and continuation can be
any expression or function call. A for loop is often used to increment a counter for a fixed number of repetitions.

Remarks:

246
• Loops can be prematurely terminated by break, and prematurely repeated by continue.
• The winged brackets can be omitted when the loop contains only one instruction.

Example:
int i;
for(i=0; i<5; i++) // repeat 5 times
x += i;

switch (expression) { case value: instructions... default: instructions... }


The switch statement allows for branching on multiple values of an int variable or expression. The expression is
evaluated and compared with the case values. If it matches any of the case values, the instructions following the colon
are executed. The execution continues until either the closing bracket or a break statement is encountered. If the
expression does not match any of the case statements, and if there is a default statement, the instructions
following default: are executed, otherwise the switch statement ends.

Example:
int choice;
...
switch(choice)
{
case 0:
printf("Zero! ");
break;
case 1:
printf("One! ");
break;
case 2:
printf("Two! ");
break;
default:
printf("None of them! ");
}

break
The break statement terminates a loop or a switch..case statement, and continues with the first instruction after the
closing bracket.

Example:
while (x < 100)
{
x+=1;
if (x == 50) break; // loop will be ended prematurely
}

continue
Jumps to the begin of a while loop or the continuation part of a for loop.

Example:
int x = 0;
int y = 0;
while (x < 100)
{
x+=1;
if(x % 2) // only odd numbers
continue; // loop continuing from the start
y += x; // all odd numbers up to 100 will be sum
}

247
#include "filename"
#include <filename>
Reads an additional script from the given file name and then continues compiling the original script file. This way a
stategy can consist of an arbitrary number of scripts.

Remarks:

• If the script name is given in angular brackets <..>, it is searched in the include folder. The include folder contains all
common .h include files and the default.c script with all the trading definitions.
• If the script name is given in double quotes "...", it can be in any folder; the path must then be given, either an absolute
path (f.i. "C:\...") or relative to the Zorro folder (f.i. "Strategy\...").
• If no #include statement is found in the main script, default.c is automatically included. Otherwise you need to
add #include <default.c> either in the included file, or in the main file before any other #include statements.

Example (lite-C)
#include <default.c> // default trading functions
#include <windows.h> // include the Windows API
#include "Strategy\common.c"

#ifdef name
#ifndef name
#else
#endif
Defined names can be used to to skip certain script lines dependent on previous #defines. All script lines
between #ifdef and #endif are skipped if name was not defined. Likewise, all lines between #ifndef and #endif are
skipped if name was#defined. The #else statement reverses the line skipping or non-skipping.

Example:
#define SCALPING
...
#ifdef SCALPING
aut.nBarMinutes = 0;
#endif

#define name
Defines the name as a condition for later including or excluding lines (see #ifdef), or for setting other special
conditions during compilation.

Example:
#define TEST
...
#ifdef TEST
printf("This is a test!");
#endif

#define name value


Every time the name appears in the script below the #define, it will be replaced by the value, which can be another
name, a number, or a simple arithmetic expression. Replacing names makes functions more 'readable', for instance by
giving the entities' general purpose skill parameters some meaningful names.

Examples:
#define PI 3.14159
#define HEALTH skill17
#define WINAPI __stdcall
...
x = 2.0*PI;
my.HEALTH -= 50;

248
long WINAPI MessageBox(HWND,char *,char *,long);

Remarks

• #defines are valid within all subsequent code.


• #defines are only evaluated during compilation, not in already-compiled scripts.
• As a convention, defined names are normally written in uppercase.

#undef name
Undefines a previously defined name.

#define macro(parameter,..) expression(parameter,..)


Defines a macro as a replacement or abbreviation for a numerical expression. Whenever the macro is encountered in
the code, the expression is executed. Macros work rather like functions, but with some minor differences. Since macros
are implemented as a textual substitution, there is no effect on program performance (as with functions), however they
produce larger code than functions. They are normally only used for fairly small expressions.

Examples:
#define set(obj,flag) obj.flags |= (flag)
#define reset(obj,flag) obj.flags &= ~(flag)
#define toggle(obj,flag) obj.flags ^= (flag)
#define is(obj,flag) (obj.flags & (flag))
#define zero(ptr) memset((void*)&ptr,0,sizeof(ptr))

#define macro(parameter,..) expression(parameter##token,..)


The merging operator ## adds the token to the parameter. Useful for redefining variable or functions names in a
macro.

Example:
#define merge3(name) merge(name##1,name##2,name##3) // merge3(test) is evaluated to merge(test1,test2,test3)

Some special #defines:


#define PRAGMA_ZERO
Initializes all local variables to 0.

#define PRAGMA_POINTER
Switches off the lite-C pointer autodetection, and treats pointers as in C/C++. The address operator (&) must be used
for passing addresses to functions, and the -> operator must be used for elements of a struct pointer. Otherwise a
syntax error will be issued.

#define PRAGMA_API FunctionName;ModuleName!ProcName


Loads the function prototype FunctionName from function ProcName in the DLL ModuleName (see Using DLLs).
Example:
#define PRAGMA_API MessageBox;user32!MessageBoxA

249
lite-C header files
The following standard header files are used in Zorro strategy scripts:

#include <litec.h>
Standard header for lite-C, contains standard variable type definitions.

#include <trading.h>
Standard header for strategy scripts, with definitions about the GLOBALS, TRADE, ASSET, and TICK structs.

#include <variables.h>
Standard header for strategy scripts; contains #defines for all predefined trading variables that are members of
the GLOBALS, STATUS, or TRADE structs. This file 'translates' the struct members into easier to memorize variable
names.

#include <functions.h>
Standard header for strategy scripts, with definitions of all common functions that are no indicators.

#include <ta.h>
Header with definitions of all traditional technical analysis functions (indicators).

#include <indicators.c>
C library containing advanced technical analysis functions, such a frequency filters and statistics functions, in source
code.

#include <default.c>
Standard header for all strategy scripts; includes all the headers above. Automatically included when
no #include statement is found in the main script. Otherwise it must be explicitly included.

#include <windows.h>
Optional header; contains common definitions and functions from the Windows API. Requires including <default.c>.

#include <stdio.h>
Optional header; contains common file, directory, and sort/search functions from the standard C
libraries io.h, stdio.h, stdlib.h, direct.h. Requires including <default.c>.

250
Using DLLs and APIs
The operating system and its subsystems provide Application Programming Interfaces (API) for programs to use their
functions. Lite-C can call API functions either based on external Dynamic Link Libraries (DLLs), or on the Component
Object Model (COM). DLLs are modules that provide external functions and variables; they are loaded at runtime. When
a DLL is loaded, it is mapped into the address space of the calling process.

DLLs can contain two kinds of functions: exported and internal. The exported functions can be called by other modules.
Internal functions can only be called from within the DLL where they are defined. Although DLLs can also export
variables, their variables are usually only used by their functions. DLLs provide a way to modularize applications so that
functionality can be updated and reused more easily. They also help reduce memory overhead when several
applications use the same functionality at the same time, because although each application gets its own copy of the
data, they can share the code.

The Microsoft® Win32® application programming interface (API) is implemented as a set of dynamic-link libraries, so
any process that uses the Win32 API uses dynamic linking.

Declaring a DLL or Windows API Function


Before an API function from an external DLL can be called, a function prototype must be declared, just as any other
function. Example:
long WINAPI MessageBoxA(HWND,char *,char *,long);

The function prototype - which is in fact a function pointer - must then be initialized with the function address. There are
three methods: static initialization in the api.def (for functions that are often used), static initialization by
an API(FunctionName,ModuleName) macro (for functions only defined in a particular header), and dynamic
initialization by DefineApi (for functions that are only used in a particular appliction).

The most common static API functions are defined in the api.def file. It's just a plain text file, so it can easily be modified.
Open api.def in the Gamestudio resp. lite-C folder, and add a line to it in the style
(FunctionName;ModuleName!ProcName). FunctionName is your declared function, ModuleName is the name of
the DLL without the ".dll" extension, and ProcName is the name of the function within that DLL (which needs not
necessarily be identical to your function name). Example:

MessageBox;user32!MessageBoxA

For initializing a function in a certain header file, simply write an API macro in the script. Example:

long WINAPI MessageBoxA(HWND,char *,char *,long);


API(user32,MessageBoxA)

WINAPI is defined as __stdcall, as required for calling a DLL function. You can find examples of this way to declare
external DLL functions in the include\windows.h header file. Also look under Hacks & Tricks for using indicators from
external DLLs.

For dynamically initializing an API function at runtime, either use the DefineApi call, or load the DLL and retrieve the
function address through normal Windows functions. The function prototype can be used as a function pointer.
Examples:

// Example1:
long WINAPI MessageBox(HWND,char *,char *,long);
MessageBox = DefineApi("user32!MessageBoxA");

// Example2:
long WINAPI MessageBox(HWND,char *,char *,long);
long h = LoadLibrary("user32");
MessageBox = GetProcAddress(h,"MessageBoxA");

By default, api.def contains a selection of C standard functions. The windows.h header contains the Windows API
functions. If you need a certain function that is not included, you can add it easily as described under Converting C++
Code to lite-C.

251
Using C++ classes

Lite-C can use classes and functions from COM DLLs; the most often used example is the DirectX DLL. Classes are
like structs, but contain not only variables but also functions (methods). Any COM class contains three standard
methods - QueryInterface(), AddRef(), and Release() - as well as any number of class specific methods. For example,
here's the lite-C code for defining a COM class that contains two specific methods, Func1() and Func2():
typedef struct _IFooVtbl
{
HRESULT __stdcall QueryInterface(void* This,IID *riid,void** ppvObject);
DWORD __stdcall AddRef(void* This);
DWORD __stdcall Release(void* This);
HRESULT __stdcall Func1(void* This);
HRESULT __stdcall Func2(void* This,int);
} IFooVtbl;

typedef interface IFoo { IFooVtbl *lpVtbl; } IFoo;

Note that each of the methods has an additional parameter called "This". You have to pass the This pointer parameter
explicitly in C, but it can be passed automatically in lite-C. Any additional parameters come after This, as above. The
interface is then typedef'd as a structure that contains a pointer to the vtable. For calling methods on COM objects, you
can use either a C++-style or 'C'-style syntax. Example:

pIFoo->Func1(); // C++ style


pIFoo->lpVtbl->Func1(pIFoo); // C style

As lite-C does not support class inheritance, just add all inherited methods, if any, to the class. Example for a DirectX
class:

typedef struct ID3DXMeshVtbl


{
// IUnknown methods
long __stdcall QueryInterface(void* This, REFIID iid, LPVOID *ppv);
long __stdcall AddRef(void* This);
long __stdcall Release(void* This);

// methods inherited from ID3DXBaseMesh


long __stdcall DrawSubset(void* This, long AttribId);
long __stdcall GetNumFaces(void* This);
long __stdcall GetNumVertices(void* This);

// ID3DXMesh methods
long __stdcall LockAttributeBuffer(void* This, long Flags, long** ppData);
long __stdcall UnlockAttributeBuffer(void* This)
long __stdcall Optimize(void* This, long Flags, long* pAdjacencyIn, long* pAdjacencyOut,
long* pFaceRemap, LPD3DXBUFFER *ppVertexRemap,
void* ppOptMesh)
} ID3DXMeshVtbl;

typedef interface ID3DXMesh { ID3DXMeshVtbl * lpVtbl; } ID3DXMesh;

...
ID3DXMesh* pMesh;
...
pMesh->DrawSubSet(0);
long num = pMesh->GetNumFaces();
...

252
Script conversion - TradeStation, MultiCharts, MetaTrader,
NinjaTrader, Neuroshell, R...
When starting with Zorro, you often have used another trade platform or computing environments before, and would like
to take over your familiar strategies, indicators, and algorithms. The examples below show how to convert the code of
different platforms.

TradeStation™ and MultiCharts™

TradeStation was the first platform that supported automated trading. Its EasyLanguage™, also used by the
MultiCharts platform, has a similar design philosophy as Zorro's lite-C. Although its syntax is a C/Pascal mix and requires
some code modifications, conversion to C is relatively straightforward. EasyLanguage Vars are equivalent to lite-C data
series. However, EasyLanguage makes no distinction between series and variables - f.i. myseries is the same
as myseries[0]. In most cases, Vars is only a single variable (var), but sometimes it's a series (vars), as movAvgVal in
the second example below. Easylanguage also stores the content of variables - if this is required, declare a static var in
lite-C. Trigonometric functions expect angles in degrees, while in lite-C and most other languages angles are in radians.
EasyLanguage has no native functions, but separate scripts can be called like functions. Aside from that, EasyLanguage
strategies and lite-C strategies look very similar.

{ Easylanguage version }
{ Choppy Market Index Function by George Pruitt }
Inputs: periodLength(Numeric);
Vars: num(0),denom(1);

if(periodLength <> 0) then


begin
denom = Highest(High,periodLength) – Lowest(Low,periodLength);
num = Close[periodLength-1] – Close;
ChoppyMarketIndex = 0.0;
if(denom <> 0) then
ChoppyMarketIndex = AbsValue(num/demon)*100;
end;
// lite-C version
// Choppy Market Index Function by George Pruitt
var ChoppyMarketIndex(int periodLength)
{
if(periodLength != 0) {
var denom = HH(periodLength) – LL(periodLength);
var num = priceClose(periodLength-1) – priceClose();
if(denom != 0)
return abs(num/denom)*100;
}
return 0;
}
{ Easylanguage version }
{ enter a trade when the RSI12 crosses over 75 or under 25 }
Inputs:
Price(Close),LengthLE(12),LengthSE(12),
OverSold(25),OverBought(75),StoplossPips(200),ProfitTargetPips(200);

variables:
var0(0),var1(0);

{ get the RSI series }


var0 = RSI( Price, LengthLE );
var1 = RSI( Price, LengthSE );

{ if rsi crosses over buy level, exit short and enter long }
condition1 = Currentbar > 1 and var0 crosses over OverBought ;
if condition1 then
Buy( "RsiLE" ) next bar at market;

{ if rsi crosses below sell level, exit long and enter short }
condition2 = Currentbar > 1 and var1 crosses under OverSold ;
if condition2 then
Sell Short( "RsiSE" ) next bar at market;

{ set up stop / profit levels }


SetStoploss(StoplossPips);
SetProfitTarget(ProfitTargetPips);
// Zorro version
// enter a trade when the RSI12 crosses over 75 or under 25
function run()
{
253
// get the RSI series
vars Close = series(priceClose());
vars rsi12 = series(RSI(Close,12));

// set up stop / profit levels


Stop = 200*PIP;
TakeProfit = 200*PIP;

// if rsi crosses over buy level, exit short and enter long
if(crossOver(rsi12,75))
reverseLong(1);
// if rsi crosses below sell level, exit long and enter short
if(crossUnder(rsi12,25))
reverseShort(1);
}

MetaTrader4™ (MT4)

MT4 is the most popular platform for private traders and supported by most brokers. The MQL4™ script language of its
"Expert Advisors" (EAs) is based on C, which would theoretically allow easy conversion to Zorro's lite-C. Unfortunately,
MT4 has many issues that make "Expert Advisors" a lot more complex and difficult to handle than scripts of other
platforms.
The MQL4 main script runs at every tick; therefore the last price candle is normally incomplete and affects indicators
in a different way than the other candles. Trades are not managed by the platform, but must be managed by code in the
script. Series are not natively supported, but emulated with loops and functions. Indicators often produce different results
in MT4 than in other platforms because the MT4 standard indicators do not use their standard algorithms, but special
MT4 variants. Time zones and account parameters are not normalized, so EAs must be individually adapted to the
broker. To complicate matters further, MT4 does not use common trade units such as lots and pips, but calculates with
"standard lots" and "points" that need to be multiplied with account-dependent conversion factors. Most code in an EA
is therefore not used for the trade algorithm, but for working around all those problems. This results in the long and
complex 'spaghetti code' that is typical for EAs.
For conversion, first remove the MQL4 specific code that is not needed in lite-C, such as trade management loops,
broker dependent pip and trade size calculations, and array loops that emulate series. Then the rest can be converted
by replacing the MQL4 indicators and trade commands by their lite-C equivalents. Note that the result can still differ due
to the effects of incomplete candles and different indicator algorithms.

// MT4 version
// enter a trade when the RSI12 crosses over 75 or under 25
int start()
{
// get the previous and current RSI values
double current_rsi = iRSI(Symbol(), Period(), 12,
PRICE_CLOSE, 1); // mind the '1' - candle '0' is incomplete!!
double previous_rsi = iRSI(Symbol(), Period(), 12, PRICE_CLOSE, 2);

// set up stop / profit levels


double stop = 200*Point;
double takeprofit = 200*Point;

// correction for prices with 3, 5, or 6 digits


int digits = MarketInfo(Symbol(), MODE_DIGITS);
if (digits == 5 || digits == 3) {
stop *= 10;
takeprofit *= 10;
} else
if (digits == 6) {
stop *= 100;
takeprofit *= 100;
}

// find the number of trades


int num_long_trades = 0;
int num_short_trades = 0;
int magic_number = 12345;

// exit all trades in opposite direction


for(int i = 0; i < OrdersTotal(); i++)
{
// use OrderSelect to get the info for each trade
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;

254
// Trades not belonging to our EA are also found, so it's necessary to
// compare the EA magic_number with the order's magic number
if(magic_number != OrderMagicNumber())
continue;

if(OrderType() == OP_BUY) {
// if rsi crosses below sell level, exit long trades
if((current_rsi < 25.0) && (previous_rsi >= 25.0))
OrderClose(OrderTicket(), OrderLots(),
Bid, 3, Green);
else
// otherwise count the trades
num_long_trades++;
}

if(OrderType() == OP_SELL) {
// if rsi crosses over buy level, exit short trades
if((current_rsi > 75.0) && (previous_rsi <= 75.0))
OrderClose(OrderTicket(), OrderLots(),
Ask, 3, Green);
else
// otherwise count the trades
num_short_trades++;
}
}

// if rsi crosses over buy level, enter long


if((current_rsi > 75.0) && (previous_rsi <= 75.0)
&& (num_long_trades == 0)) {
OrderSend(Symbol(), OP_BUY,
1.0, Ask, 3,
Ask-stop, Bid+takeprofit,
"", magic_number,
0, Green);
}
// if rsi crosses below sell level, enter short
if((current_rsi < 25.0) && (previous_rsi >= 25.0)
&& (num_short_trades == 0)) {
OrderSend(Symbol(), OP_SELL,
1.0, Bid, 3,
Bid+stop, Ask-takeprofit,
"", magic_number,
0, Green);
}

return(0);
}
// Zorro version
// enter a trade when the RSI12 crosses over 75 or under 25
function run()
{
// get the RSI series
vars Close = series(priceClose());
vars rsi12 = series(RSI(Close,12));

// set up stop / profit levels


Stop = 200*PIP;
TakeProfit = 200*PIP;

// if rsi crosses over buy level, exit short and enter long
if(crossOver(rsi12,75))
reverseLong(1);
// if rsi crosses below sell level, exit long and enter short
if(crossUnder(rsi12,25))
reverseShort(1);
}
Under Tips & Tricks you can find an example how to emulate the MQL4-style indicator parameters with Zorro.

NinjaTrader™

NinjaScript™ is based on C# and thus similar in syntax to Zorro's lite-C. NinjaScript also supports data series in the
same way as lite-C, and its basic function list is very similar; this makes script migration rather easy. One major
difference is that all NinjaTrader indicator functions return data series, while Zorro indicators return single values. Use
the series function (f.i. series(indicator(..))) for making Zorro indicators also return series.

// NinjaTrader version
255
// Trade when a fast SMA crosses over a slow SMA
protected override void Initialize()
{
// Run OnBarUpdate on the close of each bar
CalculateOnBarClose = true;

// Set stop loss and profit target at $5 and $10


SetStopLoss(CalculationMode.Ticks,5);
SetProfitTarget(CalculationMode.Ticks,10);
}

protected override void OnBarUpdate()


{
// don't trade during the LookBack period
if(CurrentBar < 20)
return;

double Fast = 10;


double Slow = 20;

// Exit short and go long if 10 SMA crosses over 20 SMA


if(CrossAbove(SMA(Close,Fast),SMA(Close,Slow),1)) {
ExitShort();
EnterLong();
}
// Exit long and go short if 10 SMA crosses under 20 SMA
else if(CrossBelow(SMA(Close,Fast),SMA(Close,Slow),1)) {
ExitLong();
EnterShort();
}
}
// Zorro version
// Trade when a fast SMA crosses over a slow SMA
void run()
{
// Set stop loss and profit target at $5 and $10
Stop = 5;
TakeProfit = 10;

vars Close = series(priceClose());


vars SMAFast = series(SMA(Close,10));
vars SMASlow = series(SMA(Close,20));

// Exit short and go long if 10 SMA crosses over 20 SMA


if(CrossOver(SMAFast,SMASlow))
enterLong();
// Exit long and go short if 10 SMA crosses under 20 SMA
else if(CrossUnder(SMAFast,SMASlow))
enterShort();
}

Neuroshell Trader™ and other DLL based trading platforms

Neuroshell Trader™ is a platform specialized in employing artificial intelligence for automated trading. Neuroshell
indicators are functions added through DLLs. They take an input array, an output array, the array size, and additional
parameters. Many other trade platforms use similar DLL based indicators. Such indicators are often based on normal
C, thus conversion to Zorro is very easy - especially when you don't have to convert it at all and can call the DLL function
directly.
When using an indicator made for a different platform, the array order convention must be take care of. Neuroshell
stores time series in ascending order (contrary to most other platforms that store them in reverse order) and passes the
end of the array, not its begin, to the indicator function. Neuroshell indicators normally return an output series instead of
a single value. Below both methods of indicator conversion are shown.

// Neuroshell version - Entropy indicator


// published by ForeTrade Technologies (www.foretrade.com/entropy.htm)
#include "math.h"
#include "stdlib.h"

__declspec(dllexport) void Entropy (double *price, double *entropy, long int size, long int numbars)
{
double *in, *out, P, G;
long int i,j;
double sumx = 0.0;
double sumx2 = 0.0;

256
double avgx = 0.0;
double rmsx = 0.0;

in=price;
out=entropy;

for (i=0; i<size; i++)


{
if (i < numbars+1) *out = 3.4e38;
else
{
sumx = sumx2 = avgx = rmsx = 0.0;
for (j=0;j<numbars+1;j++)
{
sumx += log(*(in-j) / *(in-j-1)) ;
sumx2 += log(*(in-j) / *(in-j-1)) * log(*(in-j) / *(in-j-1));
}
if (numbars==0)
{
avgx = *in;
rmsx = 0.0;
}
else
{
avgx = sumx / numbars;
rmsx = sqrt(sumx2/numbars);
}
P = ((avgx/rmsx)+1)/2.0;
G = P * log(1+rmsx) + (1-P) * log(1-rmsx);
*out=G;
}
in++; out++;
}
}
// Zorro version - Entropy indicator
// Method 1 - calling the DLL function directly
// Copy the Entropy DLL into the Zorro folder
int __stdcall Entropy(double *price, double *entropy, long size, long numbars); // function prototype
API(Entropy,entropy) // use the Entropy function from entropy.dll

var EntropyZ(vars Data,int Period)


{
Period = clamp(Period,1,LookBack-1); // prevent exceeding the Data size
double E; // single element "array" for receiving the output value
Entropy(
Data+Period+1, // end of the array (element order does not matter here)
&E, 1, // pointer to the output "array" with size 1
Period);
return E;
}
// Zorro version - Entropy indicator
// Method 2 - converting the DLL code to lite-C
var EntropyZ(vars Data,int Period)
{
Period = clamp(Period,1,LookBack-1); // prevent exceeding the Data size
var sumx = 0., sumx2 = 0.;
int j;
for (j=0; j<Period; j++) {
sumx += log(Data[j]/Data[j+1]);
sumx2 += log(Data[j]/Data[j+1]) * log(Data[j]/Data[j+1]);
}
var avgx = sumx/Period;
var rmsx = sqrt(sumx2/Period);
var P = ((avgx/rmsx)+1)/2.;
return P * log(1+rmsx) + (1-P) * log(1-rmsx);
}

R is an interactive script language and an open source environment for statistical computations and charting, developed
in 1996 by New Zealand scientists Ross Ihaka and Robert Gentleman of Oakland University. It is meanwhile the global
standard in statistical computations and has a large set of packages for all advanced fields of statistics, machine learning
and data mining.

257
Although it is possible, it normally makes no sense to convert complex R calculations to lite-C scripts, especially when
R 'packages' are used. An easier way is available through the R bridge for directly executing R functions in lite-C.

MatLab™

MatLab™ is a commercial computing environment and interactive programming language by MathWorks, Inc. Other
than R, it is not specialized on data analysis and machine learning, but allows symbolic and numerical computing in all
fields of mathematics. Its integrated compiler makes it relatively easy to convert a MatLab trading algorithm to a C/C++
DLL that can then be used with Zorro.

Lite-C for C++/C# programmers


Lite-C uses the familiar syntax of C-style languages such as C, C++, C#, or Javascript. Although it has some differences
to allow a simpler compiler structure, it is 'compatible enough' to access the Windows API and even use complex external
C++ libraries such as DirectX or OpenGL. Therefore it can be used to write any normal computer program
(see CalculatePi or Mandelbrot).

The differences of lite-C to standard C are listed here:

&&, ||
Are redefined to and and or, but you can still use the familar operators. In C/C++, comparisons are early aborted
when a && is encountered and the expression so far evaluated to false, or when a || is encountered and the
expression evaluated to true. Lite-C always calculates the expressions up to the end. This requires a different (more
logical) syntax for checking the value of a struct pointer and its element in the same expression:
if((ptr != NULL) && (ptr->element == ..)) .. // C/C++
if(ptr != NULL) if (ptr->element == ..) .. // lite-C

Trinary operators
Lite-C does not support the comparison ? expression : expression; syntax, so use an if or ifelse statement instead.
x = (x<0 ? -1 : 1); // C/C++
x = ifelse(x<0,-1,1); // lite-C

Multiple operations in one command


Lite-C does not support the comma for clustering multiple operations in one command.
x = 2, y = 2; // C/C++
x = y = 2; // C/C++, lite-C
x = 2; y = 2; // lite-C

Struct and array initialization


In C/C++ structs can be initialized just like arrays, by giving a list of member values. This is only supported for global
arrays in lite-C. Local structs can not be initialized this way, and local initialized arrays automatically become static.
Lite-C initializes all global arrays and structs automatically to zero.

Struct copying
In C++, structs can be copied into each other with the '=' operator. In C or lite-C, use memcpy for copying structs:
// C++:
D3DXVector3 vecA, vecB;
...
vecA = vecB;

// lite-C:
D3DXVector3 vecA, vecB;
...
memcpy(vecA,vecB,sizeof(D3DXVector3));

258
Enums
Enums are not supported and can be replaced by defines:
enum RGB { RED=1; BLUE=2; GREEN=3 }; // C/C++

#define RED 1 // lite-C


#define BLUE 2
#define GREEN 3

Unions
Union members of the same type can be substituted by a #define, and union members of different type can be treated
as a different members of the struct. Example:
typedef struct S_UNION {
int data1;
union { int data2; float data3; };
union { int data4; int data5; };
} S_UNION; // C/C++

typedef struct S_UNION {


int data1;
int data2;
float data3;
int data4;
} S_UNION; // lite-C
#define data5 data4

If the struct size must not change, or if for some reason the program requires different variable types to occupy the same
place in the struct, a special conversion function can be used to convert the type of a variable without converting the
content:

typedef struct S_UNION {


int data1;
union { int data2; float data3; };

} S_UNION; // C/C++
...
S_UNION s_union;
s_union.data3 = 3.14;

typedef struct S_UNION {


int data1;
int data2;
} S_UNION; // lite-C
#define data3 data2
...
int union_int_float(float x) { return *((int*)&x); }
...
S_UNION s_union;
s_union.data3 = union_int_float(3.14);

Function pointers
In C/C++, function pointers are declared like this: int (*foo)(int a, int b);. In lite-C there's no difference between
function prototypes and function pointers: int foo(int a, int b);. See details under pointers.

Signed or unsigned variables


In lite-C, floating point, long and int variables are generally signed, and pointers, char and short are generally
unsigned, according to the normal way they are used. The include\litec.h file contains definitions for all usual
unsigned variables like DWORD or WORD that are used in Windows functions. So using unsigned variables normally
does not cause any problems. However you need to take care when variables exceed their range. For instance,
subtracting 1 from (DWORD)0results in -1 under lite-C, but in 0xFFFFFFFF in standard C/C++, and would produce
different behavior in comparisons.

main() and run() functions


The main() function works like in C. The run() function indicates that the program is a strategy script. It is called once
before the start of the strategy for initialization, and then once for each bar.

259
Using C library or Windows API functions
Lite-C already contains the most often used functions from the standard C/C++ libraries - they are listed in <litec.h>,
which is automatically included in all scripts without explicit #include statement. So you normally won't need to include
C headers such as stdio.h or math.h. If you need a function that is not contained in <litec.h>, add it as described
under Using the Windows API. Here's a brief instruction of how to add an external function to lite-C:

• Find out in which Windows DLL the function is contained. C library function are normally found in the mscvrt.dll. You
can use a free DLL browser, for instance the DLL Export Viewer from http://www.nirsoft.net, for checking which
DLL contains which function.
• Declare a function prototype in your source code. In most cases you can directly copy the prototype from the .h header
file that accompanies the DLL, or from the documentation to the C library.
• Additionally to the prototype, either add a #define PRAGMA_API line for initializing the function prototype, f.i. #define
PRAGMA_API MessageBox;user32!MessageBoxA as described in the API example. Or, for dynamic initalization,
add a DefineApi call in your main function.

If you need certain structs or variable types that are not yet contained in include\windows.h or in the other standard
include files, just add them from their original file either into your script. If you think that a certain function, struct, or
variable type is often needed, suggest its inclusion on the Zorro future forum.

260
Math functions
min(int x, int y): int
min(var x, var y): var
max(int x, int y): int
max(var x, var y): var
The minimum or maximum of x and y.

Parameters:
x,y - any var or int.

Returns:
The minimum or maximum of x and y.

Remarks:
The returned variable type depends on the type of the arguments. Make sure that x and y are either both int or both var;
for constants use a decimal point to define them as var (f.i. 1 = int, but 1. = var).

Example:
var x = max(0.,y);

between(int x, int lower, int upper): bool


between(var x, var lower, var upper): bool
Returns true when the variable x lies within a lower and upper border. When lower is above upper, returns true when
either x >= upper or x <= lower, this way allowing the comparison of cyclic ranges such as hours of the day or months
of the year.

Parameters:
x, lower, upper - any var or int.

Algorithm:
if(lower <= upper)
return (x >= lower) and (x <= upper);
else<
return (x >= lower) or (x <= upper);

Example:
if(between(x,0.,1.))
... // executed when x is between 0..1
if(between(hour(),22,4))
... // executed when hour() is above 22 or below 4

261
clamp(int x, int lower, int upper): int
clamp(var x, var lower, var upper): var
Returns a variable limited to a lower and upper border.

Parameters:
x, lower, upper - any var or int.

Returns:
x > upper upper

lower <= x <= upper x

x < lower lower

Example:
x = clamp(x,0.,1.); // limit x to 0..1

ifelse(bool c, var x, var y): var


Returns x when the expression or comparison c is true or nonzero, otherwise y.

Parameters:
c - any boolean expression.
x,y - any var.

Returns:
x when c is true, otherwise y.

Example:
var a = ifelse(x > y,x,y); // a is now the maximum of x and y

abs(int x): int


abs(var x): var
Absolute amount of x.

Parameters:
x - any var or int.

Returns:
x >= 0 then x

x<0 then -x

262
Example:
x = abs(2); // x is now 2
x = abs(-599); // x is now 599
x = abs(0); // x is now 0

sign(int x): int


sign(var x): var
The sign of x: -1, 0, or 1.

Parameters:
x - any var or int.

Returns:
x>0 1

x == 0 0

x<0 -1

Example:

cdf(var x): var


Cumulative distribution function; the probability that a normal distributed variable takes a value less than or equal to x.
Can be used to compress the variable to the 0..1 range. Sometimes also referred to as pnorm(x).

qnorm(var x): var


The inverse cdf function; returns the value whose cumulative distribution matches the probability x.

Parameters:
x - any var.

Example:
x = cdf(1); // x is now 0.84
x = cdf(-1); // x is now 0.16

sqrt(var x): var


Square root of x.

Parameters:
x - any var.

Returns:

263
Square root of x.

Example:
x = sqrt(2); // x is now 1.414
x = sqrt(64); // x is now 8
x = sqrt(1); // x is now 1

exp(var x): var


e power x.

Parameters:
x - any var.

Returns:
ex

Example:
x = exp(1); // x is now 2.178
x = exp(0); // x is now 1

log(var x): var


Logarithm of x at base e.

Parameters:
x - any var.

Returns:
ln(x)

Example:
x = log(1); // x is now 0
x = log(2.178); // x is now 1 (2.178 is an approximation to the constant e).
x = log(y)/log(2); // x is log2(y), i.e. log(y) at base 2.

pow(var x, var y): var


Computes x raised to the power y. An error occurs if x is zero and y is less than or equal to 0, or if x is negative and y has
a fractional part. Note that x*x is the same, but is computed faster than pow(x,2) .

Parameters:
x - any var.
y - any var.

Returns:
xy

Example:
x = pow(100,1); // x is now 100
x = pow(2,8); // x is now 256
264
x = pow(1.414,2); // x is now 1.999

sin(var x): var


cos(var x): var
tan(var x): var
Trigonometry functions.

Parameters:
x - angle in radians.

Returns:
Sine, cosine, tangent of angle x

Example:
x = sin(pi/2); // x is 1

asin(var x): var


acos(var x): var
atan(var x): var
atan2(var a, var b): var
Arc functions - the opposite of the sin, cos, tan function, return an angle for a given value. atan2 is used for higher
precision when a is a sine and b a cosine.

Parameters:
x - any var.
a, b - any var; the tangent value is a/b.

Returns:
Angle in radians.

Example:
x = asin(1); // x is pi/2
x = asin(0.707); // x is pi/4

floor(var x): var


Rounds x down to the largest integer that is not greater than x; .

ceil(var x): var


Rounds x up to the smallest integer that is not smaller than x.

Parameters:
x - var to be rounded up or down.

265
Returns:
x rounded up or down.

Remarks:

• The floor and ceil functions are not equivalent to converting x to an integer. For instance, (int)-2.5 is -2, but floor(-
2.5) is -3.0.

Example:
int a = floor(b);

round(var x, var step): var


Rounds a variable to the closest multiple of a given step width. F.i. round(Price,PIP) rounds Price to 1 pip, and
round(x,1) rounds x to the nearest integer.

Parameters:
x - var to be rounded.
step - rounding accuracy.

Returns:
Rounded value of x.

Example:
Price = round(Price,0.1*PIP); // round price to 0.1 pip accuracy

Normalization functions
The following functions can be used for normalizing and compressing indicator values to a range that's independent of
the asset and time frame. Normalization to a fixed range, such as -100..+100 or 0..1, is often required for machine
learningalgorithms.

center (var Value, int TimePeriod): var


Centers Value by subtracting its median over the TimePeriod. Using the median instead of the mean reduces the effect
of outliers.

compress (var Value, int TimePeriod): var


Compresses Value to the -100...+100 range. For this, Value is divided by its interquartile range - the difference of the
75th and 25th percentile - taken over TimePeriod, and then compressed by a cdf function. Works best when Value is
an oscillator that crosses the zero line. Formula: 200 * cdf(0.25*Value/(P75-P25)) - 100.

scale (var Value, int TimePeriod): var


Centers and compresses Value to the -100...+100 scale. The deviation of Value from its median is divided by its
interquartile range and then compressed by a cdf function. Formula: 200 * cdf(0.5*(Value-Median)/(P75-P25)) - 100.

normalize (var Value, int TimePeriod): var


Normalizes Value to the -100...+100 range through subtracting its minimum and dividing by its range
over TimePeriod. Formula: 200 * (Value-Min)/(Max-Min) - 100 .

266
zscore (var Value, int TimePeriod): var
Calculates the Z-score of the Value. The Z-score is the deviation from the mean over the TimePeriod, divided by the
standard deviation. Formula: (Value-Mean)/StdDev.

Parameters:
Value - Variable, expression, or indicator to be normalized.
TimePeriod - Normalization period.

Returns:
Normalized Value.

Remarks:

267
• All above functions generate series and thus must be called in a fixed order in the script.

Example:
function run()
{
set(PLOTNOW);
PlotWidth = 600;
PlotHeight1 = PlotHeight2;
PlotBars = 400;
LookBack = 200;
var ATR100 = ATR(100);
plot("ATR 100",ATR100,NEW,RED);
plot("center",center(ATR100,100),NEW,RED);
plot("compress",compress(ATR100-0.003,100),NEW,RED);
plot("scale",scale(ATR100,100),NEW,RED);
plot("normalize",normalize(ATR100,100),NEW,RED);
plot("zscore",zscore(ATR100,100),NEW,RED);
}

diff (var x): var


Returns the difference of x to its value from one bar before.

Parameters:
x Variable for difference calculation.

Returns
x - previous x

Remarks

• This function generates a series and thus must be called in a fixed order in the script.
• diff(log(x)) returns the log return of x , since log(a/b) = log(a) - log(b).

Example:
// generate a price difference serie
vars Changes = series(diff(priceClose()));

random() : var
Returns a random number from the range between -1 and +1.

random(var max) : var


Returns a random number from the range between 0 and max.

Example:
if(random() > 0) ... // true in 50% of all cases

268
Vector and matrix functions
The following functions can be used for matrix and vector algebra.

matrix(int rows, int cols): mat


Creates a vector or a matrix with the given number of rows and columns. The matrix is initialized to zeros. If cols is
omitted, an identity matrix with 1s in the diagonal is created.

Parameters:
rows - number of rows of the matrix, or 1 for defining a row vector.
cols - number of columns, or 1 for defining a column vector. Omit this for a square identity matrix.

me(mat X, int row, int col): var


Macro for accessing an element of the matrix X at a given row and column, with no range check.

ve(mat V, int n): var


Macro for accessing the n-th element of the row or column vector V, with no range check.

Parameters:
X, V - matrix to be accessed.
row - row number of the element, starting with 0.
col - column number of the element, starting with 0.

matSet(mat X, mat A): mat


Copies matrix A to matrix X, and returns X. A and X must have the same number of rows and columns.

Parameters:
X - destination matrix or vector
A - source matrix or vector

matSet(mat X, int row, int col, mat A): mat


Copies matrix A to a sub-range of matrix X that starts at the given row and colunm, and returns X. A and X can have
different numbers of rows and columns.

Parameters:
X - destination matrix
A - source matrix
row - row number of the block, starting with 0.
col - column number of the block.

matSet(mat X, var c): mat


Sets all elements of matrix X to the value c, and returns X.

Parameters:
X - destination matrix.
c - value to be set.

matTrans(mat X, mat A): mat


Copies the transpose of A to X, and returns X. The number of columns of X must be identical to the number of rows
of A, and vice versa.

269
Parameters:
X - destination matrix.
A - source matrix.

matAdd(mat X, mat A, mat B): mat


Adds A and B, stores the result in X, and returns X. A, B, and X must have the same number of rows and columns.

Parameters:
X - destination matrix.
A,B - source matrices.

matSub(mat X, mat A, mat B): mat


Subtracts B from A, stores the result in X, and returns X. A, B, and X must have the same number of rows and
columns.

Parameters:
X - destination matrix.
A,B - source matrices.

matMul(mat X, mat A, mat B): mat


Multiplies A with B, stores the result in X, and returns X. X and A must have the same number of rows, X and B must
have the same number of columns, and the B number of rows must be identical to the A number of columns.

Parameters:
X - destination matrix.
A,B - source matrices.

matScale(mat X, var c): mat


Multiplies all elements of X with c, and returns X.

Parameters:
X - destination matrix.
c - multiplication factor.

Remarks:

• Matrices are created in the INITRUN and released after the EXITRUN. Just like series, matrix() creation must happen
in a fixed order in the script, preferably at the begin of the run or main function.
• The mat type is a pointer of the MATRIX struct defined in trading.h. The numbers of rows and columns are stored in
the ->rows and ->cols elements, a pointer to the content is stored in the ->dat element.
• Vectors can be defined as matrices with rows = 1 or cols = 1. The dot product is the multiplication of a row vector with
a column vector.
• If matrix arithmetics is not required and the row/column number is fixed, matrices and vectors can be alternatively
defined as var arrays, like var MyVector[3] or var MyMatrix[3][3].

Example:
void matPrintf(mat X)
{
int i,j;
printf("\n");
for(i=0; i < X->rows; i++) {
for(j=0; j < X->cols; j++)
printf("%.0f ",me(X,i,j));
printf("\n");
}
}

270
void main()
{
int i,j;
mat A = matrix(3,4);
for(i=0; i < A->rows; i++)
for(j=0; j < A->cols; j++)
me(A,i,j) = i+j;
matPrintf(A);

mat B = matTrans(matrix(A->cols,A->rows),A);
matPrintf(B);

mat C = matrix(B->rows,A->cols);
matMul(C,B,A);
matPrintf(C);

mat D = matrix(8);
matSet(D,4,4,C);
matPrintf(D);
}

271
Trading functions
run()
Main strategy function, written by the user. It is automatically started after clicking on the [Train], [Test], and [Trade]
buttons. After that, it is run once for every bar of the simulation. This is continued until the end of the simulation data or
until [Stop] is hit. The run function initializes sliders and sets up all needed indicators.

Remarks:

• The run function runs at the end of every bar. The open, close, high and low prices of the current bar are known and
lie in the past, while all prices of the next bar are unknown and lie in the future (however normally the open of the next
bar is mostly identical to the close of the current bar). In the simulation, trade enter or exit commands are normally
executed at the open of the next bar if no Entry limit is given.
• For executing the run function several times within a candle, make the BarPeriod shorter and set up the candle width
with TimeFrame so that several bars lie within a candle.
• At the first call of the run function, is(INITRUN) and is(FIRSTTINITRUN) are true. Price and bar data are not yet set
up at that initial run before the first asset call, and all price functions and indicators return 0.
• If the script has both a main and a run function, the main function is executed before the first run, and can be used
for initializing variables.
• Local variables 'forget' their values after every call of the run function, but static and global variables keep their
values. !! When AutoCompile is set, static and global variables really keep their values until the script is compiled
again, which happens only when it was modified or when the [Edit] button was clicked. Therefore make sure to initialize
them at every cycle start (is(INITRUN)) to their initial values when this is required.
• For running a simulation in greater steps for special purposes, bars can be skipped by adding a number to Bar at the
end of the run function. F.i. Bar += 9; will call the run function only every 10th bar. Anything inbetween, such as
handling trades, is skipped.
• For debugging the run function, use SINGLESTEP mode and watch commands.
• For immediate script reaction on an incoming price quote, use the tick function. For repeated actions independent on
bars or price quotes, use the tock function.

Example:
function run()
{
vars SmoothPrice = series(LowPass(series(price()),100));

if(valley(SmoothPrice)) enterLong();
if(peak(SmoothPrice)) exitLong();
}

tick()
User-supplied function that is called on arrival of a new price quote of any asset used in the strategy. This function can
be used when an immediate reaction on every new price quote is required.

tock()
User-supplied function that is called once per minute (adjustable via TockTime), regardless of price quotes. This
function can be used f.i. for periodic input/output tasks.

Remarks:

• The tick function is called not at fixed time intervals, but in irregular intervals, dependent on market activity. The more
assets are traded, the more quotes will trigger a tick run. The tick function normally executes more frequently than
the run function, but can also execute less often when few quotes are received, f.i. after market hours.
• The minimum time between two tick calls can be set up with TickTime. If a new quote arrives earlier, it is delayed
until the TickTime is over.
• Asset, price, and time stamp of the quote can be evaluated with the Asset string variable, the priceClose function and
the seconds function.

272
• The same price quote can trigger a tick function as well as a TMF. The tick functions of all assets that had quotes in
the last TickTime are executed first, afterwards all triggered TMFs are executed.
• In [Test] or [Train] mode or in the lookback period, the tick and tock functions are normally called only once per bar.
But when the TICKS flag is set, the tick function is called at any new price quote dependent on the resolution of the
historical data.
• Data series can not be created in a tick or tock function, and indicators that create data series can not be called;
however data series can be evaluated, using global variables or AssetVar variables. As long as the current bar is not
finished, the current candle is incomplete, so its range and height can be very different to the preceding complete
candles and should not be used for trade signals. The price functions also return different values because they retrieve
their open, high, and low prices from the current incomplete candle.

Example:
// print every price quote in a file
function tick()
{
file_append("Log\\ticks.txt",
strf("\n%i.%i %2i:%2i:%2.3f => %s %4.5f",
day(),month(),hour(),minute(),second(),Asset,priceClose()));
}

series(var value, int length) : vars


Creates a series of the given length, fills it with the given value, and returns the pointer to the series. A series is
an array that contains the history of a variable. It is normally used by indicators or script functions. Every element of the
array corresponds to a bar in the price chart: the [0] element to the most recent bar, the [1] element to one bar ago and
so on.

Parameters:
value Optional data value of the series. When omitted, the series is filled with 0 values.

length Optional number of elements of the series; must not change once set. When omitted or 0, the series gets
the length of LookBack. A negative number allocates a static series - a normal var array that is not shifted
or filled.

Returns:
Pointer to the var array (the vars type is just a var* pointer).

Usage:
vars Close = series(priceClose()); returns a series that contains the close prices of the current asset.

Remarks:

• Series follow a similar concept as serial variables in other trading platforms such as TradeStation™, which makes
strategy import from those platforms easy. The n-th element of a series is accessed by adding a [n] to the series name.
For instance, myseries[0] is the latest element, myseries[1] is the element of one bar period ago, and so on. n must
always be smaller than the series length.
• Series are created after the first run of the strategy, when the price data of the asset have been loaded. They are then
filled with the given value. Afterwards they are updated after every bar, so the [0] element becomes the [1] element,
the [1] element becomes the [2] element and so on. The current value becomes the new [0] element. Due to this
mechanism, a series contains its elements in reverse chronological order with the most recent element at the start.
• For shifting a series into the past, add an offset +n. This creates a pointer onto the n-th element of the series. For
instance, (myseries+1) is myseries shifted by 1 bar offset into the past, excluding the last element myseries[0]. This
allows to access series elements in different ways; for instance, myseries[3] is the same as (myseries+2)[1] and the
same as (myseries+3)[0]. As the offset +n lets the series begin at the nth element, it reduces the available length of
the series by n. When calling an indicator with a series with an offset, make sure that LookBack is always higher

273
than the required lookback period of the function plus the unstable period plus the highest possible offset of the series.
Otherwise the script will issue an error message.
• Series can be synchronized to external events or to longer time frames. For this, set the TimeFrame variable
accordingly before the series() call, and reset it afterwards. The element [n] then corresponds to the nth event or time
frame in the past.
• For adding or subtracting two series, create a series of the sum or difference of the recent elements, f.i. vars Sum =
series(Data1[0] + Data2[0]);.
• Some functions expect a single value, other functions expect a whole series as parameter. When a function expects
a single value from a series, use myseries[n]. When the function expects a whole series,
use myseries or myseries+n.
• A value returned by a function can be converted to a series by using it as the first parameter to the series function. For
instance, series(price()) is a series that contains the price value; series(SMA(series(priceClose()),30)) is a series
containing the 30-bar Simple Moving Average of the Close value.
• The rev function reverses a series so that the [0] element is the earliest.
• !! An internal counter determines the pointer to be returned by a series() call. Therefore, series must be always created
in the same number and order in every script run; series calls must not depend on if or other conditions that can
change from bar to bar (see example below). This also applies to all functions that internally create series, such as
some indicator or signal processing functions. If the number and order of created series change between runs,
an error message will be displayed.
• Since the LookBack value is automatically determined in the INITRUN, series are allocated in the FIRSTRUN. During
the INITRUN they are just filled with the initial value and keep their content only until the next series call.
• When possible, make series not longer than they need to be. The longer a series, the more memory is required and
the slower is script execution due to internal updating the series on every time frame.

Examples:
// create a series with the high-low price differences
vars PriceRange = series(priceHigh()-priceLow());

// compare the current range with the range from 3 bars ago
if(PriceRange[0] > PriceRange[3])
...

// calculate a 20-bar Simple Moving Average containing the price differences from 5 bars ago
var Average5 = SMA(PriceRange+5,20);

// wrong use of conditional series


if(priceClose() > Threshold) {
vars X = series(priceClose()); // error message!
vars Y = series(SMA(X,100)); // error message!
...
}

// correct use of conditional series


vars X = series(), Y = series();
if(priceClose() > Threshold) {
X[0] = priceClose(); // ok!
Y[0] = SMA(X,100);
...
}

274
asset (string Name) : int
Selects an asset, and opens its price history in the initial run. Price and trade functions, and asset related variables
(Spread, Symbol, AssetVar etc.) are automatically adapted to the new asset. This way different assets can be
compared for arbitraging or correlation indicators, or a script can trade multiple assets simultaneously. For loading price
history the asset function must be called in the initial run of the strategy.

Parameters:
Name The name of the asset, as in the [Asset] selector; or an empty string "" for creating a dummy asset. Up to
15 characters, with no blanks and no special characters except for slash '/' and underline '_'.

Returns:
0 when the Name string is NULL or empty, or when no prices are available for the asset; otherwise nonzero.

Usage:
asset("EUR/USD"); switches to the EUR/USD currency.

assetAdd (string Name, var Price, var Spread, var RollLong, var RollShort, var PipVal, var PipCost,
var MarginCost, var Leverage, var LotAmount, var Commission, string Symbol)
Adds a new asset to the current strategy, and sets up its parameters. Must be called in the initial run of the strategy
before the asset can be selected. Assets contained in the asset list are automatically added.

Parameters:
Name Name of the asset

Price, ... Asset parameters as described under asset list.

Usage:
assetAdd("AAPL",118,0.01,0,0,0.01,0.01,0,4,1,0.02,0);

assetList (string Filename): int


Loads a new asset list in the first run of the script, and adds all assets to the current strategy. Must be called before
any of the new assets can be selected.

Parameters:
FileName File name of the asset .csv file. The path can be omitted for asset lists in the History folder.

Returns:
Number of the loaded assets, or 0 when no assets were loaded.

Usage:
assetList("Strategy\\MyNewAssets.csv");

275
assetType (string Name) : int
Attempts to determine the type of an asset from its name; f.i. if the 3-letter abbreviation of a currency appears in the
asset name, it is identified as Forex.

Parameters:
Name Name of the asset

Returns:
0 when the type can not be identified, f.i. stock; otherwise FOREX (1), INDEX (2), CMDTY (3), or BOND (5).

Remarks:

• If the asset function is used, it must be called in the first run (INITRUN) of the script. All variables and flags that affect
the price data array, such as BarPeriod, BarZone, LookBack, Detrend, StartDate, EndDate, TICKS, Weekend,
UpdateDays, AssetList, History etc. must be set before calling asset. Otherwise the simulation period can not be
set up correctly and the script will produce an Error 030 message.
• Calling asset() by script is not exactly the same as selecting the asset with the Asset scroll box. If the script contains
no asset call, the scroll box asset is selected after the first run, and its name is appended to the training files for being
able to train different assets separately.
• The order of asset calls does matter when price histories have gaps or start at different times within the simulation
period. The first asset is used to create the array of bars. Gaps in the price history of the first asset are thus reflected
in the price data of all further assets. Price histories have no data for periods when the asset is not traded, for instance
during the weekend, or at night for assets that are only traded during business hours. Therefore set first the asset with
the most complete price history (normally a currency pair that is traded 24 hours). When a subsequent asset has a
gap where the first asset has none, the gap is filled with price data from the previous bar.
If a bar does not receive any price ticks during trading, for instance because of an Internet connection problem, a
holiday, or outside business hours, the bar is also filled with price data from the previous bar. This ensures for portfolio
systems the same behavior in live trading as in the simulation with historic price data.
• For selecting all assets of the asset list in a loop, use while(asset(loop(Assets))). For counting them, use for(N =
0; Assets[N]; N++).
• Every asset call switches the trade statistics parameters and the OptimalF factors to the current statistics and
factors of the selected asset. All asset parameters are loaded from the asset list. If the asset is not found in the list,
an error message will be printed and defaults are substituted for the asset parameters.
• The trading time of an asset can be set up with AssetZone and AssetFrame.
• The current asset name is stored in the Asset string; its full symbol, including counter currency and exchange, is stored
in the Symbol string. Call asset(Asset) for loading the asset selected by the Scrollbox already in the INITRUN.
Otherwise it's automatically loaded after the INITRUN.
• For running the same strategy with multiple assets, use the loop function; for running the strategy with all available
assets from the asset list, use loop(Assets). The Assets array contains the names of all available assets.
• Artificial assets can be created by combining the prices from several real assets (see example).
• By passing an empty string (asset("")), a dummy asset is created with all prices at 1. This is useful when a real asset
is not needed, f.i. for testing filters or indicators with artificial price curves.
• When loading price data, the prices are checked for plausibility. If a bar has invalid data, such as extreme outliers or
prices at zero, it is automatically corrected. Setting Detrend = 16; before calling asset() prevents that asset and price
data is checked and outliers are removed.
• If only a single asset is selected in the script, the [Asset] scrollbox is automatically set to that asset. If multiple assets
are selected, the [Asset] scrollbox is unchanged and can be used to determine the price curve in the chart.
• For adding a new asset to the available asset set, follow the step by step description under Data Import.
• Assets must be subscribed before their prices are available. The asset function subscribes the asset automatically,
but some brokers have a limit to the number of subscribed assets. Some platforms, for instance MT4, need a long time
after subscribing an asset before prices are available.

Examples:
// trade multiple strategies and assets in a single script
function run()
{
BarPeriod = 240;

276
StartDate = 2010;
set(TICKS); // set relevant variables and flags before calling asset()

// call different strategy functions with different assets


asset("EUR/USD");
tradeLowpass();
tradeFisher();

asset("GBP/USD");
tradeAverage();

asset("SPX500");
tradeBollinger();
}
// For adding a new asset to the [Assets] scrollbox,
// click [Trade] with the script below.
// Then edit History\AssetsFix.csv and copy the line
// beginning with "Copper" over from History\Assets.csv.
function run()
{
asset("Copper"); // add "Copper" to assets.csv
}
// Basket trading - generate a snythetic asset "USD"
// combined from the USD value of EUR, GBP, and AUD
var priceUSD()
{
var p = 0;
asset("GBP/USD"); p += price();
asset("AUD/USD"); p += price();
asset("EUR/USD"); p += price();
return p;
}

// basket trade function with stop limit


int tradeUSD(var StopUSD)
{
if((TradeIsLong && priceUSD() <= StopUSD)
or (TradeIsShort && priceUSD() >= StopUSD))
return 1; // exit the trade
else return 0; // continue the trade
}

// open a trade with the synthetic asset and a stop loss


void enterLongUSD(var StopDistUSD)
{
var StopUSD = priceUSD()-StopDistUSD;
asset("GBP/USD"); enterLong(tradeUSD,StopUSD);
asset("AUD/USD"); enterLong(tradeUSD,StopUSD);
asset("EUR/USD"); enterLong(tradeUSD,StopUSD);
}

void enterShortUSD(var StopDistUSD)


{
var StopUSD = priceUSD()+StopDistUSD;
asset("GBP/USD"); enterShort(tradeUSD,StopUSD);
asset("AUD/USD"); enterShort(tradeUSD,StopUSD);
asset("EUR/USD"); enterShort(tradeUSD,StopUSD);
}

// plot a price curve of the synthetic asset


// (the plot command is linked to the last used asset -
// so "EUR/USD" must be selected in the scrollbox)
function run()
{
set(PLOTNOW);
plot("USD",priceUSD(),0,RED);
}

277
assetHistory(string Name, int Mode)
Loads price history of an asset from online servers, such as Yahoo™, or from the broker's server.

Parameters:
Name Asset name used by Yahoo, or Quandl code, or 0 for the current asset.

Mode FROM_YAHOO - download daily (D1) price data from Yahoo Finance.
FROM_YAHOO|UNADJUSTED - use unadjusted prices.
FROM_QUANDL - download daily (D1) price data from Quandl (Zorro S required).
1 - download one-minute (M1) price data from the broker's server.
0 - download tick quote (T1) data from the broker's server (Zorro S required).

Remarks:

• For trading with brokers that do not support historical prices in their API, set the PRELOAD flag and
use assetHistory() for downloading recent historical data from Yahoo or Quandl prior to the first asset call. This works
for daily data only.
• The raw .csv data from Yahoo or Quandl is stored in History\history.csv. The Quandl database name
and '/' or '.' characters are stripped from the historical data file names.
• The prices must be available on the selected broker's price server. MT4 servers usually have no price history, so the
command will print an error message or only partially download the history. IB only provides limited price history. FCXM
servers provide price history from 2002 for currencies, and from 2008 for some CFDs. Before downloading prices,
check if they are not already available in the History folder or on the Zorro download page.
• If the asset was not subscribed, it is subscribed automatically. The recent values of the asset's Spread, PipCost, etc.
are updated to the Assets.dta file even if no price history is downloaded. This way new assets can be added and
available assets can be set up to their current parameter values for further simulations.
• The price history is loaded either for the number of recent years given by NumYears, or for all years
between StartDate and EndDate. It is stored in the format described under Data Import. If a price history file already
exists, or if NumYears is set to -1, no price data is downloaded. If the price history of the current year is not up to date,
new price data is downloaded and added to the existing file. Make sure that the time resolution of the downloaded
prices matches the resolution of the historic data files - the included files are based on 1-minute data.
• D1 price data from Yahoo or Quandl is not split into year files, but stored in a single .t6 file starting with 1990. For
testing parts of a multi-year .t6 file, use an 8-digit StartDate or EndDate. Connection to a broker is not required for
downloading Yahoo or Quandl data. Yahoo Open, high, low, and close prices are automatically adjusted for splits and
dividends when the UNADJUSTED flag is not added. The unadjusted close price is stored in the marketVal stream.
Yahoo data is downloaded from ichart.finance.yahoo.com, Quandl data from www.quandl.com/api/v3/datasets.
The Quandl CHRIS/CME, CHRIS/ICE, and YAHOO EOD databases are supported for price history. Any other Quandl
database can be downloaded with the dataDownload function. Check the correct spelling of the asset name or Quandl
code.
• For backtesting price history, BarPeriod can not be less than one bar. Set it to 1440 for testing D1 price data.
• A price history file with length 0 prevents downloading prices for that particular year. This way, by storing a 0-byte file,
price data that is not available or that is of bad quality can be excluded from download attempts and from the backtest.
• For using the newest prices, load the price history before calling asset(), otherwise the asset will use the existing price
data files. Use UpdateDays for calling assetHistory automatically to keep the price history up to date.
• The historical data format is described under Import. Single price quotes in T1 format can be loaded by
setting mode to 0 (with Zorro S). For this the price server of the broker must support quote-based price data; this is
the case for FXCM price history back to 2008, but not for IB or for most MT4 brokers. Dependent on the price server
speed, downloading T1 quotes can take a long time, such as a whole day for one year of price quotes.

Example:
// Update M1 price history of a list of assets
function run()
{
NumYears = 2;
while(loop("AUD/USD","EUR/USD","GBP/USD","GER30","NAS100",
"SPX500","UK100","US30","USD/CAD","USD/CHF","USD/JPY",
"XAG/USD","XAU/USD"))
{
assetHistory(Loop1,1);
}
}

278
algo (string name): int
Sets the algorithm identifier for identifying trades. Using algorithm identifiers is recommended in portfolio strategies that
contain different trade algorithms; they are used to create separate strategy parameters, rules, and capital allocation
factors per algorithm.

Parameters:
name The algorithm identifier (max. 15 characters, no spaces). If name ends with ":L", the algo is for long trades
only; if it ends with ":S" the algo is for short trades only.

Returns:
0 when the name string is NULL or empty, otherwise nonzero.

Usage:
algo("TREND:L"); defines the identifier "TREND:L" for the current algorithm for long trades.

Remarks:

• If different algorithms or parameters for long and short trades are used, the identifiers should end
with ":L" resp. ":S" for being consistent with the trade names used in the message window and parameter files. Long
trades should be suppressed by script on ":S" algos and vice versa.
• The algorithm identifier is stored in the Algo string variable, and can be evaluated in strategies with the strstr function.
• Every algo call switches the trade statistics parameters and the OptimalF factors to the current statistics and factors
of the selected algorithm identifier.
• Any algo/asset combination is a component in a portfolio strategy. The performance report lists strategy results
separated by components. The Component variable is the number of the current component, starting with 0, and can
be used as an array index.
• Algorithm specific data can be stored in the AlgoVar variables.
• For training algo dependent parameters separately, switch algos in a loop.

Example:
algo("TREND:L"); // algorithm identifier for a trend trading algorithm with long trades

Futures and options


The following functions can be used for trading and analyzing futures, binary, american, or european options, and
futures options (contract.c must be included):

contractUpdate (string Name, int Handle, int Mode): int


Deletes all options or futures, and loads a new contract chain for the current day and the underlying with given Name.
Returns the number of contracts in the chain. In [Test] or [Train] mode the chain is copied from a historical dataset
of CONTRACT records with the given Handle that was previously loaded with dataLoad or dataParse. In [Trade]
mode the chain is downloaded from the broker API, which can take several minutes dependent on Internet speed and
number of contracts. Contract chains have no history and can only be loaded after the Lookback period. While the
chain is loading, no trades can be entered or closed, so don't call this function too often. The ask/bid prices are only
loaded for the contracts of open trades; use contractPrice to get prices of other contracts. This function needs only be
called once per day; it must not be called in a TMF or trade enumeration loop.

contract (int Type, int Expiry, var Strike): CONTRACT*


Selects the option or future contract from the option chain that matches exactly the given Type, Expiry date,
and Strike value. Returns a CONTRACT pointer to the found contract, or 0 when no such contract was found. If a
contract is selected, enterLong and enterShort will buy or write (sell short) the contract instead of the underlying asset.
Calling asset, contractUpdate, or contract(0) deselects the contract and allows again trading with the underlying.

279
contract (int Type, int Days, var Strike): CONTRACT*
As above, but selects the option or future contract with the given Type closest to the given minimum life time
in Days and the given Strike value. Returns a CONTRACT pointer, or 0 when no contract was found.

contract (CONTRACT*): CONTRACT*


As above, but selects directly the given option or future contract.

contract (TRADE*): CONTRACT*


As above, but selects the contract of the given trade from the current contract chain. Returns 0 if no contract for this
trade was found.

contractDays (CONTRACT*): var


contractDays (TRADE*): var
Returns the fractional number of calendar days until contract resp. trade expiration date, assuming an expiration time
of 20:00 UTC. Returns a negative number when the expiration date is in the past.

contractPrice (CONTRACT*): var


contractPrice (TRADE*): var
Updates the current ask and bid price of the selected contract or trade from the broker or from the historical data, and
returns the bid/ask average. If the contract is not traded, the prices are 0. For a fast reaction on changed prices, call this
function once per bar (or even more often) for all open contract trades.

contractPosition (TRADE*): int


Returns the current number of open contracts of the given trade (negative values for a short position). Can be used for
determining if a certain contract was expired or exercised by the broker. The GET_POSITION command must be
supported by the broker API, and no other trades with the same contract type, strike, and expiry must be opened.

contractCheck (TRADE*): int


Checks if the option trade is still open. If not, it is assumed that it was expired or exercised. Open positions of the
underlying are then also checked and automatically sold at market. This function can be called regularly in
a for(open_trades) loop for keeping the positions between broker and strategy in sync. The GET_POSITION command
must be supported by the broker API, and no other trades with the same contract type, strike, expiry, and underlying
must be opened.

contractRoll (TRADE*, int Days, var Strike, function TMF): TRADE*


Rolls a closed option or future contract by opening a duplicate with the same type, volume, stop loss and profit distances,
the given number of Days until expiration, the given Strike (0 for using the previous strike) and an optional trade
management function. Returns a pointer to the new trade, or 0 if the contract could not be rolled. Source code
in contract.c.

contractExercise (TRADE*)
Exercises an option. In the backtest, the option is closed at its intrinsic price. In live trading, an order to exercise the
option is sent to the broker. It is normally not immediately executed, but pending. Use contractCheck() for closing the
trade and selling the underlying as soon as the order was executed. Don't call this function for European options before
expiration, or for options that are not in the money.

contractVal (CONTRACT*, var Price, var HistVol, var Dividend, var RiskFree, var* Delta, var*
Gamma, var* Vega, var* Theta, var* Rho): var
Returns the theoretical value and optionally the greeks of the given option contract at the given Price of the underlying.
Uses the RQuantLib package; R must be installed and initRQL() must be called in the initial run. Source code
in contract.c.

280
contractIntrinsic (CONTRACT*, var Price): var
contractIntrinsic (TRADE*, var Price): var
Returns the intrinsic value of the option contract at the given Price of the underlying. The intrinsic value of a call option
is the difference between price and strike; for put options it's the difference between strike and price. A positive
difference means that the option is in the money. Source code in contract.c.

contractVol (CONTRACT*, var Price, var HistVol, var Value, var Dividend, var RiskFree): var
Returns the implied volatility of the of the given option contract based on the given Value. Uses
the RQuantLib package; R must be installed and initRQL() must be called in the initial run. Source code in contract.c.

yield(): var
Helper function, returns the current yield rate of 3-months US treasury bills in percent. Can be used as a proxy of the
risk-free interest for calculating the values of option contracts. Works in backtest as well as live trading. Quandl
Bridge or Zorro S required. Source code in contract.c.

dmy(int Expiry): var


ymd(var Date): int
Helper functions, convert an expiration date in the YYYYMMDD format to the Windows DATE format and vice versa.
Source code in contract.c.

nthDay(int Year, int Month, int Dow, int N): var


Helper function, returns the Windows DATE format of the Nth given weekday of the given month. For
instance, nthDay(2016,11,FRIDAY,3) returns the date of the third friday in November 2016. Source code in contract.c.

Parameters:
Name The name of the underlying, f.i. "ES" or "SPY", or Asset for the current asset.

Handle A number from 1...1000 that identifies the dataset containing a CONTRACT list with historical options
data. See remarks and dataset handling.

Mode PUT|CALL for options, FUTURE for futures, PUT|CALL|FUTURE for options on futures.

Type The contract type, any meaningful combination of FUTURE, PUT, CALL, EUROPEAN, BINARY.
Add ONLYW3 for selecting only contracts that expire between the 16th and the 21th of the month.

Days The number of calendar days until expiration, with one week tolerance.

Expiry The expiration date in the YYYYMMDD format.

Strike Option strike price rounded to 1 dollar, or 0 for futures.

HistVol The historical volatility of the underlying asset, f.i. from the Volatility indicator. Normally taken from the
last 20 days.

Dividend The annual dividend yield of the underlying, as a fraction, f.i. 0.02 for 2% dividend yield.

RiskFree The risk-free interest rate, as a fraction, f.i. yield()/100.

Value The value of the option for determining the implied volatility.

TRADE* The pointer of an option or future trade. Use ThisTrade for the trade pointer in a TMF or trade
enumeration loop.

281
Remarks:

• An introduction to options trading can be found on Financial Hacker. The TradeOptions script can be used for testing
the opening and closing of SPY options via broker API (note that it only runs at NY market hours and takes about 30
seconds for downloading the option chain at start).
• Entry, Stop, and TakeProfit limits also work for trading options and futures, and refer to the ask and bid prices of the
contract. They must be given as an absolute price limit, not as a distance. When an exit limit is hit, the contract is sold
at market.
• Before entering a trade, make sure by testing ContractType or ContractExpiry that a valid contract is selected.
Otherwise you would unwillingly open a position of the underlying.
• When loading underlying prices with the assetHistory function for the backtest, consider that those prices are normally
split and dividend adjusted and therefore won't match the option values. Use the UNADJUSTED flag for getting the
real prices, or extract them from the fUnl element of the CONTRACT struct.
• When an option is closed, either by an exit command or by reaching a stop or profit target, it is sold back or covered
at the current ask or bid price. If no market price is available, the option stays open until the next exit command or until
it expires, or is exercised. The backtest assumes that underlying positions by expired or exercised options are
immediately sold at market.
• Options and futures can only be traded, and their prices can only be downloaded when the market is open. Do not
send enter, exit, or exercise orders outside market hours.
• Some broker APIs, for instance IB, do not manage trades, but only positions. With those APIs it should be avoided to
exercise options or let them expire in the money, since the API can not distinguish between an underlying position
opened by a normal trade or opened by an exercised option. It is recommended to sell or roll in-the-money options
before expiration.
• The contractUpdate function is only executed after the LookBack period. Loading option chains (contractUpdate)
and receiving option prices (contractPrice) can take a relatively long time with some broker APIs, for instance IB.
• Buying long and short the same contract with the same strike and expiry is treated as two different trades by Zorro,
but as two positions that cancel each other by most brokers. Use the exit command if you want to close a contract by
selling it or buying it back. On NFA accounts, functions like contractPosition or contractCheck only check the net
position of open option trades, and will return wrong results when several long and short positions of the same contract
are open. For the same reason, multiple scripts can trade options on the same NFA account only when they don't open
the same contracts.
• Slippage is not simulated for contracts. Commission and margin are assumed as the commission and margin of the
underlying multiplied with the Multiplier. By setting the Commission and MarginCost parameters in the script prior
to entering a trade, different commission and margin structures can be realized in the script. For instance when trading
vertical spreads, margin cost is often determined by the strike price difference and not by the underlying price, and can
be calculated by setting MarginCost = 0.5 * (Strike1-Strike2) before entering the two trades.
• Phantom trades and virtual hedging are not used for contracts. Options can be hedged regardless of
the Hedge setting.
• Options before 2014 often had expiry dates inside the weekend (Saturday), so make sure to sell them in time in
backtests.
• The CONTRACT struct is defined in trading.h. For backtests the following struct elements must be filled in the data
set; for futures: time, fAsk, fBid, Expiry, Type; for options: time, fAsk, fBid, fUnl, fStrike, Expiry, Type. The other
elements are optional. fUnl is used for determining the intrinsic option value and must contain unadjusted price data.
• Historical data of some contracts - for instance, KC futures - uses prices in cents instead of dollars. For backtesting
those contracts, divide the prices and strikes by 100 when loading the historical data.
• The yield function requires the Quandl bridge. Enter your Quandl key in the QuandlKey field in Zorro.ini.
• The optionVal and optionVol functions require R and the RQuantLib 0.4.2 package installed. The RquantLib
package for Windows can be downloaded from:
https://cran.r-project.org/bin/windows/contrib/3.3/RQuantLib_0.4.2.zip, and installed f.i. with the following R
command:

install.packages("C:/Users/YourName/Downloads/RQuantLib_0.4.2.zip", repos = NULL, type = "win.binary")

• For calculating option values, American options are approximated with the Crank-Nicolson method, while European
options are calculated with the (much faster) Black-Scholes formula. For short-term options the results of both methods
are not very different.

Example:
// Sell call and put options at 1 stddev of the underlying
// Buy them back 2 days before expiration
#include <contract.c>

void run()

282
{
BarPeriod = 1440;
BarZone = ET;
BarOffset = 15*60; // trade at 3 pm Eastern time
if(is(FIRSTINITRUN)) {
assetList("AssetsIB");
assetHistory("SPY",FROM_YAHOO|UNADJUSTED);
asset("SPY");
dataLoad(1,"SPY_Options.t8",9); // historical options data
}

contractUpdate(Asset,1,PUT|CALL);
vars Close = series(priceClose());
int DTE = 6*7; // look for 6-week contracts
var Strangle = StdDev(Close,20);
CONTRACT* Call = contract(CALL,DTE,Close[0] + Strangle);
CONTRACT* Put = contract(PUT,DTE,Close[0] - Strangle);
if(!NumOpenTotal && Call && Put && !is(LOOKBACK)) {
contract(Call); enterShort();
contract(Put); enterShort();
}

// Check expiration and buy them back when in the money


for(open_trades)
{
if(contractDays(ThisTrade) <= 2
and contractIntrinsic(ThisTrade,Close[0]) > 0))
exitTrade(ThisTrade);
}
}

price (int offset) : var


Returns the mean ask price of the currently selected asset - the average of all price ticks inside the bar period resp.
time frame. This is normally the preferred price for indicators because it is less susceptible to random fluctuations and
makes systems more robust and independent on the data feed.

priceOpen (int offset) : var


priceClose (int offset) : var
priceHigh (int offset) : var
priceLow (int offset) : var
Returns the open, close, maximum and minimum ask price of a bar period resp. time frame with the current asset.

marketVal (int offset) : var


marketVol (int offset) : var
Returns additional market data, such as spread, unadjusted close, trade volume per minute, quote frequency, or similar
price accompanying data dependent on the implementation in the Broker API (Zorro S only). Only when T6 price
history is used for historical data and the LEAN flag is not set.

Parameters:
offset Optional bar number for which the prices are returned, in time frames before the current time frame. If
omitted, the price of the current bar is returned. Negative offsets return future prices when the PEEK flag is
set; otherwise they give an error message.

Returns:
Price or additional market data.

283
priceSet (int offset, var Open, var High, var Low, var Close)
Modifies the open, high, low, close, and mean price of the current asset for test purposes, artificial price curves, or for
prices from different sources. Use offset = -1 for modifying the prices of the next bar, which is necessary for entering
trades at the modified prices.

priceQuote (var Timestamp, var Quote) : int


Enters a new price quote of the current asset in the system; for HFT simulation or when prices are not available from
the broker connection. Updates LastPrice or Spread.

Parameters:
offset Optional bar number for which the prices are returned, in time frames before the current time frame. If
omitted, the price of the current bar is returned. Negative offsets return future prices when
the PEEK flag is set; otherwise they give an error message.

Timestamp The exchange timestamp of the quote in DATE format. Only quotes newer than the previous quote of
the same asset and type have an effect.

Quote The price. Quote > 0 indicates an ask price, Quote < 0 a bid price.

Returns:
1 when the price was changed due to a new timestamp, 0 otherwise.

Usage:
priceOpen(10) returns the opening price of 10 bars ago. priceClose() returns the close price of the current bar, i.e. the
most recent price.

Remarks:

• All prices used for trading or indicators are generally Ask prices - no matter if for a long or short trade. This way, stops,
entry limits or profit targets can be calculated without make a distinction between long and short trades. Zorro
automatically handles conversion from Ask to Bid when entering or exiting trades or setting stop loss, take profit, or
entry limits.
• The Bid price is the Ask price minus Spread; the pip value of a price is the price divided by the PIP variable.
• Historical prices are usually dividend and split adjusted. Some price sources, such as Yahoo or Quandl, let you select
between adjusted or unadjusted prices.
• If the LEAN flag is set and M1 historical data is used, the open and close prices of a bar are the mean prices of its first
and last M1 tick.
• At the initial run of the strategy before loading an asset, all price functions return 0 and LookBack is automatically
expanded to the biggest offset value.
• N bars must have passed for accessing the price of N bars ago. If offset is higher than the current bar number (Bar),
the price of the first bar is returned.
• After switching to a different asset, the price functions automatically change to the prices of that asset. For using non-
price data, such as quote frequency, trade volume, or external indicators, pseudo assets can be created f.i. by reading
data from external websites or level II price quotes. For details see data import.
• Artificial assets can be created by combining the prices from several real assets. An example can be found
under Hacks & Tricks.
• When using multiple time frames in the strategy (TimeFrame variable), the offset parameter gives the number of time
frames rather than the number of bars. The Open price is taken from the begin of the time frame, the Close price from
its end, and the High, Low, and mean prices are calculated from all bars belonging to the time frame.
• If Detrend is set to 2 or 3, the returned prices are detrended for removing trend bias from the simulation.
• If PEEK mode is set, price functions can peek into the future by using negative offsets. This is often required for
generating the advisor AI function from historical price data. If PEEK mode is not set, negative offsets will generate
an error message.
• In a TMF or tick function, priceClose() returns the last price quote, updated every tick when new price data becomes
available. price() returns the price averaged over all received ticks of the current bar so far; therefore it is more 'jaggy'
at the begin of the bar when few ticks were received yet, and more smooth towards the end of the bar

284
period. priceHigh() and priceLow() return the highest and lowest price of the current bar so far, so their distance is
small at the begin of the bar and wide towards the end.
• A series can be filled with prices by calling the series() function with the return value of the price function (see
example).
• More variants of prices - i.e. the Center Price, the Typical Price, the Haiken Ashi Price etc. - can be found on
the indicators page.

Example:
BarPeriod = 60; // set up 1 hour bars (60 minutes)

TimeFrame = 4;
asset("EUR/USD");
vars EUR4 = series(price()); // create a series of 4 hour EUR mean prices
...
TimeFrame = frameSync(24);
asset("SPX500");
vars SPXDay = series(priceClose()); // create a series of daily S&P 500 close prices
TimeFrame = 1;
...

enterLong (int Lots, var Entry, var Stop, var TakeProfit, var Trail, var TrailSlope, var TrailLock, var
TrailStep): TRADE*
enterShort (int Lots, var Entry, var Stop, var TakeProfit, var Trail, var TrailSlope, var TrailLock, var
TrailStep): TRADE*
Enters a long resp. short trade position with static entry and exit parameters. Entry and exit conditions are handled by
Zorro.

enterLong (function, var v0, ... var v7): TRADE*


enterShort (function, var v0, ... var v7): TRADE*
Enters a long resp. short trade position with dynamic entry and exit parameters. Entry and exit conditions are handled
by user-provided algorithms in a trade management function (TMF) dependent on the optional variables v0 .. v7.

reverseLong (int MaxTrades, function): TRADE*


reverseShort (int MaxTrades, function): TRADE*
Helper functions for opening a long resp. short trade position while limiting the number of simultaneously open positions
in test and trade mode. Updates stop loss, profit target, and exit time of open trades. The MaxTrades limit does not
apply in [Train] mode. Source code in default.c.

Parameters:
Lots Optional number of lots when nonzero. The global Lots variable is then used for Phantom mode only,
and Margin and Risk are ignored.

Entry Optional entry stop when > 0, entry limit when < 0 (overrides the global Entry). A

Stop Optional stop loss when nonzero (overrides the global Stop).

TakeProfit Optional profit target when nonzero (overrides the global TakeProfit).

Trail Optional trail limit when nonzero (overrides the global Trail).

TrailSlope Optional trailing speed when nonzero (overrides the global TrailSlope).

TrailLock Optional profit lock percentage when nonzero (overrides the global TrailLock).

TrailStep Optional autotrailing step width when nonzero (overrides the global TrailStep).

285
function Optional pointer of an int function for micro-managing the trade (see TMF).

v0 ... v7 Up to 8 optional variables that are passed as further arguments to the TMF.

MaxTrades Maximum number of open positions with the same asset and algo. If at 0, no new trade is opened, but
the stop and profit targets of open trades are updated and opposite trades are closed.

Returns:
TRADE* - a pointer to the created trade struct (see include\trading.h for the definition of the TRADE struct),
or NULL when no trade could be entered because the trade volume was zero, or trading was disabled (f.i. weekend).
For pending trades a nonzero pointer is always returned.

Remarks:

• The result of entering a trade can be either a pending trade, an opened trade, or a rejected trade. If no Entry limit is
given, trades immediately opened at the current price. If a trade is pending, Zorro continues to attempt opening the
trade within the time period given by EntryTime.
• When function parameters are set to 0 or omitted in the parameter list, global trade parameters are used. When no
trade management function and only global parameters are used, the parentheses can be empty.
• Trading is automatically disabled during weekends, during the LookBack period, in the inactive period
of SKIP or DataSplit, or when the current bar is not the end of a TimeFrame.
• If a trade is rejected by the broker, the reason - such as "Outside market hours" - is printed to the log and the
message window (with the MT4 bridge the reason can be seen in the MT4 Experts Log). The number of rejected
orders is stored in the NumRejected variable. There can be several broker-dependent reasons for rejecting a trade,
for instance not enough capital on the account, a wrong asset name, the market is closed or has no liquidity, shorting
the asset is not allowed, or the stop loss is too tight, too far, or not a multiple of a pip.
• Dependent on the broker and the traffic on their server, trades entries can be delayed. Normally the delay is in the
milliseconds range, but it can be up to 5 minutes in extreme cases.
• If trading is disabled, the functions return 0. A returned TRADE* pointer means that the trade is processed, but is no
guarantee that the position is really opened - it could still be pending or is rejected later by the broker. This is indicated
by flags in the TRADE struct. The returned TRADE* pointer can be assigned to ThisTrade (f.i. ThisTrade =
enterLong();). If ThisTrade is nonzero, all trade variables - for instance, TradeIsOpen - can be evaluated for checking
the state of the trade.
• A trade management function (TMF) is automatically called every tick - i.e. whenever the price changes - with the
the optional variables (v0 .. v7) as arguments. It can evaluate the current price and other trade variables for closing
the position or adjusting stop and profit limits.
• Open, pending, and closed trades can be enumerated with the for(open_trades) and for(all_trades) macros.
• Every trade is linked to an algorithm identifier that can be set up through the Algo string. Identifiers are useful when
the script trades with several different algorithms; they are then used for selecting strategy parameters or capital
allocation factors belonging to a certain trade algorithm, or to exit or identifiy particular trades. The performance
report lists strategy results separated by their algorithm identifiers.
• The number of lots (1 mini lot = 10000 currency contracts, 1 micro lot = 1000 contracts) is determined by
the Margin or Lots variables. If Lots is negative, the trade is executed in "phantom mode". Phantom trades are not
sent to the broker and do not contribute to the Total statistics, but their projected wins and losses contribute to
the Short and Long statistics. This can be used to filter trades based on the current win/loss situation (equity curve
trading).
• Entry and exit conditions can be either set individually per trade, or set up globally through the Entry, Stop, TakeProfit,
and Trail variables for all subsequent trades. If the stop loss is too close, too far, to or on the wrong side of the price,
the trade can be rejected by the broker. Make sure that the stop loss has sufficient distance to the current price
(priceClose()). A timed exit can be set up with ExitTime.
• If TICKS is not set and different price limits are met in the same bar (f.i. a profit target and a stop loss), the simulation
assumes a pessimistic outcome, i.e. the stop loss is normally evaluated first. If TICKS is set, the price curve inside the
bar is evaluated for determining which limit is met first.
• Opening a trade automatically closes opposite trades when Hedge is 0 or 1 (i.e. enterLong can automatically close
short positions and enterShort can automatically close long positions). If the trade was not opened because
the Entry limit was not met within EntryTime, opposite trades are not closed; if the trade was not opened
because Lots is 0 or Margin or Risk are too low, opposite trades are still closed.
• How trades are filled in the simulation can be set up with the Fill variable.
• reverseLong/Short are helper functions for limiting the number of trades in test and trade mode. Their source code
is available in include\default.c. A trade is only entered when the number of open trades with the same asset and
algo is less than MaxTrades or when the strategy is in [Train] mode. Otherwise the position is still reversed, i.e. trades
286
in opposite direction are closed when hedging is not active. If Stop, TakeProfit, or ExitTime is used, open trades in
the same direction are updated with the new stop loss and take profit values, and the exit time is restarted. This way
the open trade behaves as if it was just opened.
• If a order function is defined in the script, it is called with 1 for the first argument and the TRADE* pointer for the
second argument at the moment when the buy order is sent to the broker. This function can f.i. open a message box
for manually entering the trade, or control another broker's user interface.
• When converting scripts from other trading software, keep in mind that other trading programs use sometimes different
names for trade functions. For instance, TradeStation® uses "Sell Short" for entering a short position and "Buy To
Cover" for exiting a short position.
• In [Trade] mode, all open trades are stored in a .trd file in the Data folder. The stored trades are automatically
continued when the strategy is started again, for instance after a computer crash.

Example 1: Simple SMA crossing


function run()
{
vars Price = series(priceClose());
vars SMA100 = series(SMA(Price,100));
vars SMA30 = series(SMA(Price,30));

if(crossOver(SMA30,SMA100))
enterLong();
else if(crossUnder(SMA30,SMA100))
enterShort();
}

Example 2: Grid trading script


// helper function for finding trades at a grid line
bool noTradeAt(var Price,var Grid,bool IsShort)
{
for(open_trades)
if((TradeIsShort == IsShort)
and between(TradeEntryLimit,Price-Grid/2,Price+Grid/2))
return false; // trade found
return true; // no open/pending trade at that price
}

function run()
{
BarPeriod = 1440;
Hedge = 2;
EntryTime = ExitTime = 500;
var Price;
var Grid = 100*PIP; // grid spacing
var Current = priceClose();
// place pending trades at 5 grid lines above and below the current price
for(Price = 0; Price < Current+5*Grid; Price += Grid) {
if(Price < Current-5*Grid)
continue;
if(Price < Current and noTradeAt(Price,Grid,true))
enterShort(0,Price,0,Grid);
else if(Price > Current and noTradeAt(Price,Grid,false))
enterLong(0,Price,0,Grid);
}
}

Example 3: Using a trade management function


// TMF that adjusts the stop in a special way
int TrailingStop()
{
// adjust the stop only when the trade is in profit
if(TradeProfit > 0)
// place the stop at the lowest bottom of the previous 3 candles
TradeStopLimit = max(TradeStopLimit,LL(3));
// plot a line to make the stop limit visible in the chart
plot("Stop",TradeStopLimit,MINV,BLACK);
// return 0 for checking the limits
return 0;
}

// using the trade function


function run()
{
...
Lots = 1;

287
Stop = 10*PIP;
Algo = "SIG";
if(crossOver(MySignal,MyThreshold))
enterLong(TrailingStop);
...
}

exitLong(string algo, var price, int lots)


exitShort(string algo, var price, int lots)
Exits all long resp. all short trades with the current asset that match the given algo identifier, at market or at a given
price limit, until the given number of lots is closed.

exitTrade(TRADE*, var price, int lots): int


Exits the given number of lots of a particular trade at market or at a given price limit. Returns 0 when the order failed,
otherwise nonzero.

cancelTrade(int id)
Closes a trade with a particular identifier without sending a close order to the broker. Useful for removing an externally
closed position that was not transmitted to Zorro (f.i. on an NFA account).

Parameters:
algo 0 for closing all trades with the current algo/asset combination. Alternatively, an algorithm identifier, or
the asset name when no algo is used in the script, or "*" for closing all trades with the current asset.

TRADE* A pointer to the trade to be closed, or 0 for closing the current trade in a trade enumeration loop.

price Optional price or price distance for selling the position, or 0 for selling at market. A positive price or price
distance constitutes an exit stop, a negative price is an exit limit (similar to Entry). An exit stop closes the
position when the asset price is at or worse than the given price, like a stop loss; an exit limit closes the
position when the asset price is at or better than the given price, like a profit target.

lots Optional number of lots to be closed, or 0 for closing all open lots. Partially closing trades is not available
with all brokers, for instance not with NFA compliant accounts.

id Identifier of the trade to be cancelled. Either the full number as assigned by the broker, or the last 4 digits
as displayed in the Zorro window.

Remarks:

• Before closing a trade, check the business hours of the broker. Not all assets can be traded 24 hours per day. Trading
is automatically disabled during weekends, during the LookBack period, or in the inactive period
of SKIP or DataSplit.
• Optional parameters can be omitted. F.i. exitLong() exits all long positions with the current asset and algorithm
identifier at market.
• If an exit stop or exit limit is given, the position is closed as soon as the target price is reached, no matter how long it
takes. An exit stop overrides a more distant stop loss, an exit limit overrides a previous profit target.
The exitLong/Short function can thus be used to tighten the stop loss or to change the profit target of open trades.
• If an exit order fails in live trading, Zorro will repeat it in increasing time intervals until it is executed (except
for options or when OrderLimit is used). If the position can not be closed after 2 working days, Zorro will assume an
issue with the broker server and remove the trade from its internal list. If the market is not open due to holidays or
weekend, a message "Can't exit at weekends" is printed and the position is closed as soon as the market opens
again.
• When no price was given, pending positions - positions either immediately entered before, or positions with
an entry stop or limit - are also closed.
• The price at which the trade is closed in the backtest can be affected with the Fill variable.

288
• If a order function is defined in the script, it is called with 2 for the first argument and the TRADE* pointer for the
second argument at the moment when the sell order is sent to the broker. This function can f.i. open a message box
for manually exiting the trade, or control another broker's user interface.

Example:
exitShort(0,1.234); // exits all short trades with the current Algo/Asset as soon as the price is at or above
1.234 (exit stop).

adviseLong (int Method, var Objective, var Signal0, var Signal1, ...): var
adviseShort (int Method, var Objective, var Signal0, var Signal1, ...): var
Machine learning functions for training and predicting trade results from a combination of input signals. They can be
used for finding price or indicator patterns that precede profitable trades. The advise functions are used for training the
algorithm and also for predicting the result, dependent on whether the script is run in training or in test/trade mode. In
training mode, Zorro learns rules that predict the success of a trade from the signals, and generates a C function or
model containing those rules. In test or trade mode, the trained model or C function is executed on every advise call,
applies the rules to the current signals, and returns the trade success prediction.

Zorro can use internal or external machine learning algorithms, f.i. from R packages, for the prediction. Three internal
prediction methods are available: a decision tree (Method == DTREE), a simple neural network (Method ==
PERCEPTRON), or a candle or signal pattern detector (Method == PATTERN). All three methods generate rule
functions separately for any WFO cycle, asset, algorithm identifier, and long or short trade. The correct function is
automatically selected when calling adviseLong or adviseShort in test or trade mode. The function takes the signals
as parameters, and returns the win or loss prediction accuracy in percent.

Returns:
[Train] mode: 0 when Objective != 0, otherwise 100 (so that dependent trades are always executed for training).
[Test], [Trade] mode: Prediction of Objective, or of trade success when Objective was 0 during training. Trade
success prediction is normally in ~ -100 .. +100 range. When the value is above a threshold, Zorro advises entering a
trade, otherwise not.

Parameters:
Method 0 - use the method and signals of the last advise call.
SIGNALS - don't predict; just export Signals and Objective in [Train] mode to a Data\signals.csv file
for testing external machine learning functions.
NEURAL - train and predict with an external machine learning algorithm.
DTREE - train and predict with a decision tree.
PERCEPTRON - train and predict with a simple neural net.
PATTERN - train and predict with a signal pattern analyzer.
+FAST - fast and complex pattern finding (for PATTERN).
+FUZZY - fuzzy pattern finding (for PATTERN).
+2 .. +6 - number of pattern groups (for PATTERN).
+BALANCED - enforce the same number of positive and negative target values by replication
(for SIGNALS, NEURAL, DTREE, PERCEPTRON).
Objective The training target, or 0 for using the profit or loss of the next trade as target. A positive value advises to
enter a trade when this signal combination occurs, a negative value advises against a trade. The
prediction is better when positive and negative target values occur with about the same frequency during
the training run (see remarks). The Objective parameter is only used in the training run and has no
meaning in test or trade mode. When calculating this parameter, make sure that it never becomes 0, or
else the next trade result is used for the training target.

Signal0, The features of the method. Up to 20 values that carry information about the current market situation, or
... can otherwise be used for predicting the success or failure of a trade. Useful signals could be candle

289
patterns, price differences, indicators, filters, or statistics functions. All signal values should be in the
same range (see remarks). If all signals are omitted, the signals from the last advise call are used.

Decision Tree

A decision tree is a tree-like graph of decisions by comparing signals with fixed values. The values and the tree
structure are generated in the training run. For this the training process iterates through the sets of signals and finds the
signal values with the lowest information entropy. These values are then used to split the data space in a profitable and
a non profitable part, then the process continues with iterating through the parts. Details about the decision tree algorithm
can be found in books about machine learning.

The signals should be normalized roughly to the -100..100 range for best precision. They should be carefully selected
so that the displayed prediction accuracy is well above 60% in all WFO cycles. Decision trees work best with signals
that are independent of each other. They do not work very well when the prediction depends on a linear combination of
the signals. In order to reduce overfitting, the resulting trees are pruned by removing non-predictive signals. The output
of the tree is a number between -100 .. +100 dependent on the predictive quality of the current signal combination.

The decision tree functions are stored in C source code in the \Data\*.c file. The functions are automatically included in
the strategy script and used by the advise function in test and trade mode. They can also be exported for using them
in strategy scripts or expert advisors of other platforms.The example below is a typical Zorro-generated decision tree:

int EURUSD_S(var* sig)


{
if(sig[1] <= 12.938) {
if(sig[3] <= 0.953) return -70;
else {
if(sig[2] <= 43) return 25;
else {
if(sig[3] <= 0.962) return -67;
else return 15;
}
}
}
else {
if(sig[3] <= 0.732) return -71;
else {
if(sig[1] > 30.61) return 27;
else {
if(sig[2] > 46) return 80;
else return -62;
}
}
}
}

The advise() call used 5 signals, of which the first and the last one - sig[0] and sig[4] - had no predictive power, and
thus were pruned and do not appear in the tree. Unpredictive signals are displayed in the message window.

Example of a script for generating a decision tree:

void run()
{
BarPeriod = 60;
LookBack = 150;
TradesPerBar = 2;
if(Train) Hedge = 2;
set(RULES|TESTNOW);
// generate price series
vars H = series(priceHigh()),
L = series(priceLow()),
C = series(priceClose());

// generate some signals from H,L,C in the -100..100 range


var Signal1 = (LowPass(H,1000)-LowPass(L,1000))/PIP;
var Signal2 = 100*FisherN(C,100);

// train and trade the signals


Stop = 4*ATR(100);
TakeProfit = 4*ATR(100);
if(adviseLong(DTREE,0,Signal1,Signal2) > 0)
enterLong();

290
if(adviseShort(DTREE,0,Signal1,Signal2) > 0)
enterShort();
}

Perceptron

A perceptron is a simple neural net, consisting of one neuron with up to 20 signal inputs and one binary output. It
calculates its predictions from a linear combination of weighted signals. The signal weights are generated in the training
run for producing the best possible prediction.

The perceptron algorithm works best when a weighted sum of the signals has predictive power. It does not work well
when the prediction requires a nonlinear signal combination, i.e. when trade successes and failures are not separated
by a straight plane in the signal space. A classical example of a function that a perceptron can not emulate is a logical
XOR. Often a perceptron can be used where a decision tree fails, and vice versa.

The perceptron learning algorithm generates prediction functions in C source code in the \Data\*.c file. The functions
are automatically included in the strategy script and used by the advise function in test and trade mode. They can also
be exported for using them in strategy scripts or expert advisors of other platforms. The output is binary: either >0 for a
positive or <0 for a negative prediction. A generated perceptron function with 3 signals can look like this:

int EURUSD_S(var* sig)


{
if(-27.99*sig[0] + 1.24*sig[1] - 3.54*sig[2] > -21.50)
return 100;
else
return -100;
}
Signals that do not contain useful market information get a weight of 0.

Pattern Analyzer

The pattern analyzer is an intelligent version of classic candle pattern indicators. It does not use predefined patterns,
but learns them from historic price data. It's normally fed with the open, close, high or low prices of a number of candles.
It compares every signal with every other signal, and uses the comparison results - greater, smaller, or equal - for
classifying the pattern. Equality is detected in a 'fuzzy' way: Signals that differ less than the FuzzyRange are considered
equal.

The signals can be divided into groups with the PATTERN+2 .. PATTERN+6 methods. They divide the signals into two
to six pattern groups and only compare signals within the same group. This is useful when, for instance, only the first
two candles and the last two candles of a 3-candle pattern should be compared with each other, but not the first candle
with the third candle. PATTERN+2 requires an even number of signals, of which the first half belongs to the first and
and the second half to the second group. PATTERN+3 likewise requires a number of signals that is divisible by 3, and
so on. Pattern groups can share signals - for instance, the open, high, low, and close of the middle candle can appear
in the first as well as in the second group - as long as the total number of signals does not exceed 20.

Aside from grouping, Zorro makes no assumptions of the signals and their relations. Therefore the pattern analyzer can
be also used for other signals than candle prices. All signals within a pattern group should have the same unit for being
comparable, but different groups can have different units. For candle patterns, usually the high, low, and close of the
last 3 bars is used for the signals - the open is not needed as it's normally identical with the close of the previous candle.
More signals, such as the moving average of the price, can be added for improving the prediction (but in most cases
won't).

The pattern analyzer generates pattern finding functions in C source code in the \Data\*.c file. The functions are
automatically included in the strategy script and used by the advise function in test and trade mode. They can also be
exported for using them in strategy scripts or expert advisors of other platforms. They find all patterns that occurred 4
or more times in the training data set and had a positive profit expectancy. They return the pattern's information ratio -
the ratio of profit mean to standard deviation - multiplied with 100. The better the information ratio, the more predictive
is the pattern. A typical pattern finding function with 12 signals looks like this:

int EURUSD_S(float* sig)


{
if(sig[1]<sig[2] && eqF(sig[2]-sig[4]) && sig[4]<sig[0] && sig[0]<sig[5] && sig[5]<sig[3]
&& sig[10]<sig[11] && sig[11]<sig[7] && sig[7]<sig[8] && sig[8]<sig[9] && sig[9]<sig[6])
return 19;

291
if(sig[4]<sig[1] && sig[1]<sig[2] && sig[2]<sig[5] && sig[5]<sig[3] && sig[3]<sig[0] && sig[7]<sig[8]
&& eqF(sig[8]-sig[10]) && sig[10]<sig[6] && sig[6]<sig[11] && sig[11]<sig[9])
return 170;
if(sig[1]<sig[4] && eqF(sig[4]-sig[5]) && sig[5]<sig[2] && sig[2]<sig[3] && sig[3]<sig[0]
&& sig[10]<sig[7] && eqF(sig[7]-sig[8]) && sig[8]<sig[6] && sig[6]<sig[11] && sig[11]<sig[9])
return 74;
if(sig[1]<sig[4] && sig[4]<sig[5] && sig[5]<sig[2] && sig[2]<sig[0] && sig[0]<sig[3] && sig[7]<sig[8]
&& eqF(sig[8]-sig[10]) && sig[10]<sig[11] && sig[11]<sig[9] && sig[9]<sig[6])
return 143;
if(sig[1]<sig[2] && eqF(sig[2]-sig[4]) && sig[4]<sig[5] && sig[5]<sig[3] && sig[3]<sig[0]
&& sig[10]<sig[7] && sig[7]<sig[8] && sig[8]<sig[6] && sig[6]<sig[11] && sig[11]<sig[9])
return 168;
....
return 0;
}

The eqF function in the code above checks if two signals are almost equal, i.e. differ less than FuzzyRange.

There are two additional special methods for the pattern analyzer. The FUZZY method generates a pattern finding
function that also finds patterns that can slightly deviate from the profitable patterns in the training data set. It gives
patterns a higher score when they 'match better'. The deviation can be set up with FuzzyRange. A typical fuzzy pattern
finding function looks like this:

int EURUSD_S(float* sig)


{
double result = 0.;
result += belowF(sig[1],sig[4]) * belowF(sig[4],sig[2]) * belowF(sig[2],sig[5]) * belowF(sig[5],sig[3]) *
belowF(sig[3],sig[0])
* belowF(sig[10],sig[11]) * belowF(sig[11],sig[7]) * belowF(sig[7],sig[8]) * belowF(sig[8],sig[9]) *
belowF(sig[9],sig[6]) * 19;
result += belowF(sig[4],sig[5]) * belowF(sig[5],sig[1]) * belowF(sig[1],sig[2]) * belowF(sig[2],sig[3]) *
belowF(sig[3],sig[0])
* belowF(sig[10],sig[7]) * belowF(sig[7],sig[11]) * belowF(sig[11],sig[8]) * belowF(sig[8],sig[9]) *
belowF(sig[9],sig[6]) * 66;
result += belowF(sig[4],sig[1]) * belowF(sig[1],sig[2]) * belowF(sig[2],sig[0]) * belowF(sig[0],sig[5]) *
belowF(sig[5],sig[3])
* belowF(sig[10],sig[11]) * belowF(sig[11],sig[7]) * belowF(sig[7],sig[8]) * belowF(sig[8],sig[6]) *
belowF(sig[6],sig[9]) * 30;
result += belowF(sig[1],sig[4]) * belowF(sig[4],sig[2]) * belowF(sig[2],sig[5]) * belowF(sig[5],sig[3]) *
belowF(sig[3],sig[0])
* belowF(sig[7],sig[10]) * belowF(sig[10],sig[11]) * belowF(sig[11],sig[8]) * belowF(sig[8],sig[6]) *
belowF(sig[6],sig[9]) * 70;
result += belowF(sig[4],sig[5]) * belowF(sig[5],sig[1]) * belowF(sig[1],sig[2]) * belowF(sig[2],sig[3]) *
belowF(sig[3],sig[0])
* belowF(sig[7],sig[10]) * belowF(sig[10],sig[8]) * belowF(sig[8],sig[11]) * belowF(sig[11],sig[9]) *
belowF(sig[9],sig[6]) * 108;
...
return result;
}

The belowF function is described on the Fuzzy Logic page.

The FAST method does not generate C code; instead it generates a list of patterns that are classified with alphanumeric
names. For finding a pattern, it is classified and its name compared with the pattern list. This is about 4 times faster than
the pattern finding function in C code, and can also handle bigger and more complex patterns. It can make a remarkable
difference in backtest time or when additional parameters have to be trained. A pattern name list looks like this (the
numbers behind the name are the information ratios):

/* Pattern list for EURUSD_S


HIECBFGAD 61
BEFHCAIGD 152
EHBCIFGAD 73
BEFHCIGDA 69
BHFECAIGD 95
BHIFECGAD 86
HBEIFCADG 67
HEICBFGDA 108
...*/

The FAST method can not be used in combination with FUZZY or with FuzzyRange. But the FAST as well as
the FUZZY method can be combined with pattern groups (f.i. PATTERN+FAST+2).

The find rate of the pattern analyzer can be adjusted with two variables:

292
PatternCount
The minimum number of occurrences of the found patterns in the analyzed price curve; default = 4.

PatternRate
The minimum win rate of the found patterns, in percent; default = 50.

An example of a pattern trading script can be found in Workshop 7.

Machine Learning

The NEURAL method uses external machine learning libraries with neural networks, support vector machines, or deep
learning algorithms for predicting trade results. Such algorithms are available in R packages; therefore
the NEURAL method will often call R functions for training and prediction. Alternatively, any DLL-based machine
learning library can be used (look here for accessing DLL classes and functions). The algorithm is implemented with a
single user-provided function:

neural (int mode, ínt model, int numSignals, void* Data): var
This function is called several times during the training, test, or trade process. It has access to all global and
predefined variables. Its behavior depends on mode:

mode Parameters Description

NEURAL_INIT --- Initialize the machine learning library (f.i. by calling Rstart) before running
the simulation. Return 0 if the initialization failed, otherwise 1. The script is
aborted if the system can not be initialized.

NEURAL_EXIT --- Close the machine learning library (not required for R).

NEURAL_LEARN model, Use a single sample of signals contained in the Data double array for
numSignals, training the model identified by the model index. The last
Data element, Data[numSignals], is the Objective parameter or the trade result.
The function is triggered by any advise call in [Train] mode, either
immediately (Objective != 0) or when the trade is closed (Objective == 0).

NEURAL_TRAIN model, Batch training. Alternative to NEURAL_LEARN: train a model (identified by


numSignals, the model index) with all collected data samples. The function is called at
Data the end of every asset/algo loop cycle in the training run. The model number
counts the asset, algo, and long/short combinations. The samples are
provided in CSV format in the Data string. The columns of the CSV table are
the signals, the last column is the Objective parameter or the trade result.
The prediction accuracy in percent can be optionally returned by
the neural function; otherwise return 1 if no accuracy is calculated, or 0 for
aborting the script when the training failed.
NEURAL_PREDICT model, Return the value predicted by the model with the given model index, using
numSignals, the signals contained in the Data double array. The function is called by
Data an advise call in [Test] and [Trade] mode. The model parameter is the
number of the model.

NEURAL_SAVE Data Save all trained models in the file with the name given by the string Data.
The function is called at the end of every WFO cycle in [Train] mode.

NEURAL_LOAD Data Load all trained models from the file with the name given by the string Data.
The function is called at the begin of every WFO cycle in [Test] mode, and
at the begin of a [Trade] session. It is also called every time when the model
file is updated by re-training.

The model index is the number of the trained model - for instance a set of decision rules, or a set of weights of a neural
network - starting with 0. When several models are trained for long and short predictions and for different assets or
293
algos, the index selects one of the models. In R, models can be stored in a list of lists and accessed through their index
(f.i. Models[[model+1]]). Any aditional parameters generated in the training process - for instance, a set of
normalization factors or selection masks for the signals - can be saved together with the models.

The numSignals parameter is the number of signals passed to the advise function. It is normally identical to the number
of trained features.

The Data parameter provides data to the function. The data can be of different type. For
NEURAL_LEARN/NEURAL_PREDICT it's a pointer to a double array of length numSignals+1, containing the signal
values plus the prediction objective or trade result at the end. Note that a plain data array has no "dim names" or other
R gimmicks - if they are needed in the R training or predicting function, add them there.
For NEURAL_TRAIN the Data parameter is a text string containing all samples in CSV format. The string can be stored
in a temporary CSV file and then read by the machine learning algorithm for training the model.
For NEURAL_SAVE/NEURAL_LOAD the Data parameter is the suggested file name for saving or loading the trained
models separately for any WFO cycle in the Data folder. Use the slash(string) function for converting backslashes to
slashes when required for R file paths.

This is the default neural function in the r.h file for using a R machine learning algorithm. It expects 4 R functions
named neural.train, neural.predict, neural.save, and neural.init in a R script in the Strategy folder. The R script has
the same name as the strategy script, but extension .r instead of .c. If required for special purposes, the
default neural function can be replaced by a user-supplied function.

var neural(int mode, int model, int numSignals, void* Data)


{
if(!wait(0)) return 0;
// open an R script with the same name as the stratefy script
if(mode == NEURAL_INIT) {
if(!Rstart(strf("%s.r",Script),2)) return 0;
Rx("neural.init()");
return 1;
}
// export batch training samples and call the R training function
if(mode == NEURAL_TRAIN) {
string name = strf("Data\\signals%i.csv",Core);
file_write(name,Data,0);
Rx(strf("XY <- read.csv('%s%s',header = F)",slash(ZorroFolder),slash(name)));
Rset("AlgoVar",AlgoVar,8);
if(!Rx(strf("neural.train(%i,XY)",model+1),2))
return 0;
return 1;
}
// predict the target with the R predict function
if(mode == NEURAL_PREDICT) {
Rset("AlgoVar",AlgoVar,8);
Rset("X",(double*)Data,numSignals);
Rx(strf("Y <- neural.predict(%i,X)",model+1));
return Rd("Y[1]");
}
// save all trained models
if(mode == NEURAL_SAVE) {
print(TO_ANY,"\nStore %s",strrchr(Data,'\\')+1);
return Rx(strf("neural.save('%s')",slash(Data)),2);
}
// load all trained models
if(mode == NEURAL_LOAD) {
printf("\nLoad %s",strrchr(Data,'\\')+1);
return Rx(strf("load('%s')",slash(Data)),2);
}
return 1;
}

The neural function is compatible with all Zorro trading, test, and training methods, including walk forward analysis,
multi-core parallel training, and multiple assets and algorithms. An example of using advise(NEURAL,...) for a short-
term deep learning system can be found on Financial Hacker, as well as a machine learning overview.

Remarks:

• The RULES flag must be set for generating rules or training a machine learning algorithm. See the remarks
under Training for special cases when RULES and PARAMETERS are generated at the same time.

294
• The generated rules by DTREE, PERCEPTRON, PATTERN are stored in plain text in a *.c file in the Data folder. This
allows to export the rules to other platforms, and to look into the prediction process and to check if the rules makes
sense.
• During the LookBack period, advise returns 0 and no rules are trained.
• For training trade results, call the advise function with Objective = 0 just before entering a trade; Zorro then uses the
next trade's result for learning the rules. When training long and short trades at the same time, set Hedge to make
sure that they can be opened simultaneously in training mode; otherwise any trade would close an opposite trade just
entered before. Define an exit condition for the trade, such as a timed exit after a number of bars, or a stop/trailing
mechanism for predicting a positive or negative excursion. Do not train rules with hedging explicitly disabled or with
special modes such as NFA or Virtual Hedging.
• Most machine learning algorithms require that training targets are balanced. That means the trades used for training
should result in about 50% wins and 50% losses, and negative and positive Objective values should be equally
distributed. If in doubt, add +BALANCED to the method; this will simply copy samples until balance is reached.
• Functions like scale or Normalize can be used to convert the signals to the same range. Some R machine learning
algorithms require that signals and targets are in the 0..1 range. In that case negative values or values greater than 1
lead to wrong results.
• When using a future value for prediction, such as the price change of the next bars (f.i. Objective = priceClose(-5) -
priceClose(0)), make sure to set the PEEK flag in train mode. Alternatively, use the price change of a past bar to the
current bar, and pass past signals - f.i. from a series - to the advise function in train mode, f.i.:
Objective = priceClose(0) - priceClose(5);
int Offset = ifelse(Train,5,0);
var Prediction = adviseLong(Method,Objective,Signal0[Offset],Signal1[Offset],...);
• All advise calls for a given asset/algo combination must use the same Method, the same signals, and the same
training target type (either Objective or trade result). However, different methods and signals can be used for different
assets and algorithm identifiers, so call algo() for combining multiple advise methods in the same script. Ensemble or
hybrid methods can also be implemented this way, using different algo identifiers.
• When trading is disabled, f.i. during the LookBack period, at weekend, due to a SKIP flag or during the inactive bars
of a time frame, the advise functions won't train or predict and return 0.
• For training several objectives with the NEURAL method, pass the further objectives as Signal parameters to
the advise function. For using more than 20 signals, collect them in a global array and send them to the machine
learning algorithm with NEURAL_LEARN or NEURAL_TRAIN.
• Machine learning functions have a tendency to greatly overfit the strategy. It's rather easy to get almost 1000% annual
return with rules generated from the same price data as used in the test. For this reason, strategies that use
the advise function must always be tested with unseen data, f.i. by using Out-Of-Sample testing or Walk Forward
Optimization. Use the OPENEND flag for preventing that trades prematurely close at the end of a WFO training period
and thus reduce the training quality.
• The estimated prediction accuracy resp. the number of found patterns are printed in the message window. The signals
should be selected in a way that the prediction accuracy is above 50% or that as many as possible profitable patterns
are found.

for(current_trades) { ... }
A for loop that cycles through all open and pending trades with the current asset and algo.

for(open_trades) { ... }
A for loop that cycles through all open, pending, and pool trades of all assets and algos.

for(all_trades) { ... }
A for loop that cycles through all open, pending, and closed trades; useful for a detailed trade statistic after the
simulation.

Type:
Macro

Remarks:

• The code inside the loop has access to all trade variables. All asset specific variables - Asset, Spread, etc. - are set
to the asset of the trade. Algo specific variables (f.i. AlgoVar) are not set to the algo identifier of the trade; if this is
desired, call algo(TradeAlgo) inside the loop, and do not forget to set the algo back afterwards.
295
• The trades are sorted in the order of their enter commands; the oldest trades come first.
• The winged brackets can be omitted when the loop contains only one command (see
example). long_trades, short_trades, open_trades and all_trades are macros defined in include\variables.h.
• Trade loops must not be nested. They can be called inside a TMF, but then be aware that inside the loop all trade
variables refer to the current trade of the loop, while outside the loop they refer to the trade of the TMF.
• Trade loops must not be prematurely terminated by break or return statements. Otherwise an error message will be
displayed.
• When virtual hedging is used, open_trades contains phantom as well as pool trades. Make sure to distinguish them
with TradeIsVirtual or TradeIsPool. You normally don't want to modify pool trades.
• Opening a new trade inside a trade loop is possible, but will add a new cycle to that loop. Be careful not to create an
endless loop this way.
• The pointer to the current TRADE struct is ThisTrade. For closing a particular trade in the loop,
call exitTrade(ThisTrade);.

Example:
// find all pending trades with the current asset
int numPending(bool isShort)
{
string CurrentAsset = Asset;
int num = 0;
for(open_trades)
if(strstr(Asset,CurrentAsset)
&& TradeIsPending && TradeIsShort == isShort)
num++;
return num;
}

// sum up the profit/loss of all open trades with the current asset
var val = 0;
string CurrentAsset = Asset;
for(open_trades)
if(strstr(Asset,CurrentAsset) && TradeIsOpen && !TradeIsPhantom)
val += TradeProfit;

// increase the stop of all winning trades slowly over time


for(open_trades) {
if(TradeProfit > 0 && !TradeIsPool)
TradeStopLimit -= 0.02 * TradeStopDiff;
}

// lock 80% profit of all winning trades


for(open_trades) {
if(TradeIsOpen && !TradeIsPool && TradeProfit > 0) {
TradeTrailLock = 0.80;
if(TradeIsShort)
TradeTrailLimit = max(TradeTrailLimit,TradePriceClose);
else
TradeTrailLimit = min(TradeTrailLimit,TradePriceClose);
}
}

296
Trade management functions and variables
Trade enter commands can receive a trade management function (TMF) as the first argument. A TMF is a function for
micro managing the trade. It is repeatedly called until the trade is closed. In most cases it's used for modifying entry,
stop, or profit limits in a special way, overriding the standard trailing methods. In live trading and in TICKS mode, a TMF
is executed every tick and thus has access to the most recent price quote. When the market is closed and no price ticks
are received, TMFs are not executed.

TMFs are also called at four special events in the lifetime of a trade: Right before entering or exiting due to Entry, Stop,
or TakeProfit, and right after being closed. Which event it is can be checked in the TMF with the boolean
expressions TradeIsEntry, TradeIsStop, TradeIsProfit, TradeIsClosed (described below).

A TMF has the type int and should normally return 0; other return values have a special meaning. !! When using a
TMF, do not forget the return statement with the correct value!

TMF return values:


0 - check the trade's Entry, Stop, TakeProfit, and Trail parameters, and exit or enter accordingly.
1 - if the trade is still open or pending, exit it now.
2 - if the trade is still pending, enter it now.
4 - Don't use Entry, Stop, or TakeProfit for automatically entering or exiting. Exit or enter only when the TMF
returns 1 or 2.
8 - call the TMF only once per bar, just before the run function call.
16 - call the TMF only at events (entering or exiting due to Entry, Stop, or TakeProfit, and after the trade was
closed).

The return values can be combined by addition. For instance, return value 28 (= 4+8+16) executes the TMF only once
per bar or when Entry, Stop, or TakeProfit was hit, and does not automatically enter or exit in that case.

A a list of up to 8 var variables can be passed as parameters to the TMF, f.i. enterLong(MyTMF, parameter1,
parameter2...). They can appear in the argument list of the TMF definition and keep their values during the lifetime of
the trade.

The TMF has access to the following trade specific variables (all prices are Ask prices, most variables are float):

TradePriceOpen
The ask price when the trade was opened, or the premium per unit for options or futures. If the trade was not yet
opened, it's the current price of the asset or contract.

TradePriceClose
The ask price when the trade was closed or the price at which the option or future was sold or covered. If the trade is
still open, it's the current price of the asset or contract.

TradeUnits
Conversion factor from price change to win/loss in account currency units; normally TradeLots*PIPCost/PIP for assets,
or TradeLots*Multiplier for options or futures. Examples see below.

TradeRoll
The current accumulated rollover of the trade, negative or positive. Only valid when the trade is open.

TradeProfit
The current profit or loss of the trade in units of the account currency, including costs such as spread, rollover, slippage,
and commission. TradeProfit/TradeUnits/PIP is the current profit or loss of the trade in pips. On NFA
compliant accounts the profit is simulated, as there is no profit assigned to a single trade. For options, TradeProfit is
normally the difference of premium and current price; if the option is expired or was exercised, it's the extrinsic value,
i.e. the difference of strike and underlying price minus the premium.

297
TradeStrike
The strike price of the traded option (if any).

TradeUnderlying
The underlying price of the traded option (if any).

TradeMFE
Maximum favorable excursion, the maximum price movement in favorable direction of the trade. Only valid after the
trade was opened. TradeMFE*TradeUnits is the highest profit of the trade in account currency units while it was open
(without trading costs).

TradeMAE
Maximum adverse excursion, the maximum price movement in adverse direction of the trade. Only valid after the trade
was opened. TradeMAE*TradeUnits is the highest loss of the trade in account currency units while it was open (without
trading costs).

TradeEntryLimit
Entry limit; initially calculated from Entry. The trade will be opened when the price reaches this value. Can be modified
by the TMF.

TradeStopLimit
Stop limit, initially calculated from Stop; only valid when the trade is open. The trade will be closed when the price
reaches this value. Can be modified by the TMF.

TradeStopDiff
Difference of the initial price to the initial stop limit; negative for long trades and positive for short trades. Initially
calculated from Stop and only valid when the trade is open. When TradeStopLimit was moved by trailing, the original
stop position can still be retrieved through TradePriceOpen+TradeStopDiff.

TradeProfitLimit
Profit limit, initially calculated from TakeProfit; only valid when the trade is open. The trade will be closed when the price
reaches this value. Can be modified by the TMF.

TradeTrailLimit
Trail limit, initially calculated from Trail; only valid when the trade is open and a stop limit was set. The stop limit will be
moved when the price reaches this value. Can be modified by the trade function.

TradeTrailSlope
Trail slope factor in the range 0..1; only valid when the price is over the Trail limit, and a Stop limit was set. Can be
modified by the trade function for changing the trail slope f.i. after breakeven.

TradeTrailStep
Trail step factor in the range 0..1; only valid when the price is over the Trail limit, and a Stop limit was set. Can be
modified by the trade function for changing the trail step f.i. after breakeven.

TradeTrailLock
Trail lock factor in the range 0..1; only valid when the price is over the Trail limit, and a Stop limit was set. Can be
modified by the trade function for changing the trail lock f.i. after breakeven.

Type:
float, read/only if not mentioned otherwise. Convert them to var when using them in print/printf statements!

298
TradeVar[0] .. TradeVar[7]
8 general purpose var variables (default = 0). They are stored in the TRADE struct and can be used when a trade
specific value must be preserved between trade function runs. They can be used and modified by the TMF. When they
are used, it's recommended to define meaningful names for them, f.i. #define LastPrice TradeVar[0] etc. Without a
trade, i.e. outside a TMF or trade enumeration loop, those variables have no meaning.

Type:
var

TradeLots
Number of lots.

TradeExitTime
Trade exit time in bars (the life time plus 1), or 0 for no time limit.

TradeTime
The number of bars since the trade was entered (for pending trades) or opened (for open trades).

TradeBarOpen
Number of the opening bar of the trade. For pending trades, the number of the bar at which the trade was entered. Can
be set to the current bar number (Bar) for resetting the wait time of pending trades. After the trade is opened, this
number must not be changed anymore.

TradeBarClose
Number of the closing bar of the trade, or 0 if the trade is still open.

TradeContract
The contract type for options and futures, a combination of PUT, CALL, EUROPEAN, BINARY, or FUTURE.

TradeID
Trade identifier number, identical to the ticket number in the broker platform, or 0 when the trade was not yet opened.

Type:
int, read/only

TradeIsShort
Boolean expression. Is true when the trade was entered with enterShort.

TradeIsLong
Is true when the trade was entered with enterLong.

TradeIsContract
Is true when the trade is an option or future contract.

TradeIsPhantom
Is true when the trade was entered in phantom mode for virtual hedging or for equity curve trading.

TradeIsPool
Is true for a pool trade for virtual hedging.

299
TradeIsVirtual
Is true for a phantom trade for virtual hedging.

TradeIsPending
Is true when the trade was not yet opened, f.i. because it was just entered or its Entry limit was not yet met.

TradeIsOpen
Is true when the trade was opened and is not yet closed.

TradeIsClosed
Is true when the trade was closed. The TradeProfit variable contains the final result of the trade.

TradeIsNewBar
Is true in a TMF at the first tick of a new bar.

TradeIsEntry
Is true in a TMF when the position is about to be opened because its Entry limit was just hit.

TradeIsStop
Is true in a TMF when the the position is about to be closed because its Stop limit was just hit.

TradeIsProfit
Is true in a TMF when the position is about to be closed because its TakeProfit limit was just hit.

Type:
bool, read/only

TradeAlgo
The algorithm identifier of the trade. Also set to Algo during a TMF.

TradeAsset
The asset name of the trade. Also set to Asset during a TMF or a trade loop.

Type:
string, read/only

ThisTrade
The TRADE* pointer. All trade information can be accessed through this pointer. The TRADE struct is defined in
include\trading.h. Its members are the above trade variables, redefined to easier-to-memorize names
in include\variables.h.

Remarks:

• All trade variables listed above are only valid inside a TMF or a in trade enumeration loop. Otherwise they
require ThisTrade to be set to a valid TRADE* pointer by script. This switches all trade variables to that trade.
• Asset specific variables, such as PIP, PIPCost etc. are automatically set to the trade asset during a TMF or a trade
enumeration loop, unless you explicitely prevent this by switching to a different asset. For using algorithm specific
variables in a TMF, such as trade statistics or AlgoVar, the algo function must be called in the TMF (see example).
• Most trade variables are of type float. They are normally automatically converted to var in lite-C, exept for functions
that have no fixed variable type such as printf(). !! For printing trade variables, place a (var) before them in
the printf parameter list to convert them to var.
• The TICKS flag is often required for testing TMFs that use intrabar prices or enter / exit trades.
300
• TMFs can open new trades with enterLong/Short. However be careful when assigning them the same TMF: uncorrect
entry conditions can then lead to an endless loop of entering new trades.
• Entering and exiting trades by returning 1 or 2 is attempted even during the weekend or holidays when price quotes
arrive and Weekend is at 2 or below.
• Exit a trade by returning 1, rather than calling exitTrade.
• For tasks that are not related to a certain trade, using a tick function is preferable to a TMF.
• Functions that affect the program flow - like loop, optimize, etc. - can not be called in a TMF.
• Data series can not be created in a TMF, and indicators that create data series can not be called; however series and
indicator values can be evaluated through global variables or asset/algo specific variables. The current candle is
incomplete within a TMF, so its range and height is normally smaller than the other candles. The price functions return
different values because they get their open, high, and low prices from the incomplete candle.

Examples (see also trade loops):


// TMF that moves the stop level in a special way
int TrailingStopLong()
{
// adjust the stop only when the trade is in profit.
if(TradeIsOpen and TradeProfit > 0)
// place the stop at the lowest bottom of the last 3 candles
TradeStopLimit = max(TradeStopLimit,LL(3));
// plot a line to make the stop visible in the chart
plot("Stop",TradeStopLimit,MINV,BLACK);
// return 0 to let Zorro check the stop/profit limits
return 0;
}

function run()
{
set(TICKS); // normally needed for TMF
...
enterLong(TrailingStopLong);
}
// TMF that opens an opposite trade when stopped out,
// and opens a new trade when profit target is reached (Zorro 1.22 and above)
int ReverseAtStop()
{
if(TradeIsStop) { // stop loss hit?
if(TradeIsShort)
enterLong(ReverseAtStop); // enter opposite trade
else
enterShort(ReverseAtStop);
}
if(TradeIsProfit) { // profit target hit?
if(TradeIsShort)
enterShort(ReverseAtStop); // enter same trade again
else
enterLong(ReverseAtStop);
}
// call the TMF at stop loss / profit target only
return 16;
}

function run()
{
set(TICKS); // normally needed for TMF
Weekend = 3; // don't run TMF at weekend

Stop = 100*PIP;
TakeProfit = 100*PIP;
if(Bar == LookBack) // enter the first trade directly at the first bar
enterLong(ReverseAtStop);
}
// TMF with parameters, for a Chandelier Stop
int Chandelier(var TimePeriod,var Multiplier)
{
if(TradeIsLong)
TradeStopLimit = max(TradeStopLimit,ChandelierLong(TimePeriod,Multiplier));
else
TradeStopLimit = min(TradeStopLimit,ChandelierShort(TimePeriod,Multiplier));
return 8; // only update once per bar
}

function run()
{
...
if(LongSignal) {
Stop = ChandelierLong(22,3);
301
enterLong(Chandelier,22,3);
}
...
}

optimize (var start, var min, var max, var step, var bias) : var
Optimizes a strategy parameter belonging to the current asset and algo. In [Test] or [Trade] mode, this function
returns either the previously optimized parameter value, or - if the PARAMETERS mode is not set - it just
returns start. In [Train] mode the function attempts to find the best parameter value between min and max.

For this the simulation runs through many cycles. For every cycle the optimize function generates a different value
between min and max. At the end of each cycle, the number of winning trades (bright blue bars below), losing trades
(dark blue) and the parameter's performance (red) are plotted in a chart. The best parameter value is then selected and
internally stored.

The chart above is a typical result of an optimize call in [Train] mode. The strategy parameter to be optimized here is
used for a buy/sell threshold. It varies from 0.200 to 3.00 in constant steps of 0.1. We have a small performance peak
around 0.90 and the highest peak at 1.60. The best parameter value however is 1.70, with a performance
of 2.7 and 125 winning trades. Although its adjacent bars are higher, the optimizer selects the center of the broad peak
that represents not the most profitable, but the most robust parameter value. Using the value of a very narrow peak or
of a single line bears the danger of overfitting the strategy. Once the best parameter value is determined, the optimization
process continues for the next parameters.

By using several optimize calls in the code, any number of parameters can be optimized at the same time. The more
parameters are optimized, the more often the script must be run, and the longer will it take. The best value of one
parameter is automatically used for optimizing the next parameters. At the end of a multi parameter optimization, the
best parameters are displayed in the message window in the order of their optimize calls, and they are stored in the
parameter file belonging to the strategy. This file is automatically loaded when the PARAMETERS mode is set.

If the script trades multiple assets or algorithms and contains loop calls, a separate parameter set for each loop is
optimized.

The parameter values are stored in a text file in Data\scriptname.par. The file can be opened and examined with SED.
Each line begins with the asset name and (if any) algorithm identifier of the parameter set. The values are listed in the
order of the optimize calls in the script. A '+' before a parameter value indicates that the best value is at the end of its
parameter range, so it could make sense to modify the range for better results. .

Parameters:
start Default value of the parameter, returned when the PARAMETERS mode is not set in [Test] or [Trade]
mode. This should be the estimated best value of the parameter, normally in the middle of its range.
Must be >= 0.

302
min, max Parameter range, given by its minimum and maximum values (>= 0).

step Optional step width. When 0 or omitted, 10% is added to the parameter for every optimization step.
When a positive step value is given, add this constant value every step; when a negative step value is
given, add its magnitude as a percentage every step (f.i. -20 adds 20% per step). Adding a percentage
instead of a constant value gives a better step resolution at the begin of the range. For best results,
select the step value so that every parameter has about 10..30 optimization steps. The maximum
number of steps (MAX_STEPS) is defined in trading.h.

bias Optional preference of low or high parameter values. When 0 or omitted, select the optimal parameter.
When > 0, prefer higher parameter values even when their rank is lower by the given bias in percent.
When < 0, prefer lower parmeter values even when their rank is lower by abs(bias) in percent.
Preferring values from the low or high range can make sense f.i. for setting a Stop as tight as possible
even when its value is not optimal.

Returns
Current, default, or optimized parameter value, depending on training or test mode.

Remarks:

• The parameter range must be positive, i.e. both min and max must not be less than 0. If a strategy parameter has to
be negative, multiply it with -1 or subtract a constant value to get a positive range for the optimize function. A
negative start value indicates that this optimization step can be skipped (f.i. for quickly excluding a parameter from the
optimization process when its start value is already optimal). The absolute value of start is then returned.
• The performance is calculated in the objective function in include\default.c. This function can be replaced by any
user provided function in the script that is named objective. This way, other performance values can be used for
optimizing parameters, for instance the profit/drawdown ratio, or the gross profit, or the Sharpe ratio (see examples
below). The objective function can access all trade statistics for determining the performance value. It also has
access to the system's PERFORMANCE struct (ThisPerformance, struct defined in trading.h) that contains
performance parameters. By default, the function calculates the pessimistic return ratio (PRR) with reduced influence
of the biggest win and the biggest loss.
• Different optimization modes can be set up with the Optimize variable.
• Returned parameter values are not necessarily a multiple of the step width. They can lie anywhere in the optimization
range when PEAK mode is not set. Use the round() function when optimizing an integer value.
• The bias value can be used when a high or a low value of the parameter is preferred even though another value gives
a slightly better objective result. For instance, you might prefer low stop distances to reduce the risk even when higher
stop distances give slightly better results.
• Every optimize call must be executed once per bar and per portfolio component. The number and order
of optimize calls must not change between test and training or from one run to the next. In a portfolio system, the
asset or algo must be set before calling optimize, for making sure that every portfolio component gets its own set of
parameters. No optimize calls can be placed in a TMF or tick function.
• Parameters that affect the price data array, such as BarPeriod, require special handling for optimization. They can not
be changed during the simulation, so they can not be walk forward optimized; for a walk forward test they must be
optimized in a separate training run before all other parameters. They can also not be read from the .par file, so their
optimized value must be entered directly in the script as start parameter of the optimize call before further optimizing,
testing, or trading the strategy.
• When optimizing several parameters at the same time, put the most important parameters first. They are optimized in
the order they appear in the code. For optimizing trade entry and exit parameters, optimize entry parameters first and
exit parameters afterwards. When your exit system is complex, optimize entry parameters with a simplified exit such
as a simple stop. For using a simple exit while the entry parameters are optimized, evaluate the current parameter
number (ParCycle), f.i. if(Train && ParCycle <= NumParameters-3) setExitSimple();. When parameters affect each
other - for instance, two time periods for a crossover of two moving averages - optimize only one time period, and
optimize a multiplication factor for the other (see the example below). This way the other time period also changes
when the first one is optimized.
• Special cases when RULES and PARAMETERS have to be optimized at the same time are described
under Training.
• The current optimization cycle can be evaluated with the ParCycle and StepCycle variables.
• Optimization can be aborted by setting StepNext to 0. This will end optimization after the current cycle.
• When optimizing a time period of an indicator, make sure that LookBack is set at least to the maximum period plus
the UnstablePeriod (f.i. LookBack = max(LookBack,300);). Otherwise LookBack will automatically adapt to the

303
period of the current parameter value. This can affect the result in an unexpected way because the back test period
would then get shorter when the time period gets longer.
• For avoiding randomness, slippage is not simulated during optimization. Rollover is simulated; it can have a large
effect on the result and produce long/short asymmetry in parameters and profit factors. For eliminating rollover
asymmetry, set RollLong/Short to 0 after calling asset in training mode. For eliminating trend
asymmetry, detrend the price curve in training mode. Even without rollover and trend, some commodities and stocks
still require different parameter sets for long and short trades due to inherent asymmetries in their price curves (long
trades are often dominant).
• For optimizing parameters several times, use NumOptCycles.
• For optimizing individual parameter values, use an array and optimize its index, f.i.: int Periods[5] = { 10, 20, 50, 100,
200 }; int Period = Periods[round(optimize(1,0,4,1),1)];
• For optimizing any combination of two parameters - the "brute force" optimization method, see example below -
optimize a single number that is the product of two array sizes. Divide it then back into two separate indices to two
parameter arrays. For instance, let optimize return a value from 0 to 99, and use its first digit (((int)val)/10) as an
index for the first array, and its second (((int)val)%10) for the second array.
• For optimizing a global parameter without the optimize function and outside of all WFO cycles,
use NumTotalCycles for several training/test runs, use TotalCycle for setting the parameter value, and use
the evaluate function to plot a histogram (see example there).
• In portfolio strategies, parameters are optimized for all assets separately. This means when a loop is used,
all optimize calls must be inside that loop. If this is not desired, but different assets must still be used, enumerate them
in a simple for loop, f.i. for(i=0; Name=Assets[i]; i++) { ....
• When the strategy opens no trades in [Train] mode - f.i. when no trade signal is generated or when Margin or Lots is
zero - the start values are stored in the parameter file.
• The daily balance curve of every optimize step is exported in a file for further evaluation when a Curves file name
is given. A curve is only exported when the objective function returns a nonzero value.

Examples:
// trend trading with Moving Averages and optimized parameters
function run()
{
set(PARAMETERS);
var TimeCycle = optimize(30,10,100,5);
var TimeFactor = optimize(3,1,5);
// allow 3% tolerance for preferring low stop distances
Stop = ATR(10) * optimize(3,1,10,0.5,-3);
// for optimizing time periods, set the LookBack variable to the
// maximum possible value (here, TimeCycle 100 * TimeFactor 5)
LookBack = 100*5;

vars Price = series(price(0));


vars MA1 = series(SMA(Price,TimeCycle));
vars MA2 = series(SMA(Price,TimeCycle*TimeFactor));
plot("MA1",*MA1,0,BLUE);
plot("MA2",*MA2,0,BLUE);

if(crossOver(MA1,MA2))
enterLong();
else if(crossUnder(MA1,MA2))
enterShort();
}
// brute force optimization
function run()
{
set(PARAMETERS);
int PeriodsEMA1[4] = { 5, 10, 15, 20 };
int PeriodsEMA2[3] = { 100, 150, 200 };
LookBack = 250;

int Index = optimize(1,1,4*3,1) - 1;


int PeriodEMA1 = PeriodsEMA1[Index%4];
int PeriodEMA2 = PeriodsEMA2[Index/4];

vars Price = series(price(0));


vars EMA1 = series(EMA(Price,PeriodEMA1));
vars EMA2 = series(EMA(Price,PeriodEMA2));

if(crossOver(EMA1,EMA2))
enterLong();
else if(crossUnder(EMA1,EMA2))
enterShort();
}
// alternative objective function based on Calmar ratio
var objective()

304
{
return(ThisPerformance.vWin-ThisPerformance.vLoss)/max(1,ThisPerformance.vDrawDown);
}

// alternative objective function based on Sharpe ratio


var objective()
{
if(!NumWinTotal && !NumLossTotal) return 0.;
return ThisPerformance.vMean/ThisPerformance.vStdDev;
}

// alternative objective function for binary options


var objective()
{
return ((var)(NumWinLong+NumWinShort))/max(1,NumLossLong+NumLossShort);
}

loop(void* p1, void* p2, ... ) : void*


loop(Assets) : void*
This function takes an arbitrary number of pointers, strings, or asset names as parameters, and returns the first
pointer on the first call, the next pointer on the next call and so on. After the last pointer, loop returns 0. The function
is normally used to select assets and trade algorithms for a portfolio strategy.

Returns
p1 on the first call, p2 on the next call, and so on. The last call returns 0.

Parameters:
p1, p2 ... Pointers or strings; normally a string with an asset or algo name, or a trade function pointer.

Assets Predefined array with all asset names; to be used for looping through all assets in the asset list.

Remarks:

• In [Train] mode, the loop function is handled in a special way. A separate simulation cycle is executed for
every loop parameter. This way parameters, rules, and factors are generated separately for every strategy
component. The algo function must be used to identify the strategy component in the parameter, factor, or rule file
(see the example in Workshop 6). If this is not desired, but different assets must still be used, enumerate them in a
simple for loop, f.i. for(i=0; Name=Assets[i]; i++) { ....
• Due to the special loop handling in [Train] mode, the number of loops is limited to one loop or two nested loops, used
for combining different assets with different trade algorithms (see example). The loop function must be executed in
every run.
• The predefined variables LoopNum1 and LoopNum2 contain the current parameter number in the first and second
loop, starting with 1; the variables Loop1 and Loop2 contain the current return values from the first and second loop.
• Use a static or global variable inside a loop only when its content is independent of the asset or algo. Otherwise use
a AlgoVar or a series.
• Use loop(Assets) together with individual asset lists for externally specifying the traded assets.

Example (see also Workshop 6):


// portfolio strategy with 3 assets and 3 trade functions

function tradeTrendLong()
{
algo("TRL");
...
}

function tradeTrendShort()
{
algo("TRS");
...

305
}

function tradeBollinger()
{
algo("BOL");
...
}

function tradeFunc(); // empty function pointer

function run()
{
while(loop(
"EUR/USD",
"USD/CHF",
"GBP/USD")) // loop through 3 assets
while(tradeFunc = loop(
tradeTrendLong,
tradeTrendShort,
tradeBollinger)) // and 3 different trade algorithms
{
asset(Loop1); // select asset
tradeFunc(); // call the trade function
}
}

Status flags
Zorro's current status can be checked through status flags that can be either on (active) or off (not active). The
following function is used for checking a status flag:

is(int flag): int


Returns nonzero resp. true when the given flag (or any flag of the given flag combination) is set, otherwise zero
resp. false. Flags can be combined with the '+' or '|' operator. For instance, is(TRAINMODE | TESTMODE) returns
nonzero when either the TRAINMODE or the TESTMODE is active.

The following status flags are available:

TESTMODE
TRAINMODE
TRADEMODE
The script is running in [Test], [Train], resp. [Trade] mode.

DEMO
The demo account is selected through the [Account] scrollbox.

SPONSORED
The script is running on a Zorro S version.

CHANGED
The [Script] or [Asset] scrollboxes have been changed since the last script run, or the [Edit] button has been clicked.
The sliders are then set back to their default positions.

AFFIRMED
The [Ok] button of a nonmodal message box has been clicked.

306
INITRUN
Initial run of the simulation before the price data is loaded and before the log file is opened. Can be used to initialize
global and static variables. System variables that are not yet known - f.i. variables that depend on the asset and
simulation period - are at 0 during the initial run.

FIRSTRUN
First run of the simulation with valid price data and system variables. Normally follows the INITRUN.

FIRSTINITRUN
First initial run of the script, in the case of multiple simulation runs due to training cycles or NumTotalCycles.
While INITRUN and FIRSTRUN is set at the start of any simulation run, FIRSTINITRUN is only set at the really first run.

EXITRUN
Last run of the simulation. All trades are closed. Can be used to calculate results, aside from
the evaluate or objective functions.

RUNNING
The script is currently running in the simulation. Script functions can also be executed outside a simulation run f.i. by
clicking on [Result] or on a panel button.

TRADING
The script has entered or exited at least one position.

LOOKBACK
The script is currently in the lookback period, either at the begin of the simulation, or due to a RECALCULATE run.

NEWDAY
The current bar is the first bar of the current day.

FACTORS
The script is currently generating capital allocation factors in [Train] mode.

RULES
The script is currently generating trade rules in [Train] mode. If neither FACTORS nor RULES is set in [Train] mode,
the script is generating parameters.

COMMAND
The Zorro instance was started through the command line.

EXE
The script is an executable (*.x).

PORTFOLIO
The script called the loop function.

ASSETS
The script called the asset function.

SELECTED
The current asset is the same as selected in the [Asset] scrollbox.
307
PLOTSTATS
The script called commands that plot a histogram rather than a price chart.

Some macros for often-used flags have been defined for convenience:

Train
The same as is(TRAINMODE).

Test
The same as is(TESTMODE).

ReTrain
Process for updating parameters or rules, started by clicking [Train] while live trading (Zorro S only).

ReTest
Process started by clicking [Test] while live trading, f.i. for comparing the live trading results with backtest results of the
same period (Zorro S only).

Example:
function run()
{
if(is(TRAINMODE)) set(SKIP3|TICKS);
if(is(TESTMODE)) set(LOGFILE);
...
}

Mode flags
Zorro's behavior can be set up through mode flags - that are "switches" that can be either set to on (active) or off (not
active). Mode flags can be set, reset, or tested with the following functions:

set(int flag)
Sets the given flag resp. all flags of the given flag combination. Flags can be combined with the '+' or '|' operator. For
instance, set(PARAMETERS+TICKS); activates the PARAMETERS flag and the TICKS flag.

reset(int flag)
Resets the given flag resp. all flags of the given flag combination.

mode(int flag) : int


Returns nonzero resp. true when the given flag (or any flag of the given flag combination) is set, otherwise zero
resp. false.

The following mode flags are available:

308
SKIP1
SKIP2
SKIP3
Do not enter orders in the first, second, or third of every 3 weeks of the historical price data (the period can be set up
with DataSkip). This can be used to separate out of sample price data from training data while still covering the same
time period.

PEEK
Allow peeking in the future through negative price function offsets; [Test] and [Train] mode only. Sometimes required
for advise functions and price difference analysis.

OPENEND
Do not close any open trades at the end of a simulation cycle; ignore their results. This has two purposes. In [Test]
mode it eliminates the effect of prematurely closed trades on the performance report. In [Train] mode it prevents that
rules are affected by results of prematurely closed trades.

ALLCYCLES
Do not reset the statistics values inside the STATUS structs when a new sample cycle is started. The Long/Short
statistics and the portfolio analysis are then the result of all sample cycles so far. This flag is always set in [Train]
mode.

TICKS
Tick-precise simulation. Not only the Open, Close, High, and Low of a bar, but also the real price curve inside a bar is
used for calculating entry, exit, and profit of trades. TMFs are run on every tick in the simulation, instead of only once
per bar. This flag gives a more accurate simulation result, but also requires more time for a simulation cycle, and
allocates more memory.
If this flag is not set, an intra-bar approximation is used for simulating entry and exit. In this approximation, a stop loss
is always triggered before a profit or trail target, and trades closed by trade functions are sold at the open price of the
next bar. This causes a less accurate simulation, which is however sufficient in most cases. TICKS should be used for
better precision when many trades enter and exit within the same bar, when stop loss or takeprofit distances are small,
or when tick functions or TMFs are used.

FAST
Fast simulation; to be combined with TICKS. Ticks are then sorted by asset and by trade before executing them in
a tick function or TMF. This can remarkably speed up backtests in TICKS mode, but implies some restrictions, as ticks
can now arrive in a different order than their time stamps. In FAST mode TMFs must not use information from other
trades or evaluate trade statistics, otherwise peeking bias can affect the result (for instance when a trade is closed
by the TMF dependent on the win/loss situation of another trade). For the same reason, tick functions must not evaluate
prices of other assets. Virtual hedging is simulated in FAST mode with restricted accuracy, so it's recommended to
disable it in the simulation whe results strongly depend on tick order.

LEAN
Use compressed historical data. marketVal and marketVol are not available, and the open/close price of a bar is
approximated by the center point of its first and last tick (except for EOD historical data). Set this flag when market
volume is not needed or when the historical data has a much higher resolution than one bar period, f.i. M1 data with 1-
hour bars. This flag reduces the memory requirement for backtests by 50%. It must be set before calling asset().

RECALCULATE
Run a full LookBack period at the begin of every WFO cycle in [Test] mode. Discard the content of all series at
lookback start, and recalculate them from the parameters and rules of the new cycle. This increases the test time, but
produces a slightly more realistic test by simulating the start of a new trade session at every WFO cycle.

309
PRELOAD
Use Zorro's historical price data for the LookBack period at [Trade] start, rather than loading all price data from the
broker's price server. Only the data span between the end of the history and the current time is loaded from the server.
This flag is useful for reducing the trading start time of a system, for overcoming history limitations of broker servers, or
for extremely long lookback periods. Recent price history files from the current and the last year must be available; use
the Download script for getting the most recent data. Alternatively, data can be downloaded at trading start from
different sources such as Yahoo or Quandl. Setting this flag also suppresses the warning message in [Test] mode when
the lookback period is too long for a normal trading session, and causes prices to be loaded from the broker even outside
market hours.

STEPWISE
Single step through a session in [Test] mode. A click on [Step] moves one bar forward. [Skip] moves to the next opening
or closing a position. The current chart and trade status will be displayed on every step in a browser window. For details
see debugging.

PARAMETERS
[Train] mode: generate strategy parameters with optimize calls and store them in Data/*.par. This flag must be set
before calling optimize. Otherwise parameters are not generated in the training run, but loaded from previously
generated *.par files.
[Test] / [Trade] mode: load optimized parameters. If this flag is not set, only the default parameters from
the optimize calls are used.

FACTORS
[Train] mode: generate OptimalF capital allocation factors and store them in Data/*.fac. If this flag is not set, all
OptimalF factors are 1.
[Test] / [Trade] mode: load OptimalF factors for allocating capital. If this flag is not set, all OptimalF factors are 1.
OptimalF factors can alternatively be copied from the performance report. For this, set Margin and Risk to 0 and
and Lots to 1, then do a [Test] run. Save the portfolio part of the performance report under the name of a .fac file.

RULES
[Train] mode: use the advise machine learning functions to generate trade rules and store them in Data/*.c. This flag
must be set before calling advise. Otherwise rules are not generated, but loaded from previously generated *.c files.
[Test] / [Trade] mode: load trade rules and use them in the advise functions. If this flag is not set, the advise functions
always return 100.

MARGINLIMIT
Only when Margin is used for determining the trade volume: Don't enter a trade when even the minimum amount of
1 Lot exceeds twice the given Margin value. Also don't enter a new trade when the trade margin plus the trade risk
exceeds the available margin left in the account. Trades skipped due to too-high risk or too-low account capital are
indicated in the log. This flag has no effect in training mode or for phantom trades that are always executed regardless
of the margin.

RISKLIMIT
Don't enter a trade when even with the minimum amount of 1 Lot, the trade risk is still higher than twice the than the
allowed Risk. Also don't enter a new trade when the total risk of all open trades exceeds the available margin left in the
account. Setting this flag can reduce profit, as trades with a high stop loss distance are often profitable trades. Trades
skipped due to too-high risk or too-low account are indicated in the log. This flag has no effect in training mode or
for phantom trades that are always executed regardless of the risk.

ACCUMULATE
Accumulate the Margin of trades skipped by MARGINLIMIT or RISKLIMIT, until the accumulated margin is high
enough to overcome the limits. The trade is then executed and the accumulated margin is reset. This causes trades to
be entered - although less frequently - that would otherwise require a higher margin for ever being executed. This flag

310
has no effect in training mode.

BINARY
Simulate binary options for training and testing. In this mode the trade profit is not proportional to the price difference
between entry and exit, but determined by the WinPayout or LossPayout of the selected asset. Slippage, rollover, and
commission are ignored for binary trades. Spread is 0 by default in binary mode, but can be set to a nonzero value for
simulating an additional disadvantage. The trade time can be set through ExitTime. Stop loss and profit targets are
normally not set for binary trades, but can be used for betting on negative or positive excursions in special binary modes.

NFA
Observe NFA Compliance Rule 2-43(b); often required for US based accounts or US based brokers (such as IB). Do
not place a "safety net" hard stop loss limit when sending the trade to the broker (this does not affect the Stop parameter
that is handled by software). Do not close broker positions, open a new position in opposite direction instead; do not
hedge broker positions. Zorro will handle NFA compliant trades in a transparent way so that the user and the script does
not need to care about the NFA rule.
This flag is automatically set in [Trade] mode when the selected account has a nonzero NFA parameter. Do not set
this flag for non-NFA compliant accounts, as positions then could not be closed. You can find out if your account is NFA
compliant by manually opening a long and short position of the same asset in the broker platform. If two positions are
then really open, your account is not NFA compliant. If the short position cancels the long one, your account is NFA
compliant. Note that MT4 accounts are normally not NFA compliant even when the account owner is US citizen.

EXE
Compile the script to an executable in .x file format. Useful for distributing strategies without revealing the source
code. Zorro S required.

LOGFILE
Generate the following files in the Log folder, dependent on [Test], [Train], or [Trade] mode: a *.log file containing all
trade events and messages; a *.dbl file containing a var array of the daily balance or equity values from the backtest;
a *.csv file containing a record of all closed trades for further evaluation or the tax declaration; a *.htm file containing
the last trade status; and a *.htm file with parameter charts from the training process. The file names are composed
from the script and asset name. This flag is always set in [Trade] mode.

BALANCE
Display the balance curve in the chart, and store balance rather than equity values in the *.dbl array resp. in
the Curves file.

TESTNOW
Run a test immediately after training, without clicking [Test]. Only when multiple cores are not used and when test and
training use similar mode flags. If the simulation is repeated multiple times (NumTotalCycles), TESTNOW causes the
price curve to be generated anew at the begin of every cycle, which is useful for testing different bar offsets or detrend
modes. Since the test run uses the settings from the previous training, its result can differ from a normal test. This flag
must be set before calling asset().

PLOTNOW
Plot a chart immediately after testing, without clicking [Result]. Automatically set when the script plots a histogram
with plotBar.

PLOTLONG
Begin the chart already with the LookBack period, and always plot a chart in [Trade] mode. Otherwise the chart begins
only a few bars before the test period or when trades were opened.

311
Example:
function run()
{
if(is(TRAINMODE)) set(SKIP3);
if(is(TESTMODE)) set(SKIP1+SKIP2+TICKS+FAST+LOGFILE);
...
}

order (int type): int


User-supplied function that is called by the trade engine every time when Zorro enters or exits a trade. Can be used to
access external broker APIs, control external trade programs by sending keys, or just pop up a message for manually
entering or exiting a trade.

Parameters:
type - 1 for entering, 2 for exiting a trade.

Returns:
0 when the trade could not be opened or closed, 1 otherwise.

Remarks:

• For implementing a complete broker interface instead of only a buy/sell function, a broker plugin can be written.
• All trade variables f.i. for determining the asset and algo are available in the order function.

Example:
int order(int type)
{
string bs = "Buy";
if(type == 2) bs = "Sell";
string ls = "Long";
if(TradeIsShort) ls = "Short";
play("alert.wav");
return msg("%s %s!",bs,ls);
}

login (int mode): int


Logs in or out to the broker API..

Parameters:
mode - 0 for checking the login state, 1 for logging in, 2 for logging out, 4 for completely closing the broker API.

Returns:
1 when Zorro is logged in, 0 otherwise.

Remarks:

• For recovering from a broker API crash, call login(4), then login(1).
• Zorro normally logs in and out automatically, so this function is for special purposes only.

312
brokerCommand (int Command, int Parameter): var
brokerCommand (int Command, string Text)
brokerCommand (int Command, var* Parameters)
Sets various broker plugin parameters or retrieves asset specific data for special purposes in a script. The functions can
be optionally provided by the broker plugin; they return 0 when they are not supported or no broker connection is
established. The number and types of parameters depend on Command. The following commands are predefined
(in functions.h):

Text /
Command Returns
Parameter

GET_TIME 0 Last incoming quote time in the MT4 server time zone, OLE DATE
format.

GET_DIGITS Symbol Number of relevant digits after decimal point in the price quotes.

GET_STOPLEVEL Symbol The 'safety net' stop level of the last order (non-NFA accounts only).

GET_STARTING Symbol Starting date, usually for futures (OLE DATE format).

GET_EXPIRATION Symbol Expiration date, usually for futures (OLE DATE format).

GET_TRADEALLOWED Symbol Trade is allowed for the asset.

GET_MINLOT Symbol Minimum permitted amount of a lot.

GET_LOTSTEP Symbol Step for changing lots.

GET_MAXLOT Symbol Maximum permitted amount of a lot.

GET_MARGININIT Symbol Initial margin requirements for 1 lot.

GET_MARGINMAINTAIN Symbol Margin to maintain open positions calculated for 1 lot.

GET_MARGINHEDGED Symbol Hedged margin calculated for 1 lot.

GET_MARGINREQUIRED Symbol Free margin required to open 1 lot for buying.

GET_COMPLIANCE 0 Account restrictions. 1 - no restrictions; 2 - no hedging; 3 - FIFO


compliance; 4 - no stop loss; 8 - trades must not be closed; 15 - full
NFA compliant account.

GET_NTRADES 0 Number of all open trades in this account.

GET_POSITION Symbol Net open contracts of the given symbol; negative values for short
positions.

GET_ACCOUNT String Fills the string with the account name.

GET_BOOKASKS Symbol Ask volume in the order book, in contracts.

GET_BOOKBIDS Symbol Bid volume in the order book.

GET_BOOKPRICE Rank Price quote for a given price rank in the order book, starting with 1 for
the lowest price quote. Call BOOKASKS / BOOKBIDS before for
setting the asset and quote type.

313
GET_BOOKVOL Rank Volume for a given price rank in the order book, starting with 1 for the
lowest price quote.

GET_OPTIONS CONTRACT* Get the option chain of the underlying set with SET_SYMBOL.

GET_FUTURES CONTRACT* Get the futures chain of the underlying set with SET_SYMBOL.

GET_UNDERLYING 0 Get the underlying price for the previous brokerAsset call when the
asset was an option.

GET_DELAY 0 Return delay in ms between commands sent to the broker.

SET_DELAY Time in ms Set delay in ms between commands sent to the broker.

GET_WAIT 0 Return maximum wait time for confirmation after a command was sent
to the broker, in ms.

SET_WAIT Time in ms Set maximum wait time in ms for confirmation after a command was
sent to the broker.

SET_LOCK 0, 1 Set (1) or release (0) a lock for synchonizing commands among several
connected Zorros (see remarks). Returns 0 if the lock was already set,
otherwise 1.

SET_SLIPPAGE Pips Set the maximum allowed slippage (default = 5 pips) in adverse
direction for subsequently opening or closing trades. Higher allowed
slippage causes less requotes, but allows trades to be entered at a
worse price. Note that the allowed slippage is not guaranteed; trades
can be still entered at higher slippage dependent on the brokerm,
market access method, and server setup.

SET_SYMBOL Symbol Set the symbol for subsequent commands.

SET_MAGIC Number Set a "magic number" for identifying trades opened by the current script.

SET_MULTIPLIER Number Set the multiplier for retrieving option and future chains.

SET_CLASS Name Sets the name of the trading class. Call this before setting the symbol
for option and future chains; use an empty string ("") for retrieving all
trading classes.

SET_LIMIT Price* pointer Sets an entry limit for the next order. The next trade will be filled at or
better than the limit price, or not at all.

SET_ORDERTEXT Text Set an order comment (255 characters max). Often required for special
MT4 orders, f.i. for trading binary options.

SET_PATCH Patch value Work around broker API issues by calculating the following API
parameters on the Zorro side:
1 - Balance and Equity; 2 - TradeProfit of open trades;
4 - TradeProfit of all trades; 8 - Server time. Numbers can be
combined by addition.

SET_COMMENT Text Display the given text (255 characters max) in the broker platform,
usually at the top of the chart window.

DO_EXERCISE Lots Exercise the given number of contracts of the option type set
with SET_SYMBOL.

PLOT_HLINE Parameters Place a horizontal line at a given price in the chart window of the broker
platform. 5 parameters are used: P[0] = always 0; P[1] = price

314
position; P[2] = line color; P[3] = line width; P[4] = line style. Return the
identfier number of the line.

PLOT_TEXT Parameters Place a text element at a price position at the right border of the chart
window. 5 parameters are used: P[0] = always 0; P[1] = price
position; P[2] = text color; P[3] = text size in points. Return the identfier
number of the text element.

PLOT_MOVE Parameters Move the graphical element with the identifier given by P[0] to the
horizontal position given by P[1] and the vertical position given by P[2].

PLOT_STRING Text Set or modify the text content for the last created or moved text element.

PLOT_REMOVE Identifier Delete the graphical element with the given identifier.

PLOT_REMOVEALL 0 Remove all graphical elements from the chart.

Parameters:
Command Input, one of the commands from the list above.

Parameter Input, parameter or data to the command.

Parameters Input, array of up to 8 vars for commands with multiple parameters.

Symbol Broker symbol of an asset (see Symbol). Often, but not always identical to the asset name.

Text Input, for commands that require a text string.

Returns:
0 when the command is not supported by the broker plugin, otherwise the data to be retrieved.

Remarks:

• The command SET_PATCH is always supported. Which of the other commands are supported can be found on the
description page of the broker plugin.
• User defined broker commands for special broker API settings should be defined with a command number > 256.
Numbers below 256 are reserved for predefined commands.
• The SET_PATCH command lets Zorro estimate account and trade parameters, rather than retrieving them from the
broker API. This is required when the broker API calculates those parameters wrong, as is the case for the account
balance, equity, and open trade profit by the FXCM API. Replacing those parameters has no effect on trading, but on
the displayed equity and profit values on the Zorro panel. The estimated equity includes only the profit by trades of the
current strategy, and thus is different to the account equity when other systems trade on the same account. The
estimated trade result can also differ to the real result due to slippage and rollover.
• The SET_LOCK command can be used similar to a Windows Mutex for preventing the interruption of a command
sequence, f.i. when setting order parameters before sending the order. Before the sequence, while(0 ==
brokerCommand(SET_LOCK,1)) wait(5); waits until the lock was released and sets it then;
brokerCommand(SET_LOCK,0) releases the lock after sending the command sequence.

315
Analysis functions

Indicators
A library of functions for filtering and retrieving information of price curves, from traditional technical analysis up to
more advanced transformation and statistics function: moving averages, oscillators, bands, momentum, strength
indices, linear regression, Hilbert transforms, Ehlers indicators, and spectral analysis.

The indicators are listed in alphabetical order. Traditional indicators use the TA-Lib indicator library by Mario Fortier
(www.ta-lib.org) that has established itself as a standard. Information about the usage, the algorithms, and the source
code of the TA-Lib indicators can be found online at www.tadoc.org; the source is also included in
the Zorro/Source folder. The source code of most other indicators and analysis functions can be found
in Zorro/include/indicators.c. Spectral filters and amplitude/frequency analysis functions are listed in the spectral
library. Classical candle patterns can be found in the pattern library.

AC(vars Data): var


Accelerator Oscillator; the difference of the AO indicator (see below) and its 5-bar simple moving average (SMA).
Believed to indicate acceleration and deceleration of a 'market driving force' (whatever that means). For Data normally
a MedPrice or price series is used. Source code in indicators.c.

ADO(): var
Accumulation/Distribution Oscillator: ((Close-Low)-(High-Close))/(High-Low). Ranges from -1 when the close is the
low of the bar, to +1 when it's the high. Supposed to gauge supply and demand by determining whether traders are
generally "accumulating" (buying) or "distributing" (selling). This indicator was published in many individual variants to
the formula, but none of them seems any better than the other. Uses the current asset price series. Source code
in indicators.c.

ADX(int TimePeriod): var


Average Directional Movement Index. Moving average of the DX indicator (see below). Uses the current asset price
series. Does not support TimeFrame. The returned values range from 0 to 100.

ADXR(int TimePeriod): var


Average Directional Movement Index Rating. The average of the current ADX and the ADX from TimePeriod bars
ago. Uses the current asset price series. Does not support TimeFrame.

Alligator(vars Data): var


Alligator Indicator. Consist of three lines: blue = SMA(13) delayed by 5 bars; red: SMA(8) delayed by 2 bars;
green: SMA(5). Indicates a down trend with lines in the order blue-red-green (top to bottom), and an uptrend with
green-red-blue. The closer the Alligator’s lines move, the weaker the trend gets and vice versa. Does not contain the
additional 3 bars lag of the original Alligator algorithm (use Data+3 for that). For Data normally the high/low average
(MedPrice series) is used. Result in rRed, rGreen, rBlue. Source code in indicators.c.

ALMA(vars Data, int TimePeriod, int Sigma, var Offset): var


ALMA(vars Data, int TimePeriod): var
Arnaud Legoux Moving Average. Based on a Gaussian distribution with a bias towards the begin of the Data series
(i.e. more recent prices). Parameters: Sigma (distribution width, default 6); Offset (bias factor, default 0.85). Source
code in indicators.c.

AO(vars Data): var


Awesome Oscillator; simply the difference of a 5-bar and a 34-bar SMA. For Data normally
a MedPrice or price series is used. Source code in indicators.c.

316
APO(vars Data, int FastPeriod, int SlowPeriod, int MAType): var
Absolute Price Oscillator; a more general version of the AO. Returns the difference between two moving averages.
Parameters: FastPeriod (Number of period for the fast MA), SlowPeriod (Number of period for the slow
MA), MAType (Type of Moving Average).

Aroon(int TimePeriod): var


Aroon indicator. Consists of two lines (Up and Down) that measure how long it has been since the highest high/lowest
low has occurred within the time period. Uses the current asset price series. Does not support TimeFrame. Result
in rAroonDown, rAroonUp.

AroonOsc(int TimePeriod): var


Aroon Oscillator. Calculated by subtracting the Aroon Down from the Aroon Up. The return value will oscillate between
+100 and -100. Uses the current asset price series. Does not support TimeFrame.

ATR(int TimePeriod): var


Average True Range. A measure of price volatility; useful for calculating stop loss or profit target distances.
Formula: ATR = (ATR1 * (TimePeriod-1) + max(High,Close)-min(Low,Close)) / TimePeriod, where ATR1 is
the ATR from the last bar. Uses the current asset prices. The function internally creates series when TimeFrame is >
1, and must then be called in a fixed order in the script. See also: Volatility, CVolatilty, TrueRange, ATRS.

ATR(vars Open, vars High, vars Low, vars Close, int TimePeriod): var
Average True Range from arbitrary price series, with arbitrary offset and time frame.

ATRS(int TimePeriod): var


Simple Average True Range. SMA of the TrueRange over the TimePeriod, using the current asset price series. A
measure of price volatility, simpler to calculate than the ATR, but adapting slow to volatility changes and thus less
suited for stop loss / profit targets. Used by the MT4 platform instead of the real ATR. Does not support TimeFrame.
Source code in indicators.c.

AvgPrice(): var
Average Price. Simply (Open+High+Low+Close)/4 with the current asset price series.

BBands(vars Data, int TimePeriod, var NbDevUp, var NbDevDn, int MAType)
Bollinger Bands. Consist of three lines; the middle band is a simple moving average (generally 20 periods) of the typical
price (TP). The upper and lower bands are n standard deviations (generally 2) above and below the middle band. The
bands widen and narrow when the volatility of the price is higher or lower, respectively. Bollinger Bands indicate when
the price has become relatively high or low, which is signaled through the touch or minor penetration of the upper or
lower line. Result in rRealUpperBand, rRealMiddleBand, rRealLowerBand. Parameters: NbDevUp (Deviation
multiplier for upper band), NbDevDn (Deviation multiplier for lower band), MAType (Type of Moving Average). Example
in Indicatortest.c.

BBOsc(vars Data, int TimePeriod, var NbDev, int MAType): var


Bollinger Bands Oscillator; the percentage of the current value of the series within the Bollinger Bands.

Beta(vars Data, vars Data2, int TimePeriod): var


Beta value. A measure of a single asset's prices versus the overall market index. The asset price is given in Data and
the market prices are given in Data2. The algorithm calculates the change between prices in both series and then 'plots'
these changes as points in the Euclidean plane. The x value of any point is the Data2 (market) change and the y value
is the Data (asset) change. The beta value is the slope of a linear regression line through these points. A beta of 1 is
simple the line y=x, so the asset varies percisely with the market. A beta of less than one means the asset varies less
317
than the market and a beta of more than one means the asset varies more than the marke.

BOP(): var
Balance Of Power; simply (Close - Open)/(High - Low). Uses the current asset price series.

CCI(int TimePeriod): var


Commodity Channel Index. Variation of the price from its statistical mean, typically oscillates between +/-100. Uses the
current asset price series. Does not support TimeFrame.

CI(int TimePeriod): var


Choppiness Index; measures single bar volatility in relation to the volatility of the past TimePeriod in a 1..100 range.
Uses the current asset price series. Does not support TimeFrame.

ChandelierLong(int TimePeriod, var Multiplier): var


ChandelierShort(int TimePeriod, var Multiplier): var
Chandelier exit; the highest price of TimePeriod minus the ATR multiplied with Multiplier. Normally used as a
trailing Stop Loss, for keeping trades in a trend and preventing an early exit as long as the trend continues. Source
code in indicators.c. Does not support TimeFrame. Example in the TMF chapter.

CGOsc(vars Data, int TimePeriod): var


Center of Gravity oscillator, by John Ehlers; computes the deviation of prices from their center within the TimePeriod.
Can be used to identify price turning points with almost zero lag. Source code in indicators.c.

Chikou(int Shift): var


Chikou line belonging to the Ichimoku indicator; simply the Close shifted forward by Shift (optional; default = 26). Uses
the current asset price series. Source code in indicators.c.

CMO(vars Data, int TimePeriod): var


Chande Momentum Oscillator. Similar to the RSI, but divides the total data movement by the net movement ((up - down)
/ (up + down)).

Coral(vars Data): var


Coral Indicator, simply a T3 with TimePeriod = 60 and VolumeFactor = 0.4.

Correlation(vars Data1, vars Data2, int TimePeriod): var


Pearson's correlation coefficient between two data series over the given TimePeriod, in the range between -1..+1. A
coefficient of +1.0, a "perfect positive correlation," means that changes in Data2 cause identical changes in Data1 (e.g.,
a change in the indicator will result in an identical change in the asset price). A coefficient of -1.0, a "perfect negative
correlation," means that changes in Data2 cause identical changes in Data1, but in the opposite direction. A coefficient
of zero means there is no relationship between the two series and that a change in Data2 will have no effect on Data1.
This function can be also used to get the autocorrelation of a series by calculating the correlation coefficient between
the original series and the same series lagged by one or two bars (series+1 or series+2).

Covariance(vars Data1, vars Data2, int TimePeriod): var


Covariance between two data series. Can be used to generate a covariance matrix f.i. for the markowitz efficient
frontier calculation.

318
DChannel(int TimePeriod)
Donchian Channel; the minimum and maximum value of the priceHigh() and priceLow functions over the time period.
Basis of the famous Turtle Trading System. Uses the current asset price series. Does not support TimeFrame. Result
in rRealUpperBand, rRealLowerBand.

DCOsc(vars Data, int TimePeriod): var


Donchian Channel Oscillator; the percentage of the current Data value within the Donchian Channel. Uses the current
asset and current TimeFrame.

Decycle(vars Data, int CutOffPeriod): var


Ehlers' Decycler, a low-lag trend indicator; simply Data - HighPass2(Data,CutOffPeriod). Removes all cycles
below CutOffPeriod from the Data series and keeps the trend. The function internally creates series and thus must be
called in a fixed order in the script. Source code in indicators.c.

DEMA(vars Data, int TimePeriod): var


Double Exponential Moving Average.

DPO(vars Data, int TimePeriod): var


Detrended Price Oscillator; believed to detect early changes in price direction. DPO = Data[0] - SMA(Data[n/2+1],n),
where n is the TimePeriod. Source code in indicators.c.

DX(int TimePeriod): var


Directional Movement Index by Welles Wilder (who, by the way, discovered that "the interaction of sun, moon, and earth
is the basis of all market movement". In case that sun, moon, and earth suddenly refrain from moving the market, he
also invented some traditional indicators). The DX is believed to indicate trend strength. The values range from 0 to 100,
but rarely get above 60. The DX uses the current asset price series and does not support TimeFrame. Formula: DX =
100 * abs(PlusDI-MinusDI) / (PlusDI+MinusDI). For PlusDI and MinusDI see the description below.

EMA(vars Data, int TimePeriod): var


EMA(vars Data, var alpha): var
Exponential Moving Average. Emphasizes more recent data values. It uses the formula EMA = alpha * data + (1-alpha)
* EMA1, where alpha is a recursion factor between 0 .. 1 that is calculated from 2.0/(TimePeriod+1), and EMA1 is the
previous EMA value. The smaller alpha is, the higher is the smoothing effect of the EMA formula. Both EMA functions
use slightly different algorithms. The first (using a TimePeriod) does not create a series, is slower, and requires
a Data length of TimePeriod+UnstablePeriod+1. The second (using alpha) creates an internal series, needs only
a Data length of 2 and is much faster.

Fisher(vars Data): var


Fisher Transform; transforms a normalized Data series to a normal distributed range. The return value has no theoretical
limit, but most values are between -1 .. +1. All Data values must be in the -1 .. +1 range, f.i. by normalizing with
the AGC, Normalize, or cdf function. The minimum Data length is 1. Source available in indicators.c.

FisherInv(vars Data): var


Inverse Fisher Transform; compresses the Data series to be between -1 and +1. The minimum length of
the Data series is 1. Source available in indicators.c.

FisherN(vars Data, int TimePeriod): var


Fisher Transform with normalizing; normalizes the Data series with the given TimePeriod and then transforms it to a
normal distributed range. Similar to a Normalize filter (see below), but more selective due to the normal distribution of
the output. The return value has no theoretical limit, but most values are in the -1.5 .. +1.5 range. The minimum length

319
of the Data series is equal to TimePeriod. The function internally creates series and thus must be called in a fixed
order in the script. Source available in indicators.c.

FractalDimension(vars Data, int TimePeriod): var


Fractal dimension of the Data series, by John Ehlers; normally 1..2. Smaller values mean more 'jaggies'. Can be used
to detect the current market regime or to adapt moving averages to the fluctuations of a price series. Source available
in indicators.c.

FractalHigh(vars Data, int TimePeriod): var


Fractal High, an indicator by Bill Williams, believed to signal when the market reverses (has nothing to do with
fractals). Returns the highest Data value when it is in the center of the TimePeriod, otherwise 0.

FractalLow(vars Data, int TimePeriod): var


Fractal Low. Returns the lowest Data value when it is in the center of the TimePeriod, otherwise 0.

Gauss(vars Data, int TimePeriod): var


Gauss Filter, returns a weighted average of the data within the given time period, with the weight curve equal to the
Gauss Normal Distribution. Useful for removing noise by smoothing raw data. The minimum length of the Data series
is equal to TimePeriod, the lag is half the TimePeriod.

HAOpen(): var
HAClose(): var
HAHigh(): var
HALow(): var
Haiken Ashi prices, based on the current asset prices. Source code in indicators.c. Alternatively, the price curve can
be converted to Haiken Ashi bars using the bar function.

HH(int TimePeriod, int Offset): var


Highest value of the priceHigh function over the TimePeriod ending with Offset (default 0). F.i. HH(3) returns the
highest price of the last 3 bars. Uses the current asset preice series. Does not support TimeFrame; for multiple time
frames, use MaxVal(High+Offset,Period) with a time synchronized High series instead. See also dayHigh.

HMA(vars Data, int TimePeriod): var


Hull Moving Average by Alan Hull; attempts to address lag as well as to smooth out some choppiness. Formula:HMA(n)
= WMA(2*WMA(n/2) – WMA(n)),sqrt(n)). The function internally creates a series and thus must be called in a fixed
order in the script. Source code in indicators.c.

HTDcPeriod(vars Data): var


Hilbert Transform - Dominant Cycle Period, developed by John Ehlers. Hilbert transform algorithms are explained in
Ehler's book "Rocket Science for Traders" (see book list). This function is equivalent, but less accurate than
the DominantPeriod function.

HTDcPhase(vars Data): var


Hilbert Transform - Dominant Cycle Phase.

HTPhasor(vars Data): var


Hilbert Transform - Phasor Components. Result in rInPhase, rQuadrature.

320
HTSine(vars Data): var
Hilbert Transform - SineWave. Result in rSine, rLeadSine.

HTTrendline(vars Data): var


Hilbert Transform - Instantaneous Trendline.

HTTrendMode(vars Data): int


Hilbert Transform trend indicator - returns 1 for Trend Mode, 0 for Cycle Mode.

Hurst (vars Data, int TimePeriod): var


Hurst exponent of the Data series; between 0..1. The Hurst exponent measures the 'memory' of a series. It quantifies
the autocorrelation, i.e. the tendency either to revert to the mean (Hurst < 0.5) or to continue trending in a direction
(Hurst > 0.5). This way the Hurst exponent can detect if the market is in a trending state. The TimePeriod window
(minimum 20) must have sufficient length to catch the long-term trend. The function internally creates a series and thus
must be called in a fixed order in the script. Source available in indicators.c.

Ichimoku()
Ichimoku(int PeriodTenkan, int PeriodKijun, int PeriodSenkou, int Offset)
Ichimoku Kinko Hyo indicator. Invented by the journalist Goichi Hosoda in 1930. A mix of the medium prices of 3 time
periods; believed to give deep insight into market trends due to its enormous number of colorful lines. Offset (default
0) determines the bar for calculating the indicator. Returns 4 variables:

rTenkan = (HH+LL)/2 with PeriodTenkan (default 9)


rKijun = (HH+LL)/2 with PeriodKijun (default 26)
rSenkouA = (rTenkan+rKijun)/2, shifted forward by PeriodKijun. Forms a "cloud band" with rSenkouB.
rSenkouB = (HH+LL)/2 with PeriodSenkou (default 52), shifted forward by PeriodKijun

Another line belonging to the Ichimoku, the Chikou line, is future peeking and calculated separately. Uses the current
asset price series. The function internally creates series when TimeFrame is > 1, and must then be called in a fixed
order in the script. Source code in indicators.c.

IBS(): var
Internal Bar Strength; simply (Close - Low)/(High - Low). Uses the current asset price series.

KAMA(vars Data, int TimePeriod): var


Kaufman Adaptive Moving Average. An exponential moving average adjusted by price volatility, so its time period
becomes shorter when volatility is high.

Keltner(vars Data, int TimePeriod, var Factor): var


Keltner Channel, by Charles Keltner. A Simple Moving Average - SMA(Data,TimePeriod) - with side bands in the
distance Factor * ATRS(TimePeriod). Results in rRealUpperBand, rRealMiddleBand, rRealLowerBand. Source
code in indicators.c.

Laguerre(vars Data, var alpha): var


4-element Laguerre filter. Used for smoothing data similar to an EMA, but with less lag and a wide tuning range given
by the smoothing factor alpha (0..1). The low frequency components are delayed much more than the high frequency
components, which enables very smooth filters with only a short amount of data. The minimum length of the Data series
is 1, the minimum lookback period is 4. The function internally creates series and thus must be called in a fixed order
in the script. Source available in indicators.c.

321
LinearReg(vars Data, int TimePeriod): var
Linear Regression, also known as the "least squares method" or "best fit." Linear Regression attempts to fit a straight
trendline between several data points in such a way that the distance between each data point and the trendline is
minimized. For each point, the straight line over the specified previous bar period is determined in terms of y = b + m*x.
The LinearReg function returns b+m*(TimePeriod-1). For higher order regression, use the polyfit /
polynom functions. For logistic regression with multiple variables, use the advise(PERCEPTRON,...) function.

LinearRegAngle(vars Data, int TimePeriod): var


Linear Regression Angle. Returns m converted to degrees. Due to the different x and y units of a price chart, the angle
is normally of little use, except maybe for Gann followers.

LinearRegIntercept(vars Data, int TimePeriod): var


Linear Regression Intercept. Returns b.

LinearRegSlope(vars Data, int TimePeriod): var


Linear Regression Slope. Returns m as price difference per bar.

LL(int TimePeriod, int Offset): var


Lowest value of the priceLow function over the TimePeriod ending with Offset (default 0). F.i. LL(3,10) returns the
lowest price between the last 10 and the last 13 bars. Uses the current asset price series. Does not support TimeFrame;
for multiple time frames, use MinVal(Low+Offset,Period) with a time synchronized Low series instead. See
also dayLow.

MACD(vars Data, int FastPeriod, int SlowPeriod, int SignalPeriod)


Moving Average Convergence/Divergence. The MACD is an intermediate-term trend indicator, created by subtracting
a 26-period Exponential Moving Average (EMA, see above) from a 12-period EMA. A nine-period EMA is then applied
to the MACD result to create a 'signal line'. A MACD Histogram line is finally created from the difference of
the MACD to its signal line. It is believed that the zero crossing of the histogram from below is a buy signal, zero
crossing from above a sell signal. The formula is:

rMACD = EMA(Data,FastPeriod)-EMA(Data,SlowPeriod);
rMACDSignal = EMA(rMACD,SignalPeriod);
rMACDHist = rMACD - rMACDSignal;

Results in rMACD, rMACDSignal, rMACDHist. Returns: rMACD. Parameters: FastPeriod (time period for the fast
MA), SlowPeriod (time period for the slow MA), SignalPeriod (time period for smoothing the signal line).

MACDExt(vars Data, int FastPeriod, int FastMAType, int SlowPeriod, int SlowMAType, int
SignalPeriod, int SignalMAType)
MACD with controllable MA type. Result in rMACD, rMACDSignal, rMACDHist. Parameters: FastPeriod (time period
for the fast MA), FastMAType (Type of Moving Average for fast MA), SlowPeriod (time period for the slow
MA), SlowMAType (Type of Moving Average for slow MA), SignalPeriod (time period for smoothing the signal
line), SignalMAType (Type of Moving Average for signal line).

MACDFix(vars Data, int SignalPeriod)


Moving Average Convergence/Divergence Fix 12/26. Result in rMACD, rMACDSignal, rMACDHist.
Parameters: SignalPeriod (time period for smoothing the signal line).

322
MAMA(vars Data, var FastLimit, var SlowLimit)
MESA Adaptive Moving Average, developed by John Ehlers (see links). Result in rMAMA, rFAMA.
Parameters: FastLimit (Upper limit use in the adaptive algorithm), SlowLimit (Lower limit use in the adaptive
algorithm).

MaxVal(vars Data, int TimePeriod): var


Highest value over a specified period.

MaxIndex(vars Data, int TimePeriod): int


Index of highest value over a specified period. 0 = highest value is at current bar, 1 = at one bar ago, and so on.

Median(vars Data, int TimePeriod): var


Median Filter; sorts the elements of the Data series and returns their middle value within the given time period. Useful
for removing noise spikes by eliminating extreme values. The minimum length of the Data series is equal
to TimePeriod, the lag is half the TimePeriod. See also Percentile.

MedPrice(): var
Center price; simply the center point (High+Low)/2 of the current candle. For the mean price - the average of all price
ticks of the candle - use price().

MidPoint(vars Data, int TimePeriod): var


MidPoint over period. Simply (highest value + lowest value)/2.

MidPrice(int TimePeriod): var


Midpoint price over period. Simply (highest high + lowest low)/2 of the current asset price series. Does not support
TimeFrame.

MinusDI(int TimePeriod): var


MinusDI(vars Open, vars High, vars Low, vars Close, int TimePeriod): var
Minus Directional Indicator, a part of the DX indicator. If the function is not called with different price series, the current
asset price series is used.

MinusDM(int TimePeriod): var


MinusDM(vars Open, vars High, vars Low, vars Close, int TimePeriod): var
Minus Directional Movement, two versions. If the function is not called with different price series, the current asset price
series is used.

MinVal(vars Data, int TimePeriod): var


Lowest value over a specified period.

MinIndex(vars Data, int TimePeriod): int


Index of lowest value over a specified period. 0 = lowest value is at current bar, 1 = at one bar ago, and so on.

323
MinMax(vars Data, int TimePeriod): var
Lowest and highest values and their indices over a specified period. Result in rMin, rMax, rMinIdx, rMaxIdx.

MinMaxIndex(vars Data, int TimePeriod): int


Indexes of lowest and highest values over a specified period. Result in rMinIdx, rMaxIdx. 0 = current bar, 1 = one bar
ago, and so on.

MMI(vars Data, int TimePeriod): var


Market Meanness Index by Financial Hacker. Measures the meanness of the market, i.e. its mean reversal tendency,
in a 0..100% range. Random numbers have a MMI of 75%. Real prices are more or less autocorrelated, so the
probability of a real price series to revert to the mean is less than 75%, but normally more than 50%. The higher it is,
the 'meaner' is the market. The Market Meanness Index can determine when trend following systems will become more
profitable (MMI is falling) or less profitable (MMI is rising), and thus prevent losses in unprofitable periods. Source code
in indicators.c.

Mom(vars Data, int TimePeriod): var


Momentum. Simply Data[0] - Data[TimePeriod]. See also diff.

Moment(vars Data, int TimePeriod, int N): var


The statistical moment N (1..4) of the Data series section given by TimePeriod. The first moment is the mean, the
second is the variance, third is skewness, and fourth ist kurtosis. Source available in indicators.c.

MovingAverage(vars Data, int TimePeriod, int MAType): var


Moving average. Parameters: MAType (Type of Moving Average, see remarks).

MovingAverageVariablePeriod(vars Data, vars Periods, int MinPeriod, int MaxPeriod, int MAType):
var
Moving average with variable period given by the Periods series. Parameters: MinPeriod (Value less than minimum
will be changed to Minimum period), MaxPeriod (Value higher than maximum will be changed to Maximum
period), MAType(Type of Moving Average, see remarks).

NATR(int TimePeriod): var


Normalized Average True Range, by John Forman. Similar to the ATR, except it is being normalized as follows: NATR
= 100 * ATR(TimePeriod) / Close. Uses the current asset price series. Does not support TimeFrame.

Normalize(vars Data, int TimePeriod): var


Transforms the Data series to the -1...+1 range within the given TimePeriod. Similar to the AGC function, but does not
differentiate between attack and decay. The minimum length of the Data series is equal to TimePeriod. Source
available in indicators.c. See also scale.

NumInRange(vars Low, vars High, var Min, var Max, int Length): var
Number of data ranges, given by their Low and High values, that lie completely inside the interval from Min to Max
within the given Length. Can be used to calculate the distribution of prices or candles. Low and High can be set to the
same value for counting all values in the interval, or swapped for counting all candles that touch the interval. Range
= 1..TimePeriod. Source available in indicators.c.

NumRiseFall(vars Data, int TimePeriod): var


Length of the current sequence of rising or falling values in the Data array, back to the given TimePeriod. For a rising
sequence its length is returned, for a falling sequence the negative length. Range = 1..TimePeriod resp. -1..-

324
TimePeriod. Source available in indicators.c. See the RandomWalk script and the Strategy chapter for an example.
Source available in indicators.c.

NumWhiteBlack(var Body, int Offset, int TimePeriod): var


Number of white minus black candles in the given TimePeriod. Offset is the distance to the current bar (0 = current
bar), Body is the minimum length of a candle to be counted. Source available in indicators.c.

Percentile(vars Data, int Length, var Percent): var


Returns the given percentile of the Data series with given Length; f.i. Percent = 95 returns the Data value that is above
95% of all other values. Percent = 50 returns the Median of the Data series. For calculating the percentage of a given
percentile value, use the NumInRange function and count the elements below the percentile.

PlusDI(int TimePeriod): var


PlusDI(vars Open, vars High, vars Low, vars Close, int TimePeriod): var
Plus Directional Indicator, a part of the DX indicato, two versions. In the first version the current asset price series is
used.

PlusDM(int TimePeriod): var


PlusDM(vars Open, vars High, vars Low, vars Close, int TimePeriod): var
Plus Directional Movement, two versions. In the first version the current asset price series is used.

PPO(vars Data, int FastPeriod, int SlowPeriod, int MAType): var


Percentage Price Oscillator. Parameters: FastPeriod (Number of period for the fast MA), SlowPeriod (Number of
period for the slow MA), MAType (Type of Moving Average).

ProfitFactor(vars Data, int Length): var


Returns the profit factor of the Data series. The profit factor is the ratio of the sum of positive returns (i.e. Data[i-1] >
Data[i]) to the sum of negative returns (i.e. Data[i-1] < Data[i]). The returned value is clipped to the 0.1...10 range. Its
reciprocal must be used when the Data array is in not in series order, but in chronological order, as wins and losses
are then swapped. Source available in indicators.c.

ROC(vars Data, int TimePeriod): var


Rate of change, 100 scale: ((price-prevPrice)/prevPrice)*100.

ROCP(vars Data, int TimePeriod): var


Rate of change Percentage: (price-prevPrice)/prevPrice. See also diff.

ROCR(vars Data, int TimePeriod): var


Rate of change ratio: (price/prevPrice).

ROCL(vars Data, int TimePeriod): var


Logarithmic return: log(price/prevPrice).

ROCR100(vars Data, int TimePeriod): var


Rate of change ratio, 100 scale: (price/prevPrice)*100.
325
Roof(vars Data, int CutoffLow, int CutoffHigh): var
Ehler's roofing filter, prepares the Data series for further computation by removing trend and noise. Applies a 2-pole
highpass filter followed by the Smooth filter. Recommended values for the low and high cutoff periods are 10 and 50.
The minimum length of the Data series is 2. The function internally creates series and thus must be called in a fixed
order in the script. Source available in indicators.c.

RSI(vars Data, int TimePeriod): var


Relative Strength Index, by Welles Wilder. Ratio of the recent upwards data movement to the total data movement;
range 0..100. The RSI is believed to indicate overbought/oversold conditions when the value is over 70/below 30.
Formula: RSI = 100 * Up/(Up+Dn), where Up = EMA(max(0,Data[0]-Data[1]),TimePeriod) and Dn =
EMA(max(0,Data[1]-Data[0]),TimePeriod).

RVI(int TimePeriod): var


Relative Vigor Index, by John Ehlers. Ratio of price change to the total price range: (C-O)/(H-L), averaged over the time
period and smoothed with a FIR filter. Oscillates between -1 and 1. The function internally creates a series and thus
must be called in a fixed order in the script. Source code in indicators.c.

SAR(var Step, var Min, var Max): var


Parabolic SAR, by Welles Wilder. The SAR runs above or below the price curve, depending on the current trend; each
price curve crossing is believed to indicate a trend change. Parameters: Step (acceleration factor increment,
normally 0.02), Min (acceleration factor minimum value, normally 0.02), Max (acceleration factor maximum value,
normally 0.2). SAR is a recursive function that depends on the direction of the initial price candle; for consistent values
the LookBack period should be long enough to contain at least one price curve crossing. Uses the current asset prices.
The function internally creates a series and thus must be called in a fixed order in the script. Source code
in indicators.c, example in Indicatortest.c.

ShannonGain(vars Data, int TimePeriod): var


Expected logarithmic gain rate of the Data series in the range of about +/-0.0005. The gain rate is derived from the
Shannon probability P = (1 + Mean(Gain) / RootMeanSquare(Gain)) / 2, which is the likeliness of a rise or fall of a high
entropy data series in the next bar period. A positive gain rate indicates that the series is more likely to rise, a negative
gain rate indicates that it is more likely to fall. The zero crossover could be used for a trade signal. Algorithm by John
Conover. Source available in indicators.c.

ShannonEntropy(vars Data, int Length, int PatternSize): var


Entropy of patterns in the Data series, in bit; can be used to determine the 'randomness' of the data. PatternSize (2..8)
determines the partitioning of the data into patterns of up to 8 bit. Each Data value is either higher than the previous
value, or it is not; this is a binary information and constitutes one bit of the pattern. The more random the patterns are
distributed, the higher is the Shannon entropy. Totally random data has a Shannon entropy identical to the pattern size.
Algorithm explained on the Financial Hacker blog; source available in indicators.c.

SIROC(vars Data, int TimePeriod, int EMAPeriod): var


Smoothed Rate of Change (S-RoC) by Fred G Schutzman. Differs from the ROC (see above) in that it is based on the
exponential moving average (EMA) of the Data series. Believed to indicate the strength of a trend by determining if the
trend is accelerating or decelerating. Formula: (Current EMA - Previous EMA)/(Previous EMA) x 100. Source code
in indicators.c.

SMA(vars Data, int TimePeriod): var


Simple Moving Average; the mean of the data, i.e. the sum divided by the time period. Use Moment when long time
periods are required.

Smooth(vars Data, int CutoffPeriod): var


Ehler's super-smoothing filter, a 2-pole Butterworth filter combined with a SMA that suppresses the Nyquist frequency.
Can be used as a low-lag universal filter for removing noise from price data. The minimum length of the Data series is

326
2. The function internally creates series and thus must be called in a fixed order in the script. Source available
in indicators.c.

SMom(vars Data, int TimePeriod, int CutoffPeriod): var


Smoothed Momentum by John Ehlers; indicates the long term trend direction. TimePeriod is the momentum
period, CutoffPeriod is a Butterworth filter constant for lowpass filtering the momentum. Source code in indicators.c.

Spearman(vars Data, int TimePeriod): var


Spearman's rank correlation coefficient; correlation between the original Data series and the same series sorted in
ascending order within TimePeriod (1..256). Returns the similarity to a steadily rising series and can be used to
determine trend intensity and turning points. Range = -1..+1, lag = TimePeriod/2. For usage and details, see Stocks &
Commodities magazine 2/2011. Source available in indicators.c.

StdDev(vars Data, int TimePeriod): var


Standard Deviation of the Data series in the time period, from the ta-lib. Use the square root of the second Moment
when high accuracy or long time periods are required.

Stoch(int FastK_Period, int SlowK_Period, int SlowK_MAType, int SlowD_Period, int


SlowD_MAType)
Stochastic Oscillator (unrelated to stochastics, but its inventor, George Lane, looked for a fancy name). Measures
where the Close price is in relation to the recent trading range. Formula: FastK = 100 * (Close-LL)/(HH-LL); SlowK =
MA(FastK); SlowD = MA(SlowK). Uses the current asset price series and does not support TimeFrame. Result
in rSlowK, rSlowD. Some traders believe that the SlowK crossing above SlowD is a buy signal; others believe they
should buy when SlowD is below 20 and sell when it is above 80. Parameters:
FastK_Period - Time period for the HH and LL to generate the FastK value, usually 14.
SlowK_Period - Time period for smoothing FastK to generate rSlowK; usually 3.
SlowK_MAType - Type of Moving Average for Slow-K, usually MAType_EMA.
SlowD_Period - Time period for smoothing rSlowK to generate rSlowD, usually 3.
SlowD_MAType - Type of Moving Average for Slow-D, usually MAType_EMA.

StochEhlers(vars Data, int TimePeriod, int CutOffLow, int CutOffHigh): var


Predictive stochastic oscillator by John Ehlers. Measures where the Data value is in relation to its range
within TimePeriod. The data runs through a 2-pole highpass filter with period CutOffHigh and through a Butterworth
lowpass filter with period CutOffLow. Indicator algorithm explained in Ehler's "Predictive Indicators" paper; usage
example in the Ehlers script. Source code in indicators.c. The function internally creates series and thus must be
called in a fixed order in the script.

StochF(int FastK_Period, int FastD_Period, int FastD_MAType): var


Stochastic Fast. Measures where the Close price is in relation to the recent trading range; Formula: Fast-K = 100 *
(Close-LL)/(HH-LL); Fast-D = MA(Fast-K). Uses the current asset price series. Does not support TimeFrame. Result
in rFastK, rFastD. Returns: FastK. Parameters:
FastK_Period (Time period for the HH and LL of Fast-K, usually 14),
FastD_Period (Moving Average Period for Fast-D; usually 3),
FastD_MAType (Type of Moving Average for Fast-D, usually MAType_EMA).

StochRSI(vars Data, int TimePeriod, int FastK_Period, int FastD_Period, int FastD_MAType): var
Stochastic Relative Strength Index (RSI). Result in rFastK, rFastD. Returns: FastK. Parameters:
FastK_Period (Time period for building the Fast-K line),
FastD_Period (Smoothing for making the Fast-D line. Usually set to 3),
FastD_MAType (Type of Moving Average for Fast-D).

327
Sum(vars Data, int TimePeriod): var
Sum of all Data elements in the time period.

T3(vars Data, int TimePeriod, var VFactor): var


An extremely smoothed Moving Average by Tim Tillson. Uses a weighted sum of multiple EMAs.
Parameters: VFactor (Volume Factor, normally 0.7).

TEMA(vars Data, int TimePeriod): var


Triple Exponential Moving Average by Patrick Mulloy, calculated from (3xEMA)-(3xEMA of EMA)+(EMA of EMA of
EMA).

Trima(vars Data, int TimePeriod): var


Triangular Moving Average (also known under the name TMA); a form of Weighted Moving Average where the weights
are assigned in a triangular pattern. F.i. the weights for a 7 period Triangular Moving Average would be 1, 2, 3, 4, 3, 2,
1. This gives more weight to the middle of the time series. It causes better smoothing, but greater lag.

Trix(vars Data, int TimePeriod): var


1-day Rate-Of-Change (see ROC) of a Triple EMA (see TEMA).

TrueRange(): var
True Range (TR); max(High[0],Close[1])-min(Low[0],Close[1]) of the current asset price series. See
also ATR, ATRS.

TSF(vars Data, int TimePeriod): var


Time Series Forecast. Returns b + m*(TimePeriod), i.e. the Linear Regression forecast for the next bar.

TSI(vars Data, int TimePeriod): var


Trend Strength Index, an indicator by Frank Hassler who believed that it identifies trend strength. A high TSI value
(above ~ 1.65) indicates that short-term trend continuation is more likely than short-term trend reversal. The function
internally creates series and thus must be called in a fixed order in the script.

TypPrice(): var
Typical Price. Simply (High + Low + Close)/3. Uses the current asset price series.

UltOsc(int TimePeriod1, int TimePeriod2, int TimePeriod3): var


Ultimate Oscillator. Parameters: TimePeriod1 (Number of bars for 1st period.), TimePeriod2 (Number of bars for 2nd
period), TimePeriod3 (Number of bars for 3rd period). Uses the current asset price series. Does not support
TimeFrame.

UO(vars Data, int CutOff): var


Universal oscillator by John Ehlers, from S&C Magazine 1/2015. Removes white noise from the data, smoothes it and
runs it through the AGC filter. Detects trend reversals very early. Output in the -1..+1 range. Source code
in indicators.c. The function internally creates series and thus must be called in a fixed order in the script.

328
Variance(vars Data, int TimePeriod): var
Variance of the Data series in the time period, from the ta-lib. Use Moment when high accuracy or long time periods
are required.

Volatility(vars Data, int TimePeriod): var


Annualized volatility of the Data series; standard deviation of the log returns, multiplied with the square root of time
frames in a year. This is the standard measure of volatility used for financial models, such as the Black-Scholes model.
The function internally creates series and thus must be called in a fixed order in the script. Source code in indicators.c.

VolatilityC(int TimePeriod, int EMAPeriod): var


Chaikin Volatility indicator by Marc Chaikin; measures volatility in percent as momentum of the smoothed difference
between High and Low. An increase in the Chaikin Volatility indicates that a bottom is approaching, a decrease indicates
that a top is approaching. TimePeriod is the period of the momentum (normally 10), EMAPeriod determines the
smoothing (also, normally 10). Uses the current asset price series. The function internally creates series and thus must
be called in a fixed order in the script. Source code in indicators.c.

VolatilityMM(vars Data, int TimePeriod, int EMAPeriod): var


Min/Max volatility of the Data series; the difference of MaxVal and MinVal in the time period, smoothed by an EMA
(set EMAPeriod = 0 for not smoothing). The function internally creates a series when EMAPeriod > 0, and then must
be called in a fixed order in the script. Source available in indicators.c. For the volatility of price candles,
use ATR or ATRS.

VolatilityOV(int Days): var


Annualized volatility of the current asset, calculated over the given number of Days (usually 20). Empirical formula used
by some options software packages (OptionsVue™) for estimating the values of options, alternatively to Volatility().
Source code in options.c, which must be included for using this indicator.

WCLPrice(): var
Weighted Close Price. Uses the current asset price series.

WillR(int TimePeriod): var


Williams' Percent Range. Formula: -100* (HH-Close)/(HH-LL). Uses the current asset price series. Does not support
TimeFrame.

WMA(vars Data, int TimePeriod): var


Linear Weighted Moving Average; the weight of every bar decreases linearly with its age.

ZigZag(vars Data, var Depth, int Length, int Color): var


ZigZag indicator; converts the Data series into alternating straight trend lines with at least the given Depth and Length.
Non-predictive; can only identify trends in hindsight. Returned: rSlope (the slope of the last identified trend line; upwards
trends have a positive slope, downwards trends a negative slope); rPeak (the bar offset of the last identified
peak); rSign (1 if the last peak was a top, -1 if the last peak was a bottom); rLength (the number of bars of the last
trend line ending with rPeak). If a nonzero Color is given, the trend lines are plotted in the chart. Source code
in indicators.c, example in Indicatortest.c. The function internally creates series and thus must be called in a fixed
order in the script.

ZMA(vars Data, int TimePeriod): var


Zero-lag Moving Average by John Ehlers; smoothes the Data series with an Exponential Moving Average (EMA) and
applies an error correction term for compensating the lag. The function internally creates a series and thus must be
called in a fixed order in the script. Source in indicators.c.
329
Standard parameters:
TimePeri The number of bars for the time period of the function, if any; or 0 for using a default period.
od

MAType The type of the moving average used by the


function: MAType_SMA (default), MAType_EMA, MAType_WMA, MAType_DEMA, MAType_TEMA,
MAType_TRIMA, MAType_KAMA, MAType_MAMA, MAType_T3.

Data A data series, often directly derived from the price functions price(), priceClose() etc.. Alternatively a
user created series or any other double float array with the given minimum length can be used. If not
mentioned otherwise, the minimum length of the Data series is TimePeriod. Some functions require a
second data array Data2.

Open, Price data series can be explicitly given for some indicators, for using price series generated from a
High, different asset or with a different TimeFrame. Otherwise the prices of the current asset with a time frame
Low, equivalent to the bar period are used.
Close

Returns:
Price variation or percentage, dependent on the function, for the current bar.

Usage example:
MACD(Price,12,26,9) calculates the standard MACD for the given Price series. The results are stored in the global
variables rMACD, rMACDSignal, and rMACDHistory.

Remarks:

• The TA-Lib function prototypes are defined in include\ta.h. Information about the usage and the indicator algorithms
can be found online at www.tadoc.org. The C++ source code of all included TA-Lib indicators is contained
in Source\ta_lib.zip and can be studied for examining the algorithms. Some TA-Lib indicators that originally didn't
work properly - such as Correlation or SAR - have been replaced by working versions. The lite-C source code of most
additional indicators that are not part the the TA-Lib is contained in include\indicators.c.
• All TA functions are applied on series and do normally not accept other data arrays. In the INITRUN, all TA functions
return 0, and LookBack is automatically increased to the largest required lookback time by a TA function.
• Recursive TA functions - f.i. EMA or ATR - need a higher lookback period than their TimePeriod parameter
(see UnstablePeriod). LookBack can be exceeded when TA functions are later called with a series offset or a
different TimePeriod; this will generate an Error 046 message. Make sure that LookBack is always higher than the
maximum TimePeriod plus the UnstablePeriod plus the highest possible offset of all used series.
• Some functions return more than one value, f.i. MACD. The returned results are stored in global variables beginning
with "r"; they can be accessed after the function is called.
• Some functions only require a single Data value. Rather than creating a Data series of length 1, simply a pointer to
the Data value can be used. Example: var Raw = MyIndicator(); var Transformed = AGC(&Raw,0); .
• TimeFrame affects subsequent data series and thus also affects all indicators that use the data series as input.
The TimePeriod is then not in Bar units, but in time frame units. TimeFrame has no effect on indicators that do not
use data series.
• Indicators that rely on the standard deviation (f.i. Bollinger Bands) become inaccurate when the standard deviation is
below 0.0001, as it is then assumed to be zero by the TA-Lib. This can happen on very short bar periods when the
price does (almost) not move.
• For writing your own indicators, have a look at the examples inside indicators.c. But please do not
modify indicators.c - write the indicators in your own script, or in a dedicated script that you can then include in your
strategies. If you need a complex indicator that you can not be easily add, please ask for it on the Zorro user forum.

330
Examples:
// plot some indicators
function run()
{
set(PLOTNOW);
var* Price = series(price());

// plot Bollinger bands


BBands(Price,30,2,2,MAType_SMA);
plot("Bollinger1",rRealUpperBand,BAND1,0x00CC00);
plot("Bollinger2",rRealLowerBand,BAND2,0xCC00FF00);
plot("SAR",SAR(0.02,0.02,0.2),DOT,RED);
ZigZag(Price,20*PIP,5,BLUE);

// plot some other indicators


plot("ATR (PIP)",ATR(20)/PIP,NEW,RED);
plot("Doji",CDLDoji(),NEW+BARS,BLUE);
plot("FractalDim",FractalDimension(Price,30),NEW,RED);
plot("ShannonGain",ShannonGain(Price,40),NEW,RED);
}

// compare the impulse responses of some low-lag MAs


function run()
{
set(PLOTNOW);
BarPeriod = 60;
MaxBars = 500;
LookBack = 150;
asset(""); // dummy asset
ColorUp = ColorDn = 0; // don't plot a price curve
PlotWidth = 800;
PlotHeight1 = 400;

vars Impulse = series(1-genSquare(400,400));


int Period = 50;
plot("Impulse",Impulse[0],0,GREY);
plot("SMA",SMA(Impulse,Period),0,BLACK);
plot("EMA",EMA(Impulse,Period),0,0x800080);
plot("ALMA",ALMA(Impulse,Period),0,0x008000); // best representation of the impulse
331
plot("Hull MA",HMA(Impulse,Period),0,0x00FF00);
plot("Zero-Lag MA",ZMA(Impulse,Period),0,0x00FFFF); // fastest MA with no overshoot
plot("Decycle",Decycle(Impulse,Period),0,0xFF00FF); // fastest MA with some overshoot
plot("LowPass",LowPass(Impulse,Period),0,0xFF0000);
plot("Smooth",Smooth(Impulse,Period),0,0x0000FF);
}

332
Traditional Candle Patterns
Classical rice market candle patterns, returning an integer value of -100 for bearish patterns, +100 for bullish patterns,
and 0 for no pattern match. They use the current asset price series. All candle pattern functions are listed below in
alphabetical order. They are based on the TA-Lib indicator library by Mario Fortier (www.ta-lib.org).

Disclaimer: All patterns had been developed for trading the Japanese rice market in the 18th century. Some traders
believe that they are still valid for the 21th century financial markets. Problem is that the patterns were based on daily
candles with limited market hours, and that a price curve can produce very different candles dependent on the time
frame, the time zone, the bar offset, and the broker's price feed. Therefore rice candle patterns are mostly useless for
today's 24-hour traded markets. For finding out which patterns work today, use the pattern analyzer.

CDL2Crows(): int
Two Crows, a bearish candle pattern.

CDL3BlackCrows(): int
Three Black Crows.

CDL3Inside(): int
Three Inside Up/Down.

CDL3LineStrike(): int
Three-Line Strike.

CDL3Outside(): int
Three Outside Up/Down.

CDL3StarsInSouth(): int
Three Stars In The South.

CDL3WhiteSoldiers(): int
Three Advancing White Soldiers.

CDLAbandonedBaby(var Penetration): int


Abandoned Baby. Parameters: Penetration (Percentage of penetration of a candle within another candle).

CDLAdvanceBlock(): int
Advance Block.

CDLBeltHold(): int
Belt-hold.

333
CDLBreakaway(): int
Breakaway. Bullish + Bearish.

CDLClosingMarubozu(): int
Closing Marubozu.

CDLConcealBabysWall(): int
Concealing Baby Swallow.

CDLCounterAttack(): int
Counterattack.

CDLDarkCloudCover(var Penetration): int


Dark Cloud Cover. Parameters: Penetration (Percentage of penetration of a candle within another candle).

CDLDoji(): int
Doji, single candle pattern. Trend reversal.

CDLDojiStar(): int
Doji Star. Bullish + Bearish.

CDLDragonflyDoji(): int
Dragonfly Doji.

CDLEngulfing(): int
Engulfing Pattern. Bullish + Bearish.

CDLEveningDojiStar(var Penetration): int


Evening Doji Star. Parameters: Penetration (Percentage of penetration of a candle within another candle).

CDLEveningStar(var Penetration): int


Evening Star. Parameters: Penetration (Percentage of penetration of a candle within another candle).

CDLGapSideSideWhite(): int
Up/Down-gap side-by-side white lines.

CDLGravestoneDoji(): int
Gravestone Doji.

334
CDLHammer(): int
Hammer. Bullish.

CDLHangingMan(): int
Hanging Man. Bearish.

CDLHarami(): int
Harami Pattern. Bullish + Bearish.

CDLHaramiCross(): int
Harami Cross Pattern.

CDLHignWave(): int
High-Wave Candle.

CDLHikkake(): int
Hikkake Pattern. Bullish + Bearish.

CDLHikkakeMod(): int
Modified Hikkake Pattern.

CDLHomingPigeon(): int
Homing Pigeon.

CDLIdentical3Crows(): int
Identical Three Crows.

CDLInNeck(): int
In-Neck Pattern.

CDLInvertedHammer(): int
Inverted Hammer.

CDLKicking(): int
Kicking.

CDLKickingByLength(): int
Kicking - bull/bear determined by the longer marubozu.

335
CDLLadderBottom(): int
Ladder Bottom.

CDLLongLeggedDoji(): int
Long Legged Doji.

CDLLongLine(): int
Long Line Candle.

CDLMarubozu(): int
Marubozu.

CDLMatchingLow(): int
Matching Low.

CDLMatHold(var Penetration): int


Mat Hold. Parameters: Penetration (Percentage of penetration of a candle within another candle).

CDLMorningDojiStar(var Penetration): int


Morning Doji Star. Parameters: Penetration (Percentage of penetration of a candle within another candle).

CDLMorningStar(var Penetration): int


Morning Star. Parameters: Penetration (Percentage of penetration of a candle within another candle).

CDLOnNeck(): int
On-Neck Pattern.

CDLPiercing(): int
Piercing Pattern.

CDLRickshawMan(): int
Rickshaw Man.

CDLRiseFall3Methods(): int
Rising/Falling Three Methods.

CDLSeperatingLines(): int
Separating Lines.

336
CDLShootingStar(): int
Shooting Star.

CDLShortLine(): int
Short Line Candle.

CDLSpinningTop(): int
Spinning Top.

CDLStalledPattern(): int
Stalled Pattern.

CDLStickSandwhich(): int
Stick Sandwich.

CDLTakuri(): int
Takuri (Dragonfly Doji with very long lower shadow).

CDLTasukiGap(): int
Tasuki Gap.

CDLThrusting(): int
Thrusting Pattern.

CDLTristar(): int
Tristar Pattern.

CDLUnique3River(): int
Unique 3 River.

CDLUpsideGap2Crows(): int
Upside Gap Two Crows.

CDLXSideGap3Methods(): int
Upside/Downside Gap Three Methods.

Returns:
-100 for a bearish pattern, +100 for a bullish pattern, and 0 for no pattern match at the current bar.

337
Remarks:

• The TA-Lib function prototypes are defined in include\ta.h. Information about the usage and the indicator algorithms
can be found online at www.tadoc.org. The source code of the TA-Lib indicators is available on www.ta-lib.org.
• All patterns are based on the UTC time zone. For different time zones, add a BarOffset.
• At the initial run of the strategy when price data is not yet set up, all CDL functions return 0.

Example:
function run()
{
set(PLOTNOW);
PlotBars = 300;
PlotScale = 8;
if(CDLDoji())
plot("Doji",1.002*priceHigh(),TRIANGLE4,BLUE);
if(CDLHikkake() > 0)
plot("Hikkake+",0.998*priceLow(),TRIANGLE,GREEN);
if(CDLHikkake() < 0)
plot("Hikkake-",1.002*priceHigh(),TRIANGLE4,RED);
}

338
Spectral filters and frequency analysis
The following functions retrieve frequency spectra from a data series, or apply spectral filters. They are based on signal
processing theory and generate faster and more accurate trading signals than traditional indicators. Most filters below
are inspired from concepts and ideas by John Ehlers (see book list).

AGC(vars Data, int TimePeriod): var


AGC(vars Data, var alpha): var
Automatic gain control of the Data series, by John Ehlers. Transforms the Data series to the -1 .. +1 range with a fast-
attack, slow-decay algorithm. TimePeriod determines the time for reducing the gain by 1.5 db;
alternatively, alpha determines the reduction factor per bar period (< 1). Adjustment to higher values happens
immediately. The minimum length of the Data series is 1. The function internally creates series and thus must be called
in a fixed order in the script. Source available in indicators.c. See also scale.

Amplitude(vars Data, int TimePeriod): var


Returns the amplitude - the highest high minus the lowest low - of the Data series, smoothed over the TimePeriod with
an EMA so that the more recent fluctuations become more weight.

BandPass(vars Data, int TimePeriod, var Delta): var


Bandpass filter; lets only pass the cycles close to the given TimePeriod. The Delta value (0..1) gives the width of the
filter curve. Useful for retrieving a certain cycle from the data, while ignoring trend and cycles with different periods. The
filter can be made steeper by connecting two or more bandpass filters, f.i. BandPass (series (BandPass (Data,
TimePeriod, Delta)), TimePeriod, Delta). The minimum length of the Data series is 3. The function internally
creates series and thus must be called in a fixed order in the script.

Butterworth(vars Data, int CutoffPeriod): var


3-pole Butterworth lowpass filter that suppresses all cycles below the cutoff period. The suppression at
the CutoffPeriod is 3 dB. Often used for super-smoothing data. The minimum length of the Data series is 1. The
function internally creates series and thus must be called in a fixed order in the script. Source available in indicators.c.

DominantPeriod(vars Data, int Period): var


Time period of the current dominant cycle in the Data series, in bars; valid range = 10..60 bars, lag = ~10 bars. The
dominant cycle is the main periodicity in the data; its period is calculated with a Hilbert transform of the Data series. This
function is useful to detect seasonal behavior or to set up moving averages, bandpass or highpass filters so that they
adapt to the current market cycle. Period gives the bar period of maximum sensibility. This function works best with
a Lookback period of above 100 bars for detecting cycles. If Data is omitted, the current asset price series is used. The
function internally creates series and thus must be called in a fixed order in the script.

DominantPhase(vars Data, int Period): var


Current phase angle of the dominant cycle in the Data series, lag corrected; range 0..2*PI. The phase is normally
increasing, but can stop or even go backwards when the dominant period is undefined or changes suddenly. The
dominant cycle can be synthesized from the data by passing the phase to a sin function
(f.i. sin(DominantPhase(Data,30))). This allows determining the maxima and minima of the current market cycle. By
adding a constant to the phase (f.i. PI/4), a leading cycle can be generated for detecting the maxima and minima in
advance. Period gives the bar period of maximum cycle sensibility. This function works best with a Lookback period of
above 100 bars for detecting cycles. It also calculates the dominant period; results
in rDominantPeriod, rDominantPhase (returned). If Data is omitted, the current asset price series is used. The
function internally creates series and thus must be called in a fixed order in the script.

339
FIR3(vars Data): var
FIR4(vars Data): var
FIR6(vars Data): var
Simple finite impulse response lowpass filters with 3, 4, and 6 taps. Often used in more complex filters and indicators
for removing noise and smoothing the data. The lag is 1, 1.5, and 2.5 bars; the minimum length of the Data series is
equal to the number of taps. Source available in indicators.c

HighPass(vars Data, int CutoffPeriod): var


Wide band highpass filter for separating the cycles in the Data curve from the underlying trend. Attenuates cycles longer
than the cutoff period. Often used as an oscillator for identifying price turning points. Compared to traditional oscillators,
such as RSI or Stoch, this filter generates much smoother signals with less lag. The minimum length of the Data series
is 3. The function internally creates series and thus must be called in a fixed order in the script.

HighPass1(vars Data, int CutoffPeriod): var


1-pole digital highpass filter, attenuates cycles longer than the cutoff period and returns a curve consisting only of the
high frequency part of the data. Often used by other filters for retrieving the cycle part from the data. The minimum
length of the Data series is 8. The function internally creates series and thus must be called in a fixed order in the script.

HighPass2(vars Data, int CutoffPeriod): var


2-pole digital highpass filte, attenuates cycles longer than the cutoff period and returns a curve consisting only of the
high frequency part of the data. The minimum length of the Data series is 3. The function internally creates series and
thus must be called in a fixed order in the script. Source available in indicators.c

LowPass(vars Data, int CutoffPeriod): var


2-pole digital lowpass filter; suppresses high frequencies in the Data series, and thus attenuates all cycles that are
shorter than the cutoff period. Compared to an EMA (which is in fact a 1-pole lowpass filter), cycles longer than the
cutoff period are unaffected and passed without lag. This allows the LowPass function to react much faster and
generate signals earlier than moving averages. Replacing moving averages by lowpass filters improves most algorithms
and often makes the difference between a losing and a winning system. The minimum length of the Data series is 3.
The function internally creates series and thus must be called in a fixed order in the script.

Spectrum(vars Data, int TimePeriod, int SamplePeriod): var


Spectral analysis; returns the relative amplitude of the spectral component given by TimePeriod (~5..200). Can be used
to analyze the frequency spectrum of a price curve over the given SamplePeriod. The minimum length of
the Data series is SamplePeriod; set SamplePeriod (default = 2*TimePeriod) to a multiple of the TimePeriod in order
to compensate for spectral dilation. TimePeriod must keep its value from bar to bar, but Spectrum can be called
multiple times with different time periods and sample periods during the same bar, for generating a spectrum. The
function internally creates series and thus must be called in a fixed order in the script. See the Spectrum script and
the Strategy chapter for an example.

genSine(var Period1, var Period1): var


genSquare(var Period1, var Period2): var
Sine and square wave chirp generators for testing filters and algorithms. Generates a hyperbolic chirp with 1.0 amplitude
and a wave period changing linearly from Period1 to Period2. For a constant wave period, set both periods to the same
value. Source available in indicators.c. See Filter script and example below.

340
Output of some spectral filters applied to a sine chirp with a period from 5..60 bars (generated by the Filter test script);
top to bottom: Data, Leading Cycle, DominantPhase,
DominantPeriod, BandPass, HighPass, HighPass1, HighPass2, LowPass, Butterworth.

341
Standard parameters:
TimePeriod Number of bars for the time period of the function.

CutoffPeriod Number of bars for the period that determines the filter frequency.

Data A data series, often directly derived from the price functions price(), priceClose() etc.. Alternatively
a user created series or any other double float array with the given minimum length can be used. If
not mentioned otherwise, the minimum length of the Data series is TimePeriod.

Returns:
Filtered value from the Data series.

Remarks:

• Most filter functions are available in source code in the script file include\indicators.c, and can be studied for learning
how to code advanced filters and indicators.
• Some filter functions internally create data series, and thus require that they are called in a fixed order in the script
and don't depend on if conditions.
• All function parameters, except for data series, can be set to 0. Default values are then used.
• All filters can be tested with the Filter script (see above image).

Examples:
// plot some filters
function run()
{
set(PLOTNOW);
vars Price = series(price());

plot("LowPass",LowPass(Price,20),0,BLUE);
plot("HighPass",HighPass(Price,50),NEW,RED);
}
// test the dominant cycle detection with a sine chirp
function run()
{
set(PLOTNOW);
MaxBars = 2000;
ColorUp = ColorDn = 0; // don't plot a price curve
asset(""); // dummy asset

vars Sine = series(genSine(5,60));


plot("Sine",Sine[0],0,BLACK);
plot("Period",DominantPeriod(Sine,50),NEW,BLUE);
}
// plot the frequency spectrum of a price curve
function run()
{
set(PLOTNOW);
BarPeriod = 60;
StartDate = 20130101;
EndDate = 20130201;
LookBack = 300; // 2 x maximum Cycle

vars Price = series(price());


int Cycle;
for(Cycle = 5; Cycle < 150; Cycle += 1)
plotBar("Spectrum",Cycle,Cycle,Spectrum(Price,Cycle),BARS+AVG+LBL2,RED);
}

342
price (int offset) : var
Returns the mean ask price of the currently selected asset - the average of all price ticks inside the bar period resp.
time frame. This is normally the preferred price for indicators because it is less susceptible to random fluctuations and
makes systems more robust and independent on the data feed.

priceOpen (int offset) : var


priceClose (int offset) : var
priceHigh (int offset) : var
priceLow (int offset) : var
Returns the open, close, maximum and minimum ask price of a bar period resp. time frame with the current asset.

marketVal (int offset) : var


marketVol (int offset) : var
Returns additional market data, such as spread, unadjusted close, trade volume per minute, quote frequency, or similar
price accompanying data dependent on the implementation in the Broker API (Zorro S only). Only when T6 price
history is used for historical data and the LEAN flag is not set.

Parameters:
offset Optional bar number for which the prices are returned, in time frames before the current time frame. If
omitted, the price of the current bar is returned. Negative offsets return future prices when the PEEK flag is
set; otherwise they give an error message.

Returns:
Price or additional market data.

priceSet (int offset, var Open, var High, var Low, var Close)
Modifies the open, high, low, close, and mean price of the current asset for test purposes, artificial price curves, or for
prices from different sources. Use offset = -1 for modifying the prices of the next bar, which is necessary for entering
trades at the modified prices.

priceQuote (var Timestamp, var Quote) : int


Enters a new price quote of the current asset in the system; for HFT simulation or when prices are not available from
the broker connection. Updates LastPrice or Spread.

Parameters:
offset Optional bar number for which the prices are returned, in time frames before the current time frame. If
omitted, the price of the current bar is returned. Negative offsets return future prices when
the PEEK flag is set; otherwise they give an error message.

Timestamp The exchange timestamp of the quote in DATE format. Only quotes newer than the previous quote of
the same asset and type have an effect.

Quote The price. Quote > 0 indicates an ask price, Quote < 0 a bid price.

Returns:
1 when the price was changed due to a new timestamp, 0 otherwise.

343
Usage:
priceOpen(10) returns the opening price of 10 bars ago. priceClose() returns the close price of the current bar, i.e. the
most recent price.

Remarks:

• All prices used for trading or indicators are generally Ask prices - no matter if for a long or short trade. This way, stops,
entry limits or profit targets can be calculated without make a distinction between long and short trades. Zorro
automatically handles conversion from Ask to Bid when entering or exiting trades or setting stop loss, take profit, or
entry limits.
• The Bid price is the Ask price minus Spread; the pip value of a price is the price divided by the PIP variable.
• Historical prices are usually dividend and split adjusted. Some price sources, such as Yahoo or Quandl, let you select
between adjusted or unadjusted prices.
• If the LEAN flag is set and M1 historical data is used, the open and close prices of a bar are the mean prices of its first
and last M1 tick.
• At the initial run of the strategy before loading an asset, all price functions return 0 and LookBack is automatically
expanded to the biggest offset value.
• N bars must have passed for accessing the price of N bars ago. If offset is higher than the current bar number (Bar),
the price of the first bar is returned.
• After switching to a different asset, the price functions automatically change to the prices of that asset. For using non-
price data, such as quote frequency, trade volume, or external indicators, pseudo assets can be created f.i. by reading
data from external websites or level II price quotes. For details see data import.
• Artificial assets can be created by combining the prices from several real assets. An example can be found
under Hacks & Tricks.
• When using multiple time frames in the strategy (TimeFrame variable), the offset parameter gives the number of time
frames rather than the number of bars. The Open price is taken from the begin of the time frame, the Close price from
its end, and the High, Low, and mean prices are calculated from all bars belonging to the time frame.
• If Detrend is set to 2 or 3, the returned prices are detrended for removing trend bias from the simulation.
• If PEEK mode is set, price functions can peek into the future by using negative offsets. This is often required for
generating the advisor AI function from historical price data. If PEEK mode is not set, negative offsets will generate
an error message.
• In a TMF or tick function, priceClose() returns the last price quote, updated every tick when new price data becomes
available. price() returns the price averaged over all received ticks of the current bar so far; therefore it is more 'jaggy'
at the begin of the bar when few ticks were received yet, and more smooth towards the end of the bar
period. priceHigh() and priceLow() return the highest and lowest price of the current bar so far, so their distance is
small at the begin of the bar and wide towards the end.
• A series can be filled with prices by calling the series() function with the return value of the price function (see
example).
• More variants of prices - i.e. the Center Price, the Typical Price, the Haiken Ashi Price etc. - can be found on
the indicators page.

Example:
BarPeriod = 60; // set up 1 hour bars (60 minutes)

TimeFrame = 4;
asset("EUR/USD");
vars EUR4 = series(price()); // create a series of 4 hour EUR mean prices
...
TimeFrame = frameSync(24);
asset("SPX500");
vars SPXDay = series(priceClose()); // create a series of daily S&P 500 close prices
TimeFrame = 1;
...

344
frame (int Offset): bool
Returns true when the current TimeFrame ends at the given bar.

frameSync (int Period): int


Synchronizes TimeFrame to a fixed time period, such as a hour, a day, or a week. Returns 0 inside the time frame
and the negative number of skipped bars at the end of the time frame.

Parameters:
Offset Optional bar number, calculated from the current bar. Must be 0 when TimeFrame is synchronized to a
fixed time period or to an external event.

Period Time period to be synchronized, in bars. Must be an integer multiple of a hour, a day, or a week.

Remarks:

• TimeFrame = 24 is different to TimeFrame = frameSync(24). In the former case the time frame changes every 24
bars, in the latter case every day (assuming a 1-hour bar period), even if there is a different number of bars per day.

Examples:
function run()
{
BarPeriod = 60;
FrameOffset = 3;
TimeFrame = frameSync(24);
// the time frame will now change daily at 3:00
...
}

dayOpen (int zone, int day) : var


dayClose (int zone, int day) : var
dayHigh (int zone, int day) : var
dayLow (int zone, int day) : var
dayPivot (int zone, int day) : var
Returns the open, close, high, low, and pivot point calculated from stock exchange working hours (9:30 am until 16:00
pm) of a given working day and time zone. Can be used for seasonal trading, gap trading, or daily price action. The
pivot point is defined as (High+Low+Close)/3.

Parameters:
zone UTC for UTC time or ET for New York time, or a number giving the zone offset in hours to UTC. Daylight
saving time is considered in non-UTC zones from 2000 to 2019.

day Working day offset, i.e. 0 = today (see remarks), 1 = yesterday, 2 = day before yesterday, and so on.
Weekends are skipped, f.i. if today is Monday, 1 refers to last Friday.

Returns
Price.

Usage:
dayClose(ET,1) returns yesterday's closing price of the current asset at the New York Stock Exchange.

345
Remarks:

• During the LookBack period all day price functions return 0.


• The BarPeriod matters for precision. F.i. with a bar period of 60 minutes, opening and closing prices can be inaccurate
by one hour. For a time precision of 30 minutes use a BarPeriod of 30 or below.
• Requesting a time travel - for instance today's close price when the market is still open - will cause an error message
when the PEEK flag is not set.
• The working hours can be changed through the global variables StartMarket (default: 930)
and EndMarket (default: 1600) in the hhmm format. If the hours don't matter, use alternatively the price functions
from daily bars, or the HH / LL functions. Daily bars change at midnight UTC. By setting StartMarket and EndMarket
accordingly, arbitrary time periods can be used. F.i. for getting the high-low range of the first hour of a trading day,
set StartMarket at 930 and EndMarket at 1030.
• Some trade systems are based on fixed "support and resistance" price levels named S1, S2, S3, R1, R2, R3. They
can be calculated from dayPivot, dayHigh, and dayLow using the following formula:
S1 = 2*Pivot - High;
S2 = Pivot - (High - Low);
S3 = 2*Pivot - (2*High - Low);
R1 = 2*Pivot - Low;
R2 = Pivot + (High - Low);
R3 = 2*Pivot + (High - 2*Low);
• The source code of the day price functions can be found in include\indicators.c.

Example:
// simple gap trading system,
// based on a publication by David Bean
function run()
{
BarPeriod = 10;
LookBack = 100*24*60/BarPeriod; // 100 days lookback

asset("SPX500");
Stop = 100*PIP;
TakeProfit = 40*PIP;

vars Price = series(price());


var High = dayHigh(ET,1);
var Low = dayLow(ET,1);
var Close = dayClose(ET,1);

// enter a trade when the NYSE opens


if(High > 0 && Low > 0 && Close > 0
&& timeOffset(ET,0,9,30) == 0)
{
var Avg = SMA(Price,LookBack);
if(*Price > Close
&& *Price < High
&& *Price < Avg)
enterShort();

if(*Price < Close


&& *Price > Low
&& *Price > Avg)
enterLong();
}
}

346
Time and Calendar Functions
year (int offset): int
Year number of the given bar, f.i. 2011.

month (int offset): int


Month number of the given bar, 1..12.

week (int offset): int


Week number of the given bar, 1..52, aligned to Thursday.

day (int offset): int


Day of month of the given bar, 1..31.

date (int offset): int


Date of the given bar in the form yyyymmdd, f.i. 20150703 for July 3rd, 2015.

dom (int offset): int


Number of days of the month of the given bar, 28..31.

tdm (int offset): int


Trading day of the month of the given bar, 1..23. Weekends (see Weekend) are skipped when counting trading days.

tom (int offset): int


Number of trading days of the month of the given bar, 20..23. (tom(offset)-tdm(offset)) gives the days until the last
trading day of the month.

dow (int offset): int


Day of week of the given bar, MONDAY (1), TUESDAY (2) ... SUNDAY (7).

ldow (int zone,int offset): int


Local day of week of the given bar in the given time zone (see timeOffset below), considering daylight saving time
(see dst below). If BarZone is set to the local zone, ldow() is identical to dow().

hour (int offset): int


Closing hour of the given bar, 0..23. hour() gives the current hour and can be used to apply different trade tactics in the
US, European, or Asia-Pacific session.

lhour (int zone, int offset): int


Closing local hour of the given bar in the given time zone (see timeOffset below), considering daylight saving time
(see dst below). F.i. lhour(ET) returns the current hour at the New York Stock Exchange. If BarZone is set to the local
zone, lhour() is identical to hour().

minute (int offset): int


Closing minute of the given bar, 0..59. minute() gives the current minute.

tod (int offset): int


Time of day of the given bar in the form hhmm, f.i. 2107 for 9:07 pm.

347
ltod (int zone, int offset): int
Local time of day of the given bar in the form hhmm.

tow (int offset): int


Time of week of the given bar in the form dhhmm. d is the weekday number starting with Monday(1), f.i. 52107 = Friday
9:07 pm. See also StartWeek, EndWeek.

ltow (int zone, int offset): int


Local time of week of the given bar in the form dhhmm.

second (): var


Closing second of the current bar. The decimals give the fraction of the second with 1 ms precision.

wdate(int offset): var


Date and time of the given bar in Windows DATE format. Days are represented by whole number increments starting
with 30 December 1899, midnight UTC. Hour, minute, and second values are expressed as a fraction of a day in the
fractional part of the number. This format allows easy and precise date comparison.

minutesAgo (int offset): int


Total number of minutes between the end of the bar with the given offset and the end of the current bar.

minutesWithin (int offset): var


Total number of minutes from start to end of the bar with the given offset. Normally identical to BarPeriod, but can be
different due to weekends, holidays, daylight saving time, gaps due to lack of price quotes, or when called from an
intrabar function.

workday (int offset): bool


Returns true when the end of the given bar is not within a holiday or a weekend determined
by StartWeek and EndWeek (for details see Weekend).

market (int zone, int offset): bool


Returns true when the end of the given bar is within market hours given by StartMarket and EndMarket.

dst (int zone, int offset): int


Returns 1 when daylight saving time is active at the end of the given bar, otherwise 0. A simplified daylight saving
schedule is used, based on US east coast daylight saving periods for all time zones in America, Sydney daylight saving
periods for Australian and Pacific time zones, and European daylight saving periods for all other time zones. UTC and
JST time zones have no daylight saving time.

Parameters:
offset Optional bar number for which the date or time is calculated, relative to the current bar; or 0 for the current
bar; or NOW for the current PC time. When multiple time frames are used, the offset is multiplied
with TimeFrame.

zone UTC for UTC time (GMT+0), WET for London, CET for Frankfurt, ET for New York, JST for Tokyo, AEST for
Sydney, or a number giving the zone offset in hours to UTC. Daylight saving time is considered in non-UTC
zones for all years from 2000 to 2024.

Usage:
hour(0) calculates the hour of the current bar.

348
strdate (string Format, int offset): string
strdate (string Format, var Date): string
Returns a temporary string with the date and time of the given bar offset resp. DATE variable, to be used f.i.
in printf statements.

strwdate (string Format, string DateTime): var


Returns date and time of the given DateTime string in Windows DATE format, like wdate(). Can be used to parse
date/time fields in CSV files in various formats. Zorro 1.51 or above.

Parameters:
Format Format string with the following codes (similar to the C strftime function):
%a - Abbreviated weekday name
%A - Full weekday name
%b - Abbreviated month name
%B - Full month name
%d - Day of month as decimal number (01 – 31)
%H - Hour in 24-hour format (00 – 23)
%j - Day of year as decimal number (001 – 366)
%m - Month as decimal number (01 – 12)
%M - Minute as decimal number (00 – 59)
%S - Second as decimal number (00 – 59)
%U - Week of year as decimal number, with Sunday as first day of week (00 – 53)
%W - Week of year as decimal number, with Monday as first day of week (00 – 53)
%y - Year without century, as decimal number (00 – 99)
%Y - Year with century, as decimal number
%% - Percent sign

offset Optional bar number; see above.

Date Date/time in DATE format, as returned from wdate().

DateTime Date/time string in various formats.

Usage:
printf("\nTime: %s",strdate("%Y-%m-%d %H:%M:%S")); prints the current bar time in year-month-day
hour:minute:second format.

timeOffset (int zone, int days, int hour, int minute): int
Returns the offset (in bars) of the bar closing at the given time, or 0 if the time lies within the current bar (including the
weekend), or -1 if the time lies in the future.

Parameters:
zone UTC for UTC time, ET for New York, WET for London, CET for Frankfurt, AEST for Sydney, JST for Tokyo,
or a number giving the zone offset in hours to UTC. Daylight saving time is considered in non-UTC zones
from 2000 to 2024.

days Number of days in the past, or 0 for the day of the current bar.

hour Hour of the day, 0..23. If above 23, days is adjusted accordingly.

minute Minute of the hour, 0..59. If above 59, hour is adjusted accordingly.

349
Usage:
timeOffset(ET,1,16,0) returns the bar offset of yesterday's NYSE closing time. timeOffset(ET,0,9,30) returns 0 when
the NYSE is just opening.

Remarks:

• If not stated otherwise, all dates and times are UTC (Greenwich mean time without daylight saving time).
• Date and time functions return 0 at the initial run of the strategy when the bars are not set up yet.
• When offset is omitted, the time of the current bar is returned. When offset is set to NOW, the current time is returned.
• The offset parameter is affected by TimeFrame. F.i. when TimeFrame is 6 and offset is 3, the time of 3*6 = 18 bars
ago is returned.
• The time functions require a LookBack period of at least the highest used or returned offset.
• Most date and time functions return int, but some return var - take care of that when using the functions in
a printf statement (see example).
• In live trading the time reflects the server time. !! Do not rely on 100% precision when using time conditions for trade
entry. A typical trap is checking the weekday (dow()) exactly at midnight. If the server time is a second slower than
your PC time, you might get a tick with a 23:59:59 time stamp, while your PC clock has already the next day at 00:00:00.
When using daily bars, check the whole time and not only the day, or set the bar offset so that the bars do not change
exactly at midnight.

Examples:
// buy at UTC midday at the last day of the month,
// sell 2 trading days later
function run()
{
BarPeriod = 60;
algo("Monthly");
if(NumOpenLong == 0) { // not yet bought this month
if((tom(0) == tdm(0)) && hour() >= 12)
enterLong();
} else if(tdm(0) >= 2)
exitLong();
}

// helper function in default.c for printing the date


string datetime()
{
return strf("%02d.%02d.%02d %02d:%02d:%02d",
day(),month(),year()-2000,hour(),minute(),(int)second());
}

350
crossOver (vars Data1, vars Data2) : bool
crossUnder (vars Data1, vars Data2) : bool
crossOver (vars Data, var border) : bool
crossUnder (vars Data, var border) : bool
Determines if the data1 series crosses over or under the data2 series, or over or under a fixed border value. Often
used for trade signals.

Parameters:
Data1 First data series.

Data2 Second data series.

border border value.

Returns:
true if the first series crosses the second, false otherwise.

Modifies
rMomentum - Data movement in percent per time frame at the time of the crossing; indicates the 'speed' of the
crossover.

Algorithms:
bool crossOver(var* data1,var* data2) { return (data1[0] > data2[0]) && (data1[1] < data2[1]); }
bool crossUnder(var* data1,var* data2) { return (data1[0] < data2[0]) && (data1[1] > data2[1]); }
bool crossOver(var* data,var border) { return (data[0] > border) && (data[1] < border); }
bool crossUnder(var* data,var border) { return (data[0] < border) && (data[1] > border); }

Remarks:

• The data series must have a minimum length of 2.


• For a fuzzy logic version that also detects 'almost crossovers', use crossOverF / crossUnderF.
• For checking if a crossover occurred n bars before, use crossOver(Data1+n,Data2+n).
• For predicting a future crossover, or for detecting only 'strong crossovers', use the predict function.
• !! Identical values of the two series for one or several bars will not produce a crossover signal. If that is desired, define
a function similar to those above, but with >= and <= comparisons instead of > and < for the first term.

Example:
function run()
{
vars Price = series(priceClose());
vars SMA100 = series(SMA(Price,100));
vars SMA30 = series(SMA(Price,30));

if(crossOver(SMA30,SMA100))
enterLong();
else if(crossUnder(SMA30,SMA100))
enterShort();
}

351
peak (vars Data) : bool
valley (vars Data) : bool
Determines if a series had a maximum (peak) or minimum (valley) at the previous bar.

Parameters:
Data Data series.

Returns:
true if the series had a peak resp. valley at the previous bar, false otherwise.

Modifies
rMomentum - Data movement in percent per time frame; indicates the 'sharpness' of the peak or valley.

Algorithm:
bool peak(var* data) { return (data[2] < data[1]) && (data[1] > data[0]); }
bool valley(var* data) { return (data[2] > data[1]) && (data[1] < data[0]); }

Remarks:

• The series must have a minimum length of 3.


• A 'flat line' of the data series for several bars will not produce a peak or valley signal.
• For a fuzzy logic version that also detects 'almost peaks' and 'almost valleys', use peakF / valleyF.
• For checking if a peak occurred n bars before, use peak(Data+n).
• For predicting a future peak or valley, or for detecting only 'strong peaks' or 'strong valleys', use the predict function.

Example:
function run()
{
vars Trend = series(LowPass(series(price()),1000),3);
if(valley(Trend))
enterLong();
if(peak(Trend))
enterShort();
}

rising (vars Data) : bool


falling (vars Data) : bool
Determines if a series rises or falls, i.e. if Data[0] is above or below Data[1].

Parameters:
Data Data series.

Returns:
true if the series rises resp. falls, false otherwise.

Modifies
rMomentum - Data movement in percent per time frame; indicates the 'speed' of the rising or falling.

Remarks:

• The data series must have a minimum length of 2.


352
• For a fuzzy logic version, use risingF/fallingF.
• For checking if the series was rising n bars before, use rising(Data+n).

Example:
function run()
{
...
vars Price = series(price());
if(rising(Price))
enterLong();
else
exitLong();
}

sortData (var* Data, int Length)


Sorts the Data array in increasing order.

sortIdx (var* Data, int Length): int*


Does not affect the Data array, but returns a sorted array of numbers, beginning with the number of the
smallest Data value and ending with the number of the greatest Data value.

Parameters:
Data Array or series to be sorted

Length Length of the Data array

Returns
Temporary pointer to an int array containing the numbers of the Data values sorted in increasing order (sortIdx).

Remarks

• For sorting or searching arrays, the standard C functions qsort and bsearch can alternatively be used. They are
available in the stdio.h include file.

Example:
// Spearman trend indicator
var Spearman(var* Data,int Period)
{
Period = clamp(Period,2,g->nLookBack);
int* Idx = sortIdx(Data,Period);
var sum = 0;
int i,n;
for(i=0,n=Period-1; i<Period; i++,n--)
sum += (n-Idx[i])*(n-Idx[i]);
return 1. - 6.*sum/(Period*(Period*Period-1.));
}

353
rev (vars Data, int length): vars
Returns a reverse copy of the Data series so that it starts with the oldest elements. Useful for sending data series to
other software that needs the series in old-to-new order.

Parameters:
Data The series or array to be reversed.

length The number of elements to be reversed; LookBack if omitted.

Returns
New series in reverse order.

Remarks

• This function generates a series and thus must be called in a fixed order in the script.

Example:
// generate reverse price series (latest prices last)
vars O = rev(series(priceOpen())),
H = rev(series(priceHigh())),
L = rev(series(priceLow())),
C = rev(series(priceClose()));

diff (var x): var


Returns the difference of x to its value from one bar before.

Parameters:
x Variable for difference calculation.

Returns
x - previous x

Remarks

• This function generates a series and thus must be called in a fixed order in the script.
• diff(log(x)) returns the log return of x , since log(a/b) = log(a) - log(b).

Example:
// generate a price difference serie
vars Changes = series(diff(priceClose()));

354
adviseLong (int Method, var Objective, var Signal0, var Signal1, ...): var
adviseShort (int Method, var Objective, var Signal0, var Signal1, ...): var
Machine learning functions for training and predicting trade results from a combination of input signals. They can be
used for finding price or indicator patterns that precede profitable trades. The advise functions are used for training the
algorithm and also for predicting the result, dependent on whether the script is run in training or in test/trade mode. In
training mode, Zorro learns rules that predict the success of a trade from the signals, and generates a C function or
model containing those rules. In test or trade mode, the trained model or C function is executed on every advise call,
applies the rules to the current signals, and returns the trade success prediction.

Zorro can use internal or external machine learning algorithms, f.i. from R packages, for the prediction. Three internal
prediction methods are available: a decision tree (Method == DTREE), a simple neural network (Method ==
PERCEPTRON), or a candle or signal pattern detector (Method == PATTERN). All three methods generate rule
functions separately for any WFO cycle, asset, algorithm identifier, and long or short trade. The correct function is
automatically selected when calling adviseLong or adviseShort in test or trade mode. The function takes the signals
as parameters, and returns the win or loss prediction accuracy in percent.

Returns:
[Train] mode: 0 when Objective != 0, otherwise 100 (so that dependent trades are always executed for training).
[Test], [Trade] mode: Prediction of Objective, or of trade success when Objective was 0 during training. Trade
success prediction is normally in ~ -100 .. +100 range. When the value is above a threshold, Zorro advises entering a
trade, otherwise not.

Parameters:
Method 0 - use the method and signals of the last advise call.
SIGNALS - don't predict; just export Signals and Objective in [Train] mode to a Data\signals.csv file
for testing external machine learning functions.
NEURAL - train and predict with an external machine learning algorithm.
DTREE - train and predict with a decision tree.
PERCEPTRON - train and predict with a simple neural net.
PATTERN - train and predict with a signal pattern analyzer.
+FAST - fast and complex pattern finding (for PATTERN).
+FUZZY - fuzzy pattern finding (for PATTERN).
+2 .. +6 - number of pattern groups (for PATTERN).
+BALANCED - enforce the same number of positive and negative target values by replication
(for SIGNALS, NEURAL, DTREE, PERCEPTRON).
Objective The training target, or 0 for using the profit or loss of the next trade as target. A positive value advises to
enter a trade when this signal combination occurs, a negative value advises against a trade. The
prediction is better when positive and negative target values occur with about the same frequency during
the training run (see remarks). The Objective parameter is only used in the training run and has no
meaning in test or trade mode. When calculating this parameter, make sure that it never becomes 0, or
else the next trade result is used for the training target.

Signal0, The features of the method. Up to 20 values that carry information about the current market situation, or
... can otherwise be used for predicting the success or failure of a trade. Useful signals could be candle
patterns, price differences, indicators, filters, or statistics functions. All signal values should be in the
same range (see remarks). If all signals are omitted, the signals from the last advise call are used.

Decision Tree

A decision tree is a tree-like graph of decisions by comparing signals with fixed values. The values and the tree
structure are generated in the training run. For this the training process iterates through the sets of signals and finds the
signal values with the lowest information entropy. These values are then used to split the data space in a profitable and
a non profitable part, then the process continues with iterating through the parts. Details about the decision tree algorithm
can be found in books about machine learning.

The signals should be normalized roughly to the -100..100 range for best precision. They should be carefully selected

355
so that the displayed prediction accuracy is well above 60% in all WFO cycles. Decision trees work best with signals
that are independent of each other. They do not work very well when the prediction depends on a linear combination of
the signals. In order to reduce overfitting, the resulting trees are pruned by removing non-predictive signals. The output
of the tree is a number between -100 .. +100 dependent on the predictive quality of the current signal combination.

The decision tree functions are stored in C source code in the \Data\*.c file. The functions are automatically included in
the strategy script and used by the advise function in test and trade mode. They can also be exported for using them
in strategy scripts or expert advisors of other platforms.The example below is a typical Zorro-generated decision tree:

int EURUSD_S(var* sig)


{
if(sig[1] <= 12.938) {
if(sig[3] <= 0.953) return -70;
else {
if(sig[2] <= 43) return 25;
else {
if(sig[3] <= 0.962) return -67;
else return 15;
}
}
}
else {
if(sig[3] <= 0.732) return -71;
else {
if(sig[1] > 30.61) return 27;
else {
if(sig[2] > 46) return 80;
else return -62;
}
}
}
}

The advise() call used 5 signals, of which the first and the last one - sig[0] and sig[4] - had no predictive power, and
thus were pruned and do not appear in the tree. Unpredictive signals are displayed in the message window.

Example of a script for generating a decision tree:

void run()
{
BarPeriod = 60;
LookBack = 150;
TradesPerBar = 2;
if(Train) Hedge = 2;
set(RULES|TESTNOW);
// generate price series
vars H = series(priceHigh()),
L = series(priceLow()),
C = series(priceClose());

// generate some signals from H,L,C in the -100..100 range


var Signal1 = (LowPass(H,1000)-LowPass(L,1000))/PIP;
var Signal2 = 100*FisherN(C,100);

// train and trade the signals


Stop = 4*ATR(100);
TakeProfit = 4*ATR(100);
if(adviseLong(DTREE,0,Signal1,Signal2) > 0)
enterLong();
if(adviseShort(DTREE,0,Signal1,Signal2) > 0)
enterShort();
}

Perceptron

A perceptron is a simple neural net, consisting of one neuron with up to 20 signal inputs and one binary output. It
calculates its predictions from a linear combination of weighted signals. The signal weights are generated in the training
run for producing the best possible prediction.

The perceptron algorithm works best when a weighted sum of the signals has predictive power. It does not work well
when the prediction requires a nonlinear signal combination, i.e. when trade successes and failures are not separated

356
by a straight plane in the signal space. A classical example of a function that a perceptron can not emulate is a logical
XOR. Often a perceptron can be used where a decision tree fails, and vice versa.

The perceptron learning algorithm generates prediction functions in C source code in the \Data\*.c file. The functions
are automatically included in the strategy script and used by the advise function in test and trade mode. They can also
be exported for using them in strategy scripts or expert advisors of other platforms. The output is binary: either >0 for a
positive or <0 for a negative prediction. A generated perceptron function with 3 signals can look like this:

int EURUSD_S(var* sig)


{
if(-27.99*sig[0] + 1.24*sig[1] - 3.54*sig[2] > -21.50)
return 100;
else
return -100;
}
Signals that do not contain useful market information get a weight of 0.

Pattern Analyzer

The pattern analyzer is an intelligent version of classic candle pattern indicators. It does not use predefined patterns,
but learns them from historic price data. It's normally fed with the open, close, high or low prices of a number of candles.
It compares every signal with every other signal, and uses the comparison results - greater, smaller, or equal - for
classifying the pattern. Equality is detected in a 'fuzzy' way: Signals that differ less than the FuzzyRange are considered
equal.

The signals can be divided into groups with the PATTERN+2 .. PATTERN+6 methods. They divide the signals into two
to six pattern groups and only compare signals within the same group. This is useful when, for instance, only the first
two candles and the last two candles of a 3-candle pattern should be compared with each other, but not the first candle
with the third candle. PATTERN+2 requires an even number of signals, of which the first half belongs to the first and
and the second half to the second group. PATTERN+3 likewise requires a number of signals that is divisible by 3, and
so on. Pattern groups can share signals - for instance, the open, high, low, and close of the middle candle can appear
in the first as well as in the second group - as long as the total number of signals does not exceed 20.

Aside from grouping, Zorro makes no assumptions of the signals and their relations. Therefore the pattern analyzer can
be also used for other signals than candle prices. All signals within a pattern group should have the same unit for being
comparable, but different groups can have different units. For candle patterns, usually the high, low, and close of the
last 3 bars is used for the signals - the open is not needed as it's normally identical with the close of the previous candle.
More signals, such as the moving average of the price, can be added for improving the prediction (but in most cases
won't).

The pattern analyzer generates pattern finding functions in C source code in the \Data\*.c file. The functions are
automatically included in the strategy script and used by the advise function in test and trade mode. They can also be
exported for using them in strategy scripts or expert advisors of other platforms. They find all patterns that occurred 4
or more times in the training data set and had a positive profit expectancy. They return the pattern's information ratio -
the ratio of profit mean to standard deviation - multiplied with 100. The better the information ratio, the more predictive
is the pattern. A typical pattern finding function with 12 signals looks like this:

int EURUSD_S(float* sig)


{
if(sig[1]<sig[2] && eqF(sig[2]-sig[4]) && sig[4]<sig[0] && sig[0]<sig[5] && sig[5]<sig[3]
&& sig[10]<sig[11] && sig[11]<sig[7] && sig[7]<sig[8] && sig[8]<sig[9] && sig[9]<sig[6])
return 19;
if(sig[4]<sig[1] && sig[1]<sig[2] && sig[2]<sig[5] && sig[5]<sig[3] && sig[3]<sig[0] && sig[7]<sig[8]
&& eqF(sig[8]-sig[10]) && sig[10]<sig[6] && sig[6]<sig[11] && sig[11]<sig[9])
return 170;
if(sig[1]<sig[4] && eqF(sig[4]-sig[5]) && sig[5]<sig[2] && sig[2]<sig[3] && sig[3]<sig[0]
&& sig[10]<sig[7] && eqF(sig[7]-sig[8]) && sig[8]<sig[6] && sig[6]<sig[11] && sig[11]<sig[9])
return 74;
if(sig[1]<sig[4] && sig[4]<sig[5] && sig[5]<sig[2] && sig[2]<sig[0] && sig[0]<sig[3] && sig[7]<sig[8]
&& eqF(sig[8]-sig[10]) && sig[10]<sig[11] && sig[11]<sig[9] && sig[9]<sig[6])
return 143;
if(sig[1]<sig[2] && eqF(sig[2]-sig[4]) && sig[4]<sig[5] && sig[5]<sig[3] && sig[3]<sig[0]
&& sig[10]<sig[7] && sig[7]<sig[8] && sig[8]<sig[6] && sig[6]<sig[11] && sig[11]<sig[9])
return 168;
....
return 0;
}

357
The eqF function in the code above checks if two signals are almost equal, i.e. differ less than FuzzyRange.

There are two additional special methods for the pattern analyzer. The FUZZY method generates a pattern finding
function that also finds patterns that can slightly deviate from the profitable patterns in the training data set. It gives
patterns a higher score when they 'match better'. The deviation can be set up with FuzzyRange. A typical fuzzy pattern
finding function looks like this:

int EURUSD_S(float* sig)


{
double result = 0.;
result += belowF(sig[1],sig[4]) * belowF(sig[4],sig[2]) * belowF(sig[2],sig[5]) * belowF(sig[5],sig[3]) *
belowF(sig[3],sig[0])
* belowF(sig[10],sig[11]) * belowF(sig[11],sig[7]) * belowF(sig[7],sig[8]) * belowF(sig[8],sig[9]) *
belowF(sig[9],sig[6]) * 19;
result += belowF(sig[4],sig[5]) * belowF(sig[5],sig[1]) * belowF(sig[1],sig[2]) * belowF(sig[2],sig[3]) *
belowF(sig[3],sig[0])
* belowF(sig[10],sig[7]) * belowF(sig[7],sig[11]) * belowF(sig[11],sig[8]) * belowF(sig[8],sig[9]) *
belowF(sig[9],sig[6]) * 66;
result += belowF(sig[4],sig[1]) * belowF(sig[1],sig[2]) * belowF(sig[2],sig[0]) * belowF(sig[0],sig[5]) *
belowF(sig[5],sig[3])
* belowF(sig[10],sig[11]) * belowF(sig[11],sig[7]) * belowF(sig[7],sig[8]) * belowF(sig[8],sig[6]) *
belowF(sig[6],sig[9]) * 30;
result += belowF(sig[1],sig[4]) * belowF(sig[4],sig[2]) * belowF(sig[2],sig[5]) * belowF(sig[5],sig[3]) *
belowF(sig[3],sig[0])
* belowF(sig[7],sig[10]) * belowF(sig[10],sig[11]) * belowF(sig[11],sig[8]) * belowF(sig[8],sig[6]) *
belowF(sig[6],sig[9]) * 70;
result += belowF(sig[4],sig[5]) * belowF(sig[5],sig[1]) * belowF(sig[1],sig[2]) * belowF(sig[2],sig[3]) *
belowF(sig[3],sig[0])
* belowF(sig[7],sig[10]) * belowF(sig[10],sig[8]) * belowF(sig[8],sig[11]) * belowF(sig[11],sig[9]) *
belowF(sig[9],sig[6]) * 108;
...
return result;
}

The belowF function is described on the Fuzzy Logic page.

The FAST method does not generate C code; instead it generates a list of patterns that are classified with alphanumeric
names. For finding a pattern, it is classified and its name compared with the pattern list. This is about 4 times faster than
the pattern finding function in C code, and can also handle bigger and more complex patterns. It can make a remarkable
difference in backtest time or when additional parameters have to be trained. A pattern name list looks like this (the
numbers behind the name are the information ratios):

/* Pattern list for EURUSD_S


HIECBFGAD 61
BEFHCAIGD 152
EHBCIFGAD 73
BEFHCIGDA 69
BHFECAIGD 95
BHIFECGAD 86
HBEIFCADG 67
HEICBFGDA 108
...*/

The FAST method can not be used in combination with FUZZY or with FuzzyRange. But the FAST as well as
the FUZZY method can be combined with pattern groups (f.i. PATTERN+FAST+2).

The find rate of the pattern analyzer can be adjusted with two variables:

PatternCount
The minimum number of occurrences of the found patterns in the analyzed price curve; default = 4.

PatternRate
The minimum win rate of the found patterns, in percent; default = 50.

An example of a pattern trading script can be found in Workshop 7.

358
Machine Learning

The NEURAL method uses external machine learning libraries with neural networks, support vector machines, or deep
learning algorithms for predicting trade results. Such algorithms are available in R packages; therefore
the NEURAL method will often call R functions for training and prediction. Alternatively, any DLL-based machine
learning library can be used (look here for accessing DLL classes and functions). The algorithm is implemented with a
single user-provided function:

neural (int mode, ínt model, int numSignals, void* Data): var
This function is called several times during the training, test, or trade process. It has access to all global and
predefined variables. Its behavior depends on mode:

mode Parameters Description

NEURAL_INIT --- Initialize the machine learning library (f.i. by calling Rstart) before running
the simulation. Return 0 if the initialization failed, otherwise 1. The script is
aborted if the system can not be initialized.

NEURAL_EXIT --- Close the machine learning library (not required for R).

NEURAL_LEARN model, Use a single sample of signals contained in the Data double array for
numSignals, training the model identified by the model index. The last
Data element, Data[numSignals], is the Objective parameter or the trade result.
The function is triggered by any advise call in [Train] mode, either
immediately (Objective != 0) or when the trade is closed (Objective == 0).

NEURAL_TRAIN model, Batch training. Alternative to NEURAL_LEARN: train a model (identified by


numSignals, the model index) with all collected data samples. The function is called at
Data the end of every asset/algo loop cycle in the training run. The model number
counts the asset, algo, and long/short combinations. The samples are
provided in CSV format in the Data string. The columns of the CSV table are
the signals, the last column is the Objective parameter or the trade result.
The prediction accuracy in percent can be optionally returned by
the neural function; otherwise return 1 if no accuracy is calculated, or 0 for
aborting the script when the training failed.
NEURAL_PREDICT model, Return the value predicted by the model with the given model index, using
numSignals, the signals contained in the Data double array. The function is called by
Data an advise call in [Test] and [Trade] mode. The model parameter is the
number of the model.

NEURAL_SAVE Data Save all trained models in the file with the name given by the string Data.
The function is called at the end of every WFO cycle in [Train] mode.

NEURAL_LOAD Data Load all trained models from the file with the name given by the string Data.
The function is called at the begin of every WFO cycle in [Test] mode, and
at the begin of a [Trade] session. It is also called every time when the model
file is updated by re-training.

The model index is the number of the trained model - for instance a set of decision rules, or a set of weights of a neural
network - starting with 0. When several models are trained for long and short predictions and for different assets or
algos, the index selects one of the models. In R, models can be stored in a list of lists and accessed through their index
(f.i. Models[[model+1]]). Any aditional parameters generated in the training process - for instance, a set of
normalization factors or selection masks for the signals - can be saved together with the models.

The numSignals parameter is the number of signals passed to the advise function. It is normally identical to the number
of trained features.

The Data parameter provides data to the function. The data can be of different type.
For NEURAL_LEARN/NEURAL_PREDICT it's a pointer to a double array of length numSignals+1, containing the
signal values plus the prediction objective or trade result at the end. Note that a plain data array has no "dim names" or
359
other R gimmicks - if they are needed in the R training or predicting function, add them there.
For NEURAL_TRAIN the Data parameter is a text string containing all samples in CSV format. The string can be stored
in a temporary CSV file and then read by the machine learning algorithm for training the model.
For NEURAL_SAVE/NEURAL_LOAD the Data parameter is the suggested file name for saving or loading the trained
models separately for any WFO cycle in the Data folder. Use the slash(string) function for converting backslashes to
slashes when required for R file paths.

This is the default neural function in the r.h file for using a R machine learning algorithm. It expects 4 R functions
named neural.train, neural.predict, neural.save, and neural.init in a R script in the Strategy folder. The R script has
the same name as the strategy script, but extension .r instead of .c. If required for special purposes, the
default neural function can be replaced by a user-supplied function.

var neural(int mode, int model, int numSignals, void* Data)


{
if(!wait(0)) return 0;
// open an R script with the same name as the stratefy script
if(mode == NEURAL_INIT) {
if(!Rstart(strf("%s.r",Script),2)) return 0;
Rx("neural.init()");
return 1;
}
// export batch training samples and call the R training function
if(mode == NEURAL_TRAIN) {
string name = strf("Data\\signals%i.csv",Core);
file_write(name,Data,0);
Rx(strf("XY <- read.csv('%s%s',header = F)",slash(ZorroFolder),slash(name)));
Rset("AlgoVar",AlgoVar,8);
if(!Rx(strf("neural.train(%i,XY)",model+1),2))
return 0;
return 1;
}
// predict the target with the R predict function
if(mode == NEURAL_PREDICT) {
Rset("AlgoVar",AlgoVar,8);
Rset("X",(double*)Data,numSignals);
Rx(strf("Y <- neural.predict(%i,X)",model+1));
return Rd("Y[1]");
}
// save all trained models
if(mode == NEURAL_SAVE) {
print(TO_ANY,"\nStore %s",strrchr(Data,'\\')+1);
return Rx(strf("neural.save('%s')",slash(Data)),2);
}
// load all trained models
if(mode == NEURAL_LOAD) {
printf("\nLoad %s",strrchr(Data,'\\')+1);
return Rx(strf("load('%s')",slash(Data)),2);
}
return 1;
}

The neural function is compatible with all Zorro trading, test, and training methods, including walk forward analysis,
multi-core parallel training, and multiple assets and algorithms. An example of using advise(NEURAL,...) for a short-
term deep learning system can be found on Financial Hacker, as well as a machine learning overview.

Remarks:

• The RULES flag must be set for generating rules or training a machine learning algorithm. See the remarks
under Training for special cases when RULES and PARAMETERS are generated at the same time.
• The generated rules by DTREE, PERCEPTRON, PATTERN are stored in plain text in a *.c file in the Data folder. This
allows to export the rules to other platforms, and to look into the prediction process and to check if the rules makes
sense.
• During the LookBack period, advise returns 0 and no rules are trained.
• For training trade results, call the advise function with Objective = 0 just before entering a trade; Zorro then uses the
next trade's result for learning the rules. When training long and short trades at the same time, set Hedge to make
sure that they can be opened simultaneously in training mode; otherwise any trade would close an opposite trade just
entered before. Define an exit condition for the trade, such as a timed exit after a number of bars, or a stop/trailing
mechanism for predicting a positive or negative excursion. Do not train rules with hedging explicitly disabled or with
special modes such as NFA or Virtual Hedging.

360
• Most machine learning algorithms require that training targets are balanced. That means the trades used for training
should result in about 50% wins and 50% losses, and negative and positive Objective values should be equally
distributed. If in doubt, add +BALANCED to the method; this will simply copy samples until balance is reached.
• Functions like scale or Normalize can be used to convert the signals to the same range. Some R machine learning
algorithms require that signals and targets are in the 0..1 range. In that case negative values or values greater than 1
lead to wrong results.
• When using a future value for prediction, such as the price change of the next bars (f.i. Objective = priceClose(-5) -
priceClose(0)), make sure to set the PEEK flag in train mode. Alternatively, use the price change of a past bar to the
current bar, and pass past signals - f.i. from a series - to the advise function in train mode, f.i.:
Objective = priceClose(0) - priceClose(5);
int Offset = ifelse(Train,5,0);
var Prediction = adviseLong(Method,Objective,Signal0[Offset],Signal1[Offset],...);
• All advise calls for a given asset/algo combination must use the same Method, the same signals, and the same
training target type (either Objective or trade result). However, different methods and signals can be used for different
assets and algorithm identifiers, so call algo() for combining multiple advise methods in the same script. Ensemble or
hybrid methods can also be implemented this way, using different algo identifiers.
• When trading is disabled, f.i. during the LookBack period, at weekend, due to a SKIP flag or during the inactive bars
of a time frame, the advise functions won't train or predict and return 0.
• For training several objectives with the NEURAL method, pass the further objectives as Signal parameters to
the advise function. For using more than 20 signals, collect them in a global array and send them to the machine
learning algorithm with NEURAL_LEARN or NEURAL_TRAIN.
• Machine learning functions have a tendency to greatly overfit the strategy. It's rather easy to get almost 1000% annual
return with rules generated from the same price data as used in the test. For this reason, strategies that use
the advise function must always be tested with unseen data, f.i. by using Out-Of-Sample testing or Walk Forward
Optimization. Use the OPENEND flag for preventing that trades prematurely close at the end of a WFO training period
and thus reduce the training quality.
• The estimated prediction accuracy resp. the number of found patterns are printed in the message window. The signals
should be selected in a way that the prediction accuracy is above 50% or that as many as possible profitable patterns
are found.

predict (int type, vars Data, int TimePeriod, var Threshold) : int
Predicts an event, such as a crossover of two curves or a peak of a curve, several bars before it happens.
A polynomial regression function is used for the prediction. This function can be used to generate very early trade
signals.

Most trade systems analyze the recent price curve with functions or indicators that introduce more or less lag. This
means that trade signals are always late, which greatly reduces the profit of a system. One way to minimize lag is using
low-lag functions (for instance, lowpass filters instead of moving averages). Another way is predicting the signals
before they actually occur. This is the purpose of the predict function that works by extrapolating signal curves into the
future.
361
Parameters:
type Event to be predicted:
CROSSOVER - crossing of Data over the zero line
PEAK - Data peak
VALLEY - Data valley
+PARABOLIC - use parabolic instead of linear regression.

Data Data series to be predicted, with a minimum length of TimePeriod.

TimePeriod Number of bars used for the prediction.

Threshold Prediction threshold, or 0 for no threshold.

Returns
Bar offset (negative) of the predicted event, in the -TimePeriod..0 range. 0 predicts that the event will happen during
the next bar. -TimePeriod is returned when no event is predicted within the time period.

Modifies
rMomentum - Data movement per bar at the time of the event.

Remarks:

• The predict function does not peek into the future, but uses past data for predicting events on the future curve.
• For predicting a crossover of two data series, or a series crossing over a fixed value, call predict with a series of the
differences, f.i. vars Difference = series(Data1[0] - Data2[0]);. For a crossunder, reverse the differences (see
example).
• Crossovers are detected with higher precision and less false signals than peaks or valleys.
• Threshold can be used to filter out 'weak' signals, f.i. from a crossing of two almost parallel lines. It is the minimum
momentum of the Data line divided by the correlation coefficient.
• For parabolic regression, add +PARABOLIC to the type. It detects events earlier than linear regression, but tends to
produce more false signals.

Examples:
function run()
{
vars Price = series(price());
var LP50 = LowPass(Price,50);
var LP150 = LowPass(Price,150);

var CO = predict(CROSSOVER,series(LP50-LP150),10,0.5*PIP); // predict crossover


var CU = predict(CROSSOVER,series(LP150-LP50),10,0.5*PIP); // predict crossunder

plot("LP50",LP50,0,RED);
plot("LP150",LP150,0,BLUE);
plot("CrossOver",CO,NEW,BLUE);
plot("CrossUnder",CU,0,RED);
}
// Trading with crossover vs. trading with prediction
#define USE_PREDICT
function run()
{
BarPeriod = 1440;
asset("SPX500");
vars Osc = series(StochEhlers(series(price()),20,10,10));
#ifndef USE_PREDICT // use normal crossover
if(crossOver(Osc,0.8))
reverseShort(1);
if(crossUnder(Osc,0.2))
reverseLong(1);
#else // use predicted crossover
if(predict(CROSSOVER,series(Osc[0]-0.8),10,0.01) > -5)
reverseShort(1);
if(predict(CROSSOVER,series(0.2-Osc[0]),10,0.01) > -5)
reverseLong(1);
#endif
}

362
Examples of signal prediction can also be found in the Predict and Ehlers scripts.

frechet (vars Data, int TimeFrame, var Scale, var* Pattern) : var
Compares the recent part of a price series with a predefined curve pattern, and returns the similarity between the two
curves. This function is used for detecting cups, zigzags or similar triggers in the price curve.

Parameters:
Data The series to be compared.

TimeFrame The number of bars in the series to be compared, or 0 for using the length of the pattern.

Scale The vertical size of the pattern for comparison (f.i. 10*PIP for detecting a 10 pips tall pattern). Use a
negative scale for inverting the pattern.

Pattern The pattern to be detected in the series, given by an array of positive values that starts with the oldest
value and ends with 0 as an end mark.

Returns
Similarity between Data and Pattern in percent, normally in the 20..80 range.

Remarks:

• The algorithm is based on the Fréchet distance, a measure of similarity between two curves, often used for
handwriting recognition. For the algorithm, imagine a dog walking along one curve and the dog's owner walking along
the other curve. They are connected by a leash and walk from the start point to the end point of the curve. Both may
vary their speed and even stop anytime, however neither can backtrack. The Fréchet distance is the length of the
shortest possible leash required for traversing the curves in this manner.
• The absolute values of the pattern array don't matter, as it is normalized to Scale before comparison. The pattern is
also aligned with the minimum of the Data series. For automatically adapting the pattern size to the data amplitude,
set Scale = MaxVal(Data,TimeFrame) - MinVal(Data,TimeFrame);.
• Because series arrays have reverse time order, the pattern array is also reversed before comparison. This must be
considered when comparing the pattern with a data array that is not a series.

Example:
//detect 10-pip 10-bar cup formations in the price curve
function run()
{
vars Price = series(price());
static var cup[10] = { 6,3,2,1,1,1,2,3,6,0 };
plot("Cup Similarity",frechet(Price, 0, 10*PIP, cup),NEW,RED);
}

363
markowitz (var* Covariances, var* Means, int N, var Cap) : var
Mean / Variance optimization (MVO) using the algorithm from the 1959 publication Portfolio Selection: Efficient
Diversification of Investments by Harry M. Markowitz. Calculates the optimal distribution of capital among a portfolio
of assets or algorithms for achieving a given total return or variance. The algorithm starts with assigning all capital to
the component with the highest return. It then adds or removes more components step by step, calculating the optimal
return/variance combination on any step. The efficient frontier is a line consisting of corner points in a return vs. variance
plot. At any corner point the portfolio composition changes by adding or removing one or several components. Between
the corner points the included components don’t change, only their capital allocation changes linearly. Connecting all
corner points with lines establishes the efficient frontier with the maximum return for any given variance (see image).

Efficient frontier of an ETF portfolio, x = variance, y = annual return in %

Parameters:
Covariances A var[N*N] array containing the covariance matrix of the component returns.

Means A var[N] array containing the mean of the component returns.

N Number of components.

Cap Optional weight cap in 0..1 range; maximum weight of a single asset at the minimum variance point.

Returns
Variance at the efficient frontier point with the best Sharpe Ratio (i.e. return divided by standard deviation), or 0 if the
efficient frontier could not be calculated. The efficient frontier line segments are internally stored for
subsequent markowitzReturn or markowitzVariance calls.

markowitzReturn (var* Weights, var Variance) : var


Calculates the return and the optimal capital allocation weights for a given variance at a previously calculated efficient
frontier.The markowitz() function must be called before for calculating the frontier.

markowitzVariance (var* Weights, var Return) : var


Calculates the variance and the optimal capital allocation for a given return at a previously calculated efficient frontier.

364
Parameters:
Weights A var[N] array to be filled with the capital allocation weights of the N portfolio components. The sum of
the weights is 1. Pass 0 when no weights are needed.

Variance The desired variance. When below the lowest variance, the return and weights at the left end of the
efficient frontier are returned.

Return The desired return. When below the lowest return of the efficient frontier, the variance and weights at the
left end of the efficient frontier are returned.

Returns
The optimal portfolio return at the given variance, or vice versa..

Modifies
Weights - set to the capital allocation weights.

Remarks:

• For getting the minimum variance point, call markowitzReturn with Variance at 0. For maximum Sharpe ratio,
call markowitzReturn with the Variance returned by markowitz(). For the maximum return point at the right side of
the diagram, call markowitzReturn with Variance at 1. For the maximum and minimum variances,
call markowitzVariance with a return value above resp. below the maximum and minimum portfolio return.
• Markowitz weights can be used alternatively to OptimalF factors for allocating capital to a portfolio system.
Unlike OptimalF, they require only a relatively short time horizon and thus can be adapted during live trading. For a
long-term ETF rotation strategy, use the last year price returns of the ETFs (see example) and recalculate their
markowitz weights every 4 weeks.
• For calculating the returns of portfolio components with currently zero weight in real time, use phantom trades.
• In some cases it makes sense to establish a weight cap at the minimum variance point (f.i. 50% maximum weight) for
getting a higher portfolio diversification. This is not an optimal portfolio anymore, but can produce better out-of-sample
results.
• Applications of the markowitz function are described in the blog post Get Rich Slowly.

Example:
#define N 10 // 10 assets
#define DAYS 252 // 1 year

vars Returns[N];
var Means[N];
var Covariances[N][N];

function run()
{
BarPeriod = 1440;
StartDate = 20150101;
LookBack = DAYS;

string Name;
int n = 0;
while(Name = loop(.../*list of 10 assets*/ ... ))
{
asset(Name);
Returns[n] = series((price(0)-price(1))/price(1));
Means[n] = SMA(Returns[n],DAYS);
n++;
}

int i,j;
if(!is(LOOKBACK))
{
// generate covariance matrix
for(i=0; i<N; i++)
for(j=0; j<N; j++)
Covariances[i][j] = Covariance(Returns[i],Returns[j],DAYS);

365
// calculate efficient frontier
var OptimalV = markowitz(Covariances,Means,N);
printf("\nBest daily return %.3f %% at variance %.4f",
100*markowitzReturn(0,OptimalV),OptimalV);

// plot the frontier


for(i=1; i<70; i++) {
var VStep = i*OptimalV*0.03;
var Return = markowitzReturn(0,VStep);
plotBar("Frontier",i,VStep,Return,LINE|LBL2,BLACK);
}
plotGraph("Max Sharpe",1./0.03,markowitzReturn(0,OptimalV),SQUARE,RED);
PlotScale = 6;
PlotHeight1 = 300;
quit("");
}
}

polyfit (var* coeff, vars Data, int TimePeriod, int order, var weight) : var
Polynomial regression. Generates a polynomial that is the best fit to a section of a price series or any other data series.
This polynomial can be used for extrapolating the Data series into the future, and thus predicting future prices.

Parameters:
coeff A var[8] array for storing the calculated polynomial coefficients, or 0 for storing the coefficients
internally.

Data Data series to be approximated by the polynomial.

TimePeriod Number of elements (1..1000) in the Data series to be approximated by the polynomial.

order Order of the polynom (1..7). Use 1 for linear regression, 2 for parabolic regression, and higher
numbers for nth-order regression.

weight Ratio of the weight of the last data value to the weight of the first value, for "fading-memory"
polynomials. Use 1 for equal weights.

Returns
Correlation coefficient, normally in the 0..1 range. Gives the similarity of the price curve and the polynomial.

Modifies
coeff - set to the coefficients of the polynomial, in the order of their index, starting with coeff[0].

polynom (var* coeff, int num) : var


Returns the value of the polynom with the given coefficients at a given bar number.

Parameters:
coeff A var[8] array that contains the polynomial coefficients, or 0 for using the last coefficients generated
by polyfit.

num The bar offset of the returned polynomial value (0 = current bar). Use negative bar numbers for predicting the
future.

Returns
366
The predicted data at the given bar number, based on the polynomial.

Remarks:

• Polynomials of order 1 (straight line), 2 (parabola), or 3 are useful for price change predictions. Higher order
polynomials are unlikely to give good predictions.
• The weight ratio can be used for giving recent data more weight; however the best predictions are usually generated
with weight at 1.
• For better accuracy, don't fit the polynomial to a price series, but to a series of price differences. Price differences vary
more than prices and thus give more accurate correlation coefficients.
• As data series are stored in reverse order, a rising data series generates a polynomial with a falling slope, and vice
versa.
• For predicting curve events with polynomial regression, such as crossovers, peaks, or valleys, use
the predict function.
• For logistic linear regression with multiple variables, use the advise(PERCEPTRON,...) function.

Example:
function run()
{
vars Diff = series(price(0)-price(1));

var Correlation = polyfit(0,Diff,15,2,1);


// sum the differences for predicting the price change over the next 3 bars
var Change3 = polynom(0,-1)+polynom(0,-2)+polynom(0,-3);

plot("Prediction",price(0)+Change3,MAIN,BLUE);
plot("Correlation",Correlation,NEW,GREEN);
}

randomize(int Method, var *Out, var *In, int Length): var


Randomizes a data array by different methods.

Returns:
Last value of the resulting data array; usually the accumulated return.

Parameters:
Method BOOTSTRAP Randomize the data differences by bootstrap with replacement.

DETREND Detrend the data before randomizing, by subtracting their mean difference.

Out Array to be filled with the randomized data; must be different to In. Can be set to 0 for returning only the
last value.

In Array containing the original data.

Length Number of elements of the In and Out arrays.

Remarks:

• This function can be used for generating randomized return distributions f.i. for White's Reality Check.

Example:
var OriginalReturn = EquityCurve[Length-1];
var RandomizedReturn = randomize(BOOTSTRAP,0,EquityCurve,Length);
367
Fuzzy logic functions
Fuzzy logic is a form of many-valued logic. In contrast with traditional binary logic that deals only with true or false,
fuzzy logic functions have a truth value that ranges in degree between 1 and 0, from 'completely true' over 'almost true'
to 'completely false'. Trade signals derived with fuzzy logic are often less susceptible to random noise in the price data,
especially with complex logic expressions.

The following functions return a fuzzy truth value:

equalF (var v1, var v2): var


aboveF (var val, var border): var
belowF (var val, var border): var
Fuzzy comparison, equivalent to the binary ==, > and < operators.

betweenF (var val, var lower, var upper): var


Fuzzy between function.

peakF (vars Data): var


valleyF (vars Data): var
Fuzzy peak and valley functions.

risingF (vars Data): var


fallingF (vars Data): var
Fuzzy rising and falling functions.

crossOverF (vars Data1, vars Data2): var


crossUnderF (vars Data1, vars Data2): var
crossOverF (vars Data, var border): var
crossUnderF (vars Data, var border): var
Fuzzy crossOver and crossUnder functions.

andF (var a, var b): var


orF (var a, var b): var
notF (var a, var b): var
Fuzzy logic functions, equivalent to the &&, ||, and ! operators.

Parameters:
a, b Fuzzy truth values, 0.0 .. 1.0

Data, Data1, Data2 Data series.

val, v1, v2 Values to compare.

lower, upper, border Comparison borders.

Returns
Fuzzy truth, from 0.0 (completely false) to 1.0 (completely true)

368
fuzzy (var a): bool
Defuzzyfication, converts a fuzzy truth value to binary true or false. Use this function to convert the result of a fuzzy
logic expression back to binary logic.

eq (var v1, var v2): bool


Fuzzy comparison, returns true when the parameters differ less than FuzzyRange, otherwise false.

Parameters:
a Fuzzy truth value, 0.0 .. 1.0

Returns
Boolean true or false

The following system variables affect the fuzzy logic calculation:

FuzzyRange
Determines the 'fuzziness' range of the input data (default: 0 = no fuzziness). When comparing two variables, the truth
value goes from 0 to 1 within that range. Set this to a small fraction of the price volatility, or f.i. to 0.1*PIP for comparing
moving averages, or to 0.1 for comparing percentage values. The smaller this value, the 'less fuzzy' is the logic. At the
default value 0 the logic is binary. The FuzzyRange variable is also used for classifying signal patterns for price action
trading.

FuzzyLevel
Determines the level above which fuzzy true becomes binary true (default: 0.5); used by the fuzzy function.

Remarks:

• All fuzzy logic functions work just like their binary counterparts, with the difference that the output value depends on
the 'closeness' of the result. For instance, equalF already returns a nonzero value when the two compared values are
close, and crossOverF will already return nonzero when two lines are so close that they almost touch. The functions
use the same number of bars as their binary counterparts.
• The FuzzyRange variable is critical for the result of fuzzy logic expressions, and can be subject to
an optimize process.

Example:
// Fuzzy version of a Workshop 4 variant ///////////////////
function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,1000));
vars Signals = series(0)

Stop = 4*ATR(100);
FuzzyRange = 0.001*ATR(100);

var Valley = valleyF(Trend),


Peak = peakF(Trend);
Signals[0] = orF(Valley,Peak); // store the signal
if(fuzzy(Valley))
exitShort();
if(fuzzy(andF(Valley,notF(Sum(Signals+1,3)))))
enterLong();
if(fuzzy(Peak))
exitLong();
if(fuzzy(andF(Peak,notF(Sum(Signals+1,3)))))
enterShort();
}

369
// Binary version for comparison
function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,1000));
vars Signals = series(0);

Stop = 4*ATR(100);
bool Valley = valley(Trend),
Peak = peak(Trend);
if(Valley or Peak)
Signals[0] = 1; // store the signal
if(Valley)
exitShort();
if(Valley and Sum(Signals+1,3) == 0)
enterLong();
if(Peak)
exitLong();
if(Peak and Sum(Signals+1,3) == 0)
enterShort();
}

370
profile.c - price, trade, and seasonal analysis
The profile.c add-on library contains functions for generating price or trade profiles, MAE charts, or seasonal analysis
charts in [Test] mode. The functions are normally used during strategy development for analyzing trade profits or for
finding seasonal effects. They can also be used as templates for building user-specific profile charts.

plotPriceProfile (int bars, int type)


Generates a price difference profile at the current bar. Call this function when entering a trade. The price difference
profile shows the further price development at this point in the simulation and gives a much better insight in the
development of the trade than just the profit or loss. From the average of all profiles, a price difference chart is generated
after the test run. The red bars in the chart are the average price differences to the current bar in pips, the blue bars are
their standard deviations, divided by 4 for better scaling.

Price difference profile at entering a trade with the workshop 4 system. Note the small adverse excursion after 24 hours.

Parameters:
bars Number of bars to plot in the price profile..

type Type of the plot.


0 = plot price difference to the first bar.
1 = plot price change from the previous bar.
2 = plot negative price difference to the first bar (for short trades).
3 = plot negative price change from the previous bar.

plotTradeProfile (int bars)


Generates a profit distribution chart after the test run. The distribution shows the frequency of trades dependent on their
profit or loss. The blue bars in the chart are the number of trades (right y axis), the red bars are the sum of their returns
in pips (left y axis). The x axis is the trade profit or loss in pips. The example chart below shows a system with about
100 trades that hit their profit target at 100 pips, and about 30 trades that hit their stop at 100 pips loss. Inbetween
there's a distibution of trades with mostly small losses. Note that the shown profit/loss is not exactly 100 pips due to
spread and slippage.

371
Profit distribution of a system with Stop and TakeProfit distances both at 100 pips.

Parameters:
bars > 0 Number of bars to plot in the profit distribution. The more bars, the finer the resolution.

bars < 0 Step width of the profit distribution in pips per bar. The smaller the step width, the finer the resolution.

plotMAEGraph (int bars)


Generates a Maximum Adverse Excursion graph after the test run. The Maximum Adverse Excursion is the worst
drawdown of an individual trade, the difference of its highest interim profit to its worst interim loss. The graph displays
any trade as a dot in a chart showing its MAE on the x axis and its profit on the y axis in pip units. Winning trades are
green, losing trades are red. This graph can be used for analyzing the exit behavior of the trades and finding the optimal
stop or take profit distances.

MAE graph of the Workshop 5 system.

Parameters:
bars > 0 Number of bars to plot. The more bars, the finer the resolution.

372
bars < 0 Step width of the MAE distribution in pips per bar. The smaller the step width, the finer the resolution.

plotDay (var value, int type)


plotWeek (var value, int type)
plotMonth (var value, int type)
plotYear (var value, int type)
Seasonal analysis; generates a day, week, month or year distribution of a given value in a histogram (see also seasonal
strength). This way seasonal effects of a trading system, a variable, or a price curve can be found. The red bars in the
chart are the average value at a certain hour or day, the blue bars are their standard deviations divided by 4 for better
scaling.

Week statistics with hourly returns of the workshop 4 EUR/USD system. Note the seasonal oscillation of the standard deviation - a price curve effect known
as heteroskedasticity.

plotWFOCycle (var value, int type)


Analyzes the development of the value over a WFO cycle (x axis = trading days since cycle start). Can be used to
determine if the profit deteriorates towards the end of the cycle, thus suggesting shorter cycles.

Parameters:
value Value to be evaluated, for instance price(0) or Equity.

type 0 = evaluate value difference to the season begin.


1 = evaluate value difference to the previous bar.

373
plotDayProfit ()
plotWeekProfit ()
plotMonthProfit ()
plotQuarterProfit ()
plotWFOProfit ()
Plots bars equivalent to the gain or loss of the previous day, week, month, quarter, or WFO cycle in a new window in
the price chart (thus, clicking [Result] is required). The blue bars are the profit in units of the account currency, the red
bars are the loss.

Quarterly profit/loss of the Workshop 4 EUR/USD system.

plotCorrelogram(var Value, int Lag)


plotCorrelogram(var Value1, var Value2, int Lag)
Generates in the EXITRUN a histogram of the correlation of a variable with itself, or with another variable, for sequential
values of lag. On random data, such autocorrelations should be near zero for all time lags. But if the data is non-random,
one or more of the autocorrelations will be significantly non-zero.

The above correlogram displays a significant correlation (0.14) of the variable with its value from the previous bar. The
correllations with earlier bars (2...49) are not as significant.

Parameters:
Value1 First variable to be evaluated, for instance price(0)-price(1).

Value2 Second variable to be evaluated.

Lag Maximum autocorrelation lag.

374
plotHeatmap(string name,var* Data, int Rows, int Cols)
Plots a heatmap in the EXITRUN from the Rows*Cols matrix Data, f.i. a correlation matrix. The matrix elements must
be in the 0..1 range, and are displayed as colored squares with a color range from blue (0) to red (1).

Parameters:
Data Pointer of a var array with N * N elements.

N Array width.

Remarks:

• All profile functions are contained in source code in include\profile.c, and can be used as templates for writing other
chart plotting or analysis functions of any kind.
• For adding the profile functions, the line #include <profile.c> must be added at the begin of the script.
• The profile or distribution charts must only be plotted for a single asset and algorithm, since every market and every
system will generate a different profile. In a portfolio strategy, temporarily disable the other assets and algos for plotting
a profile of a component.
• Only one chart can be plotted, not several at the same time; unused plot commands must be commented out for not
interfering with the active chart. The size and resolution of the chart can be set up with the plot parameters.
• The charts are only plotted in [Test] mode, not in [Train] and [Trade] mode.
• The week, month, and year analysis is based on trading days, not real days. Therefore the week has 5 days, the month
22 days, and the year 260 days. Due to the different number of trading days (20..23) in a month, they are normalized
to 22, so day 22 is always the last trading day of the month.
• The standard deviations (blue bars in the chart) show how much the values differ from their average, and thus indicate
the significance of a nonzero average. The smaller the blue bar and the bigger the red bar, the more significant is the
seasonal effect. In the case of prices or returns, the standard deviation is a measure of volatility.
• When developing a strategy, it is helpful to examine the price difference profile at every trade entry bar for verifying
the trade entry rules and for determining the trade exit rules.

Examples:
#include <profile.c>

// price difference profile


375
function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,1000));

if(valley(Trend)) {
plotPriceProfile(40,0);
enterLong();
} else if(peak(Trend)) {
plotPriceProfile(40,2);
enterShort();
}
}
#include <profile.c>

// Trade distribution
function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,1000));

Stop = 100*PIP;
TakeProfit = 100*PIP;
if(valley(Trend))
enterLong();
else if(peak(Trend))
enterShort();

PlotHeight1 = 320;
PlotScale = 4;
plotTradeProfile(50);
}
#include <profile.c>

// Weekly return analysis


function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,1000));

if(valley(Trend))
enterLong();
else if(peak(Trend))
enterShort();

PlotHeight1 = 320;
PlotScale = 3;
plotWeek(Equity,1);
}
#include <profile.c>

// Correlogram of High-to-High differences


function run()
{
PlotHeight1 = 320;
PlotScale = 10;
plotCorrelogram(priceHigh(0)-priceHigh(1),50);
}
#define DAYS 252 // 1 year
#define NN 30 // max number of assets

#include <profile.c>

// plot a heatmap of asset correlations


function run()
{
BarPeriod = 1440;
StartDate = 20150101;
LookBack = DAYS;

vars Returns[NN];
var Correlations[NN][NN]; // NN*NN matrix
int N = 0;
while(asset(loop(.../*some assets*/ ..)))
{
Returns[N] = series((price(0)-price(1))/price(0));
N++; // count number of assets
}

int i,j;
if(!is(LOOKBACK)) {
for(i=0; i<N; i++)
376
for(j=0; j<N; j++)
Correlations[N*i+j] = Correlation(Returns[i],Returns[j],DAYS);

plotHeatmap(Correlations,N);
quit("");
}
}

Seasonal and directional strength


move(vars Data, int Length, int Horizon, var Percentile): var
Returns the price move within a given time horizon in percent, based on the given percentile of all such price moves in
the Data series. Example: if the function returns 10 at Horizon = 20 and Percentile = 95, then in 95% of all cases
the Data value rised by 10% or less within 20 bars (meaning that it rised by more than 10% in 5% of all cases). Uses
the Percentile function. Generates a series and therefore must be called in a fixed order in the script.

season(vars Data, int Length, int Horizon, int Season): var


Returns the price move within a given time horizon in percent, based on the current time/date and the average seasonal
move in the Data series. For instance, if the function returns 10 at Horizon = 20 and Season = 4, then the Data series
rised by an average 10% within 20 bars after the current time/date in the past years.

Parameters:
Data A data series, usually from price functions price(), priceClose() etc.

Length The length of the data series, f.i. the LookBack period.

Horizon The price change duration in bars.

Percentile The price changes percentile to return, f.i. 5 for the lowest 5% or 95 for the highest 5%.

Season 1 for daily, 2 for weekly, 3 for monthly, or 4 for annual changes.

Remarks:

• Source code in indicators.c.

Example:
function run()
{
StartDate = 2010;
BarPeriod = 1440;
LookBack = 252; // 1 year

vars Prices = series(price());


if(!is(LOOKBACK)) {
LifeTime = 1;
var Season = season(Prices,LookBack,LifeTime,2);
if(Season > 0)
enterLong();
else if(Season < 0)
enterShort();
}
}

377
Currency strength functions
ccySet(var Strength)
Stores the strength of the current Forex asset separately for currency and counter currency. The Strength value can
be any indicator or other price function that depends on "strength" or "weakness" of a currrency. It is decomposed into
separate strength values for the currency and the counter currency.

ccyReset()
Resets the stored strengths of all currencies. Usually called at the begin of every bar before the strengths are summed
up by ccySet.

ccyStrength(string Currency): var


Returns the accumulated average strength of the given Currency (3 letters, f.i. "USD") or the strength difference of the
given currency pair (7 letters, f.i. "EUR/USD").

ccyMax(): string
Returns the currency pair with the strongest currency and the weakest counter currency.

ccyMin(): string
Returns the currency pair with the weakest currency and the strongest counter currency.

Parameters:
Strength Strength value, for instance the price rate of change. Must be in the same range for all currency pairs.

Currency Either a single currency ("USD") or a currency pair ("EUR/USD").

Remarks:

• Currency strength functions can be used to detect currency price shocks that affect several Forex markets
simultaneously (see example).
• The source code of the currency strength functions is contained in include\indicators.c.

Example:
// Currency Strength Strategy /////////////////////
// Exploits price shocks f.i. by CHF cap and Brexit

function run()
{
BarPeriod = 60;
ccyReset(); // reset strengths at begin of any bar
string Name;
while(Name = (loop(Assets)))
{
if(assetType(Name) != FOREX)
continue; // Currency pairs only
asset(Name);
vars Prices = series(priceClose());
ccySet(ROC(Prices,1)); // store price change as strength
}

// get currency pairs with highest and lowest strength difference


string Best = ccyMax(), Worst = ccyMin();
var Threshold = 1.0;

static char OldBest[8], OldWorst[8]; // static for keeping contents between runs
if(*OldBest && !strstr(Best,OldBest)) { // new strongest asset?
asset(OldBest);
exitLong();
if(ccyStrength(Best) > Threshold) {
asset(Best);
enterLong();
}
}

378
if(*OldWorst && !strstr(Worst,OldWorst)) { // new weakest asset?
asset(OldWorst);
exitShort();
if(ccyStrength(Worst) < -Threshold) {
asset(Worst);
enterShort();
}
}

// store previous strongest and weakest asset names


strcpy(OldBest,Best);
strcpy(OldWorst,Worst);
}

379
Input/Output
slider (int number, int pos, int min, int max, string name, string tooltip) : var
Returns the current position of one of the four sliders; optionally also gives the slider an initial position, range, name,
and tooltip. Depending on the selected asset or other conditions, different slider positions can be preset at the beginning.

Parameters:
number The number of the slider: 0 for the Period slider, 1..3 for the 3 user-specific sliders.

pos Optional initial position of the slider (default = 0). The position is set when the script is run the first time.

min, max Optional minimum and maximum positions of the slider, left and right.

name Optional name for the slider that is displayed in the main panel (f.i. "Panic"), or 0 for not changing the
name.

tooltip Optional tool tip text to describe the slider function, or 0 for not changing the tool tip.

Returns
Current value of the slider.

Slider1 .. Slider3
Variable containing the current value of the 3 user-specific sliders. The slider can be moved at runtime by setting those
variables to a different value.

Type:
var

Remarks:

• For defining a slider initial position, range, name, and tooltip, the slider function must be called in the first run of a
strategy. The Period slider has fixed parameters and is hard-wired to the BarPeriod variable. Only its initial position
(pos) can be set. The other three sliders can be freely set and redefined.
• The sliders jump to their default positions (pos parameter) when the script is started the first time, or when [Script],
[Asset], or [Edit] was clicked. Otherwise the sliders keep their previous position. For testing a script with a certain
slider position, click [Test], wait until the sliders appear, then click [Stop], put the sliders to the desired position and
click [Test] again. In [Trade] mode the sliders are stored in the .trd file and thus always 'remember' their last position.
• The slider positions can be read and set at runtime through the BarPeriod, Slider1, Slider2, and Slider3 variables.
Those variables can also be used to optimize the slider positions for a certain strategy with the optimize function.
Directly setting the BarPeriod, Slider1, Slider2, and Slider3 variables moves the slider to the variable value. This
way the script can display numerical values to the user while running.
• In [Test] mode the sliders could be used to set up time periods, buy/sell thresholds, or stop/profit distances. In [Trade]
mode, the sliders could be used for entering separate thresholds for buying long and short, or to set the number of lots
per trade. This way the strategy can be adapted in real time to the expectation of rising or falling prices, or to the risk.

380
• Aside from the sliders, a panel with entry fields can also be used for user input.

Example:
Lots = slider(1,2,1,10,"Lots","Lots per Trade");
Stop = PIP*slider(2,15,0,30,"Stop","Stop Loss in PIPs");

Stategy control panels


Strategy parameters are normally set up with the sliders or simply by editing the script. For executable strategies that
do not come with source code, or for complex strategies with multiple parameters and options, a strategy specific control
panel can be created by script or from a spreadsheet template. The panel is a rectangular grid of multi-purpose cells.
Every cell can display text, numbers, and a color, and can serve as a numeric or text input field or as a push, toggle,
radio, or spin button. With this concept, even complex control functions for commercial strategies can be realized in a
simple way. Such a control panel does not look particularly pretty, but it's functional:

The following functions can be used for defining and handling a control panel (Zorro S or executable strategies only):

panel (int rows, int columns, int color, int size)


Opens an empty control panel with the given number of rows and columns, background color, and horizontal
cell size in pixels. If a control panel was already open, it is replaced by the new one. The panel is opened next to the
Zorro window, and can be freely moved by dragging it with the mouse. Use size = 0 for removing the panel.

panel (string filename, int color, int size)


Opens a control panel with the structure and content from a .csv spreadsheet file. Every cell of the panel is set up from
the corresponding spreadsheet cell. The cells in the file are separated with ; or , delimiters (delimiters must not appear
in cell content). Every row must end with a new line character. The following characters in the cell content have a special
meaning: !... - cell is a button; #... - cell can be edited; @ - merge the cell with the next not-empty cell to the left. The
default colors are grey for text cells, red for numbers, yellow for editable cells, and blue for buttons; they can be changed
with the ColorPanel variables. The panel above is generated from the following file:
Leverage,@,400,,, ,Algo 1,Algo 1,Algo 3
Win Ratio,@,30%,,Asset,@,#EUR/CHF,#EUR/USD,#USD/GBP
Lot size,@,100,,Max Allowed Loss %,@,#5,#20,#25
Commission,@,1.2,,First Step Volume+,@,#1,#5,#20
,,,,Volume Growth,@,#2,#2,#2
Long Trades,@,34,,Volume Max,@,#10,#20,#50
Average Profit,@,4.01,,Max Stop Distance,@,,,#300
Short Trades,@,44,,,,,,

381
Average Profit,@,2.34,,,,,,
Quotes,@,3,,!Close All,,@,,
Average Spread,@,10.2,,!Open New,,@,,
Average slippage,@,1.4,,!Hide Candles,,@,,
Average Trade Cost,@,15.34,,!Show Statistics,,@,,

panelSet (int row, int col, string text, int color, int style, int type)
Set properties or update content of an individual cell.

row - row number, starting with 0.


col - column number, starting with 0.
text - content to appear in the cell, or 0 for not changing the content. Numbers can be converted to text with strf.
color - background color of the cell, or 0 for not changing the color.
style - 1 normal cell, 2 red highlighted text, 3 greyed-out text, 4 right-aligned, 8 left-aligned, 12 centered, 0 for not
changing the style.
type - 1 normal cell, 2 editable cell, 4 button, 0 for not changing the type.

panelGet (int row, int col): string


Returns a temporary string with the current content of the cell. Numbers can be converted from the returned string
with sscanf, atof, or atoi.

panelSave (string filename)


Saves the panel content in .csv format to the given file.

panelLoad (string filename)


Loads the panel content in .csv format from the given file. Only editable or button cells are affected.

click(int row, int col)


User-supplied function that is triggered by mouse click either on a button cell with the given row and col numbers, or
on the [Result] button with row at -1. Dependent on the desired behavior of the button, the function can then change
the cell color or content through panelSet.

Remarks:

• Control panels can only be created with Zorro S, but can be included in executable strategies (.x files) and then also
be used with the free Zorro version.
• Even without a panel, a click on the [Result] button can trigger the click function.
• Text on the panel caption bar can be displayed with the print(TO_PANEL,...) function.
• If the number of rows or columns exceed the screen size, scrollbars are added to the panel.
• The panel stays open after the end of the strategy unless it is explicitely removed either with panel(0,0,0,0), or by
clicking [Edit], or by selecting a new script. As long as it is open, it can still be edited and can even trigger functions
when clicked on. For preventing that functions are triggered when the script is not running, check the RUNNING flag.
For keeping the previous panel content when the strategy is restarted, call panel() or panelLoad() only when both
the INITRUN and the CHANGED flags are set.

Example: (see also Source\Download.c)


function click(int row,int col)
{
if(!is(RUNNING)) return;
sound("click.wav");
string Text = panelGet(row,col);
if(Text == "Buy") // toggle button between "Buy" and "Sell"
panelSet(row,col,"Sell",0,0,0);
else if(Text == "Sell")

382
panelSet(row,col,"Buy",0,0,0);
else if(Text == "Close All") // push button
closeAllTrades();
else if(Text == "Cancel") // back to last saved state
panelLoad("Data\\mypanel.csv");
}

printf (string format, ...)


Prints text and variables in a log file and the message window. This allows to display messages and record variables.

print (int to, string format, ...)


Prints text and variables to a target channel given by the to parameter, such as the log file, a .csv spreadsheet file, the
HTML status page, or the performance report.

msg (string format, ...): int


Displays text and variables in a modal or nonmodal message box with [Yes] and [No] buttons, and waits for the user
to click one of the buttons.

Returns
0 when [No] was clicked, 1 when [Yes] was clicked.

Parameters:
to TO_WINDOW - print in [Test] and [Trade] mode to the message window and the log file (default
for printf).
TO_LOG - print in [Test] mode only to the log file, in [Trade] and in STEPWISE mode also to the
message window.
TO_FILE - print in all modes to the log file.
TO_ANY - print in all modes to the message window and log file.
TO_DIAG - print to the diag.txt file (see Verbose). Note that log files and diag.txt file are not yet open in
the INITRUN.
TO_CSV - print into a file with the name of the script and extension ".csv", for exporting data.
TO_REPORT - print in the EXITRUN to the performance report, for displaying additional performance
parameters.
TO_HTML - print in [Trade] or in STEPWISE mode to the HTML file that displays the live trading status.
HMTL format codes can be included in the text.
TO_TITLE - print to the title bar.
TO_INFO - print to the info field. An empty format string - print(TO_INFO, ""); - displays the current
account or simulation state in the info field.
TO_PANEL - print to the caption bar of a control panel.
format C-style format string, containing the text to be displayed and optional placeholders for subsequent
variables. The placeholders begin with a percent sign '%' and define the format of the displayed number
in the following way: "%[flags][width][.precision]f" for var or double variables with decimals.
Example: "%+4.1f" prints a var with +/- sign, 4 digits, and one decimal. For printing float variables, use
the %f placeholder and typecast them to (var).
"%[flags][width]d" for decimal int, long, short, char, or bool variables.
"%s" for strings.
[flags] = Optional one or more of the following characters:
- = Left align the number within the given field width. Default: Right align.
+ = Prefix the number with a + or - sign. Default: Sign only for negative values.
0 = Leading zeros are added until width is reached. Default: Blanks are added.
blank (' ') = Prefix positive numbers with a single blank. Default: No single blank.
# = Forces the number to contain a decimal point. Default: Decimal point only when the number is no
integer.
[width] = Optional minimum number of characters displayed.
[.precision] = Optional maximum number of digits after the decimal point.

383
... Expressions or variables, one for each placeholder in the format text.

Remarks:

• For printing a percent sign in the text, use "%%"; for a backslash, use "\\". A more detailed description of C-style
format strings can be found in any C/C++ book.
• For printing float variables with the %f placeholder, typecast them to (var) or (double). The lite-C printf function does
not automatically promote float to double.
• For printing on a new line in the log or message window, begin the text with the linefeed symbol "\n".
Otherwise printf appends the text to the end of the current line. For printing on a new line in a HTML file, begin the
text with the tag "<br>".
• For deleting the current line in the message window and replacing it with new content, begin the text with the carriage-
return symbol "\r". This way running counters or similar effects can be realized.
• The maximum text size depends on the print channel. For the message window it's 1000 characters.
• For printing into a string, use the sprintf or strf functions. For printing into a file, use the file_append function.
• For printing with printf only to the log file and not to the window, add a "#" at the begin of the text (printf("#\n...",...);).
• For opening a nonmodal message box with the msg function, add a "#" at the begin of the text (msg("#...",...);). A
nonmodal message box has an [Ok] and a [Cancel] button and does not wait for the user to click a button; the script
just continues. When the user later clicks [Ok], the AFFIRMED flag is set (see example). An empty message
(msg("#");) lets the nonmodal box disappear.
• For refreshing the GUI so that printed messages are updated in the window, use the wait function.

Examples:
// printf example
var vPrice = 123.456;
float fDiscount = 1.5;
int nNumber = 77;
...
printf("\nPrice %.3f for %d items, total %.3f, discount %.1f%%",
vPrice, nNumber, nNumber*vPrice, (var)fDiscount);
// HTML status message example
if(ATR(30) < Threshold)
print(TO_HTML,"<br>Low volatility - ATR30 = %.3f",ATR(30));
// nonmodal box example
function run()
{
if(is(INITRUN))
msg("#Nonmodal message box.\nClick [Ok] to exit script!");
if(is(AFFIRMED))
quit("Ok!");
Sleep(10);
}

progress (int n1, int n2)


Displays a red/green progress bar during a [Test] run.

Parameters:
n1 Length of the first part of the progress bar in percent. Green when > 0, red when < 0.

n2 Length of the second part of the progress bar in percent; green when > 0, red when < 0, 0 for a neutral bar color.

Remarks:

• If n1 and n2 are 0 (default), the progress bar displays the progress of the current process or the current win/loss
situation.

Example:
sound("win.wav");
384
sound(string name)
Plays a .wav sound file.

Parameters:
name Name of the sound file, f.i. "test.wav". If the file is not found in the Zorro folder, a standard chime is played.

Remarks:

• Sounds are not played when the MUTE flag is set.

Example:
sound("win.wav");

plot (string name, var value, int type, int color)


plot (string name, vars data, int type, int color)
Plots a variable or the recent value of a series on the current bar position of a new curve either in the main price chart,
or on a new chart. The plot curve is linked to the current asset. Not for histograms.

plotBar (string name, int pos, var label, var value, int type, int color)
Adds a value to a given x axis position in a histogram. A histogram consists of vertical bars that display the sum, average,
standard deviation, maximum, or minimum of the values added to any x position.

plotGraph (string name, var x, var y, int type, int color)


Plots a dot, symbol, line, or polygon at a given xy position with the given color into the recent chart or histogram. Call
this function to mark certain events in the price chart, or to plot a scatter graph in a histogram.

plotData (string name): DATA*


Returns the data stored by a chart plot. The DATA struct is defined in include\trading.h. DATA->Data[DATA->start] is
the first valid data, and DATA->Data[DATA->end] the last. The function can be used for retrieving or modifying data
before plotting. The returned data is in straight chronological order.

Parameters:
name The name for the chart legend; must be a string constant with less than 16 characters. Different curves must
have different names. If the name begins with '#', it does not appear in the chart legend.

pos The bar number in a histogram (-2000..2000), determines the bar's position on the horizontal axis.
All plotBar commands within a +/-0.5 range of the same bar number plot to the same bar.

x,y In a histogram, x is the bar number and y is the position on the vertical axis.
In a price chart, x is the bar offset (0 = current bar), and y is the position on the left or right vertical axis.

Label A number printed on the horizontal axis at the given bar position, or 0 for no label. For readability, label
numbers should be in the ±0.1 .. ±1000 range.

value The data value to be plotted or added to the bar. Use 1 in combination with SUM for counting events in a
histogram.

385
data The data series whose recent value is plotted.

type The type of the data curve in the chart; must normally not change during the plot. Either 0 for a simple line,
or a combination of:

BARS - plot bars instead of lines. Can also be used for plotBar.
DOT - plot colored dots. Can also be used for plotBar and plotGraph.
BAND1 - for plot, plot the upper line of a band, use the color for the lines.
BAND2 - for plot, plot the lower line of a band, use the color for filling the band.
LINE - for plot, use a thick line. Otherwise it's a thin line.

MAIN - for plot, plot this and all following curves in the main price chart.
NEW - for plot, plot this and all following curves in a new extra chart (see remark below about plot order).
AXIS2 - use a second y axis on the right side of the chart.
LOG - use a logarithmic y scale.

LBL2 - for plotBar, label only every second x axis position.


AVG - for plotBar, plot the average of all values of the current x position. For plot, use the average of
all sample cycles.
DEV - for plotBar, plot the standard deviation of all values of the current x position.
SUM - for plotBar, plot the sum of all values at the current x position.
MINV - for plotBar, plot the minimum of all values at the current x position.
MAXV - for plotBar, plot the maximum of all values at the current x position.
NRM - for plotBar, normalize the values of all x positions to 1.

LINE - for plotGraph, draw a line from the last position to the current position.
END - for plotGraph, end point of a line or polygon started with LINE.
DEL - for plotGraph, delete the previous plot and start over.
DOT - draw a colored dot. The size can be determined with PlotScale.
SQUARE - draw a colored square.
TRIANGLE - draw a upwards pointing colored triangle.
TRIANGLE2,3,4 - draw a colored triangle pointing to the left, right, or down.
CROSS - draw a colored cross.
CROSS2 - draw a colored diagonal cross.
color The color and transparency of the data curve in the chart, in the form as described under Colors. Symbols
and lines on a histogram can have different colors; on a chart all elements belonging to a certain name must
have the same color. If at 0, no curve is plotted (for merely collecting data for later analyzing by plotData).
Use the color function for assigning different colors to numbers.

Remarks:

• Either a histogram or a chart can be plotted, but not both at the same time. plotBar overrides plot.
• For zooming in a certain date, use either StartDate and EndDate for the whole simulation, or PlotDate
and PlotBars for zooming the chart only. For more pixels per chart, set up PlotScale and PlotWidth. For not plotting
a chart when [Result] is clicked, set PlotWidth at 0.
• When extra charts are opened with NEW, the order of plot calls determines in which chart the curves are plotted. The
order is determined by the first plot call in or after the FIRSTRUN. Subsequent calls can then be in arbitrary order.
When plot order matters and plots depend on if(...) conditions, enforce the right order in the first run f.i.
with if(is(FIRSTRUN) or ...).
• If plot is called several times with the same name in the same run cycle, only the last data value is plotted. If
a plot command is not called in a run, the plotted curve will have gaps at the skipped bars.
• Every plot name must have the same plot type and color; there can not be different plot types or colors per name.
For plotting a curve in different colors, use alternating curves with the same value and different name.
• plotGraph has a limit to the number of elements (lines, symbols) per name. Exceeding the limit causes an Error
038 message. For adding more elements to the chart, use plotGraph with different element names.
• Any plot command is linked to the current asset. When multiple assets are traded, only prices, trades, and curves
linked to the asset selected with the [Asset] scrollbox are plotted; curves linked to other assets are not plotted. For
plotting parameters of different assets together, store them in different variables and then plot all together without
calling asset() inbetween. For instance, inside an asset loop you can store them in a var array with one element per

386
asset, then plot all elements of the array after the asset loop. The curves are then visible on the chart when you select
the last asset of the loop and click [Result].
• Price charts are not plotted in a training run. Instead parameter charts are plotted that show the information ratio and
the number of winning and losing trades for each parameter.
• For removing default chart elements such as price candles or equity curves, set their Color to 0.
• Trade profiles, MAE graphs, seasonal profiles, correlograms or heat maps can be plotted with the functions from
the profile library.
• More plot functions and options can be implemented if required. If you have a certain plot task that can not be realized
with the current functions, please ask for it on the Zorro user forum.

Examples:
// Compare price distribution (red) with random distribution (blue)
function run()
{
vars Price = series(price(0));
int num = NumRiseFall(Price,20);
plotBar("Price",2*num,num,1,SUM|BARS,RED);

vars Random = series(0);


Random[0] = Random[1] + random();
num = NumRiseFall(Random,20);
plotBar("Random",2*num+1,0,1,SUM|BARS,BLUE);
}
// plotGraph test
function run()
{
set(PLOTNOW);
// plot green dots above the price at every 20th bar
if(Bar%20 == 0)
plot("Dotted",1.01*price(),DOT,GREEN);

if(Bar == 500) {
// plot a single blue rectangle in the price chart
plotGraph("Rectangle",0,0.99*price(),LINE,BLUE); // start point
plotGraph("Rectangle",-500,0.99*price(),LINE,BLUE); // 1st corner
plotGraph("Rectangle",-500,1.01*price(),LINE,BLUE); // 2nd corner
plotGraph("Rectangle",0,1.01*price(),LINE,BLUE); // 3rd corner
plotGraph("Rectangle",0,0.99*price(),LINE|END,BLUE); // 4th corner and end point

// plot a single red dot


plotGraph("Single Dot",-250,price(),DOT,RED);
}
}
// Plotting a histogram bar
void plotHistogram(string Name,var Value,var Step,int Color)
{
var Bin = floor(Value/Step);
plotBar(Name,Bucket,Step*Bin,1,SUM+BARS+LBL2,Color);
}
Examples of plotting symbols can be found in the Predict script and in the profile library.

profile.c - price, trade, and seasonal analysis


The profile.c add-on library contains functions for generating price or trade profiles, MAE charts, or seasonal analysis
charts in [Test] mode. The functions are normally used during strategy development for analyzing trade profits or for
finding seasonal effects. They can also be used as templates for building user-specific profile charts.

plotPriceProfile (int bars, int type)


Generates a price difference profile at the current bar. Call this function when entering a trade. The price difference
profile shows the further price development at this point in the simulation and gives a much better insight in the
development of the trade than just the profit or loss. From the average of all profiles, a price difference chart is generated
after the test run. The red bars in the chart are the average price differences to the current bar in pips, the blue bars are
their standard deviations, divided by 4 for better scaling.

387
Price difference profile at entering a trade with the workshop 4 system. Note the small adverse excursion after 24 hours.

Parameters:
bars Number of bars to plot in the price profile..

type Type of the plot.


0 = plot price difference to the first bar.
1 = plot price change from the previous bar.
2 = plot negative price difference to the first bar (for short trades).
3 = plot negative price change from the previous bar.

plotTradeProfile (int bars)


Generates a profit distribution chart after the test run. The distribution shows the frequency of trades dependent on their
profit or loss. The blue bars in the chart are the number of trades (right y axis), the red bars are the sum of their returns
in pips (left y axis). The x axis is the trade profit or loss in pips. The example chart below shows a system with about
100 trades that hit their profit target at 100 pips, and about 30 trades that hit their stop at 100 pips loss. Inbetween
there's a distibution of trades with mostly small losses. Note that the shown profit/loss is not exactly 100 pips due to
spread and slippage.

388
Profit distribution of a system with Stop and TakeProfit distances both at 100 pips.

Parameters:
bars > 0 Number of bars to plot in the profit distribution. The more bars, the finer the resolution.

bars < 0 Step width of the profit distribution in pips per bar. The smaller the step width, the finer the resolution.

plotMAEGraph (int bars)


Generates a Maximum Adverse Excursion graph after the test run. The Maximum Adverse Excursion is the worst
drawdown of an individual trade, the difference of its highest interim profit to its worst interim loss. The graph displays
any trade as a dot in a chart showing its MAE on the x axis and its profit on the y axis in pip units. Winning trades are
green, losing trades are red. This graph can be used for analyzing the exit behavior of the trades and finding the optimal
stop or take profit distances.

MAE graph of the Workshop 5 system.

Parameters:
bars > 0 Number of bars to plot. The more bars, the finer the resolution.

389
bars < 0 Step width of the MAE distribution in pips per bar. The smaller the step width, the finer the resolution.

plotDay (var value, int type)


plotWeek (var value, int type)
plotMonth (var value, int type)
plotYear (var value, int type)
Seasonal analysis; generates a day, week, month or year distribution of a given value in a histogram (see also seasonal
strength). This way seasonal effects of a trading system, a variable, or a price curve can be found. The red bars in the
chart are the average value at a certain hour or day, the blue bars are their standard deviations divided by 4 for better
scaling.

Week statistics with hourly returns of the workshop 4 EUR/USD system. Note the seasonal oscillation of the standard deviation - a price curve effect known
as heteroskedasticity.

plotWFOCycle (var value, int type)


Analyzes the development of the value over a WFO cycle (x axis = trading days since cycle start). Can be used to
determine if the profit deteriorates towards the end of the cycle, thus suggesting shorter cycles.

Parameters:
value Value to be evaluated, for instance price(0) or Equity.

type 0 = evaluate value difference to the season begin.


1 = evaluate value difference to the previous bar.

390
plotDayProfit ()
plotWeekProfit ()
plotMonthProfit ()
plotQuarterProfit ()
plotWFOProfit ()
Plots bars equivalent to the gain or loss of the previous day, week, month, quarter, or WFO cycle in a new window in
the price chart (thus, clicking [Result] is required). The blue bars are the profit in units of the account currency, the red
bars are the loss.

Quarterly profit/loss of the Workshop 4 EUR/USD system.

plotCorrelogram(var Value, int Lag)


plotCorrelogram(var Value1, var Value2, int Lag)
Generates in the EXITRUN a histogram of the correlation of a variable with itself, or with another variable, for sequential
values of lag. On random data, such autocorrelations should be near zero for all time lags. But if the data is non-random,
one or more of the autocorrelations will be significantly non-zero.

The above correlogram displays a significant correlation (0.14) of the variable with its value from the previous bar. The
correllations with earlier bars (2...49) are not as significant.

Parameters:
Value1 First variable to be evaluated, for instance price(0)-price(1).

Value2 Second variable to be evaluated.

Lag Maximum autocorrelation lag.

391
plotHeatmap(string name,var* Data, int Rows, int Cols)
Plots a heatmap in the EXITRUN from the Rows*Cols matrix Data, f.i. a correlation matrix. The matrix elements must
be in the 0..1 range, and are displayed as colored squares with a color range from blue (0) to red (1).

Parameters:
Data Pointer of a var array with N * N elements.

N Array width.

Remarks:

• All profile functions are contained in source code in include\profile.c, and can be used as templates for writing other
chart plotting or analysis functions of any kind.
• For adding the profile functions, the line #include <profile.c> must be added at the begin of the script.
• The profile or distribution charts must only be plotted for a single asset and algorithm, since every market and every
system will generate a different profile. In a portfolio strategy, temporarily disable the other assets and algos for plotting
a profile of a component.
• Only one chart can be plotted, not several at the same time; unused plot commands must be commented out for not
interfering with the active chart. The size and resolution of the chart can be set up with the plot parameters.
• The charts are only plotted in [Test] mode, not in [Train] and [Trade] mode.
• The week, month, and year analysis is based on trading days, not real days. Therefore the week has 5 days, the month
22 days, and the year 260 days. Due to the different number of trading days (20..23) in a month, they are normalized
to 22, so day 22 is always the last trading day of the month.
• The standard deviations (blue bars in the chart) show how much the values differ from their average, and thus indicate
the significance of a nonzero average. The smaller the blue bar and the bigger the red bar, the more significant is the
seasonal effect. In the case of prices or returns, the standard deviation is a measure of volatility.
• When developing a strategy, it is helpful to examine the price difference profile at every trade entry bar for verifying
the trade entry rules and for determining the trade exit rules.

Examples:
#include <profile.c>

// price difference profile


392
function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,1000));

if(valley(Trend)) {
plotPriceProfile(40,0);
enterLong();
} else if(peak(Trend)) {
plotPriceProfile(40,2);
enterShort();
}
}
#include <profile.c>

// Trade distribution
function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,1000));

Stop = 100*PIP;
TakeProfit = 100*PIP;
if(valley(Trend))
enterLong();
else if(peak(Trend))
enterShort();

PlotHeight1 = 320;
PlotScale = 4;
plotTradeProfile(50);
}
#include <profile.c>

// Weekly return analysis


function run()
{
vars Price = series(price());
vars Trend = series(LowPass(Price,1000));

if(valley(Trend))
enterLong();
else if(peak(Trend))
enterShort();

PlotHeight1 = 320;
PlotScale = 3;
plotWeek(Equity,1);
}
#include <profile.c>

// Correlogram of High-to-High differences


function run()
{
PlotHeight1 = 320;
PlotScale = 10;
plotCorrelogram(priceHigh(0)-priceHigh(1),50);
}
#define DAYS 252 // 1 year
#define NN 30 // max number of assets

#include <profile.c>

// plot a heatmap of asset correlations


function run()
{
BarPeriod = 1440;
StartDate = 20150101;
LookBack = DAYS;

vars Returns[NN];
var Correlations[NN][NN]; // NN*NN matrix
int N = 0;
while(asset(loop(.../*some assets*/ ..)))
{
Returns[N] = series((price(0)-price(1))/price(0));
N++; // count number of assets
}

int i,j;
if(!is(LOOKBACK)) {
for(i=0; i<N; i++)
393
for(j=0; j<N; j++)
Correlations[N*i+j] = Correlation(Returns[i],Returns[j],DAYS);

plotHeatmap(Correlations,N);
quit("");
}
}

color (var Value, int color1, int color2, int color3, int color4) : int
Returns the color corresponding to a position within a range of up to 4 colors; used for plotting heatmaps or multiple
curves.

Parameters:
Value The color position within the range in percent; from 0 for color1 until 100 for color4.

color1..color4 The color range, starting with color1 and ending with color4. For 2-color or 3-color
ranges, color3 and color4 can be omitted. See Colors for the color format.

Returns
Color within the range.

Remarks:

• color(value,WHITE,BLACK) produces a greyscale.

Example:
for(i=0; i<N; i++)
for(j=0; j<N; j++)
plotGraph("Heatmap",j+1,-i-1,SQUARE,color(Correlations[i][j]*100,BLUE,RED));

assetHistory(string Name, int Mode)


Loads price history of an asset from online servers, such as Yahoo™, or from the broker's server.

Parameters:
Name Asset name used by Yahoo, or Quandl code, or 0 for the current asset.

Mode FROM_YAHOO - download daily (D1) price data from Yahoo Finance.
FROM_YAHOO|UNADJUSTED - use unadjusted prices.
FROM_QUANDL - download daily (D1) price data from Quandl (Zorro S required).
1 - download one-minute (M1) price data from the broker's server.
0 - download tick quote (T1) data from the broker's server (Zorro S required).

Remarks:

• For trading with brokers that do not support historical prices in their API, set the PRELOAD flag and
use assetHistory() for downloading recent historical data from Yahoo or Quandl prior to the first asset call. This works
for daily data only.
• The raw .csv data from Yahoo or Quandl is stored in History\history.csv. The Quandl database name
and '/' or '.' characters are stripped from the historical data file names.
• The prices must be available on the selected broker's price server. MT4 servers usually have no price history, so the
command will print an error message or only partially download the history. IB only provides limited price history. FCXM
394
servers provide price history from 2002 for currencies, and from 2008 for some CFDs. Before downloading prices,
check if they are not already available in the History folder or on the Zorro download page.
• If the asset was not subscribed, it is subscribed automatically. The recent values of the asset's Spread, PipCost, etc.
are updated to the Assets.dta file even if no price history is downloaded. This way new assets can be added and
available assets can be set up to their current parameter values for further simulations.
• The price history is loaded either for the number of recent years given by NumYears, or for all years
between StartDate and EndDate. It is stored in the format described under Data Import. If a price history file already
exists, or if NumYears is set to -1, no price data is downloaded. If the price history of the current year is not up to date,
new price data is downloaded and added to the existing file. Make sure that the time resolution of the downloaded
prices matches the resolution of the historic data files - the included files are based on 1-minute data.
• D1 price data from Yahoo or Quandl is not split into year files, but stored in a single .t6 file starting with 1990. For
testing parts of a multi-year .t6 file, use an 8-digit StartDate or EndDate. Connection to a broker is not required for
downloading Yahoo or Quandl data. Yahoo Open, high, low, and close prices are automatically adjusted for splits and
dividends when the UNADJUSTED flag is not added. The unadjusted close price is stored in the marketVal stream.
Yahoo data is downloaded from ichart.finance.yahoo.com, Quandl data from www.quandl.com/api/v3/datasets.
The Quandl CHRIS/CME, CHRIS/ICE, and YAHOO EOD databases are supported for price history. Any other Quandl
database can be downloaded with the dataDownload function. Check the correct spelling of the asset name or Quandl
code.
• For backtesting price history, BarPeriod can not be less than one bar. Set it to 1440 for testing D1 price data.
• A price history file with length 0 prevents downloading prices for that particular year. This way, by storing a 0-byte file,
price data that is not available or that is of bad quality can be excluded from download attempts and from the backtest.
• For using the newest prices, load the price history before calling asset(), otherwise the asset will use the existing price
data files. Use UpdateDays for calling assetHistory automatically to keep the price history up to date.
• The historical data format is described under Import. Single price quotes in T1 format can be loaded by
setting mode to 0 (with Zorro S). For this the price server of the broker must support quote-based price data; this is
the case for FXCM price history back to 2008, but not for IB or for most MT4 brokers. Dependent on the price server
speed, downloading T1 quotes can take a long time, such as a whole day for one year of price quotes.

Example:
// Update M1 price history of a list of assets
function run()
{
NumYears = 2;
while(loop("AUD/USD","EUR/USD","GBP/USD","GER30","NAS100",
"SPX500","UK100","US30","USD/CAD","USD/CHF","USD/JPY",
"XAG/USD","XAU/USD"))
{
assetHistory(Loop1,1);
}
}

saveStatus(string name)
loadStatus(string name)
Saves and loads the current trading status to a .trd file, containing the open trades, the status of the sliders, and
component specific variables (AlgoVar).

Parameters:
name Name and path of the file, or 0 for a default name like "Log\\scriptname.trd".

Remarks:

• The trading status is not saved or loaded in the initial run of the script. The Bar number must be higher than 0 for
saving or loading.
• The system status is automatically saved in [Trade] mode when trading is stopped, and loaded when it is started
again.
• The elements to be saved or loaded can be determined with the SaveMode variable.

395
Example:
function run()
{
if(Bar >= 1) {
AlgoVar[0] = 123;
SaveMode = SV_ALGOVARS;
saveStatus("Log\\Test.trd");
AlgoVar[0] = 456;
printf("\n %.0f",AlgoVar[0]);
loadStatus("Log\\Test.trd");
printf("\n %.0f",AlgoVar[0]);
quit();
}
}

login (int mode): int


Logs in or out to the broker API..

Parameters:
mode - 0 for checking the login state, 1 for logging in, 2 for logging out, 4 for completely closing the broker API.

Returns:
1 when Zorro is logged in, 0 otherwise.

Remarks:

• For recovering from a broker API crash, call login(4), then login(1).
• Zorro normally logs in and out automatically, so this function is for special purposes only.

exec (string program, string options, int mode)


Opens an external program, document, URL, or batch file.

Parameters:
program - file name of the exe, batch file, or document, or URL to be opened.
options - command line parameter string to be passed to the program, or 0 for no command line options.
mode - 1 for waiting until the external program was terminated, otherwise 0.

Returns:
0 if the program was not found or could not be started, nonzero otherwise. When mode was 1, the return code of the
program.

Remarks:

• The program parameter can specify a full path (from the root), a partial path (from the Zorro folder), or just a filename.
In the latter case the exec function first looks for the file in the Zorro folder, and then in the in the folders specified by
the system's PATH environment variable.
• If the program string does not have a filename extension, the exec function first tries the .COM extension, then the
.EXE extension, then the .BAT extension, and finally the .CMD extension.
• If the program string contains a URL or the name of a document, the standard internet browser or the standard editor
for that document is opened.
• '\' characters in strings, like for file paths, have to be given in C-Notation as "\\".

396
• The external program can be controlled with the keys function.

Examples:
exec("notepad","test.txt"); // open notepad
exec("c:\\programs\\internet explorer\\iexplore.exe","http://www.zorro-trader.com"); // open an URL with
Internet Explorer
exec("http://www.zorro-trader.com",0); // open an URL with the standard browser

keys(string format, ...)


Sends key strokes and mouse clicks to the active window or dialog. This way a script can 'remote control' other
external programs, f.i. for placing orders in the window of a trade software.

Parameters:
format - string containing the key characters to be sent, in the same format as in printf. Within the string, the
following tokens in square barackets have a special meaning:

[Ctrl-] - the following character is sent together with the [Ctrl]-key.


[Alt-] - the following character is sent together with the [Alt]-key.
[Shift-] - the following character is sent together with the [Shift]-key.
[Win-] - the following character is sent together with the [Window]-key.
[Click x,y] - mouse click on the spot given by the x,y coordinates relative to the window's upper left corner.
[...] - special function keys, such as [enter], [tab], [del], [f1] etc. are given in square brackets.
Use [cur], [cul], [cuu], [cud] for the cursor keys.
[]], [[] - the right and left square bracket can be given in square brackets.

Remarks:

• The keys are sent to the active window's message loop. This will work for all normal Windows applications, but not for
special programs that don't use a message loop.
• The function returns as soon as the key strokes are sent, but the reaction of the controlled program can take more
time. If the key stroke opens a dialog window, use window to wait until the dialog is active before sending further keys.
• This function should not be used to send keys to Zorro itself. Therefore, it should not be called when Zorro is the active
window. However, it can be used to send keys to other instances of Zorro.

Example:
// Opens Notepad, writes something, saves it, and closes Notepad.
function main()
{
// start Notepad before
while(!window("Editor")) wait(100); // wait until Notepad is open
keys("Zorro is cool!"); // write something
keys("[Ctrl-]s"); // open Save dialog (Ctrl-S)
while(!window(NULL)) wait(100); // wait until the dialog window is open
keys("cool.txt[Enter][Alt-][F4]"); // save as "cool.txt", and exit (Alt-F4)
}

keys(string format, ...)


Sends key strokes and mouse clicks to the active window or dialog. This way a script can 'remote control' other
external programs, f.i. for placing orders in the window of a trade software.

Parameters:
format - string containing the key characters to be sent, in the same format as in printf. Within the string, the
following tokens in square barackets have a special meaning:

[Ctrl-] - the following character is sent together with the [Ctrl]-key.


397
[Alt-] - the following character is sent together with the [Alt]-key.
[Shift-] - the following character is sent together with the [Shift]-key.
[Win-] - the following character is sent together with the [Window]-key.
[Click x,y] - mouse click on the spot given by the x,y coordinates relative to the window's upper left corner.
[...] - special function keys, such as [enter], [tab], [del], [f1] etc. are given in square brackets.
Use [cur], [cul], [cuu], [cud] for the cursor keys.
[]], [[] - the right and left square bracket can be given in square brackets.

Remarks:

• The keys are sent to the active window's message loop. This will work for all normal Windows applications, but not for
special programs that don't use a message loop.
• The function returns as soon as the key strokes are sent, but the reaction of the controlled program can take more
time. If the key stroke opens a dialog window, use window to wait until the dialog is active before sending further keys.
• This function should not be used to send keys to Zorro itself. Therefore, it should not be called when Zorro is the active
window. However, it can be used to send keys to other instances of Zorro.

Example:
// Opens Notepad, writes something, saves it, and closes Notepad.
function main()
{
// start Notepad before
while(!window("Editor")) wait(100); // wait until Notepad is open
keys("Zorro is cool!"); // write something
keys("[Ctrl-]s"); // open Save dialog (Ctrl-S)
while(!window(NULL)) wait(100); // wait until the dialog window is open
keys("cool.txt[Enter][Alt-][F4]"); // save as "cool.txt", and exit (Alt-F4)
}

mouse(int* x, int* y, HWND hWnd): int


Returns the mouse button state and mouse coordinates relative to a window.

Parameters:
x, y - pointers to int variables to be filled with the mouse coordinates.
hWnd - window handle for returning mouse coordinates relative to the upper left corner, or 0 for returning screen
coordinates.

Returns:
0 for no mouse button pressed down, 1 for left, 2 for right, 4 for middle button.

Remarks:

• The mouse function can be used for calibrating the positions of "Buy" and "Sell" buttons on a broker's web interface,
f.i. for binary options. Automated buying and selling can then be performed by clicking those buttons with
the keys function.

Example:
void main()
{
while(wait(50)) {
int x,y;
HWND h = window("Script Editor");
int button = mouse(&x,&y,h);
printf("\r%04d %04d %d",x,y,button);
}
}

398
window(string title) : HWND
Returns a handle to the active window when its title bar contains the title string. Can be used to wait until a certain
window or dialog becomes active.

Parameters:
title - part of the window title (case sensitive), or 0 for returning the handle to the window that recently became active.

Returns:
HWND of the found active window. Otherwise 0.

Remarks:

• Normally Zorro's own window is the active window, unless another application was started or a Windows dialog became
active.

Example:
See keys.

String functions
The following functions - mostly from the standard C library - can be used to manipulate strings:

strcpy (string dest, string src): string


Fills the char array dest with the content of string src. The buffer size of dest must not be exceeded.

Parameters:
dest - destination string, either a char array of sufficient length or a temporary string as returned from strmid, strf,
or strx.
src - source string

strcat (string dest, string src): string


Appends a copy of src to the end of the string contained in the char array dest. The buffer size of dest must not be
exceeded.

Parameters:
dest - destination string, either a char array of sufficient length or a temporary string as returned from strmid, strf,
or strx.
src - source string

strlen (string str): int


Returns the number of characters in the given string.

Parameters:
str - string

strcount (string str, char c): int


Returns the number of occurrences of the character c in the given string. This is not a standard C string function.

399
Parameters:
str - string
c - character to be counted, f.i. 0x0a for a line feed.

strcmp(string str1, string str2): int


Compares the strings (case sensitive) and returns 0 if they have the same content. For comparing strings with text
constants and case insensivity, the == and != operators can alternatively be used.

Parameters:
str1 - first string to compare
str2 - second string to compare

strstr(string str1, string str2): string


Returns a substring beginning with the first occurrence (case sensitive) of str2 within str1, or NULL when str2 is not
contained in str1. This function is often used to check if a string has the same content or is contained in another string.

Parameters:
str1 - string to search within
str2 - substring to be found

strchr(string str, int c): string


strrchr(string str, int c): string
Returns a substring beginning with the first (strchr) or last (strrchr) occurrence of the character c in the string str,
or NULL when c is not contained in str. This function can be used f.i. for getting the extension or the file name from a
path name.

Parameters:
str - string to search within
c - character to be found

strtok(string str, string delimiter): string


Returns a substring from the string str. The set of characters in the delimiter string specifies the characters that
separate the substrings.

Parameters:
str - string to separate the first substring from, or 0 for continuing the search with the next substring. The string is
modified by replacing the delimiters with 0 bytes.
delimiter - character or set of characters that separate the substrings, f.i. "," for the comma that separates fields in a
CSV file.

strvar(string str, string name, var val): var


Parses a number from the string str that is preceded by the identifier name. If no identifier name is found in the
string str, the function returns val (default 0). Can be used to parse an .ini file. This is not a standard C string function.

Parameters:
str - string containing the number.
name - substring, variable identifier, or 0 to read the first number out of the string.
val - returned when no identifier name or no number was found in the string.

400
strtext(string str, string name, string default): string
Parses text preceded by the identifier name. If the text contains spaces, it should be in double quotes. If no
identifier name is found in the string str, the function returns default. Can be used to parse an .ini file. This is not a
standard C string function.

Parameters:
str - string containing the text to be extracted.
name - substring, text identifier.
default - returned when no identifier name was found in the string.

strmid(string str, int first, int count): string


Returns a temporary copy of a substring of str, with length count and starting at position first. This is not a standard
C string function.

Parameters:
str - string containing the source text.
first - position of the first character of the copy; first = 0 copies the string from the begin.
count - number of characters in the copy; 1000 characters maximum. If count exceeds the length of str, it is copied
until the end.

strx(string str, string orig, string repl): string


strxc(string str, char orig, char repl): string
Returns a temporary copy of str where all ocurrences of orig are exchanged with repl. This is not a standard C string
function. For cutting off a string at a certain character, replace it with 0 (f.i. strxc(Name, '.', 0) cuts off the extension
from a file name).

Parameters:
str - string containing the source text.
orig - original text or char to be exchanged.
repl - replacement text or char; the returned string must not exceed 1000 characters.

strw(string str): short*


Converts a 8-bit char string to a temporary 16-bit wide string (an array of shorts). Useful for passing small strings, such
as file names, to DLLs that require 16-bit characters. This is not a standard C string function.

Parameters:
str - string containing the text to be converted, 1000 characters max.

strf(string format, ...): int


Returns a temporary string with data and variables formatted by the format string as in printf. This is not a standard C
string function.

Parameters:
format - format string (see printf); limited to a single line (no "\n" characters) and to maximum 1000 characters of the
returned string.

sprintf(string dest, string format, ...): int


Like strf, but fills a given char array with formatted data, and returns the number of characters written. The dest char
array must have sufficient size.

Parameters:

401
dest - destination string, a char array of sufficient length.
format - format string (see printf).

sscanf(string str, string format, ...): int


Reads formatted data from a string into variable pointers or substrings according to the format string (see example).
Returns the number of variables read from the string. The execution time of this function depends on the string length,
so don't use it for very long strings such as the content of a whole file. Special format codes are supported,
f.i. "%[^\n]" reads a substring until the end of the line, or "%[^,]" parses a line into comma-separated strings. Details
can be found in any C/C++ manual or online documentation. !! Unlike sprintf and printf, for reading floating point
numbers with %f the target variable must be float; var or double require %lf.

Parameters:
str - string to parse
format - format string (see printf).

atof(string str): var

atoi(string str): int


Converts a number from a string to a var or int variable. If the string does not begin with a valid number, 0 is returned.

Parameters:
str - string containing a number.

Remarks:

• Most of the string functions are standard C functions; a more extensive description with examples can be found in
C/C++ books or tutorials.
• C functions like strcpy, strcat, sprintf are normally not fool proof. They will crash when the original length of the
destination string is exceeded. Use strf for extending the length of a string.
• For convenience, some string functions (like strf) return a temporary string that can be used for passing string
arguments to other functions without allocating a char array first. Temporary strings are taken from a string pool, and
have a maximum length of 1023 characters and a lifetime of 10 function calls, i.e. they are recycled and overwritten
after 10 subsequent calls.

Examples (see also file functions and Simulate.c):


// read a variable out of a string
// with a format like "name = 123.456"
var readVar(string str,string name)
{
float f = 0; // sscanf needs float
string s = strstr(str,name);
if(s) s = strstr(s,"=");
if(s) sscanf(s+1,"%f",&f);
return f;
}

function main()
{
var Test1 = 0,Test2 = 0;
string s = file_content("Strategy\\vars.ini");
if(s) {
Test1 = readVar(s,"Test1");
Test2 = readVar(s,"Test2");
}
printf("\nTest1 = %f, Test2 = %f",Test1,Test2);
}

402
File functions
The following functions can be used to read, write, or otherwise handle files:

file_copy (string dest, string src)


Copies the file with the name src to the file with the name dest. If the dest file already exists, it is overwritten.

Parameters:
dest - destination file path (either absolute, or relative to the Zorro folder, e.g. "Data\\Trend.fac").
src - source file path.

file_delete (string name)


Deletes a file.

Parameters:
name - file path

file_length (string name): long


Checks if a file with the given name exists, and returns its length in bytes. If the file was not found, 0 is returned.

Parameters:
name - file path

file_date (string name): long


Checks if a file with the given name exists, and returns its last modification date as the number of seconds since
January 1, 1970. If the file was not found, 0 is returned.

Parameters:
name - file path

file_select (string path, string filter): string


Opens a file dialog box at a given directory that lets the user select a file to open. Returns the selected file name
including path.

Parameters:
path - initial directory to open, f.i. "Data", or 0 for opening the Zorro directory.
filter - list of pairs of null-terminated filter strings. The last string must be terminated by two null characters ("\0"). The
first string in each pair describes the filter (f.i. "Parameter Files"), and the second string specifies the filter pattern
(f.i. "*.par"). Multiple filter patterns can be separated with a semicolon (f.i. "*.par;*.fac;*.c"). Do not include spaces in
the pattern string. Example of a filter list with three pairs: "All files (*.*)\0*.*\0Par, Fac\0*.par;*.fac\0"Text
files"\0*.txt\0\0".

file_content (string name): string


Returns a temporary null-terminated string containing the content of the file, or 0 when the file was not found. The
string is preserved until the next file_content call.

Parameters:
name - file path

file_read (string name, string content, int length): long


Reads the content of a file into a string or array. Returns the number of read bytes.
403
Parameters:
name - file path
content - string (or array of any type) to be filled with the content of the file; must have a size of at least length+1.
length - maximum number of characters resp. bytes to read.

file_write (string name, string content, int length)


Stores the content of a string, series, or other data array in a file.

Parameters:
name - file path
content - string or other data to be written.
length - number of bytes to be written, or 0 for writing the complete content of a string.

file_append (string name, string content, int length)


Opens a file and appends text or other data to it; can be used to export data to Excel or other programs. If the file does
not exist, it is created.

Parameters:
name - file path
content - text or other data to be appended at the end of the file.
length - number of bytes to be written. Can be omitted for writing the content of a string.

Remarks:

• If a file to be read from or written into is not found, an error message will be printed to the message window, and the
function returns 0. The error message can be suppressed by adding '#' at the begin of the file name (f.i. file_content
("#myfilename.txt")).

Examples:
//script for merging all WFO .par and .fac files
//from two strategies to build a combined strategy

string src1 = "Z1"; // first strategy, can be identical to dest


string src2 = "Z1add"; // second strategy, must be different to dest
string dest = "Z1"; // combined strategy

int file_merge(int n,string ext)


{
char name1[40],name2[40],name3[40];
if(n) {
sprintf(name1,"Data\\%s_%i.%s",src1,n,ext);
sprintf(name2,"Data\\%s_%i.%s",src2,n,ext);
sprintf(name3,"Data\\%s_%i.%s",dest,n,ext);
} else {
sprintf(name1,"Data\\%s.%s",src1,ext);
sprintf(name2,"Data\\%s.%s",src2,ext);
sprintf(name3,"Data\\%s.%s",dest,ext);
}
if(!file_date(name1))
return 0; // file does not exist
if(0 != strcmp(name3,name1))
if(!file_copy(name3,name1))
return 0;
if(!file_append(name3,file_content(name2)))
return 0;
return 1;
}

function main()
{
int cycles = 1;
for(; cycles < 100; cycles++)
if(!file_merge(cycles,"par"))
break;

cycles += file_merge(0,"par");
cycles += file_merge(0,"fac");

404
if(cycles > 3)
printf("%i files combined!",cycles);
else
printf("Error!");
}
// set up strategy parameters from a .ini file
function run()
{
static var Parameter1 = 0, Parameter2 = 0;
if(is(INITRUN)) { // read the parameters only in the first run
string setup = file_content("Strategy\\mysetup.ini");
Parameter1 = strvar(setup,"Parameter1");
Parameter2 = strvar(setup,"Parameter2");
}
}

// mysetup.ini is a plain text file that contains


// the parameter values in a format like this:
Parameter1 = 123
Parameter2 = 456

putvar (string FileName, string VarName, var Value)


getvar (string FileName, string VarName): var
Writes, updates or reads a variable to or from a file. Can be used for storing data globally or exchanging data between
different scripts or Zorro instances.

Parameters:
FileName Name of the file. If it does not exist, it is created by putvar.

VarName Name of the variable.

Value Value to be written or updated.

Returns:
Current variable value (getvar). If the variable does not yet exist, 0 is returned.

Remarks:

• If several Zorro instances access the same variable at the same time, it should be locked between reading and writing.
• The variables are stored in a normal text file that can be viewed with a text editor or spreadsheet program.

Example:
function increase_global_counter(string Name)
{
string FileName = "\\Log\\Counters.csv";
lock();
int Counter = getvar(FileName,Name);
putvar(FileName,Name,Counter+1);
unlock();
}

Dataset handling
The following functions can be used for downloading and parsing historical or recent data records from various sources
on the Internet. Every record begins with a time stamp. The rest of the data can have arbitrary content, such as option
chains, order book content, reports, interest rates, or any other CSV formatted data.

405
dataDownload (string Code, int Mode, int Period): int
Downloads the dataset with the given Code from Quandl™ or Yahoo™, and stores it in CSV format in
the History folder. Returns the number of data records. Data is only downloaded when it is more recent than the last
downloaded data plus the given Period in minutes (at 0 the data is always downloaded). The Quandl Bridge or Zorro
S is required for loading Quandl datasets.

dataParse (int Handle, string Format, string FileName): int


Parses data records from the CSV file FileName and appends them to the begin of the dataset with the
given Handle number. Records can have time/date, floating point, integer, and text fields. CSV headers are skipped.
Several CSV files can be appended to the same dataset when their record format is identical. The CSV file can be in
ascending or descending chronological order, but the resulting dataset should be always in descending order, i.e. the
newest records are at the begin. Any record begins with the time stamp field in wdate format, followed by the other
fields in the order determined by the Format string. Each record thus has a size of 8 bytes for the time stamp plus 4
bytes * number of f, i, s placeholders in the format string. The function returns the number of records read, or 0 when
the file can not be read or has a wrong format.

dataSort (int Handle)


Sorts the dataset with the given Handle in descending time stamp order.

dataSave (int Handle, string FileName, int Start, int Num)


Stores a part of the dataset with the given Handle number in a binary file in the History folder for faster
access. Num records are stored, beginning with the record Start. If both parameters are omitted or zero, the whole
dataset is stored.

dataLoad (int Handle, string FileName, int Fields): int


Reads a dataset from a binary file. Fields is the number of fields per record, including the date/time field at the begin of
any record. Returns the number of records read, or 0 when the file can not be read or has a wrong size.

dataNew (int Handle, int Records, int Fields): void*


Deletes the given dataset and creates a new empty dataset with the given number of Records and Fields. If they are 0,
the dataset is just deleted and the memory freed. Returns a pointer to the begin of the first record, or 0 when no new
dataset was created.

dataMerge (int Handle1, int Handle2): int


Merges the dataset Handle2 into Handle1 so that it contains all records in descending time stamp order.
The Handle1 dataset must be either empty or have the same number of columns as Handle2. The number of rows may
be different. Returns the total number of records, or 0 when the datasets could not be merged.

dataFind (int Handle, var Date): int


Returns the number of the first record at or before the given Date in wdate format. Returns -1 when no matching record
was found or when no data array with this handle exists.

dataVar (int Handle, int Row, int Column): var


Returns the value of the floating point field Column from the record Row. If Column is 0, the time stamp of the record
is returned in wdate format. If Row is negative, the record is taken from the end of the dataset, i.e. Row = -1 accesses
the oldest record.

dataInt (int Handle, int Row, int Column): int


Returns the value of the integer field Column from the record Row.

dataStr (int Handle, int Row, int Column): string


Returns a string of up to 3 characters from the text field Column from the record Row. If Column is 0, it returns a pointer
to the timestamp field, i.e. the start of the record. For getting a pointer to the first record, call dataStr(Handle,0,0).
If Row or Column exceed the number of records and fields, 0 is returned.

406
dataSet (int Handle, int Row, int Column, var Value)
dataSet (int Handle, int Row, int Column, int Value)
Stores the Value in the floating point or integer field Column of the record Row. Can be used for modifying datasets f.i.
for removing outliers or adding parameters. When modifying the time stamp field of the record (Column = 0), make sure
to keep descending order of dates in the array.

dataFromQuandl (int Handle, string Format, string Code, int Column): var
Helper function for generating an indicator based on a Quandl™ dataset. Works in live trading as well as in backtest
mode, and returns the content of the field Column from the dataset Code in the given Format. Source code
in options.c, which must be included for using this function. Quandl Bridge or Zorro S required.

Parameters:
Code The Yahoo asset name or Quandl database/dataset code, f.i. "WIKI/AAPL". The name of the stored
file is composed from Code with "/" replaced by "-", plus "1" when only the most recent record was
downloaded, plus ".csv".

Mode FROM_YAHOO for downloading the dataset from Yahoo™, FROM_QUANDL for downloading it from
Quandl™, FROM_QUANDL|1 for downloading only the most recent record for live trading.

Period Minimum time in minutes to keep the last downloaded file until a newer file is downloaded, or 0 for
always downloading the file.

Handle A number from 1...800 that identifies the dataset. Handles above 800 are interally used for Zorro's pre-
defined indicators.

FileName Name of the file. If no path is included, the file is expected in the History folder. If the name has no
extension, ".csv" is added.

Format Format string with placeholders, similar to the printf and wdatef format, for parsing CSV records into a
dataset. Fields are separated with commas or semicolons (not mixed). Any field can be either empty,
or contain a placeholder that determines the field content. Empty fields are skipped. The following
placeholders are supported:

+ at the begin of the format string - ascending date order in the .csv file, and appending the file to the
end of the dataset.
- at the begin of the format string - the .csv file contains no header. Otherwise the first line is assumed
a header and is skipped.
f - for a floating point field, f.i. 123.456.
i - for an integer field. Nonnumerical characters are skipped, f.i. "07/21/16 13:57" is parsed
as 721161357.
s - for a text field. Only the first 3 characters are stored.
% - for the date/time field in the wdate format. A record must contains at least one date/time field. If
there are more, f.i. separate fields for date and time, they are added.

The f, i, s placeholders can be followed by a field number in the destination dataset.


Example: "+%Y%m%d %H%M%S,f3,f1,f2,f4,f6" parses Histdata™ CSV files into a dataset
in T6 format (more examples below). If the number is omitted, the fields are parsed in ascending order.
The number of fields in the format string can be different to the fields in the CSV record. The remaining
fields are then filled with 0.
Records Number of records in the dataset.

Fields Number of fields per record, including the date field.

Date Timestamp in Windows DATE (wdate) format.

407
Start, The first record and the number of records to be stored.
Num

Row, The record and field number, starting with 0. The date is always the first field of the record. If Row is
Column negative, the record is taken from the end of the file, i.e. Row = -1 accesses the most recent record.

Value New value of the addressed field.

Remarks:

• For loading data from Quandl, register on their website and enter the Quandl API key in the Zorro.ini file. The Quandl
Bridge or Zorro S is required.
• For speed reasons, full historical data arrays should be only loaded in the initial run of the system. While live trading,
use FROM_QUANDL|1 or FROM_YAHOO|1 for downloading the most recent data record only (see example).
• Binary dataset files are a list of records in descending order that begin with a 8-byte date/time field. They have the
same format as the .t1 and .t6 historical data files, which can therefore also be loaded with dataLoad.
• For splitting extremely large CSV files into smaller parts, a good tool is CSVSplitter.

Example:
// parse iVolatility historical option chain data and store the resulting array
void main()
{
string Format = "+,,%m/%d/%y,,,i,f,s,s,f,f,f,,f";
int records = dataParse(1,Format,"iVolatility_SPY_2014_1.csv");
records += dataParse(1,Format,"iVolatility_SPY_2014_2.csv");
records += dataParse(1,Format,"iVolatility_SPY_2015_1.csv");
records += dataParse(1,Format,"iVolatility_SPY_2015_2.csv");
records += dataParse(1,Format,"iVolatility_SPY_2016_1.csv");
printf("\n%d records parsed",records);
dataSave(1,"SPY_Options.t8");
}

// dataFromQuandl source code


var dataFromQuandl(int Handle,string Format,string Code,int Column)
{
string Filename = strxc(Code,'/','-');
if(dataFind(Handle,0) < 0) { // data array not yet loaded
dataDownload(Code,FROM_QUANDL,12*60);
dataParse(Handle,Format,Filename);
}
if(is(TRADEMODE) && !is(LOOKBACK)) {
strcat(Filename,"1");
int Rows = dataDownload(Code,FROM_QUANDL+1,60);
if(Rows) dataParse(Handle,Format,Filename); // add new record to the begin
return dataVar(Handle,0,Column);
} else {
int Row = dataFind(Handle,wdate());
return dataVar(Handle,Row,Column);
}
}

// US treasury 3-months interest rate


var DTB3() {
return dataFromQuandl(801,"%Y-%m-%d,f","FRED/DTB3",1);
}

// COT report for S&P500


var CFTC_SP(int Column) {
return dataFromQuandl(802,"%Y-%m-%d,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f","CFTC/TIFF_CME_SP_ALL",Column);
}

// more format examples


string Format = "%Y-%m-%d,f3,f1,f2,f4,,,f6,f5"; // Quandl futures data to .t6, f.i. "CHRIS/CME_CL1"
string Format = "%Y-%m-%d,f3,f1,f2,f4,f6,f5"; // Yahoo data to unadjusted .t6, with adjusted close stored in
fVal

408
Dataset handling
The following functions can be used for downloading and parsing historical or recent data records from various sources
on the Internet. Every record begins with a time stamp. The rest of the data can have arbitrary content, such as option
chains, order book content, reports, interest rates, or any other CSV formatted data.

dataDownload (string Code, int Mode, int Period): int


Downloads the dataset with the given Code from Quandl™ or Yahoo™, and stores it in CSV format in
the History folder. Returns the number of data records. Data is only downloaded when it is more recent than the last
downloaded data plus the given Period in minutes (at 0 the data is always downloaded). The Quandl Bridge or Zorro
S is required for loading Quandl datasets.

dataParse (int Handle, string Format, string FileName): int


Parses data records from the CSV file FileName and appends them to the begin of the dataset with the
given Handle number. Records can have time/date, floating point, integer, and text fields. CSV headers are skipped.
Several CSV files can be appended to the same dataset when their record format is identical. The CSV file can be in
ascending or descending chronological order, but the resulting dataset should be always in descending order, i.e. the
newest records are at the begin. Any record begins with the time stamp field in wdate format, followed by the other
fields in the order determined by the Format string. Each record thus has a size of 8 bytes for the time stamp plus 4
bytes * number of f, i, s placeholders in the format string. The function returns the number of records read, or 0 when
the file can not be read or has a wrong format.

dataSort (int Handle)


Sorts the dataset with the given Handle in descending time stamp order.

dataSave (int Handle, string FileName, int Start, int Num)


Stores a part of the dataset with the given Handle number in a binary file in the History folder for faster
access. Num records are stored, beginning with the record Start. If both parameters are omitted or zero, the whole
dataset is stored.

dataLoad (int Handle, string FileName, int Fields): int


Reads a dataset from a binary file. Fields is the number of fields per record, including the date/time field at the begin of
any record. Returns the number of records read, or 0 when the file can not be read or has a wrong size.

dataNew (int Handle, int Records, int Fields): void*


Deletes the given dataset and creates a new empty dataset with the given number of Records and Fields. If they are 0,
the dataset is just deleted and the memory freed. Returns a pointer to the begin of the first record, or 0 when no new
dataset was created.

dataMerge (int Handle1, int Handle2): int


Merges the dataset Handle2 into Handle1 so that it contains all records in descending time stamp order.
The Handle1 dataset must be either empty or have the same number of columns as Handle2. The number of rows may
be different. Returns the total number of records, or 0 when the datasets could not be merged.

dataFind (int Handle, var Date): int


Returns the number of the first record at or before the given Date in wdate format. Returns -1 when no matching record
was found or when no data array with this handle exists.

dataVar (int Handle, int Row, int Column): var


Returns the value of the floating point field Column from the record Row. If Column is 0, the time stamp of the record
is returned in wdate format. If Row is negative, the record is taken from the end of the dataset, i.e. Row = -1 accesses
the oldest record.

dataInt (int Handle, int Row, int Column): int


Returns the value of the integer field Column from the record Row.

409
dataStr (int Handle, int Row, int Column): string
Returns a string of up to 3 characters from the text field Column from the record Row. If Column is 0, it returns a pointer
to the timestamp field, i.e. the start of the record. For getting a pointer to the first record, call dataStr(Handle,0,0).
If Row or Column exceed the number of records and fields, 0 is returned.

dataSet (int Handle, int Row, int Column, var Value)


dataSet (int Handle, int Row, int Column, int Value)
Stores the Value in the floating point or integer field Column of the record Row. Can be used for modifying datasets f.i.
for removing outliers or adding parameters. When modifying the time stamp field of the record (Column = 0), make sure
to keep descending order of dates in the array.

dataFromQuandl (int Handle, string Format, string Code, int Column): var
Helper function for generating an indicator based on a Quandl™ dataset. Works in live trading as well as in backtest
mode, and returns the content of the field Column from the dataset Code in the given Format. Source code
in options.c, which must be included for using this function. Quandl Bridge or Zorro S required.

Parameters:
Code The Yahoo asset name or Quandl database/dataset code, f.i. "WIKI/AAPL". The name of the stored
file is composed from Code with "/" replaced by "-", plus "1" when only the most recent record was
downloaded, plus ".csv".

Mode FROM_YAHOO for downloading the dataset from Yahoo™, FROM_QUANDL for downloading it from
Quandl™, FROM_QUANDL|1 for downloading only the most recent record for live trading.

Period Minimum time in minutes to keep the last downloaded file until a newer file is downloaded, or 0 for
always downloading the file.

Handle A number from 1...800 that identifies the dataset. Handles above 800 are interally used for Zorro's pre-
defined indicators.

FileName Name of the file. If no path is included, the file is expected in the History folder. If the name has no
extension, ".csv" is added.

Format Format string with placeholders, similar to the printf and wdatef format, for parsing CSV records into a
dataset. Fields are separated with commas or semicolons (not mixed). Any field can be either empty,
or contain a placeholder that determines the field content. Empty fields are skipped. The following
placeholders are supported:

+ at the begin of the format string - ascending date order in the .csv file, and appending the file to the
end of the dataset.
- at the begin of the format string - the .csv file contains no header. Otherwise the first line is assumed
a header and is skipped.
f - for a floating point field, f.i. 123.456.
i - for an integer field. Nonnumerical characters are skipped, f.i. "07/21/16 13:57" is parsed
as 721161357.
s - for a text field. Only the first 3 characters are stored.
% - for the date/time field in the wdate format. A record must contains at least one date/time field. If
there are more, f.i. separate fields for date and time, they are added.

The f, i, s placeholders can be followed by a field number in the destination dataset.


Example: "+%Y%m%d %H%M%S,f3,f1,f2,f4,f6" parses Histdata™ CSV files into a dataset
in T6 format (more examples below). If the number is omitted, the fields are parsed in ascending order.
The number of fields in the format string can be different to the fields in the CSV record. The remaining
fields are then filled with 0.
Records Number of records in the dataset.

410
Fields Number of fields per record, including the date field.

Date Timestamp in Windows DATE (wdate) format.

Start, The first record and the number of records to be stored.


Num

Row, The record and field number, starting with 0. The date is always the first field of the record. If Row is
Column negative, the record is taken from the end of the file, i.e. Row = -1 accesses the most recent record.

Value New value of the addressed field.

Remarks:

• For loading data from Quandl, register on their website and enter the Quandl API key in the Zorro.ini file. The Quandl
Bridge or Zorro S is required.
• For speed reasons, full historical data arrays should be only loaded in the initial run of the system. While live trading,
use FROM_QUANDL|1 or FROM_YAHOO|1 for downloading the most recent data record only (see example).
• Binary dataset files are a list of records in descending order that begin with a 8-byte date/time field. They have the
same format as the .t1 and .t6 historical data files, which can therefore also be loaded with dataLoad.
• For splitting extremely large CSV files into smaller parts, a good tool is CSVSplitter.

Example:
// parse iVolatility historical option chain data and store the resulting array
void main()
{
string Format = "+,,%m/%d/%y,,,i,f,s,s,f,f,f,,f";
int records = dataParse(1,Format,"iVolatility_SPY_2014_1.csv");
records += dataParse(1,Format,"iVolatility_SPY_2014_2.csv");
records += dataParse(1,Format,"iVolatility_SPY_2015_1.csv");
records += dataParse(1,Format,"iVolatility_SPY_2015_2.csv");
records += dataParse(1,Format,"iVolatility_SPY_2016_1.csv");
printf("\n%d records parsed",records);
dataSave(1,"SPY_Options.t8");
}

// dataFromQuandl source code


var dataFromQuandl(int Handle,string Format,string Code,int Column)
{
string Filename = strxc(Code,'/','-');
if(dataFind(Handle,0) < 0) { // data array not yet loaded
dataDownload(Code,FROM_QUANDL,12*60);
dataParse(Handle,Format,Filename);
}
if(is(TRADEMODE) && !is(LOOKBACK)) {
strcat(Filename,"1");
int Rows = dataDownload(Code,FROM_QUANDL+1,60);
if(Rows) dataParse(Handle,Format,Filename); // add new record to the begin
return dataVar(Handle,0,Column);
} else {
int Row = dataFind(Handle,wdate());
return dataVar(Handle,Row,Column);
}
}

// US treasury 3-months interest rate


var DTB3() {
return dataFromQuandl(801,"%Y-%m-%d,f","FRED/DTB3",1);
}

// COT report for S&P500


var CFTC_SP(int Column) {
return dataFromQuandl(802,"%Y-%m-%d,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f,f","CFTC/TIFF_CME_SP_ALL",Column);
}

// more format examples


string Format = "%Y-%m-%d,f3,f1,f2,f4,,,f6,f5"; // Quandl futures data to .t6, f.i. "CHRIS/CME_CL1"
string Format = "%Y-%m-%d,f3,f1,f2,f4,f6,f5"; // Yahoo data to unadjusted .t6, with adjusted close stored in
fVal

411
HTTP functions
The HTTP functions can retrieve content - prices, signals, news, or indicators such as VIX or COTR - from external
websites and use them in the trading strategy. They can also execute PHP scripts f.i. for sending emails from a web
server (Zorro 1.16 and above).

http_transfer (string url, string data): string


Transfers data to or from the web address given by url. The function waits until the web page content or the PHP script
response is transferred back to Zorro. The data string can contain arguments to be passed to a PHP script (such as the
content of an email). The function returns a temporary string containing the web page content for further evaluation.

http_send (string url, string data, string header): int


Starts a HTTP_GET (when data == 0) or HTTP POST (when data != 0) data transfer to or from a web address. The
function returns a identifier number (id) for controlling the data transfer with http_status. The data string can contain
arguments to be passed to the PHP script (such as the content of an email). If the data string begins with a '#' character,
the following word is used for the request type instead of POST (f.i. "#DELETE ..." or "#PATCH ..."). The header string
contains a custom header f.i. for authorization, or 0 for not using a custom header. After the transfer is finished, the
identifier number must be freed with http_free.

http_post (string url, string data): int


Starts a HTTP POST data transfer to or from a web address. The function returns a identifier number (id) for controlling
the data transfer with http_status. The data string can contain arguments to be passed to the PHP script (such as the
content of an email). After the transfer is finished, the identifier number must be freed with http_free.

http_proxy (string proxy, int port)


Sets up a HTTP proxy server for the connection to the Internet. If no proxy is used, or if you don't know what a HTTP
proxy is, you won't need this function.

http_status (int id): long


Returns the number of bytes received with a HTTP data transfer with the identifier number id, or 0 if the transfer is still
in progress, or -1 if the transfer failed, or -2 if the identifier number is invalid.

http_result (int id, string content, long size): long


Stores the received data of a HTTP data transfer in the content string, up to size bytes, and returns the received
number of bytes.

http_free (int id)


Frees the identifier number of a HTTP data transfer and stops the transfer. Must be called at the end of each HTTP data
transfer. For stopping the last transfer, set id to 0.

Parameters:
proxy - proxy server name (example: "proxy-host.com").
port - port of the proxy server (example: 8080).
url - URL of the web page (example: "http://opserver.de/scratch/ip.php")
data - string (or char array) containing the data to be sent (example: "user=John&password=Secret"), or 0 for no
data.
content - string (or char array) to receive the web page content or the result data of a HTTP POST operation.
size - maximum number of characters to be received, or 0 for using the actual content string length.
id - identifier number, returned by http_post().

Remarks:

412
• Up to 4 HTTP data transfers can be run at the same time using http_post.
• All functions return 0 if an error occured and nonzero if everything went ok (if not otherwise mentioned in the function
description).
• The data string can be accessed in a PHP script through the $_POST string array (see example). String variables can
be named with '=' and different variables can be separated with '&', as in "user=John&password=Secret".
• The strvar function can be used to parse numbers on certain locations of web pages.

Examples:
// access the CFTC website, read the current Commitment Of Traders Report,
// and store it in a file.
function main()
{
file_delete("cot.txt"); // delete previous report
file_append("cot.txt",
http_transfer("http://www.cftc.gov/dea/futures/deacmesf.htm",0));
}
// Download historical price data from Yahoo
function main()
{
int id = http_post("http://ichart.finance.yahoo.com/table.csv?s=AAPL&c=1980",0);
if(!id) return;
while(!http_status(id))
if(!wait(100)) return; // wait for the server to reply
int length = http_status(id);
if(length > 0) { //transfer successful?
string content = (string)malloc(length);
http_result(id,content,length); // store price data in large string
file_write("History\\AAPL.csv",content,length);
free(content); // release the string
}
http_free(id); //always clean up the id!
}
// start the script "ip.php" on a remote server, and
// print dots until Zorro's IP address is returned
function main()
{
char ip_str[100]; // just a long empty string
int id = http_post("http://opserver.de/scratch/ip.php",0);
if(!id) return;
while(!http_status(id)) {
if(!wait(100)) return; // wait for the server to reply
printf(".");
}
if(http_status(id) > 0) { //transfer successful?
http_result(id,ip_str,100); //get the replied IP
printf("\n%s",ip_str);
} else
printf("\nError during transfer!");
http_free(id); //always clean up the id!
}

ip.php:
<?php
echo "Your IP address: " . $_SERVER['REMOTE_ADDR'];
?>
// send an email when a trade is entered
function sendEmailAboutTrade()
{
// compose the message
char content[1000];
sprintf(content,"content=Zorro has entered a trade!\n%d Lots of %s",
TradeLots,TradeAsset);
http_transfer("http://www.myserver.com/zorromail.php",content);
}

zorromail.php:
<?php
$to = "me@myself.com";
$subject = "Message from Zorro!";
$body = $_POST['content'];
mail($to,$subject,$body);
?>

413
FTP functions
The FTP functions can be used for uploading or downloading files from a web server (Zorro 1.16 and above).

ftp_download(string url, string path, string username, string password)


Downloads a file from a FTP server.

ftp_upload(string url, string path, string username, string password)


Uploads a file to a FTP server.

ftp_getdate(string url, string username, string password)


Ascertains the timestamp and the file size of a file stored on a FTP server.

ftp_stop()
Stops the currently running FTP transfer.

ftp_size(): long
Returns the total file size of the current/last file in bytes.

ftp_sent(): long
Returns the amount of sent data of the current/last file in bytes.

ftp_timestamp(): long
Returns the timestamp of the current/last file, after ftp_getdate() was executed successfully.

ftp_status()
Returns the status of a currently running or the last FTP transfer:

-1 if the last FTP transfer was stopped because of an error


0 if the FTP transfer is still running
1 if the last FTP transfer was successful

ftp_log (var mode)


Enables/disables the logging of FTP transfers. The logfile is named "ftp_log.txt".

Parameters:
url - URL of the file to be downloaded, or destination URL for a file upload (e.g.:
"ftp://www.testhoster.com/downloads/test.txt").
path - local path + filename (e.g.: "testdir/test.txt")
username - FTP username
password - FTP password
mode - 1 to enable, 0 to disable FTP logging

Remarks:

• The functions ftp_download(), ftp_upload(), ftp_getdate() and ftp_debug() return 0 if an error occured or nonzero
otherwise.
• After calling ftp_download(), ftp_upload() or ftp_getdate() you have to wait until the value of ftp_status() is
nonzero.
• Only one FTP transfer can be run at the same time.

414
Example:
function main()
{
//Downloads the file "myfile.txt" and saves it in the Data folder
ftp_download("ftp://www.testhost.com/files/myfile.txt","Data/myfile.txt","username","password");
while(!ftp_status()) //as long as the download is running
Sleep(50);
if (ftp_status() == 1)
printf("\nDownload successful!");
else
printf("\nDownload failed!");
}

415
System functions
version (): var
Returns Zorro's version number with two digits behind the decimal.

Remarks:

Example:
if(version() < 1.44) {
quit("Zorro 1.44 or above needed for this script!");
return;
}

exec (string program, string options, int mode)


Opens an external program, document, URL, or batch file.

Parameters:
program - file name of the exe, batch file, or document, or URL to be opened.
options - command line parameter string to be passed to the program, or 0 for no command line options.
mode - 1 for waiting until the external program was terminated, otherwise 0.

Returns:
0 if the program was not found or could not be started, nonzero otherwise. When mode was 1, the return code of the
program.

Remarks:

• The program parameter can specify a full path (from the root), a partial path (from the Zorro folder), or just a filename.
In the latter case the exec function first looks for the file in the Zorro folder, and then in the in the folders specified by
the system's PATH environment variable.
• If the program string does not have a filename extension, the exec function first tries the .COM extension, then the
.EXE extension, then the .BAT extension, and finally the .CMD extension.
• If the program string contains a URL or the name of a document, the standard internet browser or the standard editor
for that document is opened.
• '\' characters in strings, like for file paths, have to be given in C-Notation as "\\".
• The external program can be controlled with the keys function.

Examples:
exec("notepad","test.txt"); // open notepad
exec("c:\\programs\\internet explorer\\iexplore.exe","http://www.zorro-trader.com"); // open an URL with
Internet Explorer
exec("http://www.zorro-trader.com",0); // open an URL with the standard browser

window(string title) : HWND


Returns a handle to the active window when its title bar contains the title string. Can be used to wait until a certain
window or dialog becomes active.

Parameters:
title - part of the window title (case sensitive), or 0 for returning the handle to the window that recently became active.

416
Returns:
HWND of the found active window. Otherwise 0.

Remarks:

• Normally Zorro's own window is the active window, unless another application was started or a Windows dialog became
active.

Example:
See keys.

Using DLLs and APIs


The operating system and its subsystems provide Application Programming Interfaces (API) for programs to use their
functions. Lite-C can call API functions either based on external Dynamic Link Libraries (DLLs), or on the Component
Object Model (COM). DLLs are modules that provide external functions and variables; they are loaded at runtime. When
a DLL is loaded, it is mapped into the address space of the calling process.

DLLs can contain two kinds of functions: exported and internal. The exported functions can be called by other modules.
Internal functions can only be called from within the DLL where they are defined. Although DLLs can also export
variables, their variables are usually only used by their functions. DLLs provide a way to modularize applications so that
functionality can be updated and reused more easily. They also help reduce memory overhead when several
applications use the same functionality at the same time, because although each application gets its own copy of the
data, they can share the code.

The Microsoft® Win32® application programming interface (API) is implemented as a set of dynamic-link libraries, so
any process that uses the Win32 API uses dynamic linking.

Declaring a DLL or Windows API Function


Before an API function from an external DLL can be called, a function prototype must be declared, just as any other
function. Example:
long WINAPI MessageBoxA(HWND,char *,char *,long);

The function prototype - which is in fact a function pointer - must then be initialized with the function address. There are
three methods: static initialization in the api.def (for functions that are often used), static initialization by
an API(FunctionName,ModuleName) macro (for functions only defined in a particular header), and dynamic
initialization by DefineApi (for functions that are only used in a particular appliction).

The most common static API functions are defined in the api.def file. It's just a plain text file, so it can easily be modified.
Open api.def in the Gamestudio resp. lite-C folder, and add a line to it in the style
(FunctionName;ModuleName!ProcName). FunctionName is your declared function, ModuleName is the name of
the DLL without the ".dll" extension, and ProcName is the name of the function within that DLL (which needs not
necessarily be identical to your function name). Example:

MessageBox;user32!MessageBoxA

For initializing a function in a certain header file, simply write an API macro in the script. Example:

long WINAPI MessageBoxA(HWND,char *,char *,long);


API(user32,MessageBoxA)

WINAPI is defined as __stdcall, as required for calling a DLL function. You can find examples of this way to declare
external DLL functions in the include\windows.h header file. Also look under Hacks & Tricks for using indicators from
external DLLs.

417
For dynamically initializing an API function at runtime, either use the DefineApi call, or load the DLL and retrieve the
function address through normal Windows functions. The function prototype can be used as a function pointer.
Examples:

// Example1:
long WINAPI MessageBox(HWND,char *,char *,long);
MessageBox = DefineApi("user32!MessageBoxA");

// Example2:
long WINAPI MessageBox(HWND,char *,char *,long);
long h = LoadLibrary("user32");
MessageBox = GetProcAddress(h,"MessageBoxA");

By default, api.def contains a selection of C standard functions. The windows.h header contains the Windows API
functions. If you need a certain function that is not included, you can add it easily as described under Converting C++
Code to lite-C.

Using C++ classes

Lite-C can use classes and functions from COM DLLs; the most often used example is the DirectX DLL. Classes are
like structs, but contain not only variables but also functions (methods). Any COM class contains three standard
methods - QueryInterface(), AddRef(), and Release() - as well as any number of class specific methods. For example,
here's the lite-C code for defining a COM class that contains two specific methods, Func1() and Func2():
typedef struct _IFooVtbl
{
HRESULT __stdcall QueryInterface(void* This,IID *riid,void** ppvObject);
DWORD __stdcall AddRef(void* This);
DWORD __stdcall Release(void* This);
HRESULT __stdcall Func1(void* This);
HRESULT __stdcall Func2(void* This,int);
} IFooVtbl;

typedef interface IFoo { IFooVtbl *lpVtbl; } IFoo;

Note that each of the methods has an additional parameter called "This". You have to pass the This pointer parameter
explicitly in C, but it can be passed automatically in lite-C. Any additional parameters come after This, as above. The
interface is then typedef'd as a structure that contains a pointer to the vtable. For calling methods on COM objects, you
can use either a C++-style or 'C'-style syntax. Example:

pIFoo->Func1(); // C++ style


pIFoo->lpVtbl->Func1(pIFoo); // C style

As lite-C does not support class inheritance, just add all inherited methods, if any, to the class. Example for a DirectX
class:

typedef struct ID3DXMeshVtbl


{
// IUnknown methods
long __stdcall QueryInterface(void* This, REFIID iid, LPVOID *ppv);
long __stdcall AddRef(void* This);
long __stdcall Release(void* This);

// methods inherited from ID3DXBaseMesh


long __stdcall DrawSubset(void* This, long AttribId);
long __stdcall GetNumFaces(void* This);
long __stdcall GetNumVertices(void* This);

// ID3DXMesh methods
long __stdcall LockAttributeBuffer(void* This, long Flags, long** ppData);
long __stdcall UnlockAttributeBuffer(void* This)
long __stdcall Optimize(void* This, long Flags, long* pAdjacencyIn, long* pAdjacencyOut,
long* pFaceRemap, LPD3DXBUFFER *ppVertexRemap,
void* ppOptMesh)
} ID3DXMeshVtbl;

typedef interface ID3DXMesh { ID3DXMeshVtbl * lpVtbl; } ID3DXMesh;

...
ID3DXMesh* pMesh;

418
...
pMesh->DrawSubSet(0);
long num = pMesh->GetNumFaces();
...

Sleep (int milliseconds)


Windows API function; freezes the program and suspends execution for the given number of milliseconds.

wait (int milliseconds): int


Like Sleep, but does not freeze the program. The user interface remains responsive during the wait time. The function
returns 0 when the [Stop] button was hit, otherwise nonzero. Use this function in a loop for avoiding unresponsiveness
during long computations or external function calls.

Example:
Rx("TrainNeuralNet()",1); // start a long computation
while(Rrun() == 2)
if(!wait(100)) return; // wait until computation is finished or [Stop] was hit

watch (string text, ...)


Displays the given text and up to 8 following bool, int, var, float, or string variables in the message window or prints
them to the diag.txt file in all modes. Optionally stops execution and changes to single step mode. Allows to quickly
debug into functions and watch variable behavior.

Returns
0 when [No] was clicked, 1 when [Yes] was clicked.

Parameters:
text Text string to be displayed, followed by the variables. If the string begins with an exclamation mark "!...", script
execution stops at that line and Zorro changes to debugging mode. This way the behavior of variables inside
a loop or function can be debugged step by step. If the string begins with a "#" character , the text is not
displayed in the message window, but printed to the diag.txt file (see Verbose).
... Up to 8 function calls, expressions, or bool, int, var, float, or string variables to be watched. var and float are
displayed with 5 decimals.

Remarks:

• For printing variables into the log file, use a print or printf statement.
• Take care to remove or out-comment all watch statements before live trading the strategy.

Example:
int i;
for(i=0; i<10; i++)
watch("!i",i,"double",2*i,"square",i*i);

419
lock(): int
Synchronizes the behavior of several Zorro instances, f.i. for preventing that they write into the same file at the same
time (Zorro S only). If another Zorro has called lock() before, this function waits until it either calls unlock() or the [Stop]
key was pressed. In the latter case lock() returns 0, otherwise nonzero.

unlock()
Cancels a previous lock call and let other waiting Zorros continue.

Example:
lock(); // prevent that other Zorros write into file "name"
file_append(name,mytext1);
file_append(name,mytext2);
unlock();

timer(): var
High precision timer function. Measures the time between two calls, and can be used for testing function execution
speed and time optimizing the script for faster training.

Returns:
Time elapsed since the last timer() call in milliseconds (1/1000 sec).

Remarks:

• This function uses the Pentium performance counter register. The returned value has a precision of a few
nanoseconds, depending on the processor clock rate.

Example:
...
timer();
myfunction();
printf("\nmyfunction call time = %.3f ms",timer()); // execution time in microsecond accuracy for the function
call
...

malloc(long size): void*


Allocates a contiguous memory area with the given size in bytes. This can be used to create arrays of dynamic size.

Parameters:
size - Size of the allocated memory area, in bytes.

Returns:
void* pointer to the allocated memory area, or NULL when the allocation failed.

free(void* ptr)
Releases a contiguous memory area that was allocated with malloc.

Parameter:
ptr - void* pointer to the allocated memory area.

420
Remarks:

• Memory allocated with malloc must be released with free.


• It is recommended to set pointers to released memory areas to zero. Accessing a memory area after it's released will
result in a crash.

memory(int mode): int


Returns the current memory consumption of the script.

Parameters:
mode - 0: Current memory allocation in bytes, including virtual memory and cache pages that have been written to.
1: Sum of all memory allocations in bytes by functions, price data, and series during the script run.
2: Current number of allocated memory areas by functions, price data, and series.

Returns:
See mode.

Remarks:
This function was reported not to work properly with mode == 0 under some Windows emulators, such as Wine.

Example:
function run()
{
BarPeriod = 1;
LookBack = 1;
...
printf("\rMemory %i areas %i kb",memory(2),memory(0)/1024);
plot("Memory",memory(0),NEW,RED);
}

quit (string text)


Terminates [Train], [Test], or [Trade] mode after the current run cycle. Prints the optional text to the window.

Remarks:

• A quit call does not return immediately from the run function; the current function is executed until the end. If this is
not desired, place a return statement immediately after quit().
• If a non-empty text is given, the simulation will set the EXITRUN flag, close open trades, do a performance analysis
and continue with the next simulation cycle (if any). Otherwise it will abort the simulation without EXITRUN and
performance analysis, as if [Stop] was pressed.

Example:
if(Equity < 100) {
quit("Out of money!");
return;
}

421
System Variables

Mode flags
Zorro's behavior can be set up through mode flags - that are "switches" that can be either set to on (active) or off (not
active). Mode flags can be set, reset, or tested with the following functions:

set(int flag)
Sets the given flag resp. all flags of the given flag combination. Flags can be combined with the '+' or '|' operator. For
instance, set(PARAMETERS+TICKS); activates the PARAMETERS flag and the TICKS flag.

reset(int flag)
Resets the given flag resp. all flags of the given flag combination.

mode(int flag) : int


Returns nonzero resp. true when the given flag (or any flag of the given flag combination) is set, otherwise zero
resp. false.

The following mode flags are available:

SKIP1
SKIP2
SKIP3
Do not enter orders in the first, second, or third of every 3 weeks of the historical price data (the period can be set up
with DataSkip). This can be used to separate out of sample price data from training data while still covering the same
time period.

PEEK
Allow peeking in the future through negative price function offsets; [Test] and [Train] mode only. Sometimes required
for advise functions and price difference analysis.

OPENEND
Do not close any open trades at the end of a simulation cycle; ignore their results. This has two purposes. In [Test]
mode it eliminates the effect of prematurely closed trades on the performance report. In [Train] mode it prevents that
rules are affected by results of prematurely closed trades.

ALLCYCLES
Do not reset the statistics values inside the STATUS structs when a new sample cycle is started. The Long/Short
statistics and the portfolio analysis are then the result of all sample cycles so far. This flag is always set in [Train]
mode.

TICKS
Tick-precise simulation. Not only the Open, Close, High, and Low of a bar, but also the real price curve inside a bar is
used for calculating entry, exit, and profit of trades. TMFs are run on every tick in the simulation, instead of only once
per bar. This flag gives a more accurate simulation result, but also requires more time for a simulation cycle, and
allocates more memory.
If this flag is not set, an intra-bar approximation is used for simulating entry and exit. In this approximation, a stop loss
is always triggered before a profit or trail target, and trades closed by trade functions are sold at the open price of the
next bar. This causes a less accurate simulation, which is however sufficient in most cases. TICKS should be used for
better precision when many trades enter and exit within the same bar, when stop loss or takeprofit distances are small,
or when tick functions or TMFs are used.

422
FAST
Fast simulation; to be combined with TICKS. Ticks are then sorted by asset and by trade before executing them in
a tick function or TMF. This can remarkably speed up backtests in TICKS mode, but implies some restrictions, as ticks
can now arrive in a different order than their time stamps. In FAST mode TMFs must not use information from other
trades or evaluate trade statistics, otherwise peeking bias can affect the result (for instance when a trade is closed
by the TMF dependent on the win/loss situation of another trade). For the same reason, tick functions must not evaluate
prices of other assets. Virtual hedging is simulated in FAST mode with restricted accuracy, so it's recommended to
disable it in the simulation whe results strongly depend on tick order.

LEAN
Use compressed historical data. marketVal and marketVol are not available, and the open/close price of a bar is
approximated by the center point of its first and last tick (except for EOD historical data). Set this flag when market
volume is not needed or when the historical data has a much higher resolution than one bar period, f.i. M1 data with 1-
hour bars. This flag reduces the memory requirement for backtests by 50%. It must be set before calling asset().

RECALCULATE
Run a full LookBack period at the begin of every WFO cycle in [Test] mode. Discard the content of all series at
lookback start, and recalculate them from the parameters and rules of the new cycle. This increases the test time, but
produces a slightly more realistic test by simulating the start of a new trade session at every WFO cycle.

PRELOAD
Use Zorro's historical price data for the LookBack period at [Trade] start, rather than loading all price data from the
broker's price server. Only the data span between the end of the history and the current time is loaded from the server.
This flag is useful for reducing the trading start time of a system, for overcoming history limitations of broker servers, or
for extremely long lookback periods. Recent price history files from the current and the last year must be available; use
the Download script for getting the most recent data. Alternatively, data can be downloaded at trading start from
different sources such as Yahoo or Quandl. Setting this flag also suppresses the warning message in [Test] mode when
the lookback period is too long for a normal trading session, and causes prices to be loaded from the broker even outside
market hours.

STEPWISE
Single step through a session in [Test] mode. A click on [Step] moves one bar forward. [Skip] moves to the next opening
or closing a position. The current chart and trade status will be displayed on every step in a browser window. For details
see debugging.

PARAMETERS
[Train] mode: generate strategy parameters with optimize calls and store them in Data/*.par. This flag must be set
before calling optimize. Otherwise parameters are not generated in the training run, but loaded from previously
generated *.par files.
[Test] / [Trade] mode: load optimized parameters. If this flag is not set, only the default parameters from
the optimize calls are used.

FACTORS
[Train] mode: generate OptimalF capital allocation factors and store them in Data/*.fac. If this flag is not set, all
OptimalF factors are 1.
[Test] / [Trade] mode: load OptimalF factors for allocating capital. If this flag is not set, all OptimalF factors are 1.
OptimalF factors can alternatively be copied from the performance report. For this, set Margin and Risk to 0 and
and Lots to 1, then do a [Test] run. Save the portfolio part of the performance report under the name of a .fac file.

RULES
[Train] mode: use the advise machine learning functions to generate trade rules and store them in Data/*.c. This flag
must be set before calling advise. Otherwise rules are not generated, but loaded from previously generated *.c files.
[Test] / [Trade] mode: load trade rules and use them in the advise functions. If this flag is not set, the advise functions

423
always return 100.

MARGINLIMIT
Only when Margin is used for determining the trade volume: Don't enter a trade when even the minimum amount of
1 Lot exceeds twice the given Margin value. Also don't enter a new trade when the trade margin plus the trade risk
exceeds the available margin left in the account. Trades skipped due to too-high risk or too-low account capital are
indicated in the log. This flag has no effect in training mode or for phantom trades that are always executed regardless
of the margin.

RISKLIMIT
Don't enter a trade when even with the minimum amount of 1 Lot, the trade risk is still higher than twice the than the
allowed Risk. Also don't enter a new trade when the total risk of all open trades exceeds the available margin left in the
account. Setting this flag can reduce profit, as trades with a high stop loss distance are often profitable trades. Trades
skipped due to too-high risk or too-low account are indicated in the log. This flag has no effect in training mode or
for phantom trades that are always executed regardless of the risk.

ACCUMULATE
Accumulate the Margin of trades skipped by MARGINLIMIT or RISKLIMIT, until the accumulated margin is high
enough to overcome the limits. The trade is then executed and the accumulated margin is reset. This causes trades to
be entered - although less frequently - that would otherwise require a higher margin for ever being executed. This flag
has no effect in training mode.

BINARY
Simulate binary options for training and testing. In this mode the trade profit is not proportional to the price difference
between entry and exit, but determined by the WinPayout or LossPayout of the selected asset. Slippage, rollover, and
commission are ignored for binary trades. Spread is 0 by default in binary mode, but can be set to a nonzero value for
simulating an additional disadvantage. The trade time can be set through ExitTime. Stop loss and profit targets are
normally not set for binary trades, but can be used for betting on negative or positive excursions in special binary modes.

NFA
Observe NFA Compliance Rule 2-43(b); often required for US based accounts or US based brokers (such as IB). Do
not place a "safety net" hard stop loss limit when sending the trade to the broker (this does not affect the Stop parameter
that is handled by software). Do not close broker positions, open a new position in opposite direction instead; do not
hedge broker positions. Zorro will handle NFA compliant trades in a transparent way so that the user and the script does
not need to care about the NFA rule.
This flag is automatically set in [Trade] mode when the selected account has a nonzero NFA parameter. Do not set
this flag for non-NFA compliant accounts, as positions then could not be closed. You can find out if your account is NFA
compliant by manually opening a long and short position of the same asset in the broker platform. If two positions are
then really open, your account is not NFA compliant. If the short position cancels the long one, your account is NFA
compliant. Note that MT4 accounts are normally not NFA compliant even when the account owner is US citizen.

EXE
Compile the script to an executable in .x file format. Useful for distributing strategies without revealing the source
code. Zorro S required.

LOGFILE
Generate the following files in the Log folder, dependent on [Test], [Train], or [Trade] mode: a *.log file containing all
trade events and messages; a *.dbl file containing a var array of the daily balance or equity values from the backtest;
a *.csv file containing a record of all closed trades for further evaluation or the tax declaration; a *.htm file containing
the last trade status; and a *.htm file with parameter charts from the training process. The file names are composed
from the script and asset name. This flag is always set in [Trade] mode.

424
BALANCE
Display the balance curve in the chart, and store balance rather than equity values in the *.dbl array resp. in
the Curves file.

TESTNOW
Run a test immediately after training, without clicking [Test]. Only when multiple cores are not used and when test and
training use similar mode flags. If the simulation is repeated multiple times (NumTotalCycles), TESTNOW causes the
price curve to be generated anew at the begin of every cycle, which is useful for testing different bar offsets or detrend
modes. Since the test run uses the settings from the previous training, its result can differ from a normal test. This flag
must be set before calling asset().

PLOTNOW
Plot a chart immediately after testing, without clicking [Result]. Automatically set when the script plots a histogram
with plotBar.

PLOTLONG
Begin the chart already with the LookBack period, and always plot a chart in [Trade] mode. Otherwise the chart begins
only a few bars before the test period or when trades were opened.

Example:
function run()
{
if(is(TRAINMODE)) set(SKIP3);
if(is(TESTMODE)) set(SKIP1+SKIP2+TICKS+FAST+LOGFILE);
...
}

Status flags
Zorro's current status can be checked through status flags that can be either on (active) or off (not active). The following
function is used for checking a status flag:

is(int flag): int


Returns nonzero resp. true when the given flag (or any flag of the given flag combination) is set, otherwise zero
resp. false. Flags can be combined with the '+' or '|' operator. For instance, is(TRAINMODE | TESTMODE) returns
nonzero when either the TRAINMODE or the TESTMODE is active.

The following status flags are available:

TESTMODE
TRAINMODE
TRADEMODE
The script is running in [Test], [Train], resp. [Trade] mode.

DEMO
The demo account is selected through the [Account] scrollbox.

425
SPONSORED
The script is running on a Zorro S version.

CHANGED
The [Script] or [Asset] scrollboxes have been changed since the last script run, or the [Edit] button has been clicked.
The sliders are then set back to their default positions.

AFFIRMED
The [Ok] button of a nonmodal message box has been clicked.

INITRUN
Initial run of the simulation before the price data is loaded and before the log file is opened. Can be used to initialize
global and static variables. System variables that are not yet known - f.i. variables that depend on the asset and
simulation period - are at 0 during the initial run.

FIRSTRUN
First run of the simulation with valid price data and system variables. Normally follows the INITRUN.

FIRSTINITRUN
First initial run of the script, in the case of multiple simulation runs due to training cycles or NumTotalCycles.
While INITRUN and FIRSTRUN is set at the start of any simulation run, FIRSTINITRUN is only set at the really first run.

EXITRUN
Last run of the simulation. All trades are closed. Can be used to calculate results, aside from
the evaluate or objective functions.

RUNNING
The script is currently running in the simulation. Script functions can also be executed outside a simulation run f.i. by
clicking on [Result] or on a panel button.

TRADING
The script has entered or exited at least one position.

LOOKBACK
The script is currently in the lookback period, either at the begin of the simulation, or due to a RECALCULATE run.

NEWDAY
The current bar is the first bar of the current day.

FACTORS
The script is currently generating capital allocation factors in [Train] mode.

RULES
The script is currently generating trade rules in [Train] mode. If neither FACTORS nor RULES is set in [Train] mode,
the script is generating parameters.

COMMAND
The Zorro instance was started through the command line.

426
EXE
The script is an executable (*.x).

PORTFOLIO
The script called the loop function.

ASSETS
The script called the asset function.

SELECTED
The current asset is the same as selected in the [Asset] scrollbox.

PLOTSTATS
The script called commands that plot a histogram rather than a price chart.

Some macros for often-used flags have been defined for convenience:

Train
The same as is(TRAINMODE).

Test
The same as is(TESTMODE).

ReTrain
Process for updating parameters or rules, started by clicking [Train] while live trading (Zorro S only).

ReTest
Process started by clicking [Test] while live trading, f.i. for comparing the live trading results with backtest results of the
same period (Zorro S only).

Example:
function run()
{
if(is(TRAINMODE)) set(SKIP3|TICKS);
if(is(TESTMODE)) set(LOGFILE);
...
}

Predefined Strings
Script
The name of the script without the ".c" extension; can be modified for loading parameters, rules, and factors from the
training files of a different script. Script names must not contain spaces or special characters.

Algo
The current algorithm identifier, set up by the algo function or a TMF (read/only). Algo names must be short and not
contain spaces or special characters.

427
Asset
The current asset name, set up by the [Asset] scrollbox, the asset function, a tick function, a TMF or a trade
loop (read/only).

Symbol
The full symbol string of the current asset as set up in the asset list, including type, counter currency, and exchange;
or the asset name when the asset has no assigned symbol.

Assets
A NULL-terminated string array containing the names of all available assets in the asset list (read/only). Can be used
as a loop parameter or for enumerating, f.i. for(N = 0; Assets[N]; N++). The asset names are valid after the
first asset or assetList call. The number of assets in the list is returned by assetList.

FactorList
The last part of the file name - including extension - of the file containing the OptimalF factors; can be set for selecting
between different sets of factors. If not set up otherwise, the file begins with the script name and ends with ".fac",
f.i. "Z12.fac". Set FactorList f.i. to "oos.fac" for reading the factors from the file "Z12oos.fac".

History
The last part of the file name - including extension - of the historical data files; for selecting between different sets of
price histories for the same asset. If not set up otherwise, the files begin with the asset name and year number, and end
with ".t6". Examples: History = "fxcm.t6" reads price history from f.i. EURUSD_2015fxcm.t6; History = ".t1" reads
T1 price history from EURUSD_2015.t1. History = ".t6" has a special meaning - it reads price history files without a
year number, f.i. "AAPL.t6" (for daily bars only). History must be set before calling asset().

Curves
The file name - including path and extension - for exporting equity or balance curves of all optimize parameter variants
in [Train] mode (see export). Also used for storing the daily equity curve in [Test] mode. The LOGFILE flag must be
set for recording curves. If Curves is not set, no curves are exported in [Train] mode, and in [Test] mode the
equity/balance curve is exported to a *.dbl file in the Log folder.

ZorroFolder
The folder in which Zorro was installed, with trailing backslash, f.i. "C:\Program Files\Zorro\" (read/only). This is not
necessarily the root folder of the data files - see the remarks about UAC.

WebFolder
The folder for the HTML page that displays the trade status. If not set up otherwise, the HTML documents are generated
in the Log folder. Can be set up in the Zorro.ini file.

Account
The account name from the scrollbox, f.i "Demo" or "Real" or a user-defined account name from the account
list (read/only).

Broker
The broker plugin name from the selected plugin, f.i "MT4" or "FXCM" (read/only).

Factors
String containing the content of the .fac file with the OptimalF factors (read/only). The string is empty in the initial run,
as the factors file is loaded after the assets.

428
Type:
string

Remarks:

• The Script name can be modified for loading parameter, rule, and factor files that were generated by a different
strategy.
• Set AssetList to "Assets" for simulating the current asset parameters at the time of the last connection to the broker.
See also data import.

Examples:
Script = "MyScriptV2"; // store and load parameters under "MyScriptV2.par"
History = "s1.bar"; // read historical data from f.i. "EURUSD_2013s1.bar"
WebFolder = "C:\\inetpub\\vhosts\\httpdocs\\trading"; // VPS web folder

BarPeriod
The duration of one bar in minutes, with a range from 100 ms (0.100/60.) up to 1 day (24*60); default = 60. This is the
basic time frame in the script. If not explicitly set up in the script, it is determined by the [Period] slider. The run function
is called at the end of every bar. Price-movement bars (see bar function) have no fixed bar period; in that
case BarPeriod should be set to the average duration of a bar.

Type:
var

BarOffset
Bar start time offset in minutes; must be smaller than BarPeriod. Bars and frames normally start at a date/time
boundary; f.i. 60-minute bars start at every full hour, and daily bars start at midnight. This can be changed
with BarOffset. For daily bars, BarOffset determines the UTC minute into the day when the candle opens, and can be
used to shift the bar begin to a certain local or global time. BarOffset is automatically set up by NumSampleCycles by
dividing a bar into equal time intervals and decreasing the offset by one interval per cycle; on the last cycle, BarOffset is
0.

BarZone
Time zone that determines the close time of daily bars. Can be set to UTC for UTC time (default), ET for New
York, WET for London, CET for Frankfurt, AEST for Sydney, JST for Tokyo, or any number giving the zone offset in
hours to UTC. Daylight saving time is used except for UTC and JST. Please see the remarks
about StartWeek and EndWeek, which are in UTC; adapt them to the local market hours if necessary.

TimeFrame
Time frame in bars (default = 1) used for all subsequent price, time, series, advise, and trade calls. This variable can
be set within a strategy for using multiple time frames. For instance, with a bar period of 5 minutes (BarPeriod = 5) and
a time frame of 12 bars (TimeFrame = 12), series and trade signals use a one hour time frame (5*12 = 60 minutes).
TimeFrame can also be used for synchronizing time frames to external events - f.i. for skipping bars outside market
hours or when no price ticks arrive, for trading dependent on the time of the day, or for emulating price-movement bars
such as Range or Renko Bars. For this, set TimeFrame to 0 during the frame, count the skipped bars, and
set TimeFrame to the negative number of skipped bars for ending the frame (see remarks and example; see
also AssetFrame).

FrameOffset
Time frame offset in bars (default = 0 = no offset) used for all subsequent price, time, series, and trade calls; must be
smaller than TimeFrame. When fixed time frames are used, this variable determines the bar number within a time frame
429
when series are shifted and trades are executed. This variable allows to generate trade signals at different times
dependent on the asset.

Type:
int

Remarks:

• Use the bar function for special bars that are not bound to a certain bar period.
• BarPeriod can be set to any value between 0.00166 and 1440 in the script. Bar periods above 1 are rounded to a
multiple of minutes. The [Period] slider moves in fixed steps corresponding to the usual bar periods of trading
platforms; directly setting BarPeriod places the slider at the position closest to the BarPeriod value. If BarPeriod is
set directly in the script, the slider can not be moved.
• On bar periods less than ~ 10 minutes, setting the TICKS flag and using high resolution .t1 price data is recommended
for backtesting. It's mandatory on bar periods of less than a minute. For this set History to ".t1" and BarPeriod to the
number of seconds divided by 60, f.i. BarPeriod = 5./60. for 5 seconds. Mind the decimal; 5/60 would be considered
an int and thus evaluate to 0. For extremely short bar periods, make sure that TickTime is always less than a bar
period.
• For bar periods longer than a day, set BarPeriod = 1440 and use TimeFrame for setting up weekly or monthly time
periods. A week can contain 5, 6, or 7 trading days dependent on the Weekend setting.
• If multiple time frames and different trade time offsets are not required, set the time frame with BarPeriod only. This
uses the lowest possible number of bars and results in the highest speed for testing and training.
• BarPeriod, BarOffset, and BarZone affect the sampling of the price data; therefore they must be set in the first run
before any asset() call, can not change afterwards, and can not be optimized in WFO
mode. TimeFrame, FrameOffset, and AssetZone can be set anytime and can be optimized without restrictions.
• Because bars start and end at day/hour boundaries, Friday daily bars end at Saturday 00:00 midnight
when Weekend is at 1. The markets are closed at that time, therefore no trades can normally be entered after a Friday
bar. For trading on Fridays, either don't use daily bars or set BarOffset accordingly so that the Friday bar ends earlier
when the markets are still open.
• TimeFrame is not 100% equivalent to BarPeriod. Depending on the script, trading strategies can behave very different
even with the same value for (BarPeriod * TimeFrame), for the reasons listed below:
• Trades are only entered at the end of a time frame, but trade entry/exit conditions are tested at the end of every bar
period.
• Any calculations in the run function are executed at any bar period, unless they are explicitely restricted to time frames.
Use the frame function to determine the end of the current time frame.
• Beginning and ending at day/hour boundaries is guaranteed for BarPeriod, but not for TimeFrame due to the different
number of bars per day when holidays or weekends lie in the simulation period. For letting a time frame start at always
the same hour, use the hour() or lhour() functions. For aligning daily time frames to a certain hour or time zone, either
set TimeFrame by script to the negative number of day bars at midnight (see example) and to 0 otherwise, or use
the AssetFrame variable or the frameSync() function. Use BarPeriod = 1440 and TimeFrame = framesync(7) for
weekly time frames. The week begin can be set with FrameOffset; otherwise the week begins on Monday.
• TimeFrame has no effect on variables in bar period units, f.i. LookBack, LifeTime, or WaitTime. For extending the
time, multiply the variable value with TimeFrame.
• TimeFrame is not supported by indicators that do not use a Data series, but the current asset price series. This is
mentioned in the description of the indicator.

Examples:
#define H24 (1440/BarPeriod)
#define H4 (240/BarPeriod)
#define H1 (60/BarPeriod)
...
BarPeriod = 60; // 1 hour (60 minutes) bars
...
// create a 4-hour price series
TimeFrame = H4;
vars PriceH4 = series(price());

// create a 24-hour price series


TimeFrame = H24;
vars PriceH24 = series(price());

// create a midnight-aligned daily price series


static int BarsPerDay = 0;
if(hour(0) < hour(1)) { // day change
TimeFrame = -BarsPerDay; // end the frame

430
BarsPerDay = 0;
} else {
TimeFrame = 0; // inside the frame
BarsPerDay++; // count number of bars per day
}
vars PriceD1 = series(price());

// alternative: a midnight-aligned price series using frameSync()


StartWeek = 10000;
TimeFrame = frameSync(H24);
vars PriceD1 = series(price());

// back to 1-hour time frames


TimeFrame = 1;
...

NumBars
Total number of bars in the simulation, determined from Lookback, StartDate and EndDate and from the available
data in the price history files. Is 0 in the initial run before the first asset call.

Bar
Current bar number including the lookback period, from 0 to NumBars-1.

StartBar
Number of the first bar after the lookback period.

Day
Current trading day number of the simulation, starting with the end of the lookback period. Weekends are skipped.

Type:
int, read/only

Remarks:

• The Bar number is increased on every run, but not always by 1. Bars are skipped when they are not used for the
simulation. For instance, in WFO training cycles all bars preceding the LookBack period before the current training
frame are skipped.
• For running a simulation in greater steps as a bar period for special purposes (f.i. for generating histograms), bars can
be skipped by adding a number to Bar at the end of the run function. F.i. Bar += 9; will call the run function only every
10th bar. Restriction: Don't use TimeFrame and series in this case, as the series are not correctly shifted when bars
are skipped.

Example:
printf("\nBar %i of %i",Bar,NumBars);

431
LookBack
Number of bars that are executed before Zorro begins to trade (default = 80). Required for indicators that derive a
value from a time period in the past. Set it to a value that is sure to cover the longest indicator time period, or to 0 when
no lookback period is required.

UnstablePeriod
Strips off the given period (default = 40) from cumulative indicators. Some indicators, even when calculated over a finite
period, are influenced by an infinite number of past bars. They are cumulative - indicators "with memory". Examples are
the EMA (Exponential Moving Average) and the ATR (Average True Range). They use their previous bar's value in their
algorithm, which in turn use the value of their previous bars, and so forth. This way a given value will have influence on
all the subsequent values. In contrast, a simple moving average (SMA) only reflects the average of its period, without
any influence from bars further in the past.
Because a price series is always finite and starts at a certain point, the affect of missing past bars is the more significant,
the closer to the start a cumulative indicator is calculated. Thus a trade strategy using the EMA or derived indicators
(such as the MACD) could behave different dependent on its bar number. The UnstablePeriod value allows to strip off
such initial unstable period and remove the influence of all bars prior to this period. This way indicators are guaranteed
to behave the same way regardless of the amount of past data. For coming as close to the real cumulative formula as
possible, strip off as much data as you can afford. Getting rid of 40 bars (default) is reasonable for most indicators.

TradesPerBar
Maximum number of trades per asset and bar of the simulation period (default = 1). If more trades are opened, an error
message (Error 049) will be issued. Set this to a higher value when the strategy needs to enter an unusual number of
trades, for instance for grid trading or for training an advise function.

MinutesPerDay
Minimum daily trading time of the least traded asset in minutes (default = 360, i.e. 60 minutes * 6 hours). Internally used
for determining the maximum lookback time. Set this to a lower value when an asset is rarely traded and thus gets not
enough historical bars at trading start (Error 047).

Type:
int

Remarks:

• All variables on this page must be set before loading an asset.


• The number of the first bar at which a trade can be entered is greater or equal to Lookback.
• If LookBack is too small, it will be normally automatically adapted to the maximum time period of all used indicators,
and a message "Lookback set to nnnn bars" will be printed to the window and log file. Automatic adaption is not
always possible, for instance when different time frames are used or when indicator time periods change during the
simulation, f.i. in a training run. Automatically increasing LookBack can also affect the result in an unexpected way
because the back test period gets shorter when the lookback period gets longer. For this reason, it is strongly
recommended to set LookBack manually in all scripts that are trained or use different time frames.
• If indicators get their data from other indicators - for instance, taking the a SMA of another SMA, or normalizing the
value of an indicator - LookBack should be set to at least the sum of all time periods in the indicator chain.
• If LookBack is still too small for a certain indicator or other function - this can happen when UnstablePeriod was
changed, or when the indicator time period is not known at the first run of the strategy - an error message will be
issued. Set LookBack then to a higher value.
• When using series with an offset (f.i. myseries+n), make sure that LookBack is always higher than the required
lookback period of the series plus UnstablePeriod plus the highest offset. Otherwise the series length can be
exceeded, resulting in a script error at runtime.
• When optimizing the time period of an indicator, make sure that LookBack is big enough to cover the maximum time
period of the optimize range, plus the UnstablePeriod when the indicator is cumulative.
• UnstablePeriod is not used for all cumulative indicators. Cumulative indicators that use series - such
as LowPass, DominantPeriod, UO, etc. - use LookBack as unstable period. After the lookback period they can have
still differences, but they are negligible.
• In [Trade] mode (see Trading), up to 1 year price history covering the LookBack period is downloaded from the server
immediately before trading starts. Thus the lookback duration must not exceed one year for live trading a system

432
(unless PRELOAD is set and price history is already available). A too long lookback period will be indicated with a
warning message. Due to weekends and market hours, the lookback duration is normally longer than LookBack *
BarPeriod.

Example:
UnstablePeriod = 60;
LookBack = 120+UnstablePeriod; // required for ATR(120)
...
Stop = ATR(120);

NumParameters
Numbe of parameters to optimize in the current loop in training mode.

ParCycle
Current parameter to be optimized, from 1 to NumParameters.

StepCycle
Current optimize step, starting with 1.

Type:
int

Remarks:

• These variables are only available in [Train] mode and only when the script contains at least one optimize call.

NumSampleCycles
Oversampling factor of the simulation (default = 0 = no oversampling). When NumSampleCycles is set to a number n >
1, the simulation is repeated n times, and every time the bars are resampled with different BarOffset values. This
generates a slightly different price curve for every cycle, while maintaining the trend, spectrum, and most other
characteristics of the curve. The performance result is then calculated from the average of all cycles. This way more
data for test and training is generated and a more accurate result can be achieved.

SampleCycle
The number of the current cycle from 1 to NumSampleCycles. Automatically set by NumSampleCycles.

Type:
int

Remarks:

• On bar periods of one or several hours, oversampling is sometimes required to get enough trades for properly
optimizing and testing a strategy. Good values for NumSampleCycles are 2..6. Even higher oversampling factors
won't increase the accuracy much further.
• Oversampling can not be used when the strategy relies on entering and exiting trades at a certain time or date, f.i.
strategies that use daily or weekly bars or depend on opening and closing hours of a certain stock exchange.
• The performance of the separate cycles is displayed in the performance report under Cycle performance. High
performance differences between cycles normally indicates an unstable strategy.
• When the ALLCYCLES flag is set, the statistics values inside the STATUS structs and the portfolio analysis are the
sum over all bar cycles; they keep their values from the last cycle when a new cycle is started. Otherwise they are

433
reset at the begin of every cycle. The statistics values inside the PERFORMANCE struct are the average over all
sample cycles.
• In the price chart, the trade symbols are taken from the last cycle. The equity curve is the average over all cycles.
• A description of oversampling with an example can be found on http://www.financial-hacker.com/better-tests-with-
oversampling.

Example:
NumSampleCycles = 4; // 4 times oversampling

NumWFOCycles
Number of cycles in a Walk Forward Optimization / Analysis (default = 0 = no Walk Forward Optimization).
If NumWFOCycles is set to a positive number, rolling walk forward optimization is enabled with the given number of
cycles; if it is set to a negative number, anchored walk forward optimization is enabled. In Walk Forward Optimization,
a data frame consisting of a training and test period is shifted over the simulation period in the given number of cycles
(see image).

WFOCycle Simulation period

1 LookBack Training Test1

2 LookBack Training Test2

3 LookBack Training Test3

4 LookBack Training Test4

5 LookBack Training

OOS Test LookBack Test5

WFO Test Look Back Test1 Test2 Test3 Test4

Rolling Walk Forward Optimization (NumWFOCycles > 0)

WFOCycle Simulation period

1 LookBack Training Test1

2 LookBack Training Test2

3 LookBack Training Test3

4 LookBack Training Test4

5 LookBack Training

WFO Test Look Back Test1 Test2 Test3 Test4

Anchored Walk Forward Optimization (NumWFOCycles < 0)

Strategy parameters and trade rules are generated separately for every cycle in [Train] mode, and are separately
loaded for every test segment in [Test] mode. This way, the strategy test is guaranteed to be out of sample - it uses
only parameters and rules that were generated in the preceding training cycle from data that does not occur in the test.

434
This simulates the behavior of real trading where parameters and rules are generated from past price data. A Walk
Forward Test gives the best prediction possible of the strategy performance over time.

WFOPeriod
Alternative to NumWFOCycles; number of bars of one WFO cycle consisting of training + test. If a fixed test period
of T bars is given, WFOPeriod is T*DataSplit/(100-DataSplit).

WFOCycle
The number of the current WFO cycle, from 1 to NumWFOCycles. Read/only; automatically set during WFO test and
training.

WFOBar
The bar number inside the current WFO cycle, starting with 0 at the begin of the cycle. Can be used to determine when
the cycle starts: if(WFOBar == 0). Read/only; automatically set during WFO test.

SelectWFO
Set this to a certain WFO cycle number for selecting only this cycle in a training or test; otherwise the process runs over
all cycles. A negative number selects the cycle from the end, f.i. -1 selects the last cycle for an OOS Test.

Type:
int

Remarks:

• WFO is explained in the Training chapter and in Workshop 5.


• If DataSplit is not set otherwise, WFO uses a default training period of 85% and a default test period of 15%.
• DataSlope can improve the parameter quality with anchored WFO cycles.
• The optimal number of WFO cycles depends mainly on the strategy, and to a lesser extent on the simulation period
and the time frame. Large time frames or infrequent trading require a small number of WFO cycles for getting enough
trades per cycle. Very market-dependent strategies with fast expiring parameters require a high number of WFO
cycles. If the strategy performance highly varies with small changes of NumWFOCycles, a periodic seasonal effect is
likely the reason. Try to determine and eliminate seasonal effects before optimizing a strategy.
• Parameters, rules, and factors are stored in files in the Data folder. The number of the cycle is added to the file name,
except for the last WFO cycle. F.i. a parameter WFO of a script "Trade.c" and 4 cycles will generate the following
parameter files in the Data folder: Trade_1.par, Trade_2.par, Trade_3.par, Trade.par, Trade.fac. [Test] mode
reloads the parameters and rules for every segment during the test cycle. [Trade] mode uses the parameters and rules
from the last WFO cycle, without an attached number.
• Normally the LookBack period precedes the first WFO cycle in [Test] mode. If the RECALCULATE flag is set, a
dedicated lookback period precedes every WFO cycle. The content of all series is discarded and recalculated from
the parameters and rules of the new cycle. This increases the test time, but produces a slightly more realistic test by
simulating the start of a new trade session at every WFO cycle.
• When trading a walk-forward optimized strategy, re-train the last cycle perodically for making the strategy independent
on parameter settings. For this, set SelectWFO and UpdateDays both to -1 and EndDate to 0 for updating the price
data up to the current date. Every 2..3 months - the required period is displayed in the performance report under
"WFO test cycles" - click [Train]. A second Zorro instance will start, download the price data from the server and
generate a new parameter, factor, and rule set. The trading Zorro instance will continue trading with the updated files.
• The last WFO cycle (cycle 5 in the figure above) has no test period. If SelectWFO is set to the last cycle in [Test]
mode, the out-of-sample period before the training is tested instead (OOS Test in the figure; rolling WFO only). The
OOS test often generates low profit, but can inform about the long-term performance of the strategy when it is not re-
trained.
• Anchored WFO can be used to test the lifetime of parameters or rules. If anchored WFO generates a better result than
rolling WFO, the strategy is longlived and does not need to be retrained often. Normally, rolling WFO produces better
results, meaning that the market changes and that parameters / rules must be adapted from time to time.
• WFO parameters and rules are only valid for the simulation period with which they were created. If the period is
changed - for instance, when new price data is downloaded - the strategy must be trained anew. For avoiding
unintended changes of the simulation period, set a fixed StartDate and EndDate for the test.
• In training, every WFO cycle is a separate simulation run including INITRUN and EXITRUN. In testing all WFO cycles
are tested in a single simulation run.
• WFO training time can be strongly reduced by using several CPU cores.
435
• Use the plotWFOCycle and plotWFOProfit functions for analyzing the profit curve over one or several WFO cycles.
• For calculating NumWFOCycles and DataSplit from a given training and test period in days, see the example below.

Examples:
function run()
{
NumWFOCycles = -10; // anchored WFO, 10 cycles
DataSplit = 80; // 20% test cycle
DataSlope = 2; // more weight to more recent data
set(PARAMETERS+TESTNOW); // run a test immediately after WFO
...
}

// calculate NumWFOCycles from the test period in days


// DataSplit and LookBack must be set before
function setWFO2(int daysTest)
{
asset(Asset); // requires NumBars, so asset must be set
int barsTest = daysTest * 1440/BarPeriod;
int barsTrain = barsTest * DataSplit/(100-DataSplit);
int barsTotal = NumBars-LookBack;
NumWFOCycles = (barsTotal-barsTrain)/barsTest + 1;
}

NumOptCycles
Number of repeated optimization cycles (default = 0 = no repeated optimization). When NumOptCycles is set to a
number n > 1, the parameter optimization is repeated n times, each time re-optimizing the parameters from the last
cycle. This usually improves the result, but also requires a longer time for the training process.
Normally NumOptCycles should not be higher than 2, otherwise the parameters become overfitted and cause worse
results when testing or trading the strategy.

OptCycle
The number of the current optimization cycle from 1 to NumOptCycles. Automatically set by NumOptCycles.

Type:
int

Remarks:

• To prevent overfitting, use not more than 2 optimization cycles.

Example:
NumOptCycles = 2; // 2 optimization cycles

436
NumTotalCycles
Repeat the complete simulation - including training and testing - as often as set up with this variable (default = 0 = no
repetitions). This is normally used for plotting a histogram of the results after a training and test cycle.

TotalCycle
The number of the cycle from 1 to NumTotalCycles. Automatically set by NumTotalCycles.

Type:
int

Remarks:

• If BarOffset was changed or prices were shuffled, a new price curve is generated at the begin of every cycle.

Example: see Workshop 8

NumCores
Determines the number of logical CPU cores for WFO training (Zorro S only). Either a positive value for the number of
cores to use, or a negative value for the number of cores not to use, f.i. -1 for using all available cores but one. Default
= 0 for no multi-core training. The WFO cycles are evenly distributed among parallel Zorro processes assigned to
different CPU cores. The more cores, the faster is the training run; f.i. assigning 5 cores of a 6-core CPU will reduce the
training time by 80%.

Core
The current core in a WFO multicore training run, or 0 when no multicore process is running (read/only).

Type:
int

Remarks:

• NumCores must be set in the INITRUN.


• Multicore training uses the command line for passing parameters and script name to the separate training processes.
The script name must be command line compliant.
• The training process must not use slider or other user interface settings. The parallel processes have no sliders.
• The TESTNOW flag must not be used.
• It is possible, but not recommended to assign more cores than CPU threads (logical processors) are available. The
training process will then consume most CPU resources and can render the PC unresponsive during the training. The
number of physical and logical cores is displayed in the Windows Task Manager.
• Training R-based algorithms works also in multi-core mode. Several R instances will then run in parallel.
• The processes run as minimized Zorro instances. The message windows display only the cycles trained by the
dedicated process.
• Parameter diagrams in multi-core mode are generated from the main process only, not from the parallel processes.

Example:
NumCores = -2; // use all logical processors but two

437
Time/date parameters
StartDate
Start of the simulation. The start can be determined in two different ways. A 4-digit number (f.i. 2006) determines the
year number of the historical data file with which the simulation starts (f.i. EURUSD_2006.t6). If the file has no year
number (f.i. MSFT.t6), the simulation starts with the start of the file. A date in the 8-digit yyyymmdd format starts the
backtest at a certain date (f.i. 20090401 = April 1st, 2009). In the latter case the simulation period can begin earlier as
the LookBack period is added in front of the date. Therefore, StartDate = 2006 starts the backtest normally at a later
date than StartDate = 20060101. If at 0 (default), the simulation starts with the file year number given
by NumYears before the current year.

EndDate
End of the simulation, either 4 digits for determining the year number of the last historical price data file (similar
to StartDate), or a date in yyyymmdd format for ending the backtest at that date (f.i. 20091231 = December 31, 2009).
If at 0(default), or if the file has no year number, the simulation runs until the end of the available price history.

NumYears
Number of years of the simulation if no StartDate or EndDate is given (default: 6 years). The current year counts as
one full year. Set NumYears to -1 for not loading any prices.

MaxBars
Maximum number of bars of the simulation (default: 0 = no limit). The simulation ends either at EndDate or after the
given number of bars, whichever happens earlier.

UpdateDays
Interval in days for automatically downloading new price data and adding it to the price history file (default: 0 = don't
download new price data). If the price history is older than the given number of days, the download process starts
automatically at the begin of a [Test] or [Train] cycle. Set UpdateDays to -1 for always loading all prices up to the
current time.

RetrainDays
Interval in days for automatically retraining a live trading system (Zorro S required; default: 0 = no automatic
retraining). Set this to the duration of the WFO test period for keeping a WFO trained system in sync with the market.

GapDays
Maximum allowed gap in days in the historic prices and in the downloaded price data (default: 0 = no gap checking).
Set this to 2 or above in order to check the price curve for gaps and inconsistencies, and give an Error 047 message if
any are detected. Weekends and international holidays are except from gap checking. Gaps of 1 day are normal in
historic prices due to national holidays.

StartWeek
Start of the business week in dhhmm UTC format, where d = day number (1 = Monday .. 7 = Sunday), hh = hour
and mm = minute. Default: 72300 (Sunday 23:00 UTC). Used to determine the WeekEnd behavior. If required, set this
variable to the local market opening time converted to UTC. Consider daylight saving (dst function).

EndWeek
End of the business week in dhhmm UTC format. Default: 52000 (Friday 20:00 UTC). Used to determine
the WeekEnd behavior. If required, set this variable to the local market closing time converted to UTC. Friday 20:00
UTC is equivalent to 15:00 ET without daylight saving and to 16:00 ET with daylight saving.

StartMarket
Daily market opening time in hhmm local time format, hh = hour and mm = minute. Default: 930. Used for the day and
market functions.
438
EndMarket
Daily market closing time in hhmm local time format, hh = hour and mm = minute. Default: 1600. Used for the day and
market functions.

Type:
int

Now
Date/time in Windows DATE format for the date/time functions with NOW argument. When at 0 (default), the current
PC date and time is used.

Type:
var

Remarks:

• The earliest possible StartDate is determined by the availability of historic price data. M1 data back to 2002 can be
downloaded from the Zorro website. In 2002 the EUR replaced national currencies; currency backtests before that
date make not much sense as the EUR pairs were not traded in volume and the Forex markets behaved different.
• StartDate and EndDate can be used to 'zoom' the backtest to a certain period and examine the trades of that period
in more detail. For the chart, PlotDate and PlotBars can be used for zooming.
• StartDate, EndDate, NumYears and UpdateDays affect price data loading and thus must be set before loading
historic prices or calling asset.
• WFO parameters and rules are only valid for the simulation period with which they were created. If the period is
changed, the strategy must be trained again.
• When re-training the last WFO cycle, set EndDate to 0, otherwise the training period ends with EndDate.
• The initial run of a strategy (INITRUN) has no valid date since the start and end date are set in this run.

Example:
StartDate = 20050901; // start the simulation with September 2005
EndDate = 20050930; // and simulate one month.

DataSplit
Splits the simulation in a training period (given in percent) and a following test period. F.i. when set at 60, the training
period has a length of 60% and the test period has a length of 40%. This ensures that the test always uses out-of-
sample data.

Typical range:
60..90 (default = 0 = no separate training / test period).

Type:
int

DataSkip
Gives the number of bars to skip with the SKIP1...SKIP3 flags (default: number of bars corresponding to one week).

Type:
int

439
DataSlope
Applies a moving weight factor to the trade results in the training period. F.i. at DataSlope = 2 the last trades have twice
the weight than the first trades. This generates parameters that are better fitted to the most recent part of the price curve,
and thus takes care of slow market changes.

Typical range:
1..3 (default = 1 = equal weight for all trades)

Type:
var

Remarks:

• On long training periods, f.i. with anchored WFO, It is recommended to set DataSlope at 1.5 .. 2.0 for giving the last
part of the price curve more weight.

Example:
function run()
{
DataSlope = 2;
DataSplit = 80;
NumWFOCycles = -10; // anchored WFO
...
}

TickTime
Minimum time in ms between subsequent intrabar function calls (TMF or tick) in trade mode (default = 100 ms). Even
when several price quotes arrive during that time, the intrabar function is only excuted once and receives the most
recent price quote. Set this to a higher value for saving CPU resources, or to a smaller value for reducing latency. By
setting TickTime to a negative value, TMF and tick functions run at every given time period even when no new price
quote is arrived.

TockTime
Minimum time in ms between subsequent tock calls in trade mode (default = 60,000 ms).

TickSmooth
Applies smoothing to outliers in the stream of incoming price quotes in trade mode (default = 0 = no smoothing, remove
only extreme outliers). The given value serves as the time period of an EMA that is applied to all price quotes that
deviate to the last quote by more than 2 pips. F.i. with TickSmooth at 3 outliers are suppressed by 50%; at 10 they are
suppressed by 90%. The disadvantage is that sudden extreme price moves are detected with a delay. If set to -1, no
outliers will be removed at all.

Type:
int

Remarks:

• The Tick variables only affect live trading and are ignored in the simulation with historical data.

Example:
function run()
{
TickSmooth = 100; // remove outliers
...
}

440
Trade parameters 1 - entry and exit limits
Entry
Enter the trade only when the price reaches a certain value at the next bar (default = 0 = enter at market). The value
can be given either directly as an Ask price, or as a distance to the current close price. A positive price or distance
constitutes an entry stop, a negative price or distance an entry limit. An entry limit buys when the price moves against
the trade direction and reaches or crosses the limit. It increases the profit as it buys at a price that went in opposite
direction to the trade. An entry stop buys when the price moves in trade direction and reaches or crosses the limit; it
reduces the profit, but enters only when the price moved in favorable direction, and thus acts as an additional trade
filter. For a long trade, an entry limit must be below and an entry stop must be above the current price. If the entry price
is not reached within the allowed time period (set through EntryTime), the trade is cancelled and a "Missed Entry"
message is printed to the log file.

OrderLimit
Causes a limit order at the given ask or bid price, instead of a market order, to be sent to the broker at the
next enterLong, enterShort or exitTrade call. If the order is not filled, the function call returns 0. Can be used in
a tick function for filling an order at the best price within a certain time period when the precise time of entry or exit does
not matter. Works only in trade mode and only when the SET_LIMIT command is supported by the broker API.

Stop
Stop loss value or stop loss distance in price units (default = 0 = no stop loss). The trade is closed when the price
reaches the limit resp. the trade loss reaches the distance. A good value for Stop is derived from the ATR,
f.i. 3*ATR(20). Setting a stop loss is recommended for risk control.

StopFactor
Distance factor between the real stop loss, and the stop loss sent to the broker to act as a 'safety net' in case of a
computer crash. At the default value 1.5 the stop sent to the broker is 50% more distant, thus preventing 'stop hunting'
or similar broker practices. At 0, or if the NFA flag is set, the stop loss is only controlled by software; at 1.0 the broker
stop is identical to the real stop. If StopFactor is set to a negative value or if a BrokerStop function is not available in
the broker plugin, the broker stop is only placed at opening the trade, but not updated afterwards. StopFactor has no
effect on trades that have no stop, such as pool trades in Virtual Hedging Mode.

TakeProfit
Profit target value or profit target distance in price units (default = 0 = no profit target). The trade is closed when the
trade profit has reached this amount. A profit target takes profits early, which increases the number of winning trades,
but normally reduces the overall profit of a strategy. It is preferable to use TrailLock instead of setting a profit target.

Trail
Raise the stop loss value as soon as the price reaches the given value, resp. goes in favorable direction by the given
distance in price units (default = 0 = no trailing). Has only an effect when a Stop is set. The stop loss is increased in a
long position, and decreased in a short position so that it normally follows the price at the distance given by the sum of
the Stop and Trail distance . A slower or faster 'movement speed' of the stop loss can be set through TrailSlope.

TrailSlope
Trailing 'speed' in percent of the asset price change (default = 100%); has only an effect when Stop and Trail are set
and the profit is above the trail distance. Example: The asset price of a long position goes up by 10 pips. TrailSlope =
50 would then raise the stop loss by 5 pips. TrailSlope = 200 would raise the stop loss by 20 pips.

TrailLock
'Locks' a percentage of the profit (default = 0 = no profit locking); has only an effect when Stop and Trail are set and
the price has exceeded the trail distance. A stop loss is then automatically placed at the given percentage of the current
price excursion. Example: A long position is currently in profit by 20 pips above the entry price. TrailLock = 80 would
then place the stop loss at 16 pips above entry, thus locking 80% of the profit (without trading costs). TrailLock = 1 (or
any small number) would set the stop loss at the entry price when the current price reaches the Trail value.
Using TrailLock is in most cases preferable to setting a profit target.

441
TrailStep
Automatically raise the stop loss every bar by a percentage of the difference between current asset price and current
stop loss (default = 0 = no automatic trailing); has only an effect when Stop and Trail are set and the profit is above the
trail distance. Example: A long position has a stop at USD 0.98 and the price is currently at USD 1.01. TrailStep =
10 will increase the stop loss by 0.003 (30 pips) at the next bar. TrailStep reduces the trade time dependent on the
profit situation, and is often preferable to a fixed exit time with ExitTime.

TrailSpeed
Speed factor for faster raising the stop loss before break even, in percent (default = 100%). Has only an effect
when Stop and Trail are set and the profit is above the trail distance. Example: TrailSpeed = 300 will trail the stop loss
with triple speed until the entry price plus spread is reached, then continue trailing with normal speed given
by TrailSlope and TrailStep. Has no effect on TrailLock. This parameter can prevent that a winning trade with a slow
rising stop turns into a loser.

Type:
var

Remarks:

• All parameters above must be set after selecting the asset and before calling enterLong / enterShort. They have no
effect on different assets or on already entered trades. For changing a stop or profit target of a particular trade after it
has already been opened, use either a TMF, or call exit with the new price limit (see example).
• Except for OrderLimit, all prices are Ask prices, regardless of whether they are intended for entry or exit, or for a long
or a short trade. A trade is opened or closed when the Ask price reaches the given target. Zorro automatically handles
the conversion from Ask to Bid: long trades are filled at the Ask price and closed at the Bid price, short trades are filled
at the Bid price and closed at the Ask price. The Bid price is the Ask price minus the Spread.
• Either an (absolute) price, or a (relative) distance to the trade opening price (TradePriceOpen) can be used
for Stop, TakeProfit, Trail, and Entry. If the value is less than half the asset price, Zorro assumes that it's a distance,
otherwise it's a price. the For an entry limit the given price must be negative; "buy at 10 pips below the current Low" is
in code: Entry = -(priceLow()-10*PIP);. If Stop or TakeProfit are at the wrong side of the price, no trade is entered,
but a "Skipped" message is printed in the log file.
• For options, Stop, TakeProfit, Trail, and Entry refer to the contract value, not to the underlying asset, and must
always be given as an absolute price.
• For setting prices or distances in pip units, multiply the pip amount with the PIP variable (f.i. Stop = 5*PIP;). For
adapting distances to the price volatility, set them not to fixed pip values, but use a multiple of ATR(..) - this often
improves the performance.
• TrailSlope, TrailStep, and TrailLock can be set simultaneously. The stop loss is raised by all of them. If a more
complex stop loss / take profit behavior is required than provided by the trail parameters, use a TMF. The TMF runs at
every price quote - resp. every tick - and can trail and check stop and profit limits.
• When Entry, TakeProfit, and/or Stop are used at the same time, or when a TMF is used, the test should run
in TICKS mode for better precision (training can normally be run without TICKS for speed reasons).
• When absolute price limits are given for Trail, TakeProfit, Entry, or Stop, they should be at the correct side of the
current market price with sufficient distance. Limits at the wrong side of the price are accepted, but cause unfavorable
trades. When the Entry condition is already met at entering (f.i. a long trade is entered with the market price already
below the entry limit), the trade will be opened immediately at either the market price or the entry price, whatever is
worse. When the Stop limit is already met at entering, the trade will be opened and immediately closed, losing slippage
and spread. When during the trade the Stop is moved inside the price range of the current bar or tick, the trade will be
immediately closed. If a trade lasts only one or a few ticks, the result by stop or trailing can become inaccurate by a
large factor due to the limited resolution of the price history.
• All stop, profit, trail, or entry limits are handled by software and controlled at each tick. They are not sent to the broker's
server (except for the 'safety net' stop given by StopFactor) and thus not visible to the broker, this way preventing
"stop hunting" or similar practices. This also steps around NFA Compliance Rule 2-43(b) that does not allow US
citizens to place stop or profit targets.
• Except for Forex trading, it is normally not recommended to set Stop and other limits in PIP units. PIP sizes can vary
from broker to broker and even with the same broker from platform to platform.
• A stop loss - regardless if it is handled by software or sent to the broker - is no guarantee to limit losses. In the case of
price shocks (such as the EUR/CHF price cap removal in January 2015) trades can sometimes not be closed at the
stop loss limit. The loss of the trade can then be remarkably higher.
• Stop limits updated to the broker can appear to temporarily move in 'wrong' direction by a small distance. This can
happen when the spread changes at the same time, but in opposite direction as the price.

442
• When Zorro runs unobserved, stop loss limits should always be used, even when they appear to reduce the strategy
performance. They protect against price shocks. The trade engine also uses them for calculating the risk per trade;
without a stop, the capital exposure is not available and the performance statistics are less precise.
• The win rate ("accuracy") of a system mostly depends on the Stop/Takeprofit ratio (also called risk/reward ratio).

Example:
Stop = 0.01*priceClose(); // set stop at 1% distance

Trade parameters 2 - time limits


LifeTime
Trade time limit. Close the trade automatically after the given number of bars (default: 0 for no time limit). Trades are
only closed when the market is open. If the entry is delayed due to
an Entry or EntryDelay setting, LifeTime = 1 causes a trade duration of 1 bar plus the remainder of the opening bar.

EntryTime
Pending order lifetime. When an enter command is given, wait the given number of bars (default: 1) until the entry
limit is met. If that does not happen during that time, cancel the trade. A "Missed Entry" message is then printed to the
log file.

Range:
Number of bars.

Type:
int

EntryDelay
Order entry delay in seconds. Open the trade either after the given delay time (default: 0 - no entry delay), or when
the entry limit is met, whatever happens first. With the fractional part of this variable the entry time can be determined
with a precision of about 10 ms.

Range:
Second units, f.i. 1.23 == 1230 ms.

Type:
var

Remarks:

• All time limits must be set before calling enterLong/enterShort. The life time can however modified afterwards by
setting TradeExitTime in a TMF.
• LifeTime and EntryTime units are bar periods and not affected by TimeFrame.
• EntryDelay can prevent that orders are entered on minute/hour boundaries when many automated systems open their
positions and cause high slippage. This often achieves a noticeably better entry price, especially when combined with
an entry limit.
• EntryDelay can also be used for inserting artificial delays between trades that are opened at the same bar. A delay of
about 30 seconds between trades is often required by trade copy services such as ZuluTrade™.
• EntryDelay is not recommended in combination with Virtual Hedging. As it opens and closes trades not at bar
boundaries, it can cause trades to be opened and closed shortly afterwards by another trade in opposite direction after
the given delay. This causes loss of spread and commission.
• The effect by EntryDelay on the performance is simulated in TICKS mode only.
443
Examples (see also Grid.c and Hacks&Tricks):
// Use an entry limit and an entry delay for not entering trades at the same time
// Call this function before entering a trade
void setDelay(var Seconds)
{
static int PrevBar = 0;
static var Delay = 0;
if(Bar != PrevBar) { // reset delay at any new bar
Delay = 0;
PrevBar = Bar;
}
Delay += Seconds; // increase delay within the bar
EntryDelay = Delay;
Entry = -0.2*PIP * sqrt(Delay); // entry limit for additional profit
}

Trade parameters 3 - investment control


The trade size - the number of contracts or currency units purchased - can be determined in three different ways. For
directly ordering a certain number of contracts, use Lots. For investing a certain amount of your account balance,
use Margin. For risking a certain amount by determining the worst case loss, use Risk.

Lots
Trade size given by the number of lots per trade (default = 1; max = 99999). This is the unit used for sending orders to
the broker. One lot is the smallest possible order unit of the active account (see remarks). In binary trading mode
(BINARY flag) or if Margin or Risk are used for calculating the trade size, Lots determines the minimum number of lots
to be opened per trade (normally 1). If Lots is 0, no trades are opened. If Lots is -1, trades are executed
in phantom mode (see below); they are only simulated, but not sent to the broker.

Margin
Trade size given by invested margin per trade, in units of the account currency (default = 0.0001 for always opening at
least 1 lot). In binary trading mode (BINARY flag), Margin is simply the money invested per trade. In normal trading
mode, Margin is a fixed part of the real trade size - for instance 1% at 1:100 leverage - that the broker keeps as a
deposit for opening a trade. As long as the trade is open, the margin amount is locked on the account and can not be
used for further trades (of course the loss of a trade can be higher than the margin). If Margin is 0 or negative, no trades
are opened. Otherwise the trade size is calculated from the given margin. The Lots variable determines the minimum
number of lots to be opened per trade. If the trade size by Margin is lower than Lots and the MARGINLIMIT flag is set,
trades are skipped. If the ACCUMULATE flag is set, the size of skipped trades is accumulated until it reaches
the Lots amount. Keep Margin at its default value for controlling the number of lots only with the Lots variable.

Risk
Trade size given by the trade risk, in units of the account currency (default = 0 = no risk limit). The risk is the maximum
amount that a trade can lose; it depends on trade size and stop loss distance. Since risk is undefined when a trade has
no stop loss, this parameter must always be given in combination with Stop. If the risk of a trade is higher than Risk,
the trade size is accordingly reduced. Due to spread, slippage, and minimum lot size the maximum loss of a trade can
deviate from the Risk amount. Depending on the RISKLIMIT flags, trades are skipped when even with the minimum
amount (1 lot) the trade risk is still higher than twice the Risk value.

Capital
Initial invested capital in units of the account currency (default = 0 = no initial capital). This has no effect on trading, but
on calculating the strategy performance in the simulation. Set this to the initial capital when the strategy reinvests
profits; Zorro then calculates CAGR instead of AR and determines performance parameters from the invested capital
instead of the required capital. If drawdown plus margin exceeds Capital, Zorro will declare a Margin Call and abort the
simulation. Thus make sure to set Capital well above the required capital without reinvestment, and to limit
reinvestment so that negative equity is avoided.

Type:
var
444
Remarks:

• Lot can have different meanings in the trade context. Normally one lot is the smallest order unit; the trade size is
always a multiple of 1 lot and can never be less than 1 lot. The lot amount - the number of contracts equivalent to one
lot - depends on the broker, the account, and the asset type. Forex brokers often offer mini lot and micro lot accounts.
One mini lot is equivalent to 10,000 contracts and about ~50 EUR margin; and one micro lot is 1000 contracts and ~5
EUR margin. On stock broker accounts accounts the lot amounts and margins are usually higher and the leverage is
smaller. For indexes, commodities, and CFDs, some brokers offer lot sizes that are a fraction of one contract (f.i.1 lot
= 0.1 contracts). Some brokers require a minimum order size of more than one lot, f.i.10 lots. The broker normally
displays his trade parameters on his website or in his trading platform, so the lot amount per asset is always available.
• In some platforms, lot has a special meaning. 1 "Lot" in the MT4™ platform is equivalent to 100 micro lots. MT4 lots
can be converted to broker lots with the script command Lots = MT4Lots * 100000/LotAmount.
• The margin per lot can be determined with the MarginCost variable. The number of contracts per lot can be
determined with the LotAmount variable. The risk of a trade - the maximum possible loss at a given Stop distance -
is Lots * (Stop/PIP) * PIPCost. The number of lots equivalent to a given margin is (Margin/MarginCost).
• Margin, Risk and Lots must be set before calling enterLong / enterShort.
• Margin, Risk or Lots could be set up in real time with a slider (see script example). This would allow to quickly adapt
the trade risk to the market situation, or to disable trades temporarily when you hear of a market crash. The number of
lots, the current price, and the real risk is displayed in Zorro's message window when an order is placed (see Trading).
• [Train] mode always uses 1 lot for training parameters or rules, regardless of the setting of Margin, Risk and Lots.
When Margin or Lots are zero, no trade is opened.
• Because orders can only be placed in a multiples of one lot, the actual margin can be bigger or smaller than the
given Margin value. When the MARGINLIMIT flag is set, trades are not executed when the required margin is more
than twice the Margin value; increasing Margin will then increase both the trade size and the number of trades, while
increasing Lots only increases the trade size.
• Phantom trades are simulated trades that are not sent to the broker. The win or loss of a phantom trade is calculated
from the price curve and contributes to the Short/Long statistics, but not to the Total statistics. This way the
performances of phantom and real trades can be evaluated separately. Phantom trades are normally used for "Equity
Curve Trading" (see below) - they test the market and determine if a certain strategy is currently profitable or not. As
long as the equity curve is below a certain level, real trades are replaced by phantom trades. Phantom trades are also
used for "Virtual Hedging" (see Hedge) - combining long and short positions in a way that market exposure is
minimized and only net positions are sent to the broker.
• The Capital variable is used for determining the effectivity of the reinvestment algorithm. It calculates performance
parameters based on the given capital, not on the equity curve, and thus ignores the setting of the Monte
Carlo Confidence level.
• In [Train] mode, trades always open 1 lot, and phantom trades are converted to normal trades. This behavior can be
modified with the Optimize variable.

Equity curve trading

Equity curve trading is a method of avoiding losses by detecting and skipping unfavourable market periods or the
expiration of strategies. The system monitors the equity curve separately for any asset and algorithm of the strategy.
When unusual losses are recognized, real trading with that component is automatically suspended and all future trades
are simulated in phantom mode (see above). The results of the phantom trades are still recorded in the equity curve as
if they were real trades. But no money is invested until the equity curve shows that the market became profitable again.

Many methods can be used for determining if the market it profitable or not. In the example below, the equity curve is
permanently compared with its own long-term average by lowpass filtering. It the equity is below average and still falling,
trading switches to phantom mode; it goes back to normal as soon as the equity curve is rising again. Other methods
may be based on the balance instead of the equity curve, or on the ratio of winning to losing trades.

Equity curve trading is not a 'holy grail'. It does not improve the performance when there is no clear distinction between
profitable and unprofitable market periods. But it can often reduce the risk of a system. An example of a system that
suddenly became unprofitable is the 'Luxor' strategy in the script examples; here equity curve trading would have
drastically improved the overall result. According to our experience, equity curve trading is recommended especially for
systems that use many algorithms for distributing risk (such as Zorro's Z1, Z2, and Z12). It does not make much
difference in backtests, but it can make a large difference in live trading.

Examples:
// set margin from slider
Margin = slider(1,500,0,2000,"Margin","Average margin in $");
if(Margin > 0) Lots = 1;
else Lots = 0; // don't trade when slider is set to 0
445
// equity curve trading: switch to phantom mode when the equity
// curve goes down and is below its own lowpass filtered value
function checkEquity()
{
if(Train) { Lots = 1; return; } // no phantom trades in training mode
vars EquityCurve = series(ProfitClosed+ProfitOpen);
vars EquityLP = series(LowPass(EquityCurve,10));
if(EquityLP[0] < LowPass(EquityLP,100) && falling(EquityLP))
Lots = -1; // drawdown -> phantom trading
else
Lots = 1; // profitable -> normal trading
}

Asset parameters 1 - transaction costs


Spread
Simulated difference between the ask and bid price of the current asset (default taken from AssetsFix.csv when
offline, current real spread when connected to a broker). The trade profit is reduced by this amount. Spread is ignored
in binary trading mode (BINARY flag).

Slippage
Simulated extra slippage in seconds (default = 5), used in [Test] mode only. In Fill modes above 0, slippage is simulated
by filling or closing orders not at the current price, but at a price expected after the given number of seconds. The
direction and length of the next candle is used for estimating the price. For instance, with 1-minute bars and Slippage at
15, the order is filled at a price within the first quarter of the next bar. The larger the Slippage variable, the larger is the
price range and thus the deviation of the fill or close price from the current price. For hitting entry, stop, or takeprofit
limits, slippage is simulated by taking a price from the current tick range, regardless of the Slippage variable.
Extra slippage has normally a negative effect on systems that go with the trend and can noticeably reduce the profit
especially on short time frames. But can also be in favor of the trader in some cases, especially with counter-trend
systems. It is recommended to test systems with and without slippage for determining its effect on the
result. Slippage at 0 disables extra slippage, but entry, stop, or takeprofit limits still cause slippage unless Fill is also
set to 0 (naive simulation mode). Setting Slippage to a negative amount simulates asymmetric slippage that is always
in adverse direction. Asymmetric slippage is illegal, but some trading platforms allow the broker to automatically apply
asymmetric slippage.

Commission
Broker's fee for opening and closing a trade, taken from AssetsFix.csv. Roundturn commission in units of the account
currency per 10000 contracts for currencies, and per contract for all other assets. The trade profit is reduced by this
amount. Ignored in binary trading mode (BINARY flag). When set in the script, it must be set individually for every traded
asset. It is equivalent to an additional spread, with a size in pips given by Commission*LotAmount/10000/PIPCost for
currencies and Commission*LotAmount/PIPCost for all other assets.

RollShort
RollLong
Daily rollover interest (also called 'swap') per 10000 contracts for currencies, resp. per contract for all other assets.
Taken from the assets list when offline, otherwise the broker's current rollover value is used. The rollover is interest
paid to or received from the broker for holding a short or long position overnight. For instance, when you hold a EUR/USD
long position, you receive interest from the broker for borrowing the EUR and pay interest for lending the USD - the
difference is the rollover. Negative rollover contributes to the losses, positive rollover to the profits. As you can imagine,
negative rollover values are more frequent and greater than positive rollover values. For CFDs, rollover is usually trend
compensating - for instance, upwards trending index CFDs get a negative RollLong for eliminating long-term trend
profit. Rollover can heavily affect the performance of a strategy and cause an asymmetry between long and short trades,
especially when positions are hold for several weeks.

446
WinPayout
LossPayout
Payout in percent of the invested Margin for binary trading (set(BINARY)). Winning trades win the invested amount
plus the WinPayout, losing trades lose the invested amount minus the LossPayout. The payout variables must be set
individually for every traded asset. Spread and Commission must be also set to 0 for normal binary trades.

Type:
var

Remarks:

• The accumulated trading costs by spread, slippage, commission and rollover are displayed on the Performance
Report.
• When Zorro is connected to a broker, it loads the current spread and rollover values from the broker's server. When
not connected, the spread and rollover values are loaded from the History\AssetsFix.csv file or from the parameter
file given by AssetList. This file can be edited with a text editor for simulating different brokers, accounts, and assets
in backtests. For details see Data Import.
• In portfolio strategies, transaction costs are specific to the currently selected asset. The asset must be selected before
modifying any asset specificc parameters.
• The roundturn cost of a currency pair trade - without rollover and slippage - is Lots*(Commission*LotAmount/10000
+ Spread*PIPCost/PIP). For all other assets it's Lots*(Commission*LotAmount + Spread*PIPCost/PIP).
• Trade costs can be set to constant values per asset for test purposes. F.i. for a simulation with no slippage and trade
costs at all, use the following line for all traded assets (i.e. inside the asset loop): Spread = Commission = RollLong
= RollShort = Slippage = 0;. Also set Fill mode at 0 for closing all trades exactly at the stop or profit limit.
• Rollover is added to the trading cost at any bar when the trade was longer open than 12 hours. Since spread and
rollover are taken from an 'account snapshot' when the asset list was generated, they can be very different to the
current, as well as to historical spread and rollover. This can falsify backtests f.i. when trades are open a long time and
accumulate a large rollover. For being on the safe side, you could set RollShort and RollLong to their minimum
(RollLong = RollShort = min(RollLong,RollShort);) and this way avoid a false positive result.

Example:
Spread = 3*PIP; // ignore broker spread, set constant spread for performance calculation

447
Asset parameters 2
PIP
Size of 1 price point (pip) of the current asset in units of the counter currency, f.i. 0.0001 for EUR/USD. Can be used to
set a price in pips, f.i. Stop = 10*PIP. For converting a price difference to pips, divide it by PIP.

PIPCost
Value of 1 pip profit or loss per lot in units of the account currency; determined by the lot size (LotAmount) and the
exchange rate of account currency and counter currency. This value should normally remain constant during the
simulation for not adding artifacts to the result. If desired for special purposes, it can be calculated by script to fluctuate
with the exchange rate (see example below). When the asset price rises or falls by x, the equivalent profit or loss in
account currency is x * Lots * PIPCost/PIP. The value of the counter currency in account curreny units is
normally CCValue = PIPCost / (PIP * LotAmount). This is only different In special cases when a CFD corresponds to
only a fraction of the underlying asset.

MarginCost
Required initial margin for buying 1 lot of the current asset in units of the account currency; depends on leverage and
lot size. The number of lots at a given margin is Margin / MarginCost. Is updated in real time when the broker uses
the Leverage method for calculating margin cost. For special margin requirements, f.i. for covered options or vertical
spread, set this variable in the script before entering a trade.

Leverage
Asset value divided by margin cost. Determined by the account's buying power, i.e. the asset amount that can be
purchased with account currency. MarginCost and Leverage can be converted into each other: MarginCost =
LotAmount * Asset price * CCValue / Leverage = Asset price / Leverage * PIPCost / PIP.

LotAmount
The number of contracts per lot with the current asset. Determines the minimum order size and depends on the lot size
of the account. For currencies, the lot size of a micro lot account is normally 1000 contracts; of a mini lot
account 10000 contracts; and of a a standard lot account 100000 contracts. Some brokers offer also lot sizes that are
a fraction of a contract, f.i. 0.1 contracts per lot for certain CFDs.

InitialPrice
The initial asset price taken from the asset list. Can be used - when desired - to adapt MarginCost to the changes of
the asset price in the backtest (current MarginCost = Initial MarginCost * Asset Price / InitialPrice). Most brokers do
not adapt their MarginCost continously to the price, but in separate steps of a multiple of the account currency unit.

Type:
var, read/only (edit the Asset List for permanently changing asset parameters).

Remarks:

• When Zorro is connected to a broker, it loads the current asset parameters from the broker's server. When not
connected, or when the boker API does not provide the parameters, they are loaded from the asset
list (normally History\AssetsFix.csv). The asset list can be edited with a spreadsheet program or a text editor for
simulating different brokers, accounts, and assets in backtests; or it can be automatically updated to the parameters
of the current broker account. For details see Data Import.

Examples:
// set stop / profit target at a fixed pip distance
Stop = 10*PIP;
TakeProfit = 40*PIP;
// let profits fluctuate with the account currency exchange rate
function convertProfits(string Rate)
{
char OldAsset[15]; strcpy(OldAsset,Asset); // store original asset
if(!asset(Rate)) return;
var Price = price();

448
asset(OldAsset);
if(Price > 0.)
PIPCost = PIP*LotAmount/Price;
}

// call this function from run(), f.i.


convertProfits("EUR/USD"); // when account currency = EUR and counter currency = USD.

Account Parameters
Balance
The current amount in the broker account. In live trading, this parameter is updated from the broker API if available.

Equity
The current value of the account including all open trades. In live trading, this parameter is updated from the broker API
if available.

TradeVal
The current value of all open trades by the current script. This is the profit or loss if all trades would be closed
immediately. The value of all open trades of the account is Equity - Balance. In live trading, this parameter is updated
from the broker API if available.

RiskVal
The estimated maximum risk of all open trades by the current script. The risk is estimated as trade costs plus loss at
either the initial stop loss distance, or at an 1% adverse price move when no stop is used. Due to trailing and exit
mechanisms, the real risk is normally smaller than the estimated risk, but can also be higher in cases of large price
moves or large exit slippage.

RiskMax
The maximum of RiskVal over all past bars.

MarginVal
The current maintenance margin of all open trades of the account. The account is threatened by a margin call
when Equity - MarginVal approaches zero. In live trading, this parameter is updated from the broker API if available.

MarginMax
The maximum of MarginVal over all past bars.

Range:
Account currency

Type:
var, read/only

Remarks:

• When connected to the broker, the variables Balance, Equity, and MarginVal are retrieved from the broker API and
valid for the whole account, not only for the trades of the current script. Trades that are entered manually or openend
by another Zorro instance contribute to them.
• For simulating wins or losses that are not caused by trades, add the amount to WinTotal or LossTotal.

Example:
// stop trading when there's not enough money in the acount
449
if(IsTrading && Equity - MarginVal < 1000))
Lots = 0;
else
Lots = 1;

Variables for futures and options


ContractType
ContractExpiry
Type and expiration date (YYYYMM format) of the selected contract, or 0 when no valid contract was selected.

Type:
int

ContractAsk
ContractBid
ContractStrike
ContractVol
Current ask, bid, and strike price, and the trade volume of the selected contract, or 0 when no valid contract was
selected. For retrieving the ask and bid prices in live trading, contractPrice must be called before.

Type:
var

ThisContract
The currently selected contract.

Contracts
The option or future chain for the current bar, downloaded by contractUpdate. A list of CONTRACT structs of the
size NumContracts.

Type:
CONTRACT*

NumContracts
The number of contracts in the chain, 0...10000.

Type:
int

Multiplier
Number of underlying contracts per option / future contract, for selecting a contract and for filtering the options chain
(default 0 = download all options).

Type:
int

Examples: see Options and Futures

450
AlgoVar[0] .. AlgoVar[7], AlgoVar2[0] .. AlgoVar2[7]
16 general purpose variables for storing values specific to the current asset/algo combination. Every strategy component
has its own set of AlgoVar variables; the sets are automatically switched with any algo or asset call. The variables are
stored in the STATUS structs and can be used in portfolio strategies for values that are common to the algorithm, but
different for every component of the strategy. They can also be used to pass asset/algorithm specific parameters to
a TMF, or for storing parameters when a system is stopped and restarted.

AssetVar[0] .. AssetVar[7]
AssetStr[0] .. AssetStr[7]
8 general purpose locations for storing either numeric values, or strings specific to the current asset. Every asset has
its own set of AssetVar/AssetStr variables; the sets are automatically switched with any asset call. The variables are
read at start from the asset list and stored in the ASSET structs. They can be used in portfolio strategies for parameters
that are common to the algorithms, but different for every asset. AssetStr can only be modified with strcpy, and has a
maximum length of 7 characters.

Type:
var, char*

Remarks:

• Dependent on the SaveMode setting, the AlgoVars are automatically saved and loaded at the end and begin of
trading. This way their values are preserved when a trading system is interrupted or restarted.
• When training or predicting with an R machine learning algorithm, AlgoVar[0]..AlgoVar[7] are sent over to R and
can there be used for setting up parameters to the learning process.
• If saving and loading is not necessary, static series (with negative length) can be used instead of AlgoVars.
• The #define statement can be used for giving AlgoVars or AssetVars meaningful names, like #define MyVar
AlgoVar[0]. If an int is needed instead of a var, use the as_int() macro, like #define MyInt as_int(AlgoVar[0]).

Examples:
// in the run function
...
algo("XYZ");
AlgoVar[0] = 123;
...

// in the TMF
...
algo("XYZ");
var MySpecificValue = AlgoVar[0];
...

AssetZone
Time zone of the selected asset, used for calculating AssetFrame. When your strategy contains a portfolio of different
assets that you want to trade at different times, use this parameter to define the time frames of individual assets.

Type:
int, -23..+24, or UTC, WET, CET, ET, JST, AEST

AssetFrame
0 when the current bar has no price quotes of the current asset or lies inside a day in the AssetZone, negative number
of skipped bars at quote arrival or at day change, 1 otherwise. Can be used to set TimeFrame for skipping bars with no
quotes or for daily trading on different time zones (see example).

451
AssetBar
Bar number of the last received price quote of the current asset, from 0 to NumBars-1. Can be used to determine if
there was a quote in the current bar.

Type:
int, read/only

Remarks:

• The listed variables can be set and evaluated individually for every asset.
• For emulating day bars of different assets with different time zones, use 1-hour bars with AssetZone and AssetFrame
(see example). Use FrameOffset for starting the emulated bar at a certain local hour.

Example:
// trade two assets with different time zones
BarPeriod = 60;
FrameOffset = 9; // trade both assets at 9:00 of their local time
while(asset(loop("EUR/USD","USD/JPY")))
{
if(strstr(Asset,"EUR"))
AssetZone = WET;
else if(strstr(Asset,"JPY"))
AssetZone = JST;
TimeFrame = AssetFrame;
...
}

452
Portfolio trading and money management
Trading with a single asset and a single trade algorithm is usually not enough to generate a regular income. The returns
fluctuate too much and can include years with no income (or even a negative one). Strategies to live from normally use
a portfolio of many assets and many trade rules. For a relatively smooth income curve, you normally should have more
than 10 assets and more than 10 trade algorithms in your strategy. This gives you more than hundred asset/algorithm
combinations, which are the components of the strategy. They all require different strategy parameters, have different
performance, and require different capital allocation and reinvestment factors for achieving the optimal overall profit
from the portfolio.

Reinvesting profits - the square root rule

Many traders believe they should invest a fixed percentage - such as 1%, or for the more daring, 2% - of their account
balance per trade. This is one of the most common reasons of unexpected margin calls, especially with automated
systems. Here's why:

The maximum drawdown of any trade system increases over time. The longer you trade, the higher is the probability of
a long loss streak and the bigger the depth of the drawdown. That's why a system, tested over 10 years, has a worse
maximum drawdown than the same system tested over only 5 years. When modeling drawdown depth mathematically
with a diffusion model, the maximum drawdown of a break-even system is proportional to the square root of the number
of trades, and therefore also to the square root of the trading time. This also means that drawdowns have no limit. A
trading system will suffer a drawdown of any depth when you wait long enough.

Drawdown also increases with the invested amount: When investing twice the volume you'll get twice the drawdown.
Thus, when you reinvest a fixed percentage of your balance, the maximum drawdown grows with the balance. And the
balance of a profitable system also grows proportional to the trading time.

When summing up both effects, you'll get an overproportional drawdown growth with trading time: the drawdown grows
proportionally to time to the power of 1.5. The 1 comes from the reinvested profit, the 0.5 from the square root of the
number of trades (in fact the exponent will be slightly higher than 1.5 as reinvested profit also grows overproportionally,
but that shall not bother us here). In any case your drawdowns will grow faster than your account balance. At some
point, a drawdown will inevitably exceed the balance, causing a margin call. That will happen later or sooner, dependent
on the system and the reinvested percentage.

Therefore better don't invest a fixed balance percentage, no matter how often it's recommended in trading books or
seminars. There are several methods to overcome the drawdown growth issue. One method is to reinvest only an
amount proportional to the square root of the capital growth. Thus when your capital doubles, increase the trade volume
only by a factor of about 1.4 (the square root of 2), i.e. 40%. Example: You're trading with a Margin of $50. Your account
doubles from an initial $1000 to $2000. You can now increase your Margin to $70 (= 1.4 * $50) for reinvesting your
gain.

Another method is investing a variable percentage - for instance the OptimalF factor, see below - that is calculated from
the real equity curve and regularly updated so that it decreases when the drawdowns increase. In both cases, the
drawdowns of your system will then only grow at the same rate as your account balance, so you stay away from a
margin call.

In workshop 6 you can find code examples of several methods for correctly (nor not) reinvesting profits.

Withdrawing profits

If you do not reinvest, but withdraw your profits regularly, keep a part of your profit on the account for the very same
reason. As explained above, the expectancy value of the maximum drawdown depth grows with the square root of
trading time. So your account balance must grow by the same factor for keeping pace with the expected drawdown.
Thus, when your account doubled, you can remove only 60% of your profit and should let 40% stay on the account.
This lets your account grow by the required factor 1.4 (again, the square root of 2).

Example: You start with a capital of $1000 and want to withdraw profit whenever the system won $300. Thus the first
withdrawal is at $1300 account balance. Your investment grew by factor 1.3; the square root of 1.3 is 1.14. $1140 must
stay on the account and you can withdraw $160. - Now your system made another $ 300. The account balance is
now $1440, but the total growth (without the withdrawn amount) is $1600 / $1000 = 1.6. The square root
of 1.6 is 1.265. $1265 must stay, and $175 can be withdrawn.
453
What if you want to withdraw not the whole amount, but reinvest the rest? Here are some simple formulae that help you
calculate what you can withdraw, what you can reinvest, and what should remain on the account:

Balance on account: C+P-W


Must stay on account: C*f
Available to withdraw: C+P-W - C*f
Available to reinvest: C*f - W/f

where C is your initial capital at the first start of the system, P the total profit so far, W the total withdrawal so far
(including what you're just withdrawing), and f the square root growth factor sqrt(1+P/C). Example: You start with a
capital of $1000 and won $300, so your balance is now $1300. How much capital do you have for reinvestment when
you withdraw $50?

Balance on account: C+P-W = $1300


Must stay on account: C*f = $1000 * sqrt(1+$300/$1000) = $1000 * 1.14 = $1140
Available to withdraw: C+P-W - C*f = $1300 - $1140 = $160
Available to reinvest: C*f - W/f = $1140 - $50/1.14 = $1096

So when withdrawing $50 from a $300 win, you can increase your investment from $1000 to $1096 (not to $1250 as
you might have expected). Consider the difference a tax that you pay to the god of statistics. Unfortunately you'll have
to pay a real tax for that, too...

Allocating capital

Zorro can automatically calculate the optimal capital allocation factor - named OptimalF - separately for every
component in a portfolio strategy. It uses a computer algorithm that evaluates the balance curve for calculating the
percentage of the gained capital to be reinvested for maximum profit. For instance, if OptimalF is 0.05, then the
maximum margin for trading that component is 5% of the profit. The margin can be smaller - for the reasons mentioned
above not the full profit but only its square root should be reinvested - but it must not be higher. This algorithm was
developed by Ralph Vince and described in many publications (see links)*.

When the FACTORS flag is set, the OptimalF factors are calculated in a special test run at the end of the [Train]
process, and stored in a file Data\*.fac. It's a simple text file that looks like this:

AUD/USD:ES .036 1.14 45/87 0.1


AUD/USD:ES:L .036 1.14 45/87 0.1
AUD/USD:ES:S .000 ---- 0/0 0.0
EUR/USD:VO .027 2.20 24/23 3.3
EUR/USD:VO:L .027 1.58 12/11 0.9
EUR/USD:VO:S .032 2.90 12/12 2.5
NAS100:ES .114 1.42 63/90 4.6
NAS100:ES:L .101 1.39 33/44 2.1
NAS100:ES:S .128 1.46 30/46 2.5
USD/CAD:BB .030 1.41 19/25 1.3
USD/CAD:BB:L .030 1.41 19/25 1.3
USD/CAD:BB:S .000 ---- 0/0 0.0
USD/CAD:HU .012 1.74 48/36 3.3
USD/CAD:HU:L .066 1.42 24/20 0.2
USD/CAD:HU:S .012 1.79 24/16 3.1
USD/CHF:CT .104 1.60 16/17 0.6
USD/CHF:CT:L .104 1.60 16/17 0.6
USD/CHF:CT:S .000 ---- 0/0 0.0
USD/CHF:CY .025 1.10 21/24 0.1
USD/CHF:CY:L .025 1.10 21/24 0.1
USD/CHF:CY:S .000 ---- 0/0 0.0
USD/CHF:HP .025 1.45 31/48 3.2
USD/CHF:HP:L .000 ---- 0/0 0.0
USD/CHF:HP:S .025 1.45 31/48 3.2
USD/CHF:VO .011 3.93 17/8 7.6
USD/CHF:VO:L .011 3.93 17/8 7.6
USD/CHF:VO:S .000 ---- 0/0 0.0

The first column identifies the component; it consists of the asset name and the algorithm identifier. "S" or "L" are
separate statistics for short or long trades. The second column contains the OptimalF factors for that component. The
further columns display the profit factor, the number of winning and losing trades, and the weight of the component; they
are normally not used in strategies.

454
As the factors are stored in a simple text file, they can be edited anytime with a text editor, even while trading. The
higher the factor, the more capital should be reinvested by the strategy component. Zorro detects if factors have been
changed, and automatically reloads them. If the factors are evaluated in the strategy, as in some of the Z strategies, a
component can be excluded from further trading by setting its factor to zero, or by placing a minus sign in front of it for
making it negative.

Variables

The following variables can be used for evaluating or generating OptimalF factors in the script:

OptimalF
OptimalFLong
OptimalFShort
OptimalF factor, and separate factors for long and short trades of the current strategy component that is selected with
the asset and algo functions. The margin to be invested per trade can be calculated by Margin = OptimalF * Capital.
In [Train] mode or when the FACTORS flag is not set, the OptimalF factors are always 1. If a component is
unprofitable, its OptimalF factor is zero.

OptimalFRatio
Generate OptimalF factors with the given ratio of the highest to the lowest factor (default = 0 = no ratio). For instance,
at OptimalFRatio = 3 large factors are reduced and small factors are increased so that the highest OptimalF factor is
3 times the lowest factor. The average of all factors remains unchanged. Useful for preventing large component
margin differences when using OptimalF factors in portfolio systems.

Remarks

• Every algo and asset call switches the OptimalF variables to the factors belonging to the new component.
• In Ralph Vince's publications, OptimalF is defined in a different way, requiring a formula containing the maximum loss
for calculating the number of lots of a trade. Zorro's OptimalF factors are already adjusted by the maximum loss, and
thus can be directly multiplied with the earned capital for getting the optimal margin.
• OptimalF factors are calculated over the whole test period, even when WFO is enabled. This slightly violates the out-
of-sample test philosophy. Therefore when using OptimalF factors for reinvesting profits, the real trading performance
can be worse than the performance predicted by a WFO test.
• In a portfolio system, OptimalF is separately calculated for any component. The correlations of components do not
affect the calculation.
• OptimalF is affected by maximum losses in the trade history, and thus tends to decrease when the test period
increases. The reason is the same as the drawdown dependency on the test period discussed under Reinvesting
profits above.
• If the balance curve has very little drawdown, theoretically the full capital can be invested in that component for
maximum profit. OptimalF is then set to 0.999. Investing the full capital is not recommended in real trading, as the
balance curve is not guaranteed to continue this way in the future. If a component is unprofitable, OptimalF is set
to 0.000.
• Trading with portfolio strategies and money management is explained in workshop 6.
• Markowitz weights can be used alternatively for allocating capital to portfolio components. They have the
disadvantage of not considering reinvestment, but the advantage of minimizing the variance of the total portfolio.

Examples of different investment methods


// reinvest the square root of your portfolio component profits, separately for long and short trades
if(GoLong)
Margin = OptimalFLong * Capital * sqrt(1 + (WinLong-LossLong)/Capital);
else
Margin = OptimalFShort * Capital * sqrt(1 + (WinShort-LossShort)/Capital);

// reinvest the square root of your portfolio component profits


Margin = OptimalFLong * Capital * sqrt(1 + ProfitClosed/Capital);

// reinvest the square root of your total profits


Margin = OptimalFLong * Capital * sqrt(1 + (WinTotal-LossTotal)/Capital);

455
Monte Carlo Analysis
Zorrocan run a Monte Carlo analysis for its own strategies, as well as for trade lists that are generated by other software
and stored in .csv files. The Monte Carlo method improves the performance analysis. It generates not a single
sequence of trades and a single equity curve, but a distribution of many possible curves. Compared to standard
simulation, the Monte Carlo method produces results that are more accurate and less subject to random fluctuations.

When using Monte Carlo analysis, the equity curve from the backtest is randomly resampled many times. This generates
many different equity curves that each represent a different order of trades and different price movements inside each
trade. The curves are analyzed, and their results are sorted according to their performance. This way a confidence level
is assigned to every result. The confidence level determines how many of the curves had the same or better performance
than the given result.

Without Monte Carlo analysis, the annual rate of return is calculated from the backtest equity curve. For example, it
might be found that the annual return over the curve was 200%. With Monte Carlo analysis, hundreds or thousands of
different equity curves are analyzed, and the annual return determined from them might be, for instance, 145% with
95% confidence. This means that of all the thousands of possible outcomes of the simulation, 95% had annual return
rates better than or equal to 145%.

Monte Carlo analysis is particularly helpful in estimating the risk and capital requirement of a strategy. The maximum
historical drawdown is normally used as a measure of risk, but this means we're basing our risk calculations on a
historical price curve that won't repeat exactly. Even if the statistical distribution of trades is the same in the future, the
sequence of those trades and their equity movements are largely a matter of chance. Calculating the drawdown based
on one particular sequence is thus somewhat arbitrary: with a sequence of several losses in a row, you can get a very
large drawdown. But the same trades arranged in a different order, such that the losses are evenly dispersed, might
produce a negligible drawdown. This randomness in the result can be eliminated by Monte Carlo analysis that takes
many different equity curves and many different trade sequences into account.

Example of the result distribution in the performance report:

Confidence level AR DDMax Capital

10% 236% 1440$ 1390$


20% 227% 1550$ 1470$
30% 218% 1680$ 1570$
40% 209% 1830$ 1680$
50% 202% 1940$ 1760$
60% 193% 2140$ 1900$
70% 186% 2340$ 2040$
80% 174% 2730$ 2320$
90% 165% 3080$ 2580$
95% 145% 4010$ 3580$
100% 104% 5640$ 4710$

The first column identifies the confidence level; in the next columns the annual return, the maximum drawdown, and the
required capital at that level are listed. The most likely result is at 50% confidence level (here, 202% annual return). This
means that half the simulations had this or a better result, and half a worse result. The higher the confidence level, the
more pessimistic are the results. The result at 100% confidence level is the worst of all simulations; thus the probability
to get this (or an even worse) result is 1/n, where n is the number of simulations. Note that the capital can be smaller
than the maximum drawdown when the test period was longer than 3 years (capital and annual return are based on a
3-years normalized drawdown).

The following variables are used for Monte Carlo simulation:

MonteCarlo
Number of simulations for Monte Carlo analysis (default: 200). Every simulation generates a different equity curve. The
more simulations, the more accurate is the result, but the more time is needed for the computation. If set to 0, no Monte
Carlo simulation is performed.

Confidence
Confidence level for the performance analysis in percent (0..100); determines the calculation of the main performance
parameters such as annual return, drawdown, required capital, etc. The higher the confidence level, the more

456
pessimistic are the results. At 0 (default) the performance parameters are calculated from the real equity curve and not
from the Monte Carlo simulation.

Remarks

• If the Capital variable is set, performance parameters are derived from the given capital, not from the Confidence
level.
• Monte Carlos Analysis is biased when trade returns are strongly correlated (i.e. the strategy produces long winning
and losing streaks) or strongly anticorrelated (winning and losing alternate). In the former case Monte Carlo Analysis
can display too optimistic results, otherwise too pessimistic results.
• Monte Carlo analysis does not absolutely guarantee a minimum performance. If the strategy was expired or
was tested in a wrong way (f.i. in-sample), live trading returns can be worse than the Monte Carlo performance even
at 100% confidence level.

Examples
function run()
{
MonteCarlo = 1000; // 1000 simulations
Confidence = 75; // 75% confidence level
...

Detrend
Removes, inverts, or randomizes trend in historic price data. Can be set up for removing bias from the parameters, the
trades, the indicators, or the whole price curve. Backtesting with detrended, inverted, or randomizied price curves is
also a quick method to verify the robustness of a strategy. For removing trend, the curve or parts of it are "tilted" until
the start and end prices have the same value.

Range:
The following flags can be set or combined with '+' or '|':

TRADES Detrend trade results. This removes trend bias from a backtest while keeping the properties of the price
curve.

PRICES Detrend trade results and price functions; for detrending indicators and signals based on series
generated with price() calls.

CURVE Detrend the historical price data on loading. This also affects the displayed price curve in the chart.

INVERT Invert the price curve by replacing prices with their reciprocal values. This reverses all trends in the
curve, and can be used for a reality check of a system that is symmetric in long and short positions.

SHUFFLE Randomize the price curve by shuffling the price ticks. Keeps its overall trend and price range, but
removes any short-term trends and correlations between the prices. Used for reality checks.

NOPRICE Do not subscribe the asset and do not detect gaps or fix outliers on loading; for data files that contain
no prices, but other types of data.

Type:
int

457
Remarks:

• If a trade strategy has a trend bias - f.i. when it buys more long than short positions - or vice versa, it is recommended
to remove the trend from the price curve for generating strategy parameters. Otherwise, the simulation will be too
optimistic when the price curve has an overall upwards trend, or too pessimistic when it has a downwards trend. For
symmetric strategies that buy long and short positions under similar conditions and with the same strategy parameters,
detrending is normally not necessary.
• Setting Detrend at TRADES or PRICES detrends the prices from the end of the lookback period until the end of the
simulation resp. WFO training cycle. This has the effect that all WFO cycles are separately detrended in [Train]
mode. Detrend = CURVE however detrends the price data file, but keeps the shorter-term trends of the WFO cycles.
• For detrending only strategy parameters or rules, but not the test results, set Detrend only in [Train] mode: if(Train)
Detrend = CURVE;.
• For detrending only the prices of certain assets, set Detrend = CURVE before calling asset(), and set it
to 0 afterwards.
• For detrending particular trades only, set Detrend = TRADES before entering the trade, and set Detrend =
0 afterwards. This is useful for portfolio systems that use both trend following and non-trend algorithms.
• For determining if a system's profit is caused by artifacts, temporarily set Detrend = SHUFFLE+CURVE and test. If
the profit is caused by a real edge, it should then disappear.
• For determining if a system's profit is caused by randomness, temporarily set Detrend = INVERT and test. On
symmetric assets such as currencies, the system should then still achieve profits.
• Detrended, inverted, or randomized price curves can be exported to a CSV file for further tests with R or similar
statistical software.
• Reality checks using inverted or randomized price curves are mentioned on Financial Hacker | Development
Process | Step 7: Reality Check.

Example:
if(Train) Detrend = CURVE; // detrend the whole price curve for training

Hedge
Hedging behavior; determines how simultaneous long and short positions with the same asset are handled.

Range:
0 = no hedging; automatically close all opposite positions with the same asset when a new position is opened (default
for accounts with no hedging).
1 = hedging across algos; automatically close all opposite positions with the same asset and algo when a new
position is opened (default for unspecified accounts).
2 = full hedging; enter and open long and short positions simultaneously.
4 = virtual hedging; enter long and short positions simultaneously, but send only the net amount to the broker.
5 = virtual hedging with position minimizing. Several positions entered at market are combined to a single net position.
Open positions are partially closed to match the net amount (see below).

Type:
int

Virtual hedging algorithm


Virtual hedging mode (Hedge >= 4) reduces trading costs by preventing that the broker opens long and short positions
simultaneously and thus pockets commission for both. To prevent this, virtual hedging mode uses two layers of
trades, phantom trades and 'pool' trades. The pool trades hold the net amount of the asset's phantom trades. The
strategy script only handles the phantom trades, while the broker only receives the pool trades. Pool trades are opened
or closed when phantom trades open or close, but not necessarily in that order. Phantom trades can be open in both
directions at the same time, but pool trades are only open in one direction per asset, either short or long.

458
Script Phantom Pool Broker
Trades Trades

Virtual hedging mode is transparent to the user; only the Hedge variable needs to be set. If at or above 4, all trades are
automatically entered in phantom mode. When the net amount - the difference of long and short open lots - changes,
Zorro automatically opens or closes pool trades in a way that the market exposure is minimized. Example: several long
positions are open with a total amount of 100 lots. A short trade of 40 lots is entered. The net amount is now 60 lots
(100 long lots minus 40 short lots). Zorro first checks if one of the long positions has an amount of exactly 40 lots; if so,
the position is closed. Otherwise, the oldest long positions are closed until the sum of open positions is at or below 60
lots. If it's less than 60 lots, a new long trade is openend at the difference (Hedge == 4). In minimizing mode (Hedge ==
5) the last long position is only partially closed so that the net amount ends up at exactly 60 lots.

Virtual hedging is used in test and trade mode only, not in training mode. The performance statistics (Long/Short/Total)
are summed up from the pool trades only, not from the phantom trades. Only exception is NumPendingTotal, which is
affected by pending phantom trades (pool trades are never pending). Phantom trades can also be manually entered for
equity curve trading (see lots); those trades contribute to the Long/Short statistics, but do not trigger pool trades.

Virtual hedging affects the system performance. Although the equity curves of a system with Hedge = 2 and Hedge >=
4 are relatively similar, the number of trades, the profit factor, the win rate, and the average trade duration can be very
different. The total profit as well as the capital requirement of virtual hedgins can also be different due to lower trade
costs. Below is a grid trading system without and with virtual hedging:

Grid trader , Hedge = 2, Win Rate ~ 95%, Profit Factor ~ 10

459
Grid trader , Hedge = 5, Win Rate ~ 65%, Profit Factor ~ 3

Remarks:

• Hedging is prohibited for US based accounts due to NFA Compliance Rule 2-43(b). Such accounts require
the NFA flag. If NFA is set, trades are not partially closed in Hedge = 5 mode, but are always fully closed.
The NFA flag does not affect phantom trades, which can be simultaneously open in both directions.
• Pool trades have no profit target and no TMF, but - for protection against large price shocks - a very distant stop loss
at about 20%..30% difference to the current price. They are normally only controlled by opening and closing phantom
trades. However they appear in trade enumeration loops and can be identified by TradeIsPool.
• The number of open net lots of pool and phantom trades can be evaluated with the LotsPool and LotsPhantom
variables. If they get out of sync, for instance when pool trades are rejected by the broker or when they were externally
or manually closed, the next pool trade with the same asset will synchronize the variables again. As long as Lots is 0 or
no phantom trades are opened or closed, pool trades won't be opened either even when LotsPool and LotsPhantom
are different.
• Closing a phantom trade does not close the associated pool trade, but the oldest pool trade. For this reason a phantom
trade can close with a loss and the subsequent pool trade with a win, or vice versa. If a phantom trade is cancelled
(see cancelTrade), a corresponding pool trade is also cancelled.
• In virtual hedging mode, the Result Window displays only open pool trades and pending phantom trades. When algo
identifiers are used, pool trades are displayed either with the identifier of the triggering trade, or with a "NET" identifier.
• Virtual hedging is automatically disabled in [Train] mode.

Example:
if(Trade)
Hedge = 5; // virtual hedging in trade mode
else
Hedge = 2; // full hedging in test mode

460
Order filling and HFT trading
The Fill variable supports several modes for simulating naive or realistic order filling, as well as a HFT simulation
mode. HFT (High Frequency Trading) is a trading method that exploits inefficiencies on the millisecond or microsecond
time scale. It is very different to normal trading. The usual trading accessories, such as bars, candles, or indicators, are
not used. The deciding factor is speed. HFT systems do normally not run on a trading platform - even Zorro would be
too slow - but are directly programmed in a high speed language, such as C, Pentium assembler, shader language
(HLSL) or IC design language (VHDL).

Zorro can be used for testing HFT systems in Fill mode 8. This mode uses the callback function for analyzing price
quotes and sending trade orders. Bars are not available in this mode, so the run function needs not be used;
a main function for initializing the system is sufficient. Trades are only opened and closed
with enter and exit commands; TMFs, stops, or profit targets are not supported, nor are indicators and
series. Hedge mode must be 2, TradesPerBar set to the maximum number of trades in the simulation. Price quotes
are entered to the system with the priceQuote function. For considering latency, any price quote must include the
exchange time stamp. The two-way latency between exchange and PC location is set up with EntryDelay and
determines the price quote at which the enter or exit order will be filled.

Fill
Determines how order fills are simulated in [Test] and [Train] mode.

Range:
0 Naive order filling. Trades open or close at the current market price or at their Entry, Stop, or TakeProfit limits,
regardless of latency, slippage, or intrabar price movement. 'Cheating' is possible by setting unrealistic stop or
profit limits in a TMF. This mode can be used for special purposes, f.i. for comparing with results of other platforms.

1 Realistic order filling (default). Trades open or close at the current price plus extra Slippage. When Slippage is
set to 0, this mode simulates a latency-free connection to the market that immediately reacts on any price quote.
Cheating is not possible. With daily bars, this mode simulates order entry immediately before the market closes.

3 Delayed order filling. Trades open or close at the next price quote plus extra Slippage. With daily bars, this mode
simulates order entry at the open price of the next day.

8 HFT fill mode. Trades open or close at the last priceQuote with a time stamp determined by EntryDelay.

Type:
int

Example:
function run()
{
BarPeriod = 1440;
Fill = 3; // enter trades at next day's open
...
}

HFT simulation framework:


// Simulate a stock trading HFT system

#define LATENCY 2.5 // simulate 2.5 milliseconds latency


#define MAXTRADES 20000 // max number of trades

typedef struct QUOTE {


char Name[24];
var Time,Price;
} QUOTE; // Quote struct by the NxCore plugin

int callback(QUOTE *Quote) // called by the plugin


{

461
static int Counter = 0; // quote counter
if(0 == (++Counter % 1000)) { // every 1000 quotes...
Bar++; // update bar count in info window
print(TO_INFO,0); // update info window
if(!wait(0)) return 0; // check if [Stop] clicked
}

asset(Quote->Name+1); // NxCore adds an "e" to the asset name


priceQuote(Quote->Time,Quote->Price);
...
// trade algorithm here
...
if(!(NumOpenLong+NumPendingLong) && SignalLong) {
exitShort();
enterLong();
} else if(!(NumOpenShort+NumPendingShort) && SignalShort) {
exitLong();
enterShort();
}
return 1;
}

function main()
{
if(Broker != "NxCore") {
quit("Please select NxCore plugin!");
return;
}

StartDate = 20170103;
EndDate = 20170131;
LookBack = 0;
TradesPerBar = MAXTRADES;
set(LOGFILE);
assetList("AssetsHFT");
EntryDelay = LATENCY/1000.;
Hedge = 2;
Fill = 8;
Lots = 1;
Verbose = 3;

// process the NxCore history tape files, one for each day
login(1); // for initilizing the plugin
int NxDate;
for(NxDate = StartDate; NxDate = EndDate; NxDate++) {
string NxHistory = strf("History\\NxTape%8d.nx2",NxDate);
printf("\nProcessing tape %s..",NxHistory);
brokerCommand(SET_HISTORY,NxHistory);
assetHistory("WHATEVER",0);
if(!wait(0)) break; // check if [Stop] clicked
}
}

Optimize
Optimization method; affects the way parameters are generated by the optimize function.

Range:
The following flags can be set or combined with '+' or '|':

TRADES Optimize considering the trade size (Lots). Large trades get more weight. Otherwise trade sizes are
ignored in the training process.

PHANTOM Optimize ignoring phantom trades. Otherwise phantom trades are just normal trades in the training
process.

PEAK Look for peaks in the parameter space, rather than hills.

Type:
int

462
Example:
Optimize = TRADES+PEAK;

Weekend
Determines the behavior during weekend bars (by default, Friday 20:00 UTC until Sunday 23:00 UTC) and at
international business holidays.

Range:
0 - trade even during the weekend. For special purposes only.
1 - don't enter trades during the weekend, but generate bars, observe exit limits (stop / takeprofit / trail), and
run TMFs when price quotes arrive
2 - don't begin or end bars during the weekend, but observe exit limits and run TMFs when price quotes arrive (default
- see remarks).
3 - don't begin or end bars, don't observe exit limits, and don't run TMFs during the weekend.
7 - automatically log off at weekend; recommended if the broker API tends to crash when the broker server goes
offline.

Type:
int

Remarks:

• The weekend period can be set up with the StartWeek and EndWeek variables in the dhhmm format, where d = day
number (1 = Monday .. 7 = Sunday), hh = hour (UTC) and mm = minute. At the default values
(StartWeek = 72300, EndWeek = 52000) the week starts Sunday 23:00 UTC and ends Friday 20:00 UTC. Markets
close later, but it is normally not recommended to open trades in the late Friday hours.
• Weekend can noticeably affect a strategy performance. If at 1, bars can begin and end during the weekend when they
contain price quotes. However no trade is triggered by a bar that ends at weekend. If Weekend is at 2 or above, a bar
can not end during the weekend; the last Friday bar is thus extended by a multiple of BarPeriod until the weekend is
over. Thus a daily bar starting Friday 00:00 UTC will normally end Monday 00:00 UTC. If not explicitly prevented by
script, a signal occurring during that bar can trigger a trade at the start of the week. Therefore Weekend = 2 generates
less bars, but can generate more trades than Weekend = 1. This can positively or adversely affect the strategy
performance.
• When Weekend is at 1 or 0, bars are generated on workdays even when no price quotes are received. At 2 or above,
any bar must contain at least one price quote of the first used asset. Otherwise the bar is extended by a multiple
of BarPeriod until a quote arrives.
• December 25 and January 1 count as weekend. Other holidays are not included, but can be added by script.
• The Weekend variable affects the sampling of price data and thus must be set before any asset calls.

Example:
function run()
{
...
StartWeek = 10400; // start Monday 4 am
EndWeek = 51900; // end Friday 7 pm
Weekend = 7; // log off during the weekend
...
}

463
Verbose
Determines the verbosity of trade, error, and diagnostics messages.

Range:
0 Few messages. Most warnings are suppressed. Bars are only printed in [Trade] mode or when trades are
open.

1 More messages (default).

2 Even more messages. In [Trade] mode, the daily profit is printed once per day. In [Test] mode with TICKS flag
any tick preceding a trade is printed in the log.

3 Even more messages and warnings. Missing prices and internal function errors are displayed. In [Trade] mode,
all open trades are listed once per day.

7 Extensive messages and diagnostics in the log file. In [Trade] mode, the time for opening and closing trades
is recorded in the log file.

+8 Black box recorder for diagnostics (as if started with -diag command line option).

+16 Display critical messages, such as possibly orphaned trades or broker API errors, in a separate alert box.

+24 Black box recorder plus alert box. The recording is stopped at the first critical event, so the details leading to
the situation can be evaluated from the black box log (Log\...diag.txt). Recording continues when the alert box
is closed.

Type:
int

Remarks:

• In the daily trade list at Verbose >= 3, the state of any trade is printed in the form [Trade ID] - Profit/Loss - Stop -
Current price - Entry, f.i. [AUD/USD:CY:S4400] +116$ s0.8733 c0.8729 e0.8731. The same list is printed on a click
on the [Result] button.
• With Verbose >= 2, the prices of the current asset at every bar are printed in the form Open/High\Low/Close.
• Verbosity and black box recording can strongly reduce the training and test speed, so do not use this feature
unnecessarily.

Example:
function run()
{
Verbose = 7+8; // diagnostics messages plus black box recorder
...
}

SaveMode
Determines what's saved or loaded in the .trd file when restarting in [Trade] mode or when calling
the saveStatus/loadStatus functions.

464
Range:
SV_SLIDERS - save/load slider positions (default).
SV_ALGOVARS - save/load all AlgoVars (default).
SV_TRADES - save/load all open trades (default).
SV_BACKUP - after loading, save a backup of the .trd file (default).
SV_HTML - after loading trades in [Test] mode, generate a HTML file with the list of open trades.

Type:
int

Remarks:

• The modes can be combined by adding (see example).


• Set SaveMode = 0 for preventing that open trades are resumed at start of a session.

Example:
function run()
{
...
SaveMode = SV_TRADES+SV_ALGOVARS+SV_BACKUP;
...
}

SaveMode
Determines what's saved or loaded in the .trd file when restarting in [Trade] mode or when calling
the saveStatus/loadStatus functions.

Range:
SV_SLIDERS - save/load slider positions (default).
SV_ALGOVARS - save/load all AlgoVars (default).
SV_TRADES - save/load all open trades (default).
SV_BACKUP - after loading, save a backup of the .trd file (default).
SV_HTML - after loading trades in [Test] mode, generate a HTML file with the list of open trades.

Type:
int

Remarks:

• The modes can be combined by adding (see example).


• Set SaveMode = 0 for preventing that open trades are resumed at start of a session.

Example:
function run()
{
...
SaveMode = SV_TRADES+SV_ALGOVARS+SV_BACKUP;
...
}

465
Chart variables
PlotBars
Maximum number of bars to plot in the chart, or 0 for plotting all bars (default). A positive number plots the bars from
the start, a negative number from the end. Enter a small number for increasing the scale of the chart and zoom in to the
start or to the end.

PlotDate
Start date of the chart, in the yyyymmdd format. If at 0 (default), the chart starts with the end of the LookBack period.

PlotScale
Candle width in pixels (default 5). Use a negative number for displaying hi-low bars instead of candlesticks. The candle
width is automatically reduced when the chart becomes too big.

PlotWidth
Maximum chart width in pixels (default 2000), or 0 for no chart. The candle width is accordingly reduced when the chart
exceeds PlotWidth. Note that too-large chart images can't be displayed with most image viewers.

PlotHeight1
Height of the main chart window with the price bars, in pixels (default 512).

PlotHeight2
Height of additional chart windows (plot with type=NEW), in pixels (default 128).

PlotPeriod
Period in minutes for updating the chart on the trade status page (default: 1 day).

Range:
0..999999

Type:
int

Remarks:
See plot. For removing chart elements such as price candles or equity curves, set their Color to 0.

Example:
function run()
{
PlotBars = 2000;
...
}

466
Command[0] .. Command[3]
Contains the numbers passed with the -i option on the command line.

Type:
int

Remarks:

• This variable is used for passing the live trading start date and bar offset to a Zorro instance in Retest mode.

Example:
function run()
{
switch(Command[0]) {
case 1: doThis(); break;
case 2: doThat(); break;
}
...
}

Colors
ColorUp
ColorDn
Colors for the white and black candles (default: ColorUp = 0x00CCCCCC = bright grey, ColorDn = 0x00222222 = dark
grey). If both colors are 0, no price candles are plotted in the main chart.

ColorEquity
Color for the equity bars (default: 0x00666699 = blue-grey). If at 0, no equity curve is plotted in the main chart.

ColorDD
Color for the "underwater equity" bars (default: 0x00FF3333 = red). If at 0, no underwater equity is plotted in the main
chart.

ColorWin
ColorLoss
Colors for the winning and losing trades (default: ColorWin = 0x0033FF33 = green, ColorLoss = 0x00FF3333 = red).
If both colors are 0, no trades are plotted in the main chart.

ColorBars[3]
Colors for the bars on statistics charts. Default: ColorBars[0] = objective (red), ColorBars[1] = wins
(blue), ColorBars[2] = losses (blue-grey).

ColorPanel[6]
Default colors for the cells on a control panel. ColorPanel[0] = text background (light grey), ColorPanel[1] = number
background (red), ColorPanel[2] = editable (yellow), ColorPanel[3] = button (blue), ColorPanel[4] = highlighted text
(red), ColorPanel[5] = grayed out text (grey).

Range:
0..0xFFFFFFFF
467
Type:
long

Remarks:

• Colors can be defined as hexadecimal numbers just as in HTML pages, in the form 0xTTRRGGBB (f.i. 0x000000FF =
opaque blue, 0x80FF0000 = half transparent red). The transparency TT runs from 0 (full opaque) over 80 (half
transparent) to FF (full transparent).
• Some colors are predefined: RED, GREEN, BLUE, CYAN, DARKBLUE, LIGHTBLUE, PURPLE, YELLOW,
MAGENTA, ORANGE, DARKGREEN, OLIVE, MAROON, SILVER, GREY, BLACK. +TRANSP can be added for
50% transparency.
• For generating color ranges dependent on a variable, use the color function.

Example:
function run()
{
ColorEquity = 0x0000FF00; // green equity bars
ColorDD = 0; // no underwater equity
...
}

Trade management functions and variables


Trade enter commands can receive a trade management function (TMF) as the first argument. A TMF is a function for
micro managing the trade. It is repeatedly called until the trade is closed. In most cases it's used for modifying entry,
stop, or profit limits in a special way, overriding the standard trailing methods. In live trading and in TICKS mode, a TMF
is executed every tick and thus has access to the most recent price quote. When the market is closed and no price ticks
are received, TMFs are not executed.

TMFs are also called at four special events in the lifetime of a trade: Right before entering or exiting due to Entry, Stop,
or TakeProfit, and right after being closed. Which event it is can be checked in the TMF with the boolean
expressions TradeIsEntry, TradeIsStop, TradeIsProfit, TradeIsClosed (described below).

A TMF has the type int and should normally return 0; other return values have a special meaning. !! When using a
TMF, do not forget the return statement with the correct value!

TMF return values:


0 - check the trade's Entry, Stop, TakeProfit, and Trail parameters, and exit or enter accordingly.
1 - if the trade is still open or pending, exit it now.
2 - if the trade is still pending, enter it now.
4 - Don't use Entry, Stop, or TakeProfit for automatically entering or exiting. Exit or enter only when the TMF
returns 1 or 2.
8 - call the TMF only once per bar, just before the run function call.
16 - call the TMF only at events (entering or exiting due to Entry, Stop, or TakeProfit, and after the trade was
closed).

The return values can be combined by addition. For instance, return value 28 (= 4+8+16) executes the TMF only once
per bar or when Entry, Stop, or TakeProfit was hit, and does not automatically enter or exit in that case.

A a list of up to 8 var variables can be passed as parameters to the TMF, f.i. enterLong(MyTMF, parameter1,
parameter2...). They can appear in the argument list of the TMF definition and keep their values during the lifetime of
the trade.

The TMF has access to the following trade specific variables (all prices are Ask prices, most variables are float):

468
TradePriceOpen
The ask price when the trade was opened, or the premium per unit for options or futures. If the trade was not yet
opened, it's the current price of the asset or contract.

TradePriceClose
The ask price when the trade was closed or the price at which the option or future was sold or covered. If the trade is
still open, it's the current price of the asset or contract.

TradeUnits
Conversion factor from price change to win/loss in account currency units; normally TradeLots*PIPCost/PIP for assets,
or TradeLots*Multiplier for options or futures. Examples see below.

TradeRoll
The current accumulated rollover of the trade, negative or positive. Only valid when the trade is open.

TradeProfit
The current profit or loss of the trade in units of the account currency, including costs such as spread, rollover, slippage,
and commission. TradeProfit/TradeUnits/PIP is the current profit or loss of the trade in pips. On NFA
compliant accounts the profit is simulated, as there is no profit assigned to a single trade. For options, TradeProfit is
normally the difference of premium and current price; if the option is expired or was exercised, it's the extrinsic value,
i.e. the difference of strike and underlying price minus the premium.

TradeStrike
The strike price of the traded option (if any).

TradeUnderlying
The underlying price of the traded option (if any).

TradeMFE
Maximum favorable excursion, the maximum price movement in favorable direction of the trade. Only valid after the
trade was opened. TradeMFE*TradeUnits is the highest profit of the trade in account currency units while it was open
(without trading costs).

TradeMAE
Maximum adverse excursion, the maximum price movement in adverse direction of the trade. Only valid after the trade
was opened. TradeMAE*TradeUnits is the highest loss of the trade in account currency units while it was open (without
trading costs).

TradeEntryLimit
Entry limit; initially calculated from Entry. The trade will be opened when the price reaches this value. Can be modified
by the TMF.

TradeStopLimit
Stop limit, initially calculated from Stop; only valid when the trade is open. The trade will be closed when the price
reaches this value. Can be modified by the TMF.

TradeStopDiff
Difference of the initial price to the initial stop limit; negative for long trades and positive for short trades. Initially
calculated from Stop and only valid when the trade is open. When TradeStopLimit was moved by trailing, the original
stop position can still be retrieved through TradePriceOpen+TradeStopDiff.

TradeProfitLimit
Profit limit, initially calculated from TakeProfit; only valid when the trade is open. The trade will be closed when the price
reaches this value. Can be modified by the TMF.

469
TradeTrailLimit
Trail limit, initially calculated from Trail; only valid when the trade is open and a stop limit was set. The stop limit will
be moved when the price reaches this value. Can be modified by the trade function.

TradeTrailSlope
Trail slope factor in the range 0..1; only valid when the price is over the Trail limit, and a Stop limit was set. Can be
modified by the trade function for changing the trail slope f.i. after breakeven.

TradeTrailStep
Trail step factor in the range 0..1; only valid when the price is over the Trail limit, and a Stop limit was set. Can be
modified by the trade function for changing the trail step f.i. after breakeven.

TradeTrailLock
Trail lock factor in the range 0..1; only valid when the price is over the Trail limit, and a Stop limit was set. Can be
modified by the trade function for changing the trail lock f.i. after breakeven.

Type:
float, read/only if not mentioned otherwise. Convert them to var when using them in print/printf statements!

TradeVar[0] .. TradeVar[7]
8 general purpose var variables (default = 0). They are stored in the TRADE struct and can be used when a trade
specific value must be preserved between trade function runs. They can be used and modified by the TMF. When they
are used, it's recommended to define meaningful names for them, f.i. #define LastPrice TradeVar[0] etc. Without a
trade, i.e. outside a TMF or trade enumeration loop, those variables have no meaning.

Type:
var

TradeLots
Number of lots.

TradeExitTime
Trade exit time in bars (the life time plus 1), or 0 for no time limit.

TradeTime
The number of bars since the trade was entered (for pending trades) or opened (for open trades).

TradeBarOpen
Number of the opening bar of the trade. For pending trades, the number of the bar at which the trade was entered. Can
be set to the current bar number (Bar) for resetting the wait time of pending trades. After the trade is opened, this
number must not be changed anymore.

TradeBarClose
Number of the closing bar of the trade, or 0 if the trade is still open.

TradeContract
The contract type for options and futures, a combination of PUT, CALL, EUROPEAN, BINARY, or FUTURE.

TradeID
Trade identifier number, identical to the ticket number in the broker platform, or 0 when the trade was not yet opened.

470
Type:
int, read/only

TradeIsShort
Boolean expression. Is true when the trade was entered with enterShort.

TradeIsLong
Is true when the trade was entered with enterLong.

TradeIsContract
Is true when the trade is an option or future contract.

TradeIsPhantom
Is true when the trade was entered in phantom mode for virtual hedging or for equity curve trading.

TradeIsPool
Is true for a pool trade for virtual hedging.

TradeIsVirtual
Is true for a phantom trade for virtual hedging.

TradeIsPending
Is true when the trade was not yet opened, f.i. because it was just entered or its Entry limit was not yet met.

TradeIsOpen
Is true when the trade was opened and is not yet closed.

TradeIsClosed
Is true when the trade was closed. The TradeProfit variable contains the final result of the trade.

TradeIsNewBar
Is true in a TMF at the first tick of a new bar.

TradeIsEntry
Is true in a TMF when the position is about to be opened because its Entry limit was just hit.

TradeIsStop
Is true in a TMF when the the position is about to be closed because its Stop limit was just hit.

TradeIsProfit
Is true in a TMF when the position is about to be closed because its TakeProfit limit was just hit.

Type:
bool, read/only

TradeAlgo
The algorithm identifier of the trade. Also set to Algo during a TMF.

TradeAsset
The asset name of the trade. Also set to Asset during a TMF or a trade loop.

471
Type:
string, read/only

ThisTrade
The TRADE* pointer. All trade information can be accessed through this pointer. The TRADE struct is defined
in include\trading.h. Its members are the above trade variables, redefined to easier-to-memorize names
in include\variables.h.

Remarks:

• All trade variables listed above are only valid inside a TMF or a in trade enumeration loop. Otherwise they
require ThisTrade to be set to a valid TRADE* pointer by script. This switches all trade variables to that trade.
• Asset specific variables, such as PIP, PIPCost etc. are automatically set to the trade asset during a TMF or a trade
enumeration loop, unless you explicitely prevent this by switching to a different asset. For using algorithm specific
variables in a TMF, such as trade statistics or AlgoVar, the algo function must be called in the TMF (see example).
• Most trade variables are of type float. They are normally automatically converted to var in lite-C, exept for functions
that have no fixed variable type such as printf(). !! For printing trade variables, place a (var) before them in
the printf parameter list to convert them to var.
• The TICKS flag is often required for testing TMFs that use intrabar prices or enter / exit trades.
• TMFs can open new trades with enterLong/Short. However be careful when assigning them the same TMF: uncorrect
entry conditions can then lead to an endless loop of entering new trades.
• Entering and exiting trades by returning 1 or 2 is attempted even during the weekend or holidays when price quotes
arrive and Weekend is at 2 or below.
• Exit a trade by returning 1, rather than calling exitTrade.
• For tasks that are not related to a certain trade, using a tick function is preferable to a TMF.
• Functions that affect the program flow - like loop, optimize, etc. - can not be called in a TMF.
• Data series can not be created in a TMF, and indicators that create data series can not be called; however series and
indicator values can be evaluated through global variables or asset/algo specific variables. The current candle is
incomplete within a TMF, so its range and height is normally smaller than the other candles. The price functions return
different values because they get their open, high, and low prices from the incomplete candle.

Examples (see also trade loops):


// TMF that moves the stop level in a special way
int TrailingStopLong()
{
// adjust the stop only when the trade is in profit.
if(TradeIsOpen and TradeProfit > 0)
// place the stop at the lowest bottom of the last 3 candles
TradeStopLimit = max(TradeStopLimit,LL(3));
// plot a line to make the stop visible in the chart
plot("Stop",TradeStopLimit,MINV,BLACK);
// return 0 to let Zorro check the stop/profit limits
return 0;
}

function run()
{
set(TICKS); // normally needed for TMF
...
enterLong(TrailingStopLong);
}
// TMF that opens an opposite trade when stopped out,
// and opens a new trade when profit target is reached (Zorro 1.22 and above)
int ReverseAtStop()
{
if(TradeIsStop) { // stop loss hit?
if(TradeIsShort)
enterLong(ReverseAtStop); // enter opposite trade
else
enterShort(ReverseAtStop);
}
if(TradeIsProfit) { // profit target hit?
if(TradeIsShort)
enterShort(ReverseAtStop); // enter same trade again
else
enterLong(ReverseAtStop);

472
}
// call the TMF at stop loss / profit target only
return 16;
}

function run()
{
set(TICKS); // normally needed for TMF
Weekend = 3; // don't run TMF at weekend

Stop = 100*PIP;
TakeProfit = 100*PIP;
if(Bar == LookBack) // enter the first trade directly at the first bar
enterLong(ReverseAtStop);
}
// TMF with parameters, for a Chandelier Stop
int Chandelier(var TimePeriod,var Multiplier)
{
if(TradeIsLong)
TradeStopLimit = max(TradeStopLimit,ChandelierLong(TimePeriod,Multiplier));
else
TradeStopLimit = min(TradeStopLimit,ChandelierShort(TimePeriod,Multiplier));
return 8; // only update once per bar
}

function run()
{
...
if(LongSignal) {
Stop = ChandelierLong(22,3);
enterLong(Chandelier,22,3);
}
...
}

Trade statistics parameters


The following system variables can be used to obtain trade statistics separately per asset, algorithm, and long/short
trade direction. They can be evaluated in real time while trading, or at the end of a simulation cycle for calculating
statistics in [Test] mode. All parameters are read/only. Most come in three flavors:

...Long: Results of all long trades with the current asset and algorithm. Including phantom trades, but not
including pool trades.
...Short: Results of all short trades with the current asset and algorithm, including phantom trades, but no pool trades.
...Total: Results of all trades with all assets and algorithms, not including phantom trades.

In [Train] mode, trades always open 1 lot, and pool and phantom trades are converted to normal trades. The results are
summed up over all bar cycles. In [Test] mode only the ...Total results are summed up; the ...Long and ...Short results
are from the current bar cycle only. This allows to produce statistics distributions of bar cycles.

WinLong
WinShort
WinTotal
Sum of profits of all trades won so far. When oversampling or phantom trades are used, WinLong or WinShort can
be higher than WinTotal.

473
LossLong
LossShort
LossTotal
Sum of losses by all trades lost so far. The accumulated balance, i.e. the return of all closed trades is WinTotal -
LossTotal. WinTotal or LossTotal can be modified by script for simulating additional wins or losses that are not caused
by trades.

WinValLong
WinValShort
WinValTotal
Open profit of all currently winning trades.

LossValLong
LossValShort
LossValTotal
Open loss amount of all currently losing trades. The accumulated equity, i.e. the current profit of all open and closed
trades is WinTotal - LossTotal + WinValTotal - LossValTotal.

ProfitClosed
Realized component profit so far; WinLong-LossLong+WinShort-LossShort.

ProfitOpen
Unrealized component profit so far; WinValLong-LossValLong+WinValShort-LossValShort.

BalanceLong
BalanceShort
Sum of returns of all closed trades; WinLong-LossLong resp. WinShort-LossShort.

EquityLong
EquityShort
Sum of returns of all closed, plus value of all open trades; BalanceLong+WinValLong-
LossValLong resp. BalanceShort+WinValShort-LossValShort.

WinMaxLong
WinMaxShort
WinMaxTotal
Maximum profit of a trade so far.

LossMaxLong
LossMaxShort
LossMaxTotal
Maximum loss of a trade so far.

NumWinLong
NumWinShort
NumWinTotal
Number of profitable trades so far. The average return per winning trade is WinTotal/NumWinTotal.

474
NumLossLong
NumLossShort
NumLossTotal
Number of lost trades so far. The average return per trade is (WinTotal-LossTotal)/(NumWinTotal+NumLossTotal).

LossStreakLong
LossStreakShort
LossStreakTotal
Current number of losses in a row, or 0 if the last trade was a winner.

WinStreakLong
WinStreakShort
WinStreakTotal
Current number of wins in a row, or 0 if the last trade was lost.

LossStreakValLong
LossStreakValShort
LossStreakValTotal
Accumulated loss of the current loss streak, or 0 if the last trade was a winner.

WinStreakValLong
WinStreakValShort
WinStreakValTotal
Accumulated profit of the current win streak, or 0 if the last trade was lost.

NumWinningLong
NumWinningShort
Number of currently open winning trades with the current asset and algorithm, including phantom trades.

NumLosingLong
NumLosingShort
Number of currently open losing trades with the current asset and algorithm, including phantom trades.

NumOpenLong
NumOpenShort
Number of currently open trades with the current asset and algorithm, including phantom trades.

NumLongTotal
NumShortTotal
NumOpenTotal
NumOpenPhantom
Numbers of currently open trades with all assets and algorithms.

475
NumPendingLong
NumPendingShort
NumPendingTotal
Number of currently pending trades, i.e. trades that have just been entered, or that have not yet reached their Entry Stop
or Limit within their EntryTime period. NumPendingTotal includes pending phantom trades in Virtual Hedging mode,
as they also trigger real trades.

NumRejected
Number of rejected open or close orders in live trading, due to lack or market liquidity, broker connection failure, market
closures, holidays, or other reasons.

LotsPool
LotsPhantom
Open net lot sum of real trades and phantom trades for virtual hedging with the current asset. The net lot sum is the
difference of long lots and short lots. LotsPool is also valid when no virtual hedging is used.

ResultLong[0] .. ResultLong[19]
ResultShort[0] .. ResultShort[19]
Arrays containing the last 20 trade results as difference between entry and exit prices. The [0] element contains the
result of the most recent trade. Result/PIP gives the profit or loss of the trade in pips (disregarding the trade volume).
These arrays can be used for equity curve trading.

Type:
int for numbers that count something, otherwise var.

Remarks:

• The parameters are part of the GLOBALS struct and the STATUS structs. They are defined in include\variables.h.
• The parameters are only affected by trades opened with the current Zorro instance. Trades opened manually or with
other platforms on the same account do not affect the trade statistics parameters.
• Every algo and asset call changes the component-dependent ...Long and ...Short statistics variables. They are set
to the statistics of the selected asset and algorithm identifier. The ...Total statistics variables are unaffected
by algo and asset calls.
• If a backtest or training runs over several bar cycles, the ...Long and ...Short statistics variables are taken from the
last bar cycle, while the ...Total statistics variables are taken from the average of all bar cycles.
• Any other trade statistics can be calculated by enumerating trades with for(open_trades) or for(all_trades) and
summing up the desired value.
• Trade statistics are updated on every bar. They are reset when the strategy is restarted. For preventing this, they could
be copied into global variables or saved in a file that is loaded at start of the strategy.

Example:
// suspend trading after 4 losses in a row
if(LossStreakShort >= 4 || LossStreakLong >= 4)
Lots = -1; // phantom trades
else
Lots = 1;

476
Hacks & Tricks
In the following you'll find short code snippets for common tasks.

I. Trade Management
Change stops and profit targets of all open long trades with the current algo and asset
exitLong(0,NewStop);
exitLong(0,-NewTakeProfit);

Limit the number of open positions


// max. 3 open long positions per asset/algo
if(my_long_condition == true) {
exitShort(); // no hedging - close all short positions
if(NumOpenLong < 3) enterlong();
}

Exit all open trades Friday afternoon GMT


if(dow() == FRIDAY && hour() >= 18) {
exitLong("*");
exitShort("*");
}

Lock 80% profit of all winning trades


for(open_trades)
if(TradeIsOpen && !TradeIsPool && TradeProfit > 0) {
TradeTrailLock = 0.80; // 80% profit (minus trade costs)
if(TradeIsShort)
TradeTrailLimit = max(TradeTrailLimit,TradePriceClose);
else
TradeTrailLimit = min(TradeTrailLimit,TradePriceClose);
}

Iceberg trade: enter 10 long trades, one every 30 seconds


for(EntryDelay = 0; EntryDelay < 10*30; EntryDelay += 30)
enterLong();

Calculate the value of all open trades with the current asset
var valOpen()
{
string CurrentAsset = Asset; // Asset is changed in the for loop
var val = 0;
for(open_trades)
if(strstr(Asset,CurrentAsset) && TradeIsOpen)
val += TradeProfit;
return val;
}

Monitoring and modifying a certain trade


...
TRADE* MyTrade = enterlong();
...
ThisTrade = MyTrade; // connect trade variables to MyTrade
var MyResult = TradeProfit; // evaluate trade variables
...
exitTrade(MyTrade,0,TradeLots/2); // exit half the trade

Basket trading (creating an artificial asset from a 'basket' of real assets)


// generate a snythetic asset "USD" combined from the USD value of EUR, GBP, and AUD
var priceUSD()
{
var p = 0;
asset("GBP/USD"); p += price();
asset("AUD/USD"); p += price();
asset("EUR/USD"); p += price();
return p;
}

477
// basket trade function with stop limit
int tradeUSD(var StopUSD)
{
if((TradeIsLong && priceUSD() <= StopUSD)
or (TradeIsShort && priceUSD() >= StopUSD))
return 1; // exit the trade
else return 0; // continue the trade
}

// open a trade with the synthetic asset and a stop loss


void enterLongUSD(var StopDistUSD)
{
var StopUSD = priceUSD()-StopDistUSD;
asset("GBP/USD"); enterLong(tradeUSD,StopUSD);
asset("AUD/USD"); enterLong(tradeUSD,StopUSD);
asset("EUR/USD"); enterLong(tradeUSD,StopUSD);
}

void enterShortUSD(var StopDistUSD)


{
var StopUSD = priceUSD()+StopDistUSD;
asset("GBP/USD"); enterShort(tradeUSD,StopUSD);
asset("AUD/USD"); enterShort(tradeUSD,StopUSD);
asset("EUR/USD"); enterShort(tradeUSD,StopUSD);
}

// plot a price curve of the synthetic asset


// (the plot command is linked to the last used asset -
// so "EUR/USD" must be selected in the scrollbox)
function run()
{
set(PLOTNOW);
plot("USD",priceUSD(),0,RED);
}

II. Indicators & Signals


Generate an indicator with a different asset, time frame, and shift
//extended ATR function with individual asset and timeframe (in minutes)
var extATR(string symbol,int period,int length,int shift)
{
ASSET* previous = g->asset; // store previous asset
if(symbol) asset(symbol); // set new asset
if(period) TimeFrame = period/BarPeriod;
// create price series with the new asset / timeframe
vars H = series(priceHigh()),
L = series(priceLow()),
O = series(priceOpen()),
C = series(priceClose());
TimeFrame = 1; // set timeframe back
g->asset = previous; // set asset back
return ATR(O+shift,H+shift,L+shift,C+shift,length);
}

Calculate the weekend price change for gap trading


// use 1-hour bars, wait until Sunday Sunday 5pm ET,
// then get the price change from Friday 5pm ET
if(dow() == SUNDAY && lhour(ET) == 5) {
int FridayBar = timeOffset(ET,SUNDAY-FRIDAY,5,0);
var PriceChange = priceClose(0) - priceClose(FridayBar);
...
}

Use a series to check if something happened within the last n bars


// buy if Signal1 crossed over Signal2 within the last 7 bars
...
vars crosses = series(0); // generate a series and set it to 0
if(crossOver(Signal1,Signal2)
crosses[0] = 1; // store the crossover in the series
if(Sum(crosses,7) > 0) // any crossover within last 7 bars?
enterLong();
...

Use a loop to check if something happened within the last n bars


478
// buy if Signal1 crossed over Signal2 within the last 7 bars
...
int i;
for(i = 0; i < 7; i++)
if(crossOver(Signal1+i,Signal2+i)) { // crossover, i bars ago?
enterLong();
break; // abort the loop
}
...

Align a time frame to a certain event


// Let time frame start when Event == true
// f.i. frameAlign(hour() == 0); aligns to midnight
function frameAlign(BOOL Event)
{
TimeFrame = 1;
vars Num = series(0); // use a series for storing Num between calls
Num[0] = Num[1]+1; // count Num up once per bar
if(!Event)
TimeFrame = 0; // continue current time frame
else {
TimeFrame = -Num[0]; // start a new time frame
Num[0] = 0; // reset the counter
}
}

Shift a series into the future


// the future is unknown, therefore fill
// all unknown elements with the current value
vars seriesShift(vars Data,int shift)
{
if(shift >= 0) // shift series into the past
return Data+shift;
else { // shift series into the future
int i;
for(i = 1; i <= shift; i++)
Data[i] = Data[0];
return Data;
}
}

Use a function from an external DLL


// Use the function "foo" from the DLL "bar.dll"
// Copy bar.dll into the Zorro folder
int __stdcall foo(double v1,double v2); // foo prototype
API(foo,bar) // use foo from bar.dll

function run()
{
...
int result = foo(1,2);
...
}

Equity curve trading (skipping trades dependent on strategy success)


// don't trade when the equity curve goes down
// and is below its own lowpass filtered value
function checkEquity()
{
// generate equity curve including phantom trades
vars EquityCurve = series(EquityLong+EquityShort);
vars EquityLP = series(LowPass(EquityCurve,10));
if(EquityLP[0] < LowPass(EquityLP,100) && falling(EquityLP))
Lots = -1; // drawdown -> phantom trades
else
Lots = 1; // profitable -> normal trades
}

III. Auxiliary
Debugging a script
// Display a message box with the variables to be observed
// Click [Yes] for a single step, [No] for closing the box

479
static int debug = 1;
if(debug && Bar > LookBack)
debug = msg(
"High = %.5f, Low = %.5f",
priceHigh(), priceLow());

Find out if you have a standard, mini, or micro lot account


// Click [Trade] with the script below
function run()
{
asset("EUR/USD");
if(Bar > 0) {
if(LotAmount > 99999)
printf("\nI have a standard lot account!");
else if(LotAmount > 9999)
printf("\nI have a mini lot account!");
else
printf("\nI have a micro lot account!");
quit();
}
}

Download historic price data


// Click [Test] for downloading/updating the latest "NZD/USD" price data
// This extends the length of the historical price data file, therefore
// WFO strategies using that asset should be trained again afterwards
function run()
{
NumYears = 6; // download up to 6 years data
loadHistory("NZD/USD",1); // update the price history
}

Export historic price data to a .csv file


// Click [Test] for exporting price data to a .csv file in the Data folder
// The records are stored in the format: time, open, high, low, close
// f.i. "31/12/12 00:00, 1.32205, 1.32341, 1.32157, 1.32278"
// Dependent on the locale, date and numbers might require a different format
function run()
{
BarPeriod = 1440;
StartDate = 2008;
EndDate = 2012;
LookBack = 0;

string line = strf(


"%02i/%02i/%02i %02i:%02i, %.5f, %.5f, %.5f, %.5f\n",
day(),month(),year()%100,hour(),minute(),
priceOpen(),priceHigh(),priceLow(),priceClose());
if(is(INITRUN))
file_delete("Data\\export.csv");
else
file_append("Data\\export.csv",line);
}

Print the description of a trade (like "[AUD/USD:CY:S1234]") in the log


void printTradeID()
{
string ls = "L", bo = "[", bc = "]";
if(TradeIsShort) ls = "S";
if(TradeIsPhantom) { bo = "{"; bc = "}"; }
printf("#\n%s%s:%s:%s%04i%s ",
bo,TradeAsset,TradeAlgo,ls,TradeID%10000,bc);
}

Plot equity curves of single assets in a multi-asset strategy


char name[40]; // string of maximal 39 characters
strcpy(name,Asset);
strcat(name,":");
strcat(name,Algo);
var equity = EquityShort+EquityLong;
if(equity != 0) plot(name,equity,NEW|AVG,BLUE);

Set up strategy parameters from a .ini file at the start

480
function run()
{
static var Parameter1 = 0, Parameter2 = 0;
if(is(INITRUN)) { // read the parameters only in the first run
string setup = file_content("Strategy\\mysetup.ini");
Parameter1 = strvar(setup,"Parameter1");
Parameter2 = strvar(setup,"Parameter2");
}
}

// mysetup.ini is a plain text file that contains


// the parameter values in a format like this:
Parameter1 = 123
Parameter2 = 456

Check every minute in [Trade] mode if an .ini file was modified


var Parameter1 = 0, Parameter2 = 0;

function tock() // run once per minute


{
static int LastDate = 0;
if(LastDate && !Trade) return; // already updated
int NewDate = file_date("Strategy\\mysetup.ini");
if(LastDate < NewDate) {
LastDate = NewDate; // file was modified: update new parameters
string setup = file_content("Strategy\\mysetup.ini");
Parameter1 = strvar(setup,"Parameter1");
Parameter2 = strvar(setup,"Parameter2");
}
}

IV. Get Rich Quick


90% win rate
function run()
{
Stop = 10*ATR(100);
TakeProfit = 1*ATR(100);
// open new trade at random after last trade hit its target
if(NumOpenTotal == 0) {
if(random() < 0)
enterShort();
else
enterLong();
}
}

Martingale
function run()
{
BarPeriod = 1440;
// set up equal stop and profit limits
Stop = TakeProfit = ATR(100);
// double the stake after every loss
Lots = pow(2,LossStreakTotal); // winning guaranteed...
// open new trade at random after last trade hit its target
if(NumOpenTotal == 0) {
if(random() < 0)
enterShort();
else
enterLong();
}
}

Grid Trading
// helper function for finding trades at a grid line
bool findTrade(var Price,var Grid,bool IsShort)
{
for(open_trades)
if((TradeIsShort == IsShort)
and between(TradeEntryLimit,Price-Grid/2,Price+Grid/2))
return true;
return false;
}

481
function run()
{
BarPeriod = 1440;
Hedge = 2;
EntryTime = ExitTime = 500;
var Price;
var Grid = 100*PIP; // grid spacing
var Current = priceClose();
// place pending trades at 5 grid lines
// above and below the current price
for(Price = 0; Price < Current+5*Grid; Price += Grid) {
if(Price < Current-5*Grid)
continue;
if(Price < Current and !findTrade(Price,Grid,true))
enterShort(0,Price,0,Grid);
else if(Price > Current and !findTrade(Price,Grid,false))
enterLong(0,Price,0,Grid);
}
}

Script conversion - TradeStation, MultiCharts, MetaTrader,


NinjaTrader, Neuroshell, R...
When starting with Zorro, you often have used another trade platform or computing environments before, and would like
to take over your familiar strategies, indicators, and algorithms. The examples below show how to convert the code of
different platforms.

TradeStation™ and MultiCharts™

TradeStation was the first platform that supported automated trading. Its EasyLanguage™, also used by the
MultiCharts platform, has a similar design philosophy as Zorro's lite-C. Although its syntax is a C/Pascal mix and requires
some code modifications, conversion to C is relatively straightforward. EasyLanguage Vars are equivalent to lite-C data
series. However, EasyLanguage makes no distinction between series and variables - f.i. myseries is the same
as myseries[0]. In most cases, Vars is only a single variable (var), but sometimes it's a series (vars), as movAvgVal in
the second example below. Easylanguage also stores the content of variables - if this is required, declare a static var in
lite-C. Trigonometric functions expect angles in degrees, while in lite-C and most other languages angles are in radians.
EasyLanguage has no native functions, but separate scripts can be called like functions. Aside from that, EasyLanguage
strategies and lite-C strategies look very similar.

{ Easylanguage version }
{ Choppy Market Index Function by George Pruitt }
Inputs: periodLength(Numeric);
Vars: num(0),denom(1);

if(periodLength <> 0) then


begin
denom = Highest(High,periodLength) – Lowest(Low,periodLength);
num = Close[periodLength-1] – Close;
ChoppyMarketIndex = 0.0;
if(denom <> 0) then
ChoppyMarketIndex = AbsValue(num/demon)*100;
end;
// lite-C version
// Choppy Market Index Function by George Pruitt
var ChoppyMarketIndex(int periodLength)
{
if(periodLength != 0) {
var denom = HH(periodLength) – LL(periodLength);
var num = priceClose(periodLength-1) – priceClose();
if(denom != 0)
return abs(num/denom)*100;
}
return 0;
}
{ Easylanguage version }
482
{ enter a trade when the RSI12 crosses over 75 or under 25 }
Inputs:
Price(Close),LengthLE(12),LengthSE(12),
OverSold(25),OverBought(75),StoplossPips(200),ProfitTargetPips(200);

variables:
var0(0),var1(0);

{ get the RSI series }


var0 = RSI( Price, LengthLE );
var1 = RSI( Price, LengthSE );

{ if rsi crosses over buy level, exit short and enter long }
condition1 = Currentbar > 1 and var0 crosses over OverBought ;
if condition1 then
Buy( "RsiLE" ) next bar at market;

{ if rsi crosses below sell level, exit long and enter short }
condition2 = Currentbar > 1 and var1 crosses under OverSold ;
if condition2 then
Sell Short( "RsiSE" ) next bar at market;

{ set up stop / profit levels }


SetStoploss(StoplossPips);
SetProfitTarget(ProfitTargetPips);
// Zorro version
// enter a trade when the RSI12 crosses over 75 or under 25
function run()
{
// get the RSI series
vars Close = series(priceClose());
vars rsi12 = series(RSI(Close,12));

// set up stop / profit levels


Stop = 200*PIP;
TakeProfit = 200*PIP;

// if rsi crosses over buy level, exit short and enter long
if(crossOver(rsi12,75))
reverseLong(1);
// if rsi crosses below sell level, exit long and enter short
if(crossUnder(rsi12,25))
reverseShort(1);
}

MetaTrader4™ (MT4)

MT4 is the most popular platform for private traders and supported by most brokers. The MQL4™ script language of its
"Expert Advisors" (EAs) is based on C, which would theoretically allow easy conversion to Zorro's lite-C. Unfortunately,
MT4 has many issues that make "Expert Advisors" a lot more complex and difficult to handle than scripts of other
platforms.
The MQL4 main script runs at every tick; therefore the last price candle is normally incomplete and affects indicators
in a different way than the other candles. Trades are not managed by the platform, but must be managed by code in the
script. Series are not natively supported, but emulated with loops and functions. Indicators often produce different results
in MT4 than in other platforms because the MT4 standard indicators do not use their standard algorithms, but special
MT4 variants. Time zones and account parameters are not normalized, so EAs must be individually adapted to the
broker. To complicate matters further, MT4 does not use common trade units such as lots and pips, but calculates with
"standard lots" and "points" that need to be multiplied with account-dependent conversion factors. Most code in an EA
is therefore not used for the trade algorithm, but for working around all those problems. This results in the long and
complex 'spaghetti code' that is typical for EAs.
For conversion, first remove the MQL4 specific code that is not needed in lite-C, such as trade management loops,
broker dependent pip and trade size calculations, and array loops that emulate series. Then the rest can be converted
by replacing the MQL4 indicators and trade commands by their lite-C equivalents. Note that the result can still differ due
to the effects of incomplete candles and different indicator algorithms.

// MT4 version
// enter a trade when the RSI12 crosses over 75 or under 25
int start()
{
// get the previous and current RSI values
double current_rsi = iRSI(Symbol(), Period(), 12,
PRICE_CLOSE, 1); // mind the '1' - candle '0' is incomplete!!

483
double previous_rsi = iRSI(Symbol(), Period(), 12, PRICE_CLOSE, 2);

// set up stop / profit levels


double stop = 200*Point;
double takeprofit = 200*Point;

// correction for prices with 3, 5, or 6 digits


int digits = MarketInfo(Symbol(), MODE_DIGITS);
if (digits == 5 || digits == 3) {
stop *= 10;
takeprofit *= 10;
} else
if (digits == 6) {
stop *= 100;
takeprofit *= 100;
}

// find the number of trades


int num_long_trades = 0;
int num_short_trades = 0;
int magic_number = 12345;

// exit all trades in opposite direction


for(int i = 0; i < OrdersTotal(); i++)
{
// use OrderSelect to get the info for each trade
if(!OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
continue;
// Trades not belonging to our EA are also found, so it's necessary to
// compare the EA magic_number with the order's magic number
if(magic_number != OrderMagicNumber())
continue;

if(OrderType() == OP_BUY) {
// if rsi crosses below sell level, exit long trades
if((current_rsi < 25.0) && (previous_rsi >= 25.0))
OrderClose(OrderTicket(), OrderLots(),
Bid, 3, Green);
else
// otherwise count the trades
num_long_trades++;
}

if(OrderType() == OP_SELL) {
// if rsi crosses over buy level, exit short trades
if((current_rsi > 75.0) && (previous_rsi <= 75.0))
OrderClose(OrderTicket(), OrderLots(),
Ask, 3, Green);
else
// otherwise count the trades
num_short_trades++;
}
}

// if rsi crosses over buy level, enter long


if((current_rsi > 75.0) && (previous_rsi <= 75.0)
&& (num_long_trades == 0)) {
OrderSend(Symbol(), OP_BUY,
1.0, Ask, 3,
Ask-stop, Bid+takeprofit,
"", magic_number,
0, Green);
}
// if rsi crosses below sell level, enter short
if((current_rsi < 25.0) && (previous_rsi >= 25.0)
&& (num_short_trades == 0)) {
OrderSend(Symbol(), OP_SELL,
1.0, Bid, 3,
Bid+stop, Ask-takeprofit,
"", magic_number,
0, Green);
}

return(0);
}
// Zorro version
// enter a trade when the RSI12 crosses over 75 or under 25
function run()
{
// get the RSI series
vars Close = series(priceClose());
484
vars rsi12 = series(RSI(Close,12));

// set up stop / profit levels


Stop = 200*PIP;
TakeProfit = 200*PIP;

// if rsi crosses over buy level, exit short and enter long
if(crossOver(rsi12,75))
reverseLong(1);
// if rsi crosses below sell level, exit long and enter short
if(crossUnder(rsi12,25))
reverseShort(1);
}
Under Tips & Tricks you can find an example how to emulate the MQL4-style indicator parameters with Zorro.

NinjaTrader™

NinjaScript™ is based on C# and thus similar in syntax to Zorro's lite-C. NinjaScript also supports data series in the
same way as lite-C, and its basic function list is very similar; this makes script migration rather easy. One major
difference is that all NinjaTrader indicator functions return data series, while Zorro indicators return single values. Use
the series function (f.i. series(indicator(..))) for making Zorro indicators also return series.

// NinjaTrader version
// Trade when a fast SMA crosses over a slow SMA
protected override void Initialize()
{
// Run OnBarUpdate on the close of each bar
CalculateOnBarClose = true;

// Set stop loss and profit target at $5 and $10


SetStopLoss(CalculationMode.Ticks,5);
SetProfitTarget(CalculationMode.Ticks,10);
}

protected override void OnBarUpdate()


{
// don't trade during the LookBack period
if(CurrentBar < 20)
return;

double Fast = 10;


double Slow = 20;

// Exit short and go long if 10 SMA crosses over 20 SMA


if(CrossAbove(SMA(Close,Fast),SMA(Close,Slow),1)) {
ExitShort();
EnterLong();
}
// Exit long and go short if 10 SMA crosses under 20 SMA
else if(CrossBelow(SMA(Close,Fast),SMA(Close,Slow),1)) {
ExitLong();
EnterShort();
}
}
// Zorro version
// Trade when a fast SMA crosses over a slow SMA
void run()
{
// Set stop loss and profit target at $5 and $10
Stop = 5;
TakeProfit = 10;

vars Close = series(priceClose());


vars SMAFast = series(SMA(Close,10));
vars SMASlow = series(SMA(Close,20));

// Exit short and go long if 10 SMA crosses over 20 SMA


if(CrossOver(SMAFast,SMASlow))
enterLong();
// Exit long and go short if 10 SMA crosses under 20 SMA
else if(CrossUnder(SMAFast,SMASlow))
enterShort();
}

485
Neuroshell Trader™ and other DLL based trading platforms

Neuroshell Trader™ is a platform specialized in employing artificial intelligence for automated trading. Neuroshell
indicators are functions added through DLLs. They take an input array, an output array, the array size, and additional
parameters. Many other trade platforms use similar DLL based indicators. Such indicators are often based on normal
C, thus conversion to Zorro is very easy - especially when you don't have to convert it at all and can call the DLL function
directly.
When using an indicator made for a different platform, the array order convention must be take care of. Neuroshell
stores time series in ascending order (contrary to most other platforms that store them in reverse order) and passes the
end of the array, not its begin, to the indicator function. Neuroshell indicators normally return an output series instead of
a single value. Below both methods of indicator conversion are shown.

// Neuroshell version - Entropy indicator


// published by ForeTrade Technologies (www.foretrade.com/entropy.htm)
#include "math.h"
#include "stdlib.h"

__declspec(dllexport) void Entropy (double *price, double *entropy, long int size, long int numbars)
{
double *in, *out, P, G;
long int i,j;
double sumx = 0.0;
double sumx2 = 0.0;
double avgx = 0.0;
double rmsx = 0.0;

in=price;
out=entropy;

for (i=0; i<size; i++)


{
if (i < numbars+1) *out = 3.4e38;
else
{
sumx = sumx2 = avgx = rmsx = 0.0;
for (j=0;j<numbars+1;j++)
{
sumx += log(*(in-j) / *(in-j-1)) ;
sumx2 += log(*(in-j) / *(in-j-1)) * log(*(in-j) / *(in-j-1));
}
if (numbars==0)
{
avgx = *in;
rmsx = 0.0;
}
else
{
avgx = sumx / numbars;
rmsx = sqrt(sumx2/numbars);
}
P = ((avgx/rmsx)+1)/2.0;
G = P * log(1+rmsx) + (1-P) * log(1-rmsx);
*out=G;
}
in++; out++;
}
}
// Zorro version - Entropy indicator
// Method 1 - calling the DLL function directly
// Copy the Entropy DLL into the Zorro folder
int __stdcall Entropy(double *price, double *entropy, long size, long numbars); // function prototype
API(Entropy,entropy) // use the Entropy function from entropy.dll

var EntropyZ(vars Data,int Period)


{
Period = clamp(Period,1,LookBack-1); // prevent exceeding the Data size
double E; // single element "array" for receiving the output value
Entropy(
Data+Period+1, // end of the array (element order does not matter here)
&E, 1, // pointer to the output "array" with size 1
Period);
return E;
}
// Zorro version - Entropy indicator
// Method 2 - converting the DLL code to lite-C
var EntropyZ(vars Data,int Period)
{
Period = clamp(Period,1,LookBack-1); // prevent exceeding the Data size
486
var sumx = 0., sumx2 = 0.;
int j;
for (j=0; j<Period; j++) {
sumx += log(Data[j]/Data[j+1]);
sumx2 += log(Data[j]/Data[j+1]) * log(Data[j]/Data[j+1]);
}
var avgx = sumx/Period;
var rmsx = sqrt(sumx2/Period);
var P = ((avgx/rmsx)+1)/2.;
return P * log(1+rmsx) + (1-P) * log(1-rmsx);
}

R is an interactive script language and an open source environment for statistical computations and charting, developed
in 1996 by New Zealand scientists Ross Ihaka and Robert Gentleman of Oakland University. It is meanwhile the global
standard in statistical computations and has a large set of packages for all advanced fields of statistics, machine learning
and data mining.

Although it is possible, it normally makes no sense to convert complex R calculations to lite-C scripts, especially when
R 'packages' are used. An easier way is available through the R bridge for directly executing R functions in lite-C.

MatLab™

MatLab™ is a commercial computing environment and interactive programming language by MathWorks, Inc. Other
than R, it is not specialized on data analysis and machine learning, but allows symbolic and numerical computing in all
fields of mathematics. Its integrated compiler makes it relatively easy to convert a MatLab trading algorithm to a C/C++
DLL that can then be used with Zorro.

487
Troubleshooting
Sooner or later - usually, more often than not - programmers encounter problems with their scripts. The code does not
do what it should, the compiler gives an error message, or the script even crashes. Most error messages occur during
compiling the script file and indicate simple syntax errors. Something is mistyped, or a bracket or semicolon is missing,
or an object got the same name as a pre-defined function or variable. The script file and line in question is given in the
error message. It is normally easy to find and fix the mistake (all pre-defined variable names can be found in
the include\variables.h file).The answers to many other typical issues of script development can be found in
the FAQ list.

Other errors cannot be detected by the compiler, but produce a message at runtime. Under error messages you'll find
a list of all such errors and warnings, and their likely reasons. Messages related to opening and closing positions are
listed under trading; issues with brokers are commented on the related page (FXCM, IB, Oanda, MT4, ...) in this
manual.

It gets more difficult when no error messages are displayed. Below you can find the usual methods for solving those
problems. If you're not able to find the reason of the problem yourself, you have two options. Either subscribe a support
ticket on the Download Page and ask us for help. We do not fix your script, but we can help you to fix it yourself, or with
any other problems or questions that you might have with specific Zorro functions. Alternatively, post a question in the
User Forum and ask other users for help. In both cases you'll need to upload your script so that they can look into it.

Bugs

A challenge is a script that does not produce an error message, but just behaves wrong. It trades too often, or not often
enough, or at the wrong time. Debugging strategy scripts is a special case of program debugging since you're normally
not interested in the command-by-command, but in the bar-by-bar behavior. You have to look into parts of the script
that you suspect of wrong behavior, and eliminate any possibilities of a bug one after the other. There are several
methods to find bugs:

• Set the LOGFILE flag, and set Verbose to a high value, like 3 or 7. Use printf statements to print variables and other
information to the log file. Run a test, open the .log file and examine the trades in detail.

• Plot the variables in question in a curve of the whole simulation. For instance, in order to check the overall behavior of
a variable TradeSignal that determines when a trade is triggered, put this line in the script:

plot("TradeSignal", TradeSignal, NEW, RED);

In most cases the reason of the wrong behavior can be seen at once in such a plot.

• For observing the bar-to-bar behavior of suspicious variables, set the STEPWISE flag for single-step debugging, and
display the variables or indicators in one or several watch statements, like this:

watch("HL:", priceHigh(), priceLow(), MyVar1, MyVar2, MyVar3);

You can observe the change of any variable or the return value of any function this way. Debug into loops
with watch("!...",...) statements.

Crashes

Faulty scripts or other problems can cause three types of crashes:

• Zorro terminates with a Windows error message (like "Runtime Error"). This is normally a crash in an external module,
such as the broker API or some system component, caused by a software bug, a virus, a bad PC configuration, a faulty
Windows library, or even a PC hardware problem. It can also be a consequence of a script crash.

• Zorro stops the script with a message like "Crash in (function name)". The most popular mistakes that lead to a
crash in a script are: confusing var and vars; using a pointer (such as ThisTrade) when it is zero; dividing by zero or
another invalid mathematical operation; using an uninitialized variable or pointer; exceeding the length of a series or
of an array; exceeding the stack size by using too large local arrays; or running out of memory by requiring too much
historical data. After a script crash, Zorro should be restarted.

488
• Zorro freezes permanently and must be manually closed. This is usually caused by a non-terminating loop in the script,
or some endless recursion such as a trade that itself opens further trades in its TMF.

Crashes are normally easy to fix when they happen regularly or always at the same bar or script position. But they can
be relatively hard to find when they happen irregularly and their reason is not immediately obvious. Zorro has a
mechanism for detecting hard-to-find problems - the diagnostics mode. Here's a step by step instruction of how to use
it:

• Set Verbose to 15 or 31 in the script. If you have no access to the script, alternatively start Zorro with the -
diag command line option.

• Let Zorro run with the script in question until it crashes again. You'll now see a file ending with "..diag.txt" in
the Log folder. This file is a 'black box' recorder containing the last 1000 internal events or printf or watch commands.
The last line in the file is the last event immediately before the crash.

• If the crash happened at an unknown place in your script, place watch("#...") commands in the script for finding the
exact location (such as watch("#1");, watch("#2"); and so on). You can then see in the diag.txt file after which
command the crash happened. 90% of all crashes in beginner's scripts are due to confusing var and vars. Other
possible crash reasons are taking the square root of a negative number, dividing by zero, or exceeding the length of
an array or buffer.

• An alternative method to find a crash position in a script is out-commenting parts of the script until it does not crash
anymore. The crash reason can then usually be found in the last outcommented lines.

If you can't find the crash reason with the help of the diagnostics mode, or if you run into other trouble that you can't
solve on your own, please report on the Zorro forum and post the content of the diag.txt file. This will allow other people
to help you determining and fixing the problem.

Support - Frequently Asked Questions


When you're a Zorro Sponsor, you are entitled to free technical support by email. Otherwise you can post your question
on the user forum, or subscribe a support ticket in the web store. For technical support please email your question
tosupport (at) opgroup.de, and give your support ticket number or your user name that is displayed in Zorro's startup
message.

General questions about financial trading platforms and Zorro features are answered on the FAQ page on the Zorro
website. The most frequent technical questions are listed below.

Strategy development

Q. I have the idea of a complex strategy, but don't know how to start.
A. Start small. Make first a very simple version and test it thorough and carefully. Afterwards add the more complex
stuff, such as filtering, trade management functions, or special indicators, and test again at any step. For finding ideas
and methods, read through the literature in the book list.

Q. How can I add another currency pair so that I can trade it and select it with the Asset scrollbox?
A. Open the History\AssetsFix.csv spreadsheet with Excel (or SED) and add a new line with the parameters of the
new asset. The details are described in the manual under Asset List. For backtesting that asset you'll also need to
download its price history with the Download script.

Q. I got an MT4 expert advisor and want to use it with Zorro.


A. Read here how to convert MT4 indicators or "expert advisors" to Zorro scripts.

489
Q. I have the C source code of an indicator and want to convert it to lite-C.
A. C code will normally run unchanged, but you need to remove its framework, such as main() functions, DLL export
declarations, or C library headers. You can find an example here. Read here about the differences between standard
C code and lite-C code.

Q. Sometimes I edit a strategy, hit [Save] and re-test. But the changes I made are not reflected in the test. Sometime I
can't even find the .log file or the .csv trade history in the Log folder. What happened to the changed files?
A. Your changes are possibly directed by the Windows User Access Control (UAC) to a different folder on your PC.
Make sure to look in the right folder and edit the right script - or otherwise turn off the UAC. Read more about this here.

Q. I'm using global variables in my script. When I run it the second time, it behaves differently than the first time.
A. Static and global variables keep their content until the script is modified or compiled. If necessary, set them to their
initial values on script start, like this: if(is(INITRUN)) MyStaticVar = 0;. Read more about local, global, and static
variables here.

Q. How can I find out if the last trade was a winner or a loser?
A. If LossStreakTotal is > 0, the last trade was lost.

Q. How do I place two pending orders so that the order that's opened first cancels the other one?
A. Place the first pending order, and check in its TMF if the entry condition of the second order is fulfilled. In that case
enter the second order at market, and close the first while it's still pending.

Q. How can I count the number of winning trades within the last N trades?
A. Use a for(all_trades) loop, and count the number of closed trades. As soon as the number is above the total number
of closed trades (NumWinTotal+NumLossTotal) minus N, start counting the number of winning trades.

Q. How can I place two different profit targets so that 50% of the trade closes when the first one is hit, and the rest on
the second?
A. The best and simplest solution is opening two trades with two different profit targets, each with half the lot volume. If
your broker does not allow opening several trades at the same time, use EntryDelay for delaying the second trade a
few seconds.

Q. How can I run a function on every price tick of a certain asset?


A. Use a tick function. It will be executed every time when a new price quote arrives. Note that the tick execution
frequency will be different in real trading and in the backtest, due to the fewer price ticks in M1 price data. If this is an
issue for the particular strategy, use t1 price data.

Q. How can I skip all bars that fall outside market hours?
A. Bars are added whenever price quotes of the traded assets arrive from the broker API, regardless of the market hour.
For skipping them, use the TimeFrame mechanism. Set TimeFrame to 0 when the market closes, and to the negative
number of skipped bars when it opens again.

Q. How can I store variables so that a new trading session automatically starts with their their last values?
A. Use AlgoVar or TradeVar variables. If you have to store a lot more data, save and load them with file functions.

Q. How can I evaluate prices of indices that are not provided by my broker - for instance the VIX?
A. Use a http function (for accessing data from a website) or the dataDownload function and retrieve them f.i. with the
Yahoo™ finance or Quandl™ web service. Yahoo and Quandl provide historical prices as well as current prices for most
existing assets and indices, so you can use them for live trading as well as in the backtest.

Q. How can I compile my script to an executable for avoiding to have its source code on a server?
A. With the EXE flag: Put a set(EXE); command in your script, then click [Test] or [Train]. Your script will be compiled
to a machine code .x file from which the source code can not be retrieved anymore.

Q. I have an incredibly profitable system, but my script does not work. I don't know what's wrong - how can I fix it?
A. Debug the script in stepwise mode with watch statements in the code that display the content of variables at every
step of your strategy. Look for advices under Troubleshooting.

Q. My script does not still work. Here is it - please fix it!


A. Zorro Support will gladly answer questions, help with technical issues, and suggest solutions. But we can not write

490
scripts or fix bugs - in fact we're not permitted to write even a single line of code in your script. If you need help writing
or fixing your script, please contact oP group and ask them for a quote. They'll code for you - it's their job.

Live trading

Q. Can I copy the Zorro folder on a USB drive and run Zorro from there?
A. Yes. Make sure that you have full access rights to that drive.

Q. I'm getting error messages when I connect to IB / Oanda / FXCM / MT4.


A. Read the manual page about IB / Oanda / FXCM / MT4 for learning how to connect to your broker and which assets
are available

Q. I want to trade with the MT4 bridge, but MT4 has no Zorro EA.
A. You must install the MT4 bridge as described here. Please also read the remarks that cover all known issues with
MT4 bridges. If you are not familiar with installing files and folders on a PC, ask someone for assistance. oP group offers
an MT4 installation service on the Zorro download page.

Q. My system does not close trades in live trading. It works in the backtest.
A. Find out whether you live in the US. US citizens are not allowed to directly close trades (no joke!); you must trade
in NFA compliant mode.

Q. Where is the live chart? How can I visualize the price curve and the trade entries and exits in real time?
A. For live charts use your broker's trading platform. Zorro is purely for automated trading and does not display live
charts. The equity curve on the trade status page is only updated once per day.

Q. How can I change the sounds for entering, winning, or losing trades?
A. In your Zorro folder you'll find sound files with names like "win.wav", "loss.wav", or "open.wav". Replace them with
your own sounds in .wav format.

Q. I run the same strategy with the same broker on two different VPS. Both versions open different trades although they
should be identical.
A. With the same broker and account you'll normally get rather similar trades. But they can not be identical, especially
not with Forex or CFDs since any trade entry and exit is based on a individual price quote. Differences in latency can
also contribute to different trade entries and exits.

Q. I'm a Zorro S owner and use two Zorros for trading two strategies on the same real money account. In the moment
when the second Zorro logs in to the account, the first one logs off and the server indicator gets red.
A. Your account is limited to a single session by your broker. Contact your broker and ask for activation of multiple
sessions for your account.

Q. What happens when a trade can't be executed due to a requote or lack of liquidity?
A. Requotes happen with dealing-desk brokers only, lack of liquidity can also happen with no-dealing-desk brokers. In
both cases the trade is rejected and enterLong / enterShort return 0. Your script can then enter the trade again, which
is equivalent to accepting the requote. Zorro's Z systems don't reenter trades in such a situation.

Q. Sometimes my system opens trades that do not appear in the broker platform.
A. That are phantom trades, used for equity curve trading or for virtual hedging. You can distinguish them in the log
from real trades by their winged brackets {..} as opposed to the rectangular brackets [..] of real trades.

Q. Sometimes I see "Can't open" or "Can't close" error messages in the Zorro window, followed by a message like
"Broker: There is no tradeable price".
A. Your script tried to enter or exit a trade, but the broker's price providers did not offer a price at this point, due to a
closed market or lack of liquidity. In the case of closing a trade, Zorro will repeatedly send the close command until a
price is available and the trade can be closed. The meaning of all error messages is listed here.

Q. I trade with FXCM, but the equity displayed by Zorro differs from the equity displayed in the Trading Station.
A. This is a known bug of the FXCM trading API. It does not matter for the strategy, but you can let Zorro calculate the
equity instead by using the BrokerPatch setting in the Z.ini file.

491
Error messages
Below you'll find a list of possible errors that can occur during compilation or at runtime, together with suggestions for
finding the reason of the error. Messages related to opening and closing positions are listed under trading; issues with
brokers are commented on the related page (FXCM, IB, Oanda, MT4, ...) in this manual.

Errors during compiling are easy to find, as they are just caused by wrong syntax in the script. You've made a typing
mistake, forgot a bracket or semicolon, or referred to a non existing variable or object. The script file and line in question
is given in the error message:

Error in line ...

Wrong or missing token in the script code. Sometimes syntax errors are not obvious - the line in question is ok, but
some command in the preceding code was incomplete. Examples are an #ifdef without an #endif, or a missing
semicolon in the previous line, or an orphaned { or } bracket in the previous function. Another not-obvious error is
declaring a variable or function with the same name as a predefined system variable, such as Stop or Lots.

Wrong type ...

Wrong variable type. You've used a numeric operation with wrong parameter types, for instance
an AND or OR operation with a float or var type. Or you've called a function with a var although it needs a series, or
vice versa. Example: "Wrong type POINTER::DOUBLE" means that the code expects a double (or var) and you gave
it a pointer (or series) instead.

Pointer expected ...

You've called a function with a wrong parameter type, for instance with a var when a series or an array is needed.

Runtime errors
Those errors occur when the script is running, either in the message window, or in a separate message box. Some
messages are not harmful and can be ignored, others require a decision about whether to continue or to stop the script.

Error 010: Invalid value


A trade was entered with a Stop, TakeProfit, Trail, or Margin value that's unusually high, or low, or at the wrong side
of the current price. The trade is not executed. If it happens during live trading, an alert box will pop up dependent
on Verbose settings.

Error 011: xxx called with invalid parameters


An indicator or other function was called with wrong parameters.

Error 012: Bad time value


A date/time function was called with bad data, or the bar with the given time could not be found.

Error 014: Function xxx timeperiod nnn > LookBack


An indicator required a higher LookBack period. For suppressing this error message when the lookback period is
irrelevant, f.i. for statistics tests, set LookBack to 0.

Error 015: Invalid xxx data


Object xxx contains invalid data, f.i. a wrong parameter for a pointer or string, or data from a non initialized array or a
division by zero.

Error 016: Inconsistent start/end dates


StartDate, EndDate, NumYears, LookBack etc. have been set up in a way that the selected asset has no prices
between start and end date.

492
Error 017: Bad return value
A script function returned an invalid value.

Error 018: Bad name


A name was too long, too short, or contained spaces or other invalid characters.

Error 030: Check lookback, settings, asset order


The lookback period, simulation period, or price history was not set up correctly. Parameters that affect the bars
generation (StartDate, BarPeriod, LookBack, TICKS, LEAN, etc.) were either inconsistent (f.i. a too short LookBack
period for a subsequent indicator call), or changed after the bars were generated. Make sure to set up all parameters
before calling asset. Use the PRELOAD flag for extremely long lookback periods of more than 6 months.

Error 031: Price history missing


The price curve could not be initialized because historical data was missing.

Error 032: Constant parameter changed


A variable that should remain constant - such as NumWFOCyles - was changed after the initial run. Make sure not to
change those parameters at runtime.

Error 034: No asset data for ...


The asset was not found in the asset list. The simulation will still run when historical price data is found, but asset
parameters such as spread, margin, lot size, trade costs, trade profit etc. are made up and do not reflect the true values.

Error 035: Price gap / invalid ticks


Prices are missing or have invalid time stamps. The server did not provide all historical data or no price quote arrived
during the last bar. Normally not critical; the price functions return the values of the previous bar in such a case.

Error 036: Index exceeded


A function wrote past the maximum index of an allocated array or a series.

Error 037: Invalid value


A plot function was called with an invalid value, caused by a division by zero, the square root of a negative number, or
similar errors in the script.

Error 038: Too many elements


The plotGraph with the given name had more elements that bars exist on the chart. If you need this many, use another
name for the rest of the elements.

Error 039: Expression too complex


The rule generated by a machine learning function is too complex for conversion to a C expression. In case of
candlestick patterns, use the FAST pattern detection mode or reduce the number of patterns.

Error 040: Inconsistent optimize calls


Number or order of optimize calls are different between run cycles, due to a bug in the script. Make sure
that optimize is always called in the same order, the number of optimize calls is the same as the number of optimized
parameters, and the PARAMETERS flag is not changed between optimize calls.

Error 041: Inconsistent series calls / too many series


Number or order of series calls in the script are different between run cycles, due to a bug in the script. Make sure that
all series calls - or function calls that internally create series, such as LowPass, ATR, etc. - have the same order in
every run, and are not skipped with if statements. Make also sure that your script does not generate series in tick-based
intrabar functions (TMF, tick) or in endless loops.

Error 042: Parameter not found


493
Error 043: Wrong parameter
A parameter of a certain portfolio component could not be loaded from the *.par file. Check if the optimize calls or
the loop parameters were changed after training.

Error 044: No rule for ...


An AI function could not find a valid code generated for the current asset/algo combination.

Error 045: Negative price offset


A price that lies in the future was requested by a price or day function. For the simulation you can avoid this message
by setting the PEEK flag. PEEK is not available for real trading unfortunately - even though Zorro is good, it's not this
good.

Error 046: LookBack exceeded


A price or TA function required a higher lookback period than reserved through LookBack. Normally Lookback is
automatically adapted in the initial run, but manual adaption to the longest possible period is required when the time
periods change at runtime, for instance by loading optimized parameters.

Error 047: Not enough bars


Not enough price bars are available to cover the LookBack and test period. This can happen when a trade session is
started and the market is closed or the price server does not provide sufficient history (especially with IB or MT4). In
that case download recent price data from other sources and set the PRELOAD flag. Other reasons of this error can be
a wrong test period setup in the script (see asset), a gap in a historic price data file, a too high MinutesPerDay value
for one of the used assets, or a user-defined bar type that is much larger than the given BarPeriod. In the latter case
decrease BarPeriod or increase MinutesPerDay for allocating more bars.

Error 048: Endless loop or recursion


Trades are possibly generated in an endless loop or recursion, f.i. by a TMF that enters trades who themselves enter
new trades through their TMF. Or a for(trades) loop is nested inside another for(trades) loop.

Error 049: Too many trades


The script entered more than one trade per bar and asset, which is normally a sign of a script bug - therefore the error
message. If you really need a huge number of trades, for instance for unlimited grid trading or for training
several advise functions at the same time, set the TradesPerBar variable to the required number of trades per bar and
asset.

Error 050: Can't access ...


Zorro could not open a file. The file could be already opened by a different program, or externally deleted, or set to
read/only.

Error 051: Can't continue trades


Trading was stopped without closing the open trades, then resumed with a different Zorro version. Zorro can only
continue trades that the same version has opened. Use the broker platform for closing the trades manually.

Error 052: Broker interface busy


Zorro attempted to log in to the broker, but another Zorro instance on the same PC is already connected. For multiple
trading sessions and accounts, Zorro S is required.

Error 053: ... prices unavailable


The script attempted to subscribe or download prices of an asset that is either not offered by the broker, or offered under
a different name, or for which no price quotes are available due to market closure or server maintenance. Make sure
that you log in at a time when the market is open for all assets that you want to trade. Also make sure that the asset is
subscribed and visible in the asset list of the broker platform under the correct name that you've entered. Not all assets
are available to all users - for instance, on US accounts you can not trade commodity or index CFDs.

Error 054: ... invalid asset data


494
The asset is available, but data downloaded from the broker's server had no or invalid content, f.i. a price, PIP, or lot
value of zero. This can happen due to the long response time of MT4 servers to upload previously unused assets
(see remarks about MT4 issues). This can normally be solved by simply starting the script again. The MT4 server then
normally had enough time to upload the asset and the missing asset data will be available. It might be necessary to
repeat the procedure until all assets are uploaded by the server.

Error 055: ... price history missing


A part or all historical price data required for an asset used in the simulation was not found in Zorro's History folder. If
all data is missing, the simulation won't execute. You can download the missing data either from the broker with
the Download script, or from the Zorro Download page. When price data for that year is absolutely not available, create
a .t6 file of 0 bytes size with the name of the asset and year (f.i. SPX500_2007.t6). Zorro will then skip that period in the
simulation and not display the error message.

Error 056: ... can't download price history


Historical price data could not be downloaded from the broker's price server. The server can be offline or does not offer
price data for the given asset and time period.

Error 057: Not enough price ticks


Insufficient price data for the simulation; T1 data is required for testing with bar periods less than one minute. The test
will run with interpolated price data, but won't be very accurate.

Error 060: Memory fragmented / limit exceeded


Zorro can not allocate a too large memory resource - such as the ticks buffer for a simulation in TICKS mode, or the
price buffer for a large simulation period. The script can not be executed and Zorro must be restarted. This happens
when the PC is either running low on memory (a Win32 process has 3 GB max.), or when the memory is fragmented
through many previous simulation or training runs. Use a PC with at least 8 GB RAM for testing large portfolio systems.
Fragmented memory can be released simply by re-starting Zorro. The memory requirement of a strategy can be reduced
by splitting the portfolio into separate strategies, by reducing the simulation period (f.i. by setting
a EndDate or MaxBars limit), by not setting TICKS, or by using the LEAN flag and M1 rather than T1 price data.

Error 061: Compiled with different version


The executable script was compiled with a previous Zorro version that is incompatible to the current Zorro. The script
must be recompiled with the current Zorro version.

Error 062: Can't open ...


A file was not found or could not be opened. Check the file name and make sure that the file exists and the application
has full access rights to it. The Windows error code is displayed in parentheses, for instance (wb:13); the code 13
means that the file is opened in another application.

Error 063: ... not supported


An error message by the free Zorro version. The called function requires an external plugin or Zorro S.

Error 064: ... wrong format


An asset list, account list, or other .csv file has a wrong spreadsheet format. Check the file for wrong entries, missing
parameters, wrong delimiters, or the like. Every line in a .csv file must have the same number of delimiters - commas or
semicolons - and end with a 'new line' character.

Error 070: Trade unconfirmed by ...


Zorro opened a trade, but received no confirmation from the broker. This can happen in f.i. due to a temporary server
glitch or Internet connection breakdown just after sending the trade to the broker. Check in the broker platform if the
trade was be opened or not. If it was, it is orphaned; handle it manually or close it. Zorro can not handle this trade
because it did not receive the trade ID from the broker.

495
Error 071: Trade not found ...
Zorro closed the trade with the given ID, but the trade was not found in the broker's trade lists. Possibly it was never
opened, or was already closed manually or by a margin call or similar event.

Error 072: Timeout ...


A broker command to open or close a trade could not be executed due to a problem of the broker API, such as a server
crash or loss of connection. Zorro will attempt to close the trade in regular intervals until the broker's server is online
again.

Warning 073: Can't close nn lots


An exitShort/exitLong command could not be fully executed because not enough trades were open to close the given
number of lots.

Error 111: Crash in ...


A function in the script crashed due to a wrong operation, such as a division by zero, a wrong array index, or exceeding
the stack size by declaring huge local arrays. The current run is aborted (which can cause subsequent errors, f.i.
inconsistent series calls). Normally the name of the faulty function is displayed. See Troubleshooting about how to fix
bugs in your script functions or deal with other sorts of crashes.

!...
Messages beginning with an explamation mark are sent from the broker API in a live trading session, so their content
depends on the broker. On high Verbose settings, some diagnostics can be printed on events such as session start.
Other messages can indicate that the connection was temporarily interrupted or a buy/sell order was rejected
(see enter/exit). This happens when assets are traded outside their market hours, such as an UK stock index when the
London stock exchange is closed. A "Can't close trade" message can also indicate that the NFA flag was not set on a
NFA compliant account, or vice versa, the NFA flag was set on an account that is not NFA compliant. Read the
comments on the broker related page (FXCM, IB, Oanda, MT4, ...) in this manual. When trading with the MT4 bridge,
details and reason of error message is printed under the [Experts] and [Journal] tabs of the MT4 platform.

Useful links with free stuff


For starting system development after reading the Strategy Workshops, we recommend to visit the websites listed
below - especially when you're a beginner to trading:

Zorro User Forum - of course.

The Financial Hacker - many scripts, systems, and experiments with Zorro and R.

Forex-TSD, Steve Hopwood's - contrary to most trader forums that contain mostly clueless posts by clueless people,
those offer useful content for strategy developers.

Trading Systems Coding - free ebook by Investopedia about developing and testing trading strategy scripts.

496
Useful books
If traders can't make money with trading, they write trading books. Consequently there are tons of books about trading
methods and systems. The first problem is that publishers demand a certain number of pages, but the amount of trading
related information and knowledge is somewhat limited. That forces the authors to produce hundreds of pages of
platitudes and filler material. Fortunately, there are some trading books with real content. Here's a non-complete list of
useful books about automated trading and its mathematical background:

Murray R. Spiegel, Larry J. Stephens: Schaum's Outline of Theory and Problems of Statistics. Beginner's course
into probability and statistics with lots of examples. Work through this book and you have all basic knowledge for
understanding financial math.

Ruey S. Tsay, Analysis of Financial Time Series. If you read the Schaum's Outline book and hadn't had enough of
mathematics, this is the hard stuff that introduces all important mathematical models of price series.

Urban Jaekle, Emilio Tomasini: Trading Systems. How to develop a trading strategy; the book walks you through
every step from idea up to various optimization and money management methods. Highly recommended.

Johann Christian Lotter: Das Schwarze Börsenhackerbuch. Entwickeln von Handelssystemen mit Zorro.
Wärmstens empfohlen für alle, denen dieses Handbuch zu englisch ist.

David Aronson, Evidence-based Technical Analysis. Excellent, but a little elaborate book about the theory of testing
trade strategies. A classic.

Ernest P. Chan, Quantitative Trading. Insight in strategy testing and portfolio optimization with many practical advices.

John F. Ehlers, Rocket Science for Traders. Trading from an engineer's perspective with signal processing methods.
Comes with source code for all trading algorithms.

Ralph Vince, Handbook of Portfolio Mathematics. How to allocate your capital in an optimal way among different
assets and strategies.

William R. Gallacher, Winner Take All. Although we don't agree to all of Gallacher's conclusions about trade
strategies, this book (from 1994) is a funny read and an intelligent insight into the trading scene and its gurus. The
author also describes a apparently profitable trade system and then shows why it won't work - unique for a trading book.

Robert Harris, The Fear Index. A must-read for any trade system developer.

If you can't read them all, get the books by Jaekle & Tomasini, Aronson, and Chan. They give a good introduction into
the strategy development process without requiring a mathematical or technical background.

497
Free Zorro version ($30,000 profit limit)
Naturally, any Zorro user want to earn as much money as possible with as little effort as possible. We want the same
for you, but only to a certain degree. If you make too much profit, we'll get into a conflict of interest, since Zorro is
intended for distributing money from the rich to the poor, not the other way around. We're also interested in promoting
trade system development, so we prefer if you develop your own strategies instead of using ours. And we do not want
the market to be flooded with Zorro-based robots. To solve the possible conflicts of interest in all those cases, the free
Zorro license has some restrictions in place:

• You can use Zorro for an annual trading profit of US$ 30,000. We believe that's enough for a single person. If you have
reached that limit, you're required to either become a sponsor (see below) or to stop trading with Zorro for the rest of
the year.

• You can have a capital of up to US$ 7,000 real money under direct or indirect control by Zorro. If the balance in your
broker account exceeds that limit, you must withdraw your profits. This restriction does not apply to demo accounts.

• You can use Zorro for your own trading only. You must not use it, directly or indirectly, for selling trade signals, for
offering trade copy services (such as ZuluTrade™), or for trading with other people's money (such as on PAMM
accounts), neither with your own systems nor with the included Z systems.

• You can use only a single Zorro, not a multitude of Zorros for simultaneously trading or training.

• You can sell, publish, or distribute your strategies, but you must then include the source code (.c). You must not
distribute them as executables (.x).

You don't need to observe your account permanently for staying below the $7000 capital limit. The free Zorro version
knows your account balance and opens no new positions when the limit is reached on a real money account. There is
no limit on demo accounts.

If you violate the above trading restrictions, you're legally required to pay a US$ 1000 fee plus all profit gained by
violation to oP group Germany GmbH. We'll gratefully take your money and use it to add new features to Zorro.

Sponsored Zorro S version (unrestricted)


Zorro S ("Sponsor") is a special version with some additional features and without the above profit ceilings and license
restrictions. You can get Zorro S when you contribute substantially to the user community. Contributions rewarded with
Zorro S are providing the C/C++ source of a broker API plugin, serious tools or indicators, or good old money.

The main differences of the free Zorro version and Zorro S are listed below:

Zorro Zorro S

Subscription per month free EUR 25*

Annual profit limit, USD 30.000 unlimited

Account size limit, USD 7.000 unlimited

Signal provider / PAMM -- yes

Compiled scripts (EXE) -- yes

Automated retraining -- yes

Individual user interface -- yes

Multiple CPU cores -- yes


Multiple sessions -- yes

498
Minimum bar period 1 minute 100 ms

Market Volume -- yes

Command line -- yes

FXCM bridge yes yes

Oanda bridge yes yes

IB bridge demo yes

MT4 bridge demo yes

Quandl bridge - yes

R bridge yes yes

Z1, Z2, Z8 systems yes yes

Z3, Z7, Z12 systems demo yes

* monthly subscription or one-time payment; details on the Zorro download page.

Credits & Disclaimer


Zorro Trading Automaton (c) 2012, 2016 by oP group Germany GmbH, Birkenstr. 25-27, 63549 Ronneburg / Germany.
oP group is a software development group; we develop customized trading robots, tools, and trading systems.
Contact: info (at) opgroup.de.

Zorro S users have a limited period free technical support through support (at) opgroup.de. Further technical support
is available through obtaining a support ticket on the Zorro download page. The most frequent support questions are
listed here.

Zorro was written in C++ and lite-C, using the Gamestudio API. This product includes software developed by the
OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/).

TA Function Library Mario Fortier

Z Strategies Johann Christian Lotter, Richard Edye

SED Script Editor Gustav Nordvall

ZView Chart Viewer P.J. Arends

Charting Library Advanced Software Engineering Ltd*

IB TWS* Bridge Sun Bin Tsen

MT4* Bridge Richard Edye

Cryptographic Library Eric Young, Tim Hudson

499
Zorro manual and software are protected under the copyright laws of Germany and the U.S. Any trademarks used in
this documentation are trademarks of their respective owners. Any reproduction of the material and artwork printed
herein without the written permission of oP group Germany GmbH is prohibited. We undertake no guarantee for the
accuracy of any information contained herein. oP group Germany GmbH reserves the right to make alterations or
updates without further announcement.

The Zorro software is copyrighted by oP group Germany GmbH. We take great pains to make certain that the software
is free of errors, malware, spyware, and adware. However it is distributed "AS IS" without warranties of any kind, either
express or implied. We grant permission for personal use of this software without fee. Some trading restrictions apply.
Parts or derivative works based on the software may be distributed or included in trading robots, educational material,
or other products only with our express permission. The trading systems included with or developed using the Zorro
software do not guarantee that you will make profits or avoid losses. We are not a financial advisor and do not
recommend the purchase of any asset or advise on the suitability of any trade or investment. We cannot be held
responsible for any trading losses or other damages occurred as a result of using the Zorro software or any other
information gathered through this manual.

U.S. Government Required Disclaimer - Commodity Futures, Trading Commission Futures, Derivatives and Options
trading has large potential rewards, but also large potential risk. You must be aware of the risks and be willing to accept
them in order to invest in the futures and options markets. Don't trade with money you can't afford to lose. This website
is neither a solicitation nor an offer to Buy/Sell futures or options. The past performance of any trading system or
methodology is not necessarily indicative of future results.

CFTC rule 4.41 - Hypothetical or simulated performance results have certain limitations. Unlike an actual performance
record, simulated results do not represent actual trading. Also, since the trades have not been executed, the results
may have under-or-over compensated for the impact, if any, of certain market factors, such as lack of liquidity. Simulated
trading programs in general are also subject to the fact that they are designed with the benefit of hindsight. No
representation is being made that any account will or is likely to achieve profit or losses similar to those shown.

* Brokers, trading services, and trading software manufacturers mentioned herein are independent legal entities and
not affiliated with oP group Germany GmbH in any way.

500

Você também pode gostar