[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

TradeStation Precision - Summary



PureBytes Links

Trading Reference Links

Since I recently spent a lot of time looking at this topic, I decided 
to summarize my findings so that I could find them again. I thought 
this might be useful to others so am posting it.

Bob Fulks

-----

INTRODUCTION

This message will summarize my experiments with TradeStation to 
determine the precision of arithmetic operations, illustrates what 
errors it may cause, and how you might "work-around" these errors. 
Please notify me if you spot any mistakes in this as I would like it 
to be as accurate as possible.


NUMERIC VARIABLES

Numeric variables appear to be single-precision floats using the IEEE 
standard. This format in described at:

    <http://www.psc.edu/general/software/packages/ieee/ieee.html>

All variables are stored in this format. Conceptually it is easier to 
think of the limits in terms of integers and non-integers, (even 
though all numbers are stored in the floating point format).

INTEGER ARITHMETIC

The format consists of a mantissa of 23 bits plus one implied bit for 
a total of 24 bits, allowing it to represent integers in the range of:

     +-2^24 = +-16,777,216

This limit is important in cases where you might be counting. For 
example, in the following loop:

        for j = 1 to Limit begin
           k = k + 1;
        end;

if "Limit" exceeds +16,777,215 then the loop will not terminate since 
the increment by 1 will then be below the least significant digit.

The same thing applies to the limit on counters:

    Count = Count + 1;

This limit is high enough so that it is unlikely to cause any 
problems in normal use.


LARGER FLOATING POINT NUMBERS

Numbers higher than +16,777,216 such as +16,777,220 would be 
represented conceptually as:

     8,388,610 * 2^1

This would work up to 33,554,432 which would be represented 
conceptually as:

    16,777,216 * 2^1

And continuing up to 67,108,864 which would be represented 
conceptually as:

    16,777,216 * 2^2

at which point the least significant digit changes by 2^3 = 8 for the 
next value. This continues up to very large numbers.

So the poorest resolution would occur when the representation is near 
the 8,388,610 end of the range and would be about 1.2 decimal digits 
of error in 10^7, decreasing to about 0.6 decimal digits of error in 
10^7 at the 16,777,216 end of the range.

So we can approximate the error as 1 part in 10^7.


SUBTRACTING TWO NEAR-EQUAL NUMBERS

This becomes an issue in code that subtracts nearly equal numbers such as:

    1,234,623.890 - 1,234,567.803

The numbers are only accurate to about:

    1,234,623.9 and 1,234,567.8,

respectively, so the difference:

    56.1 could be from about 56.0 to 56.2

There are a lot of functions that subtract two prices to find a small 
difference between the two. For example, The Dow Jones Industrial 
Average (DJIA) is at about 10300 so

    10323.45 - 10321.20 = 2.25

is accurate to about 3 digits which is probably OK. But many 
functions subtract the square of price or Price1*Price2 and these 
can give very inaccurate results.

The usual way to avoid these errors is to offset all prices in the 
calculation by a constant. So instead of using the DJIA directly, you 
might subtract 10000 from all prices used in the calculation and 
re-scale the result at the end of the calculation by adding back the 
10000. This can keep the numbers small enough to avoid these errors.

In many cases there are several different algorithms for calculating 
the same function and some may preserve the accuracy much better than 
others.


LARGEST POSSIBLE NUMBER

The largest number that can be represented by the floating point 
format is about 3.4 * 10^38, which is a very large number. 
TradeStation 4.0 limits this to 2.147483 * 10^15 at which point some 
of the built-in routines stop working correctly. TS2000i has a higher 
limit. (This limit was not tested on all routines and the true limit 
may be much less than this.) But, in any case, the limit is large 
enough so it will not cause a problem in most cases.

The PowerEditor will let you type in a number as large as 
100,000,000,000,000,008 which would be stored as 1.000000 * 10^17.


SMALLER NUMBERS

All numbers use the same floating point representation so are 
accurate to about one part in 10^7. An example would be 0.3426749XXX 
where the XXX digits are inaccurate.


ITERATIVE CALCULATIONS

The limited precision also becomes important in calculations that 
carry a value from bar to bar, which is very common. Many 
TradeStation functions do this since it is much faster than 
recalculating everything on every bar. A simple example is the series 
version of the "Average" function:

inputs: Price(NumericSeries), Length(NumericSimple) ;
vars  : Sum(0), Counter(0) ;

if currentbar = 1
then begin
    Sum = 0;
    for counter = 0 to Length - 1
    begin
       Sum = Sum + Price[counter];
    end;
end
else
    Sum = Sum[1] + Price - Price[Length];
if Length > 0 then
    Average = Sum / Length
else
    Average = 0;


In this code, the complete accurate calculation is done at CurrentBar 
= 1. On each subsequent bar, only the changes are calculated. Since 
the error on each calculation can be can be one or two parts in 10^7, 
after 1000 bars the error might be one part in 10^4. For the DJIA, 
this would be an error of one point out of 10000. This doesn't sound 
like much. But if your trading system bought when the price crosses 
over the Average, you could get very different results based upon a 
one-point error.

There is a very simple fix for this. Simply replace:

   if currentbar = 1

with

   if Mod(currentbar, 100) = 1

This causes the code to do the complete, accurate calculation every 
100 bars, which prevents the error from accumulating. The added 
calculation would add less than 1% to the calculation time.

This solution works on any most function that carries a value from bar to bar.

One exception is the RSI function. This indicator was developed 
"before computers" so uses a different calculation for the initial 
bars than for the subsequent bars - to simplify what was then a 
manual calculation. TradeStation retained the original definition so 
if you modify it to redo the initial calculation every 100 bars, you 
will get different values.


DIVIDE-BY-ZERO AND DIVIDE-BY-SMALL-NUMBER ERRORS

In TradeStation 4.0 and 2000i, Omega advises us to add a test for 
"Divide-by-zero" errors:

   if Denom <> 0 then Result = Numer / Denom;

I have been told that they removed these checks for the function 
library in TS Pro and as a result you get random "Divide-by-zero" 
errors in code that worked in TS4.0 and TS2000i. (It is hard to 
believe that they could do this but who knows...)

The above expression allows the calculation if "Denom" is not zero. 
If "Denom" is zero, it does not allow the calculation so in that 
case, "Result" will retain the value from the previous bar. That is 
usually OK but you may want to do something else in such cases. I 
have seen some plots that do not get updated for many bars because 
the "Divide-by-zero" test prevented them from being updated.

In some cases the denominator will not be zero but will be very 
small, resulting in a very large incorrect result. This would occur 
for cases where the correct denominator is zero but, because of the 
round-off errors, is some very small value.

In many such case both the denominator and the numerator might very, 
very small or zero and the result actually has a value. (Result = 0/0)

There is a method (called "L'Hopital's Rule") to determine the value:

    if AbsValue(Denom) > Limit
       then Result = Numer / Denom
       else Result = dNumer / dDenom;

where dNumer and dDenom are the first derivative of the equations for 
the numerator and denominator, respectively. "Limit" would be set to 
some appropriate small value. (To use this trick you need an 
understanding of differential calculus.)


SUMMARY

The use single-precision floating point was probably a good design 
decision when memory was expensive and TradeStation 4.0 was limited 
to the 64K program size. Unfortunately, Omega did not write their 
function library so that it produced accurate results with the 
available arithmetic so the users must be alert for and "work-around" 
such errors.

Now days, memory is very much less expensive so most modern programs 
use double-precision floating point arithmetic. Unfortunately Omega 
retained the single-precision arithmetic in TS2000i so we still have 
to worry about these issues.

Bob Fulks
July, 2001