C-API Extensions

Equilla, the formula language of Tradesignal, is a powerful way to describe your trading strategies. There are cases when you may need additional functionality which is not directly available in Equilla. To address this issue the language can be extended via Dynamic Link Libraries (DLLs) using the Equilla Extension Application Programmer's Interface (API).

What is an Equilla Extension

Equilla Extension libraries are DLLs containing one or more functions. These functions must have a specific signature in order to be compliant with Equilla.

How to access an Equilla Extension from Equilla

To use an extension function from a script you have to first make sure the DLL is in the correct place on your system, and then you must add a reference to the function at the start of your Equilla script.

Where to put the Equilla Extension

All Equilla Extension libraries have to be placed in the directory where Tradesignal is installed.

You can specify a different directory for Extension DLLs in the options under File > Options > Folders > Equilla Paths.

How to use extension functions

Assuming you have an Equilla Extension DLL called MyEquillaExtension.dll containing a function called MyMovingAverage that takes price and period parameters, you would first import the function using the following Equilla command:
Import( "MyMovingAverage", "MyEquillaExtension.dll" );
You can then use the function as you would use a normal Equilla function.
DrawLine( MyMovingAverage( Close, 10 ), "Avg" );
Note: Each function from the DLL must have it's own import statement.

How to import a function that has the same name as an existing Equilla function

You can easily import a function and give it an alternate name as a third parameter to the Import command.
Import( "MyMovingAverage", "MyEquillaExtension.dll", "RenamedMovingAverage" );

DrawLine( RenamedMovingAverage( Close, 10 ), "Avg" );

How to write an Equilla Extension in Microsoft Visual C++ 2005

The Equilla Extension API is described in the EquillaApi.h file, which is located in the Include sub-directory of your Tradesignal installation. In order to create Equilla Extension libraries you have to add the directory to the Include files directory of Visual Studio under Tools > Options > Projects and Solutions > VC++ Directories.

Follow these steps to create a sample project:
  • Create a new Visual C++ DLL project
  1. File > New > Project... > Visual C++ > Win32 Console Application
  2. Type a name for the new project for example "MyEquillaExtension"
  3. Press OK
  4. Select the Application Settings and set the Application type to DLL
  5. Press Finish
  • Include the Equilla interface description and dependencies by adding the following lines to the top of the MyEquillaExtension.cpp file, below the #include "stdafx.h" line:
#include <wtypes.h>
#include <oleauto.h>
#include <EquillaApi.h>
  • Add the following function to the bottom of the MyEquillaExtension.cpp file:
    Note: All Equilla Extension functions must be declared with the following function prototype.
EQUILLA_API int MyMovingAverage( DWORD scriptID, int argc, EqVariable* variables[],
VARIANT* result )
{
// check that the number of parameters passed to this function is two (Price and Period)
if (argc != 2)
return EQUILLA_ARGUMENT_ERROR;

// extract the period value from the second parameter
VARIANT vPeriod;
VariantInit(&vPeriod);
variables[1]->GetValue(&vPeriod); // read the latest value from the variable

// ensure the period parameter is valid
if (V_I4(&vPeriod) < 1)
return EQUILLA_ARGUMENT_ERROR;

// calculate the sum of all prices for the given period
double sum = 0.0;
VARIANT vPrice;
VariantInit(&vPrice);
for (int index = 0; index < V_I4(&vPeriod); ++index)
{
variables[0]->GetValue(index, &vPrice); // read a historic value from the variable
sum += V_R8(&vPrice);
VariantClear(&vPrice); // release any allocated memory
}

// write the equilla result and set the type to a 8-byte floating point value
V_VT(result) = VT_R8;
V_R8(result) = sum / V_I4(&vPeriod);

// clean up and return successfully
VariantClear(&vPeriod);

return EQUILLA_SUCCESS;
}
  • Compile the project and copy the DLL to the Equilla Extension DLLs directory
    Note: Ensure you are compiling a 32-bit DLL otherwise it can not be used from Equilla!
  • You are finished. You can now access this function from Equilla as described above.

Extension function arguments and return value

All Equilla Extension functions have the same prototype as shown above. The parameters are:

ParameterDescription
DWORD scriptIDA value that uniquely identifies the instance of an Equilla script calling this function
int argcThe number of parameters being passed into this function from the Equilla script
EqVariable* variables[]An array of the parameters passed into this function from the Equilla script
VARIANT* resultA pointer to a variable receiving the result of the function that will be passed to the Equilla script


The return value is an integer that must be one of the following constants:

Return valueDescription
EQUILLA_SUCCESSThe function has completed successfully
EQUILLA_ARGUMENT_ERRORThe is a problem with one or more of the parameters passed into the function
EQUILLA_GENERAL_ERRORAny other error has occurred

Working with Equilla variables

The EqVariable object represents a variable that has been passed to this function from an Equilla script. You can use the object to access the latest value of the variable or a historic value (from a previous bar). All values accessed via the EqVariable object will be returned as VARIANTs.

Note: A VARIANT is generic variable that can hold data of various different types; for an overview of working with VARIANTs see below.
  • To access the latest value of the variable:
VARIANT vValue;
VariantInit(&vValue);
variables[0]->GetValue(&vValue);

// convert the value into a string
VariantChangeType(&vValue, &vValue, VT_BSTR);

// include windows.h to use the MessageBoxW function
MessageBoxW(NULL, V_BSTR(&vValue), L"The value is", MB_OK);

VariantClear(&vValue);
  • To access the value of the variable from the previous bar (assuming this is a series variable):
VARIANT vPreviousValue;
VariantInit(&vPreviousValue);
variables[0]->GetValue(1, &vPreviousValue);

// do something with the variable here

VariantClear(&vPreviousValue);

How to debug an Equilla Extension in Microsoft Visual Studio 2005

Important: Before you start the debugger, ensure that Visual Studio is in native debugger mode. This can be enabled as followed:
  1. Right-Click on the project in the Solution Explorer and select Properties
  2. Click on the Configuration Properties > Debugging in the tree view on the left
  3. Locate the Debugger Type property and change it's value to Native Only
  4. Click OK to close the dialog
To start debugging your DLL:
  1. Start Tradesignal and change the property File > Tradesignal Options > Equilla > File Locations > Equilla Extension DLLs to the build directory of your project (this will normally be in the sub folder called Debug). Click OK. Then shut down Tradesignal.
  2. In Visual Studio ensure that this is the Debug build configuration and select Debug > Start Debugging from the menu.
  3. A dialog will open asking you for the executable to run. Enter the location of the tse.exe file from the Tradesignal installation directory on your system and click OK.
  4. The application will now start, you should use your extension function in a new script as described above.
  5. Set breakpoints in visual studio and then apply your Equilla script to a Chart, Watchlist or Scanner. Execution will be automatically interrupted when one of your breakpoints is hit.

Advanced Topics

How do I initialize and cleanup global data in my Equilla Extension

If you want to write a library of functions that maintain some global state from call to call you will need to use global variables in your DLL. This can cause problems in determining when to initialize and free these global variables. To solve this problem, there are two optional functions that you can add to your extension DLL that will be called once when a script containing functions from your DLL is added to a chart, and once when it is removed from a chart. The following example illustrates how these functions are used:

Note: The prototypes and names of the DllAttachScript() and DllDetachScript() functions must be exactly the same as in the example.

#include "stdafx.h"
#include <wtypes.h>
#include <oleauto.h>
#include "EquillaApi.h"
#include <map>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, LPVOID lpReserved)
{
return TRUE;
}

class CGlobalCounter
{
public:
static CGlobalCounter& Instance()
{
// This is not thread-safe, included only for the brevity of the example
static CGlobalCounter singleton;
return singleton;
}

void Add(DWORD nScriptId)
{
EnterCriticalSection(&m_hCS);
m_barCounts[nScriptId] = 0;
LeaveCriticalSection(&m_hCS);
}

void Remove(DWORD nScriptId)
{
EnterCriticalSection(&m_hCS);
m_barCounts.erase(nScriptID);
LeaveCriticalSection(&m_hCS);
}

int Increment(DWORD nScriptId)
{
EnterCriticalSection(&m_hCS);
int nResult = ++m_barCounts[nScriptId];
LeaveCriticalSection(&m_hCS);
return nResult;
}
private:
CGlobalCounter()
{
InitializeCriticalSection(&m_hCS);
}

CGlobalCounter(const CGlobalCounter&);
CGlobalCounter& operator=(const CGlobalCounter&);

std::map<DWORD, int> m_barCounts;
CRITICAL_SECTION m_hCS;
};

EQUILLA_API void DllAttachScript(DWORD nScriptId)
{
CGlobalCounter::Instance().Add(nScriptId);
}

EQUILLA_API void DllDetachScript(DWORD nScriptId)
{
CGlobalCounter::Instance().Remove(nScriptID);
}

//call this function once on each bar to get the current bar count
EQUILLA_API int BarCount(DWORD nScriptID, int argc, EqVariable* variables[], VARIANT* result)
{
V_VT(result) = VT_I4;
V_I4(result) = CGlobalCounter::Instance().Increment(nScriptId);
return EQUILLA_SUCCESS;
}


Note: Because this DLL can be used by multiple scripts instances at the same time it is important to separate the global data of each script in some way. In the example above we use a map that stores the bar count against a given scriptID. We recommend you use a similar approach in your own DLLs changing the int data type to whichever data you need to store.

Working with the VARIANT data type

A VARIANT is a variable that can hold any type of data, it maps quite closely to the types of variables that are used in Equilla. The trick to using variants in your Extension DLL is in correct initialization/de-initialization and in accessing the correctly typed data.

Declaring and releasing variants

Whenever you declare a variant in your code you should always initialize it in the following way:

VARIANT vPrice;
VariantInit(&vPrice);


Whenever you have finished with the variant (even in an error case) you should clear the variant in the following way:

VariantClear(&vPrice);

Reading and writing data

The following example demonstrates the important variant functions to use to read and write various data types from and to a variant:

// initialize the variant
VARIANT vValue;
VariantInit(&vValue);

// Write and read a double value (like a price) to a variant
double nPrice = 12.1;
V_VT(&vValue) = VT_R8; // set the underlying type of the variant
V_R8(&vValue) = nPrice;
double nNewPrice = V_R8(&vValue);
VariantClear(&vValue);

// Write and read an integer value (like a period) to a variant
int nPeriod = 14;
V_VT(&vValue) = VT_I4; // set the underlying type of the variant
V_I4(&vValue) = nPeriod;
int nNewPeriod = V_I4(&vValue);
VariantClear(&vValue);

// Write and read a Boolean value (a true/false value) to a variant
bool bValue = true;
V_VT(&vValue) = VT_BOOL; // set the underlying type of the variant
V_BOOL(&vValue) = bValue ? VARIANT_TRUE : VARIANT_FALSE;
bool bNewValue = V_BOOL(&vValue) != VARIANT_FALSE;
VariantClear(&vValue);

// Write and read a string value to a variant
LPCWSTR sValue = L"Text";
V_VT(&vValue) = VT_BSTR; // set the underlying type of the variant
V_BSTR(&vValue) = SysAllocString(sValue); // you have to create a BSTR from the input string
BSTR sNewValue = SysAllocString(V_BSTR(&vValue)); //SysFreeString needs to be called once sNewValue is not required anymore
VariantClear(&vValue);

// Write and read a date/time (OLE Datetime) to a variant
SYSTEMTIME stLocal = {0};
DATE dtOle = 0;
GetLocalTime(&stLocal);
SystemTimeToVariantTime(stLocal, &dtOle);
V_VT(&vValue) = VT_DATE;
V_DATE(&vValue) = dtOle;
DATE dtNewOle = V_DATE(&vValue);
VariantClear(&vValue);

Checking function parameters are the correct type

Due to the data types used by the Equilla engine, you should always check if the type of a given parameter is what you expect. If the type differs from what you expect you can use the VariantChangeType function to perform the conversion as the following sample demonstrates (this is the same moving average example as shown above but with this checking added):

EQUILLA_API int MyMovingAverage( DWORD scriptID, int argc, EqVariable* variables[],
VARIANT* result )
{
// check that the number of parameters passed to this function is two (Price and Period)
if (argc != 2)
return EQUILLA_ARGUMENT_ERROR;

// extract the period value from the second parameter
VARIANT vPeriod;
VariantInit(&vPeriod);
variables[1]->GetValue(&vPeriod); // read the latest value from the variable

// ensure the period parameter is an integer
if (V_VT(&vPeriod) != VT_I4 && FAILED(VariantChangeType(&vPeriod, &vPeriod, VT_I4)))
{
VariantClear(&vPeriod);
return EQUILLA_ARGUMENT_ERROR;
}

// ensure the period parameter is valid
if (V_I4(&vPeriod) < 1)
return EQUILLA_ARGUMENT_ERROR;

// calculate the sum of all prices for the given period
double sum = 0.0;
VARIANT vPrice;
VariantInit(&vPrice);
for (int index = 0; index < V_I4(&vPeriod); ++index)
{
variables[0]->GetValue(index, &vPrice); // read a historic value from the variable

// ensure the price is a double
if (V_VT(&vPrice) != VT_R8 && FAILED(VariantChangeType(&vPrice, &vPrice, VT_R8)))
{
VariantClear(&vPeriod);
VariantClear(&vPrice);
return EQUILLA_ARGUMENT_ERROR;
}

sum += V_R8(&vPrice);
VariantClear(&vPrice); // release any allocated memory
}

// write the equilla result and set the type to a 8-byte floating point value
V_VT(result) = VT_R8;
V_R8(result) = sum / V_I4(&vPeriod);

// clean up and return successfully
VariantClear(&vPeriod);

return EQUILLA_SUCCESS;
}

Converting variants to strings

One of the most common conversion tasks is taking a variant (such as a price) and converting it into a string for output, this can be accomplished in the following way:

double nPrice = 12.1;

VARIANT vPrice;
VariantInit(&vPrice);
V_VT(&vPrice) = VT_R8;
V_R8(&vPrice) = nPrice;

// convert the value into a string
VariantChangeType(&vPrice, &vPrice, VT_BSTR);

// include windows.h to use the MessageBoxW function
MessageBoxW(NULL, V_BSTR(&vPrice), L"The value is", MB_OK);

VariantClear(&vPrice);