1. Easy Language Extension ( ELX ) DLL
ELX is a Microsoft Windows DLL written and
maintained by Easy Language staff for the TradeStation community at large. It is a vehicle for delivering functionality
that is not otherwise provided by the EasyLanguage workspace library.
Currently, ELX contains the following modules:
1. Buffers - a simple facility for sending and receiving global time stamped
data between cooperating indicators, strategies, and other analytic
procedures.
2. Records – a simple and efficient
facility for maintaining persistent data between sessions. Each record is stored as a text file using a
random access, key and value format.
1.1
ELX
Initialization
Most ELX function groups require an application to
initialize an internal context before calling any other function in the
group. A single function named UseELX performs initialization for all groups. UseELX
should be called once, usually on the first bar, and usually by the main routine. The current version is returned.
1.2
Bridge
Functions
Every DLL C function has a corresponding
EasyLanguage “bridge” function. Bridge
functions encapsulate the somewhat foreign syntax associated with calling DLL
functions, and provide a natural EL interface for an application. Bridge functions are documented in the
reference section that follows.
2. Buffers
ELX buffers provide a simple facility for sharing
global data in one or more EasyLanguage programs. Programs may be in the same chart or in different charts.
Buffers are typed and ordered. Every
item in a particular buffer has the same data type, and each item is time
stamped with date and time in the same way that price bars are time stamped. Items are retrieved in the same order that
they are written. Thus, a buffer
conforms to the concept of a first-in-first-out ( “fifo” ) queue.
A typical buffer application involves two
programs. One is designated to be a
“sender” while the other is a designated to be a “receiver”. A sender writes items to a buffer and a
receiver reads items from the same buffer.
Items written to a buffer are accumulated until the receiver reads
them. Reading an item removes it from
the buffer.
A buffer must be opened before it can be used.
To open a buffer, both sender and receiver must specify a unique name, a
data type, and a scale factor. Scale
factor determines how many identical and contiguous buffers to create ( see Opening A Buffer ). Both programs receive an
integer “handle” from ELX that is used to identify the buffer in subsequent
calls.
To effectively use buffers in an application, the EasyLanguage programmer must
be aware of the asynchronous relationship between sender and receiver. TradeStation controls the execution order of
studies and strategies and does not formally define that order. Nor does EasyLanguage provide a mechanism to
otherwise regulate ordering. Thus, an
application cannot invent a synchronous protocol that requires a sender to send
before a receiver receives.
Consequently, when two programs cooperate to transfer data through a
buffer, synchronization becomes an important issue.
As a practical alternative to real synchronization,
EL programs can use time stamps to effect logical synchronization. Buffers accommodate this kind of synchronization
automatically. As items are written to
a buffer, ELX tags them with current date and time. As items are read, the current date and time are used to delimit
the read operation. Current date and
time is the date and time associated with the current bar.
Items retrieved by a receiving program are never logically ahead of the
receiver’s current bar. However, it is
possible, indeed likely, that items will “lag” behind the current bar. That is, that the date and time of a
retrieved item may precede the date and time of the current bar.
Lag is an important issue that every application must address
individually. There is not a generally
suitable solution for all. Tolerance
for lag, and reaction to it, will depend on many factors. However, the possibility for arbitrarily
large lag will present insurmountable problems for any application. Fortunately, there are practical ways to
control and limit lag a priori, making cooperative applications
feasible. See Synchronization Issues for more information.
2.1
Opening A
Buffer
Before a buffer can be used, an application must
call OpenBuffer to initialize it and gain
access to it, usually on the first bar.
OpenBuffer requires a specification of
name, type, and scale. An integer
valued handle is returned to identify the buffer in subsequent calls. Zero is an invalid handle value, and
indicates an error.
Buffers are initially identified by unique global names. Since an application may employ many
buffers, and since there may be many applications using ELX, a well designed
application must give careful consideration to the generation of buffer
names. An application can call CheckBuffer to determine if a buffer name is already
in use.
There are five distinct buffer types corresponding to the five EasyLanguage
types: double, float, integer, string,
and truefalse ( aka boolean ). Float is
provided for compatibilty, but its use is deprecated.
A contiguous array of identical buffers may be created by specifying a scale
factor greater than one. Each individual
buffer in an array is referenced by adding an offset to the handle value
returned by OpenBuffer. For example, if BH is a buffer
handle, then (BH + 0) identifies the first buffer, while (BH + N
– 1) identifies the Nth buffer.
It is common for two programs to share a buffer. Both programs should call OpenBuffer
using the same specification. The
program that first calls OpenBuffer will create
the buffer as specified. The second
program locates the buffer by name and confirms type and scale.
In exchange for access flexibility, an application must specify how to handle
queued items in an existing buffer.
Logically, queued items may be pending or obsolete. A true-false value named Flush is
used to specify the proper action. If Flush
is true, the items are obsolete, and they will be discarded. Otherwise, Flush is false, and they
will be treated as pending.
There are numerous scenarios to consider in general, but for specific cases the
choice is usually clear. When two
programs are involved, one specifies Flush true and the other specifies Flush
false. The choice of true or false
depends on whether the buffer is unidirectional or bidirectional. If the buffer is unidirectional, sender
specifies true, while receiver specifies false. In the buffer is bidirectional, responsibility is arbitrary. When one program is involved, Flush
is false.
Buffers are designed to support well behaved and cooperative applications. They are not “secure” or protected from
malicious or careless practices. The application
should employ suitable naming conventions and correctly use and compute handle
values.
2.2
Reading and
Writing a Buffer
A sending program can add items to a buffer by
calling one function from a class of functions named WriteBuffer. A receiving program can retrieve items by
calling one function from a class of functions named ReadBufferNext
or ReadBufferSync. Each function class is comprised of five related functions, one
for each data type. Individual
functions are distinguished from one another by a single letter suffix that
identifies the type of data it reads or writes.
The calling convention for read and write functions is similar. Each takes a buffer handle and a value
argument. Each returns an integer to
indicate the number of items transferred ( zero or one ). The value argument has a type that matches
the type of the function. Write values
are inputs, while Read values are outputs.
Read functions also have an additional output for specification of lag.
Sending data to another cooperating program is pretty straightforward. Simply call WriteBuffer,
specifying the value to be sent. The
value is automatically time stamped using the current date and time for a
specified or default data series. WriteBuffer returns zero if there was an error,
otherwise it returns one.
Receiving data is a little trickier due to synchronization and lag issues. ReadBufferNext
reads at most one item from a buffer ( the next one ), while ReadBufferSync may read over several items as it
compares the date and time of buffered items to the current date and time. Both functions return one if an item is in
fact copied. In this case, the value
argument is set to the retrieved value, and the lag argument defines the
difference in seconds between the buffered date/time and the current
date/time. If no item is copied (
because the buffer is logically empty or there is an error ), both functions
return zero and output arguments are not modified.
Lag times in seconds may be converted to a number of bar intervals using the
function BarLag. Bar lags are not meaningful for tick data. Also, because time lag is an elapsed time,
bar lag is only definite for minute, day, and week bars. The application must provide its own
conversion for longer intervals if required.
Bar lag is rounded up to the next bar when there is a fractional time
lag. If there is an error, BarLag returns zero.
2.3
Synchronization
Issues
Although there is no precise control over execution
order in a program group, there are certain things an application can and
should do to facilitate communication with buffers.
First, determine the “natural” execution order in a set of cooperating
programs. This order is determined by
analyzing the send and receive
relationship among individual programs.
A sending program will precede its receiving program. Programs should be inserted into their
charts in natural order. Command macros
may be useful for automating insertion.
An alternative, and perhaps preferable way, is to create a workspace
with all programs inserted but disabled ( status set to “off” ). Then, each program can be enabled in proper
order.
Most receiving programs should call ReadBufferSync
immediately after OpenBuffer. This “stages” the program for regular bar processing
where lag times are assumed to be nominal.
Provided individual programs are enabled and staged in proper order, lag times
will be nominal for both historical and real-time data. This means that items read synchronously
will be close enough to the current bar that they can be considered
“timely”. A particular application
however, must decide what a normal lag is, based on the relative time frames of
sender and receiver, and adjust accordingly.
2.4
Suggested
Uses
Buffers are especially useful for coupling studies
and strategies. A study can perform
calculations using analytically meaningful intervals, while updating and
transmiting signals to a strategy tick-by-tick. Conversely, a strategy can transmit information to a study for
plotting.
One example of this technique is in the application of “pairs” trading. In this intraday scenario, an indicator
compares the price of two related securities in a bar chart, then sends buy and
sell signals to separate strategies for execution. A generic receiving strategy is inserted into two different tick
charts to trade one security each. If
the securities are fairly active with respect to the bar interval, lag times in
the receivers will be nominal because the signals are received and processed on
the next tick. Pairs trading by this
method allows back-testing of the strategy and thus has an advantage over
alternate methods ( e.g. order bar macros ).
Buffers are also useful for maintaining intrabar calculations. When a program is running with “Update Every
Tick” enabled, iterative calculations can be performed by passing values in a
buffer from one tick cycle to the next.
3.
Records
ELX records provide a simple and effective way to
manage inter-session data. Records are
best suited for storing moderate amounts of unstructured or loosely structured
data.
Logically, a
record is a randomly ordered set of entries.
Each entry is stored in a text file using a “key and value” format, one
entry per line ( see Record File
Format ).
To access an entry for input or output, an application specifies its
unique key. Values are automatically
converted from EasyLanguage data types to text during output, and from text
back to EasyLanguage types during input.
By using indexed
or regular key schemes, an application can represent fairly sophisiticated data
structures in a record. For example, it
would not be too difficult to store a multidimensional array structure. However, records are perhaps best suited for
simpler applications, like storing individual security or analysis profiles.
To create or use
a record, an application must first open it ( see Opening
a Record ).
A unique file name identifies the record. A full path may be specified, but when no path is specified, the
default path designates a TradeStation subfolder named \MyWork\Records. A program that opens a record receives an
integer valued “handle” to identify the record in subsequent function
calls. More than one program may open
the same record.
After a record has been opened, individual entries
may be created, queried, and updated ( see Reading
and Writing a Record ).
Typically, values are read into an EL program on the first bar to
initialize local variables, then, as the program cycles on the last bar, record
values that change are written back to the record, for automatic saving when
the program is paused or removed.
Although changes
to a record are saved automatically when an EasyLanguage program terminates,
changes can also be saved explicitly while the program runs ( see Saving a Record ). Explicit saving allows an
application to guarantee the contents of a record at important milestones. Also, explicit saving allows an application
to refresh file contents periodically to support a rudimentary form of
interprocess communication.
3.1
Record File
Format
Records are stored in text files using a simple key
and value format. Each record entry
occupies a single line in the file. In
general, an entry has the form
Key = Value
where Key and Value are arbitrary text.
The first ( leftmost ) occurance of an equal ( = ) character separates
Key from Value.
Not every line constitutes a valid or complete record entry. Blank lines are not translated, and if a
line doesn’t have an equal character, it is treated as an entry without a
value.
In order to facilitate comparing and sorting, entry keys are subparsed into
distinct words. Two keys are equal if
they are comprised of the same words.
Thus, the key “My dog has fleas” is equivalent to “My dog
has fleas”. Also, keys are not case sensitive. Thus, “My DOG has FLEAS” is the same as “my
dog has fleas”.
Even though blank lines and key spaces are not logically significant, they are
maintained for output. Original order
and spacing of lines is preserved. New
entries are appended to the end. If a
value is modified, the new value appears in the same location as the value it
replaced.
There are built-in size limits for a record file. Currently, a line may have up to 1000 characters, and a file may
have up to 5000 lines.
3.2
Opening a
Record
Before a record can be used, an application
must call OpenRecord to configure the record
and make it available for reading and writing.
If the record file already exists, it is opened and parsed into
individual entries as described above.
OpenRecord returns an integer valued access
handle which is used to identify the record in subsequent calls.
An open record resides in memory.
Changes, if any, are applied to the
memory image and do not become permanent until the record is explicitly
or automatically saved.
More than one EL program may open the same record. Each program shares access to the same memory image. Changes made by one are visible to others. However, there are no special
synchronization mechanisms for records, so an application must manage this
issue separately.
3.3
Reading and
Writing Records
To create a record entry, or to update the value of
an entry, an application calls one function from a class of functions named WriteRecord.
To read values from an existing entry, an application calls one function
from a class of functions named ReadRecord. Each function class is comprised of five
related functions, one for each EasyLanguage data type. Individual functions are distinguished from
one another by a single letter suffix that identifies the type of data it reads
or writes.
The calling convention for read and write functions is similar. Each takes a record handle, a key, and a
value argument. Each returns an integer
to indicate the number of values transferred ( zero or one ). The value argument has a type that matches
the type of the function. Write values
are inputs, while Read values are outputs.
3.4
Saving a
Record
Changes made to a record are automatically saved when
the EL program is turned off or removed from its chart. However, in some applications it may be
necessary or desirable to explicitly save a record before it would otherwise be
saved automatically. This can be
accomplished by calling SaveRecord. Saving a record guarantees that logical changes made to a record will become permanent.
Function:
UseELX –
create an ELX context for an EL program
Usage:
Version
= UseELX( EnableTraps );
Interface:
input truefalse EnableTraps
{ false, true }
return int Version
{ encoded value }
Protocol:
random ( simple )
Description:
UseELX
creates an internal context, associates it with a calling EL program, and
returns the current version number.
Most ELX functions require that UseELX
be called as a prerequisite. Typically,
UseELX is called from an indicator, strategy,
or some other top level routine, on the first bar.
EnableTraps
specifies a mode for runtime error handling.
If EnableTraps is true, runtime error traps are enabled, otherwise
they are not. When error traps are
enabled, ELX functions will raise error events that cause the EasyLanguage
program to halt and display an event message.
When error traps are not enabled, processing continues.
NOTE: In either case, ELX
functions validate their inputs and return specific values to indicate an
error.
NOTE: Normally, enabling
error traps is a good idea. However,
runtime error handling is not yet fully implemented in the TradeStation
platform, and its use sometimes leads to unexpected or misleading results.
Version
is an encoded three level version identifier of form XXYYZZ, where XX
identifies major version, YY identifies minor version, and ZZ is a minor
version serial number.
Errors:
none
Association:
all
Function:
CheckBuffer – check ELX buffer name
Usage:
InUse = CheckBuffer( Name );
Interface:
input string Name
A name chosen by the application
return truefalse InUse
{ false, true }
Protocol:
random ( simple )
Description:
CheckBuffer returns true if a specified buffer
is open.
ELX must be initialized prior to calling CheckBuffer.
See UseELX.
If there are errors, InUse will be false. If ELX error traps are are enabled, the
program will be disabled on the current bar.
Errors:
Indication: InUse = false
Void Context ( ELX not initialized )
Association:
UseELX, OpenBuffer
Function:
OpenBuffer - open an ELX buffer
Usage:
BH = OpenBuffer( Name, Type, Scale,
Flush );
Interface:
input string Name
A unique name chosen by the
application
input string Type
{
{ "numeric", "double" },
{ "int",
"integer" },
{ "float },
{ "string",
"text" },
{ "truefalse",
"boolean", "bool" }
}
input int Scale
{ 1, ..., 256 }
input truefalse Flush
{ false, true }
return int BH
{ 0, ... }
Protocol:
random ( simple )
Description:
OpenBuffer opens an ELX buffer according to
given specifications and returns an integer valued access handle which
identifies the buffer in subsequent calls to related functions.
Name uniquely identifies the
buffer. Buffer names are not case
sensitive. The application should
employ a naming convention that reduces the likelihood of unintentional match.
NOTE! Due consideration
should be given to this issue.
Accidental matching of buffers can lead to errors that are difficult to
resolve.
Type specifies the type of data that
will be buffered. All items in a buffer
have the same type. There are five
distinct types. Some types have more
than one expression and expressions are case insensitive.
Scale specifies the number of related
parallel buffers to allocate. Each
parallel buffer may be referenced in subsequent calls using the returned buffer
handle plus an offset. The first buffer
is referenced by (BH + 0), the second by (BH + 1), and so forth up to the last buffer which is referenced by (BH + Scale - 1).
NOTE! Do not depend on any
arithmetic relationship between handles returned by separate calls to OpenBuffer.
Flush
specifies a disposition for items already in the buffer. If Flush is true, buffered items, if
any, are discarded.
NOTE: When a buffer
conveys strictly in one direction, i.e. from sender to receiver, the sender
should specify true, and the receiver should specify false. In bi-directional applications of two
programs, one should specify true and the other false. Otherwise, in single program applications, Flush should be false.
ELX must be initialized prior to calling OpenBuffer.
See UseELX.
OpenBuffer may be called more than once for a
particular buffer. The first time OpenBuffer is called with a given Name, it
creates a buffer with specified attributes.
In subsequent calls using the same Name, attributes are
confirmed. If the buffer already
exists, and if Flush is true, OpenBuffer
will discard any previously queued items.
If there are errors, BH is zero. Otherwise, BH is greater than zero.
Errors:
Indication: BH = 0
Void Context ( ELX not initialized )
Invalid Specification ( invalid Type or Scale value
)
Incompatible Type ( buffer exists but Type did not match )
Association:
UseELX, WriteBuffer, ReadBufferNext,
ReadBufferSync
Function:
WriteBuffer – write data to an ELX buffer
Usage:
Out
= WriteBuffer.d(
BH, ValueD ) dataN;
Out = WriteBuffer.f( BH,
ValueF ) dataN;
Out = WriteBuffer.i( BH,
ValueI ) dataN;
Out = WriteBuffer.s( BH,
ValueS ) dataN;
Out = WriteBuffer.b( BH,
ValueB ) dataN;
Interface:
input int BH
{ 1, ... }
input double ValueD
{ any double
precision float }
input float ValueF
{ any single
precision float }
input int ValueI
{ any integer }
input string ValueS
{ any string }
input truefalse ValueB
{ false, true }
literal dataN (
optional )
{ data1, ...,
data50 }
return int Out
{ 0, 1 }
Protocol:
random ( simple )
Description:
WriteBuffer copies a specified value to the
end of a specified ELX buffer and returns the number of items written.
An application must call a version of WriteBuffer
that corresponds to the data type specified when the buffer was created. Data type is indicated by the last letter of
the function name.
NOTE! An application must
call the exact function that corresponds to buffer type. There are no implicit type conversions.
BH
must be a valid buffer access handle equal to, or calculated from, the value
returned by OpenBuffer.
NOTE! ELX can only
validate the value of BH in a superficial way. If BH references an existing type compatible buffer it
will be accepted, even if the buffer was not created by the same
application. This implies that ELX
buffers are not “secure.” A malicious
or careless application can create problems that are difficult to detect.
The value written is stamped with the current date and time. Current date and time is the date and time
of the current bar in the data stream specified by dataN, or the default data
stream, if none is specified.
If there are errors, Out is zero. Otherwise, Out is one.
Errors:
Indication: Out = 0
Void Context ( ELX not initialized )
Invalid Handle ( buffer does not exist )
Incompatible Type ( Value type does
not match buffer type )
Association:
UseELX, OpenBuffer, ReadBufferSync,
ReadBufferNext
Function:
ReadBufferSync – read from an ELX buffer
Usage:
In
= ReadBufferSync.d( BH,
ValueD, Lag )
dataN;
In = ReadBufferSync.f( BH,
ValueF, Lag )
dataN;
In = ReadBufferSync.i( BH,
ValueI, Lag )
dataN;
In = ReadBufferSync.s( BH,
ValueS, Lag )
dataN;
In = ReadBufferSync.b( BH,
ValueB, Lag )
dataN;
Interface:
input int BH
{ 1, ... }
output double ValueD
{ any double
precision float }
output float ValueF
{ any single
precision float }
output int ValueI
{ any integer }
output string ValueS
{ any string }
output truefalse ValueB
{ false, true }
output double Lag
{ 0, ... }
literal dataN (
optional )
{ data1, ...,
data50 }
return int In
{ 0, 1 }
Protocol:
random ( simple )
Description:
ReadBufferSync copies the next logically coincident
item from a specified ELX buffer, assigns a “lag” value, and returns the number
of items read.
An application must call a version of ReadBufferSync
that corresponds to the data type specified when the buffer was created. Data type is indicated by the last letter of
the function name.
NOTE! An application must
call the exact function that corresponds to buffer type. There are no implicit type conversions.
BH
must be a valid buffer access handle equal to, or calculated from, the value
returned by OpenBuffer.
NOTE! ELX can only
validate the value of BH in a superficial way. If BH references an existing type compatible buffer it
will be accepted, even if the buffer was not created by the same
application. This implies that ELX
buffers are not “secure.” A malicious
or careless application can create problems that are difficult to detect.
ReadBufferSync reads items in order until it encounters an item that has date
and time greater than or equal to the current date and time. If there are no coincident or lagging items,
ReadBufferSync returns zero and output
variables are not modified. Otherwise, ReadBufferSync copies an item value to Value,
sets Lag to the time difference, and returns one.
NOTE: Current date and
time is the date and time of the current bar in the data stream specified by
dataN, or the default data stream, if none is specified.
NOTE: Reading is a
“destructive” operation. Items are
removed from the buffer as they are read.
Conceptually, ReadBufferSync scans
through a buffer looking for the latest item in time that does not occur after
the current date and time. If the
lastest item has a date and time that precedes the current date and time, Lag
will be greater than zero. Otherwise,
the item is coincident, and Lag will be zero. If multiple items have the same coincident date and time, the
first one synchronizes the read operation.
Lag is expressed in seconds.
NOTE: Use BarLag to express a given time lag as a number of bar
intervals.
If there are no items to read or if there is an error, In
will be zero. Otherwise, In will be one. The two cases cannot be distinguished when In is
zero. However, in the error case, if
ELX error traps are enabled, the program will be disabled on the current bar.
Errors:
Indication: In = 0
Void Context ( ELX not initialized )
Invalid Handle ( BH does not
identify a buffer )
Incompatible Type ( Value type does not match buffer type )
Association:
UseELX, OpenBuffer, ReadBufferNext, BarLag
Function:
ReadBufferNext – read from an ELX buffer
Usage:
In = ReadBufferNext.d( BH,
ValueD, Lag )
dataN;
In = ReadBufferNext.f( BH,
ValueF, Lag )
dataN;
In = ReadBufferNext.i( BH,
ValueI, Lag )
dataN;
In = ReadBufferNext.s( BH,
ValueS, Lag )
dataN;
In = ReadBufferNext.b( BH,
ValueB, Lag )
dataN;
Interface:
input int BH
{ 1, ... }
output double ValueD
{ any double
precision float }
output float ValueF
{ any single
precision float }
output int ValueI
{ any integer }
output string ValueS
{ any string }
output truefalse ValueB
{ false, true }
output double Lag
{ 0, ... }
literal dataN (
optional )
{ data1, ...,
data50 }
return int In
{ 0, 1 }
Protocol:
random ( simple )
Description:
ReadBufferNext copies the next item from a
specified ELX buffer, assigns a “lag” value, and returns the number of items
read.
An application must call a version of ReadBufferNext
that corresponds to the data type specified when the buffer was created. Data type is indicated by the last letter of
the function name.
NOTE! An application must
call the exact function that corresponds to buffer type. There are no implicit type conversions.
BH
must be a valid buffer access handle equal to, or calculated from, the value returned
by OpenBuffer.
NOTE! ELX can only
validate the value of BH in a superficial way. If BH references an existing type compatible buffer it
will be accepted, even if the buffer was not created by the same
application. This implies that ELX
buffers are not at all “secure.” A
malicious or careless application can create problems that are difficult to
detect.
ReadBufferNext reads the next item that does not occur after the current date
and time. If there are no items to read, ReadBufferNext
returns zero and output variables are not modified. Otherwise, ReadBufferNext copies
the item value to Value, sets Lag to the time difference, and
returns one.
NOTE: Current date and time
is the date and time of the current bar in the data stream specified by dataN,
or the default data stream, if none is specified.
NOTE: Reading is a
“destructive” operation. Items are
removed from the buffer as they are read.
If the next item has a date and time that precedes the current
date and time, Lag will be greater than zero. Otherwise, the item is coincident, and Lag will be
zero. Lag is expressed in
seconds.
NOTE: Use BarLag to express a given time lag as a number of bar
intervals.
If there are no items to read or if there is an error, In
will be zero. Otherwise, In will be one. The two cases cannot be distinguished when In is
zero. However, in the error case, if
ELX error traps are enabled, the program will be disabled on the current bar.
Errors:
Indication: In = 0
Void Context ( ELX not initialized )
Invalid Handle ( BH does not identify a buffer )
Incompatible Type ( Value type does not match buffer type )
Association:
UseELX, OpenBuffer, ReadBufferSync, BarLag
Function:
BarLag –
compute lag in bars given lag in seconds
Usage:
Bars
= BarLag( TimeLag, BarType, BarInterval
);
Interface:
input double TimeLag
{ 0, ... }
input int BarType
{ 2 = minute, 3 =
daily, 4 = weekly }
input int BarInterval
{ 1, ..., 1440 }
return int Bars
{ 0, ... }
Protocol:
random ( simple )
Description:
BarLag
returns a number of bars given a time lag in seconds, bar type, and bar
interval.
TimeLag
is a value in seconds determined by ReadBufferNext
or ReadBufferSync.
If BarType is 1, BarInterval specifies the number of
minutes in the bar. Otherwise, BarInterval
is ignored.
BarLag
always returns a whole bar number. If
the actual bar calculation is fractional, the result is rounded up. Thus, BarLag
bounds the given time lag.
If there are errors, Bars is zero. Otherwise, Bars is greater than
zero.
Errors:
Indication: Bars = 0
Invalid
Specification ( invalid TimeLag, BarType, or BarInterval )
Association:
ReadBufferSync, ReadBufferNext
Function:
OpenRecord - open an ELX record
Usage:
RH = OpenRecord( Name );
Interface:
input string Name
A unique name chosen by the
application
return int RH
{ 0, ... }
Protocol:
random ( simple )
Description:
OpenRecord opens an ELX record and returns an
integer valued access handle which identifies the record in subsequent calls to
related functions.
Name uniquely identifies the
record. Record names must conform to
file naming rules. If Name
includes a proper path, the record file will be placed in the designated
folder. Otherwise, a default path is
used to place the file.
NOTE: The default path designates a TradeStation subfolder named
\MyWork\Records.
If the specified
record is already open, an access handle to the open record is returned.
If the specified
record is not already open, and if the specified record file exists, OpenRecord reads and parses the file into individual
entries, one entry per line. In
general, each entry has one key and one value.
Key is separated from value by the leftmost equal ( = ) character. A line with no separator and at least one
nonblank character is treated as an entry without a value.
Empty lines, and other explicit formatting features are maintained
for output.
After the record has been parsed into memory, the source file is
closed.
ELX must be initialized prior to calling OpenRecord.
See UseELX.
Size limits restrict line length to 1000 characters, and lines per
file to 5000.
If there are errors, RH is zero. Otherwise, RH is greater than zero.
Errors:
Indication: RH = 0
Void Context ( ELX not initialized )
Invalid Specification ( invalid Name )
LimitViolation ( truncated line or file )
Association:
UseELX, WriteRecord, ReadRecord,
SaveRecord
Function:
WriteRecord – write a value to an ELX record
Usage:
Out
= WriteRecord.d(
RH, Key, ValueD );
Out = WriteRecord.f( RH,
Key, ValueF );
Out = WriteRecord.i( RH,
Key, ValueI );
Out = WriteRecord.s( RH,
Key, ValueS );
Out = WriteRecord.b( RH,
Key, ValueB );
Interface:
input int RH
{ 1, ... }
input string Key
a unique key
chosen by the application
input double ValueD
{ any double
precision float }
input float ValueF
{ any single
precision float }
input int ValueI
{ any integer }
input string ValueS
{ any string }
input truefalse ValueB
{ false, true }
return int Out
{ 0, 1 }
Protocol:
random ( simple )
Description:
WriteRecord formats an entry for a specified record
and returns the number of values formatted.
RH
must be a valid record access handle returned by OpenRecord.
Key
identifies a specific record entry. If
an entry with the same Key does not already exist, a new entry is
created. Otherwise, the entry’s current
value is replaced.
NOTE: Key is parsed
into individual words for comparison.
More than one contiguous space or tab character is not significant.
Value
is the new entry value. If Value is not
a string type, it will be converted to equivalent text in the entry.
An application must call a version of WriteRecord
that is compatible with the data type of Value. Data type is indicated by the last letter in
the function name. If Value is a
numeric type, EasyLanguage will automatically convert it to match the function
type.
If there are errors, Out is zero. Otherwise, Out is one.
Errors:
Indication: Out = 0
Void Context ( ELX not initialized )
Invalid Handle ( record does not exist )
Association:
UseELX, OpenRecord, ReadRecord
Function:
ReadRecord – read a value from an ELX record
Usage:
In
= ReadRecord.d(
RH, Key, ValueD );
In = ReadRecord.f( RH,
Key, ValueF );
In = ReadRecord.i( RH,
Key, ValueI );
In = ReadRecord.s( RH,
Key, ValueS );
In = ReadRecord.b( RH,
Key, ValueB );
Interface:
input int RH
{ 1, ... }
input string Key
a unique key
chosen by the application
output double ValueD
{ any double
precision float }
output float ValueF
{ any single
precision float }
output int ValueI
{ any integer }
output string ValueS
{ any string }
output truefalse ValueB
{ false, true }
return int Out
{ 0, 1 }
Protocol:
random ( simple )
Description:
ReadRecord reads an entry for a specified record
and returns the number of values read.
RH
must be a valid record access handle returned by OpenRecord.
Key
identifies a specific record entry. An
entry with the same Key must already exist.
NOTE: Key is parsed
into individual words for comparison.
More than one contiguous space or tab character is not significant.
ReadRecord attempts to read the entry value as a proper value of the
function data type. Function data type
is indicated by the last letter in its name.
If the entry value cannot be properly interpreted, In will be
zero and Value will not be modified.
Otherwise, In will be one, and Value will be
significant. If Value is a
numeric type, EasyLanguage will automatically convert the value as read to the
actual type of Value.
In
will be zero if there is an error, or if a proper value cannot be extracted
from the entry. The two cases cannot be
distinguished. However, in the error
case, if ELX error traps are enabled, the program will be disabled on the
current bar.
Errors:
Indication: In = 0
Void Context ( ELX not initialized )
Invalid Handle ( record does not exist )
Association:
UseELX, OpenRecord, WriteRecord
Function:
SaveRecord - save an ELX record
Usage:
Lines
= SaveRecord(
RH );
Interface:
input int RH
{ 1, ... }
return int Lines
{ 0, ... }
Protocol:
random ( simple )
Description:
SaveRecord updates an ELX record file and
returns the number of lines output.
RH
must be a valid record access handle returned by OpenRecord.
Record file contents are completely replaced.
Lines
will be zero if the record hasn’t been modified or if there is an error
accessing the record file. The two
cases cannot be distinguished. However,
in the error case, if ELX error traps are enabled, the program will be disabled
on the current bar
Errors:
Indication: Lines = 0
Void Context ( ELX not initialized )
Invalid Handle ( record does not exist )
ReservedFile ( could not open file )
Association:
UseELX, OpenRecord