HFT Tables: Strategies

Adding a Strategy

Syntax & Example
ENABLE STRATEGY "basic" ("74300") ON SYMBOL 1 COLUMN_NO 0 TICKS 1;

To enable a strategy on a specific HFT table symbol, execute this query. The statement accepts the strategy name (e.g. "basic"), its custom parameters like threshold limits in parenthesized quotes (e.g. "74300"), the symbol index (SYMBOL 1), the column number, and the ticks interval. Ticks indicate how often the strategy will evaluate conditions to optimize CPU consumption.

Listing All Active Strategies

Syntax & Example
LIST STRATEGY;

To inspect the database's strategy registry and view all currently compiled or active strategies, run this statement. The database returns a response mapping the registered strategy names to their respective compiled binary source file paths on disk.

Adding a Strategy from a JSON File

Syntax & Example
ADD STRATEGY FROM JSON FILE '/path/to/strategy.json';

You can define a strategy entirely via a JSON configuration file and load it at runtime with a single command. No recompilation is needed. Create a JSON file with the following structure and run the command above (replace the path with the actual location of your JSON file):

JSON Structure
{
    "name": "custom",
    "symbol": "6",
    "threshold": "10",
    "operator": "greater-than",
    "result": "(0+1)*(2/3)*6z"
}

Field Reference

FieldDescription
nameThe unique name for this strategy (used when enabling it later).
symbolThe HFT table symbol index this strategy targets.
thresholdThe numeric threshold value to compare the computed result against.
operatorThe comparison operator. Supported values: "greater-than", "less-than", "equal-to".
resultAn expression combining indicator indices with + and - operators. For example, "(0+1)*(2/3)*6z" means: result of (indicator[0] + indicator[1]) * (indicator[2] / indicator[3])*6. The final value is compared against the threshold using the operator.

Enabling a JSON-Loaded Strategy

Once loaded, enable the strategy the usual way. Since the parameter slot has no meaning for JSON-defined strategies, pass -1 as a placeholder:

ENABLE STRATEGY "custom" ("-1") ON SYMBOL 6 COLUMN_NO 0 TICKS 1;

Adding a Custom Strategy (C++ Codebase)

To integrate a new custom strategy into the database codebase, you need to add it directly to the engine source code. Follow these four steps:

Step 1: Create the strategy file inside faststrategy folder

Users have to create the strategy header file (e.g. basic2.hpp) and place it inside the 'faststrategy' folder in the codebase. The custom strategy must follow the standard template structure, detailed block-by-block below:

C++ Source
// Place your strategy class header file under ./faststrategy/

(Part 1): Member Variables & State Definition

Here, the local parameters of the strategy are defined.

C++ Source
class alignas(CACHELINE) BASIC {

private:
  int64_t tick = 0;                                                
  int64_t count = 0;                                                
  int64_t window = 1;                                               
  HFT::TableColumn *tableColumn = nullptr;                          
  std::array<HFT::TableColumn, HFT::MAXHFTSYMBOL> *symbolAccessArr; 
  const int64_t *precision = nullptr;

(Part 2): Constructor

In the constructor, the tableColumn and symbolAccessArr are initialized with global values. It is suggested to keep this part as is.

C++ Source
public:
  BASIC(std::array<HFT::TableColumn, HFT::MAXHFTSYMBOL> &symbolAccessArr,
      int64_t tick, HFT::TableColumn &tableColumn) {
    this->symbolAccessArr = &symbolAccessArr;
    this->tick = tick;
    this->count = 0;
    this->tableColumn = &tableColumn;
    this->precision = tableColumn.precisions;
  }

(Part 3): Parameter Parsing

Here the parameters passed in the SQL query are parsed (e.g., setting the window size).

C++ Source
  void set_parameter(const std::vector<std::string> &win) {
    std::string param = win[0];
    int64_t window = 1;
    auto [ptr, ec] = std::from_chars(param.data(), param.data() + param.size(), window);

    if (ec != std::errc() || window <= 0) {
      throw std::runtime_error("Invalid Strategy parameter");
    }
    this->window = window;
  }

(Part 4): Strategy Decision Engine

Here the actual strategy logic is implemented. It reads the computed indicator values and compares them with the parameters.

C++ Source
  inline bool result() {
    count++;
    if (LIKELY(count != tick)) {
      return false;
    }
    count = 0;
    auto const * __restrict entry = &this->tableColumn->indicators[0];
    int64_t val = entry->result_fn(entry->ptr);
    return val > this->window;
  }

(Part 5): Wrapper Callbacks

These are wrapper callbacks used by the engine to efficiently execute your strategy's update and decision logic.

C++ Source
  void on_tick() {}

  static void run(void *p) { static_cast<BASIC *>(p)->on_tick(); }
  static bool get_result(void *p) { return static_cast<BASIC *>(p)->result(); }

  FastStrategy::StrategyEntry create() {
    FastStrategy::StrategyEntry e;
    e.checked = 1;
    e.ptr = this;                       // Pointer to this strategy instance
    e.fn = &BASIC::run;                 // Static updater callback
    e.result_fn = &BASIC::get_result;   // Static decision logic callback
    e.strategyIndex = -1;
    return e;
  }
};

Step 2: Add registration line in strategyHandler.hpp

Add the registration line inside the 'registerAllStrategy' function of 'strategyHandler.hpp'. Here 'basic2' is replaced by the name of the indicator and make sure 'BASIC2' is the name of the class in the written indicator file.

C++ Source
registerStrategy<BASIC2, HFT::MAXHFTSYMBOL>(registry, "basic2", basicPool2);
// Inside registerAllStrategy function

Step 3: Include the path inside strategyinclude.hpp

Inside 'strategyinclude.hpp', write the include statement:

C++ Source
#include "./faststrategy/basic2.hpp"

Step 4: Add the strategy pool inside strategyPool.hpp

Inside 'strategyPool.hpp', add the static StrategyPool declaration. Make sure the name of the pool in the 2nd step and this step is exactly the same.

C++ Source
static StrategyPool<BASIC3, HFT::MAXHFTSYMBOL> basicPool3;

Important Note on Compilation

After making any changes to the codebase (such as adding a custom strategy), you must recompile the C++ engine regardless of how you installed the database:

./compile.sh

If you cloned from the GitHub repository:

You only need to run ./compile.sh. The resulting binary can be used directly — no further steps required.

If you installed via a Debian package (.deb):

After compiling, you must also rename the output binary from main.exe to nanodb, otherwise the database will not be usable:

mv main.exe nanodb

Natively Available Strategies

Basic Strategy (basic)

An in-memory threshold crossover strategy. It queries the first active indicator (index 0) associated with the HFT table symbol. If the computed indicator's value exceeds the specified parameter window threshold, the strategy signals a true (buy) trigger, otherwise false. This execution happens natively inside the engine loop for sub-microsecond latency.

Again Strategy (again)

A repeating crossover logic strategy similar to Basic. It polls the leading indicator on index 0 on every tick interval and triggers signals when values breach the threshold. It runs concurrently with other strategies, isolating CPU instructions and avoiding lock contentions.

Note: Strategies and indicators are not stored in disk for a table. So, if a server restarts you will again have to apply the strategies by using ENABLE STRATEGY command.