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 option dialog under
File > Tradesignal Options > Equilla > File Locations > Equilla Extension DLLs.
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
- File > New > Project... > Visual C++ > Win32 Console Application
- Type a name for the new project for example "MyEquillaExtension"
- Press OK
- Select the Application Settings and set the Application type to DLL
- 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 )
{
if (argc != 2)
return EQUILLA_ARGUMENT_ERROR;
VARIANT vPeriod;
VariantInit(&vPeriod);
variables[1]->GetValue(&vPeriod);
if (V_I4(&vPeriod) < 1)
return EQUILLA_ARGUMENT_ERROR;
double sum = 0.0;
VARIANT vPrice;
VariantInit(&vPrice);
for (int index = 0; index < V_I4(&vPeriod); ++index)
{
variables[0]->GetValue(index, &vPrice);
sum += V_R8(&vPrice);
VariantClear(&vPrice);
}
V_VT(result) = VT_R8;
V_R8(result) = sum / V_I4(&vPeriod);
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:
| Parameter | Description |
|---|
| DWORD scriptID | A value that uniquely identifies the instance of an Equilla script calling this function |
| int argc | The 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* result | A 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 value | Description |
|---|
| EQUILLA_SUCCESS | The function has completed successfully |
| EQUILLA_ARGUMENT_ERROR | The is a problem with one or more of the parameters passed into the function |
| EQUILLA_GENERAL_ERROR | Any 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);
VariantChangeType(&vValue, &vValue, VT_BSTR);
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);
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:
- Right-Click on the project in the Solution Explorer and select Properties
- Click on the Configuration Properties > Debugging in the tree view on the left
- Locate the Debugger Type property and change it's value to Native Only
- Click OK to close the dialog
To start debugging your DLL:
- 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.
- In Visual Studio ensure that this is the Debug build configuration and select Debug > Start Debugging from the menu.
- 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.
- The application will now start, you should use your extension function in a new script as described above.
- 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()
{
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);
}
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:
VARIANT vValue;
VariantInit(&vValue);
double nPrice = 12.1;
V_VT(&vValue) = VT_R8;
V_R8(&vValue) = nPrice;
double nNewPrice = V_R8(&vValue);
VariantClear(&vValue);
int nPeriod = 14;
V_VT(&vValue) = VT_I4;
V_I4(&vValue) = nPeriod;
int nNewPeriod = V_I4(&vValue);
VariantClear(&vValue);
bool bValue = true;
V_VT(&vValue) = VT_BOOL;
V_BOOL(&vValue) = bValue ? VARIANT_TRUE : VARIANT_FALSE;
bool bNewValue = V_BOOL(&vValue) != VARIANT_FALSE;
VariantClear(&vValue);
LPCWSTR sValue = L"Text";
V_VT(&vValue) = VT_BSTR;
V_BSTR(&vValue) = SysAllocString(sValue);
BSTR sNewValue = SysAllocString(V_BSTR(&vValue));
VariantClear(&vValue);
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 )
{
if (argc != 2)
return EQUILLA_ARGUMENT_ERROR;
VARIANT vPeriod;
VariantInit(&vPeriod);
variables[1]->GetValue(&vPeriod);
if (V_VT(&vPeriod) != VT_I4 && FAILED(VariantChangeType(&vPeriod, &vPeriod, VT_I4)))
{
VariantClear(&vPeriod);
return EQUILLA_ARGUMENT_ERROR;
}
if (V_I4(&vPeriod) < 1)
return EQUILLA_ARGUMENT_ERROR;
double sum = 0.0;
VARIANT vPrice;
VariantInit(&vPrice);
for (int index = 0; index < V_I4(&vPeriod); ++index)
{
variables[0]->GetValue(index, &vPrice);
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);
}
V_VT(result) = VT_R8;
V_R8(result) = sum / V_I4(&vPeriod);
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;
VariantChangeType(&vPrice, &vPrice, VT_BSTR);
MessageBoxW(NULL, V_BSTR(&vPrice), L"The value is", MB_OK);
VariantClear(&vPrice);