Skip to content

Tutorial: ccc_signal_taker

It is a good idea to seperate signal generation logic and execution logic, and break down signal geneneration logic into multiple components. It allows you to reuse well-tested components and avoid calculating the same value multiple times. Apfiny Algo's strategy framework is designed to achieve this goal.

In this tutorial, we will use variables to implement signal generation logic, and then create a strategy to use the signal generated by the variables.

Create a simple variable

#pragma once
#include "base/Variable.h"

using namespace std;

class TrendVar: public Variable {
public:
    using Variable::Variable;
    void init() override;
    void onQuote(long cid, const SymbolInfo& si) override { setUpdated(); }
    void onTick(long cid, const SymbolInfo& si) override {}
    void onNotify() override;

private:
    SymbolInfo* _si;
    nanoseconds _dur;

    nanoseconds _lastSampleTime = nanoseconds(0);
    double _lastPx = 0.0;
};
#include "TrendVar.h"
#include "components/TimerManager.h"
#include "components/SymbolManager.h"

void TrendVar::init() {
    auto symMgr = SymbolManager::instance();

    string ticker = _cfg.at("port").at(0);
    string exch = _cfg.at("port").at(1);
    auto sh = symMgr->getSymbolHandler(ticker, exch);
    sh->addListener(this);
    _si = &sh->symbolInfo();

    int dur = _cfg.at("dur");
    _dur = nanoseconds(dur);
}

void TrendVar::onNotify() {
    const nanoseconds& now = TimerManager::instance()->currentTime();
    if(now > _lastSampleTime + _dur) {
        double px = _si->mid_px();
        if(_lastPx > EPS) {
            _value = px - _lastPx;
            _ready = true;
        }
        else {
            _value = 0.0;
            _ready = false;
        }
        _lastSampleTime = now;
        _lastPx = px;       
    }
}

Create trading strategy

#pragma once
#include "base/Strategy.h"
#include "components/TimerManager.h"
#include "base/Order.h"
#include "base/SymbolInfo.h"
#include "base/Variable.h"

class SignalTakeStrategy: public Strategy, public OrderSender {
public:
    using Strategy::Strategy;

    //implements Strategy
    void init() override;
    void onNotify() override;
    SymbolInfo *getSymbolInfo() override { return _si; }
    void onCommand(json& cmd) override {}
    bool isUseMargin() override { return _useMargin; }
    string& marginSource() override { return _marginSource; }

    //imlements SymbolListener
    void onQuote(long cid, const SymbolInfo& si) override;
    void onTick(long cid, const SymbolInfo& si) override;
    void onQuoteStale(long cid, const SymbolInfo& si) override {}

    //implements TimerListener
    void onTimer(TimerHandler* th, long msecs) override;

    //implements OrderSender
    void onOrderCreated(LOrder* order) override {};
    void onOrderAcked(LOrder* order) override {};
    void onOrderRejected(LOrder* order) override {};
    void onOrderCancelCreated(LOrder* order) override {};
    void onOrderCancelAcked(LOrder* order) override {};
    void onOrderCancelRejected(LOrder* order) override {};
    void onOrderExec(TradeDirs side, double px, double qty, Liquidity liq, LOrder* order) override {};
    void onOrderCanceled(LOrder* order) override {};
    void onOrderClosed(LOrder* order) override {};

protected:
    void sendIocOrder(TradeDirs side, double px, double qty);

protected:
    TimerManager *_timerMgr;
    SymbolInfo* _si;
    bool _useMargin = false;
    string _marginSource = "spot";
    bool _hasTimeEvt = false;

    Variable* _signalVar;
    double _order_size;
};
#include "SignalTakeStrategy.h"
#include "components/VariableManager.h"
#include "components/TradeApiManager.h"
#include "components/SymbolManager.h"

void SignalTakeStrategy::init() {
    auto symMgr = SymbolManager::instance();
    auto varMgr = VariableManager::instance();

    string symbol = _cfg.at("symbol");
    string exch = _cfg.at("trade_market");
    auto sh = symMgr->getSymbolHandler(symbol, exch);
    sh->addListener(this);
    _si = &sh->symbolInfo();

    _timerMgr = TimerManager::instance();
    TimerHandler* th = _timerMgr->getTimerHandler(60000);
    th->addListener(this);

    _order_size = _cfg.at("order_size");

    string signal = _cfg.at("signal");
    _signalVar = varMgr->getVariable(signal);
    addNotifiableChild(_signalVar); 
}

void SignalTakeStrategy::onNotify() {
    if(!_si->is_ready()) 
        return;

    if(!_signalVar->is_ready())
        return;

    if(_hasTimeEvt) {
        _hasTimeEvt = false;

        double midpx = _si->mid_px();
        double signal = _signalVar->value();

        if(signal > 0.01) {
            sendIocOrder(SELL, _si->bid_px, _order_size);
        } else if(signal < -0.01) {
            sendIocOrder(BUY, _si->ask_px, _order_size);
        }
    }
}

void SignalTakeStrategy::sendIocOrder(TradeDirs side, double px, double qty) {
    std::cout << "send IOC order: " << side << " " << px << " " << qty << std::endl;

    LOrder* ord = new LOrder();
    ord->sender = this;
    ord->account = 101;
    ord->use_margin = false;
    ord->margin_source = "spot";
    ord->side = side;
    ord->si = _si;
    ord->px = px;
    ord->qty = qty;
    ord->remainingQty = ord->qty;
    ord->signal = 0.0;
    ord->spread = _si->spread();
    ord->tif = Tif::TIF_IOC;
    ord->intention = OrderIntention::OI_AGGRESSIVE;

    TradeApi* tradeApi = TradeApiManager::instance()->tradeApi();
    tradeApi->sendOrder(ord);
}

void SignalTakeStrategy::onQuote(long cid, const SymbolInfo& si) {

}

void SignalTakeStrategy::onTick(long cid, const SymbolInfo& si) {

}

void SignalTakeStrategy::onTimer(TimerHandler* th, long msecs) {
    _hasTimeEvt = true;
}

Register your variable class and strategy class

class MyComponentFactory : public XlibComponentFactory
{
public:
    Variable *getVariable(const string &type, const string &name, const json &attr) override
    {
        if (type == "TrendVar") 
            return new TrendVar(name, attr);            
        return nullptr;
    }   

    Strategy *getStrategy(const string &type, const string &name, const json &attr) override
    {
        if (type == "SignalTakeStrategy") 
            return new SignalTakeStrategy(name, attr);            
        return nullptr;
    }   
};

Complete the application

int main(int argc, char **argv)
{
    if (argc <= 1)
    {
        std::cout << "Usage: ccc_simple_taker cfg_path" << std::endl;
        return 0;
    }

    //load configuration
    std::time_t t = std::time(0);
    std::tm *now = std::localtime(&t);
    int date = (now->tm_year + 1900) * 10000 + (now->tm_mon + 1) * 100 + now->tm_mday;
    json cfg = loadConfig(argv[1],date);
    json &instCfg = cfg["instance"];
    instCfg["tradeDate"] = date;
    instCfg["isLive"] = true;

    //setup logger
    string instName = getJsonValue(instCfg, "name", string("instname"));
    string logPath = getJsonValue(instCfg, "log_path", string("."));
    date = instCfg["tradeDate"];
    logPath += "/" + to_string(date);
    mkdirs(logPath);
    logPath += "/inst_" + instName + ".log";
    int logLevel = getJsonValue(instCfg, "log_level", qts::log4z::LOG_LEVEL_DEBUG);
    QTS_LOG_START(logLevel, logPath);

    //setup your custom component factory to load components
    XlibComponentFactoryPtr xmgr(new MyComponentFactory);
    VariableManager::instance()->addXlibComponentFactory(xmgr);
    StrategyManager::instance()->addXlibComponentFactory(xmgr);

    //start cctrader
    std::cout << "Starting trader ..." << std::endl;
    CCTradeEngine client(cfg);
    client.initialize();
    client.run();
}

Configure

We use a json file to configure the trading applications The file contains information about api keys, log path, and strategy components. Please update the API key information in the example configuration below:

{
    "instance": {
        "license_id":"",
        "license_key":"",
        "log_path": "./signal_taker_test_01",        
        "name": "signal_taker_test_01"
    },      
    "servers":{
        "redis_server":"127.0.0.1"                           
    }, 
    "exchanges":[
        {"exchange":"OKEX_SWAP","trade_type":"Direct","market_data_type":"Direct"}
    ],
    "apikeys": {
     "OKEX_SWAP": {
        "key": "enter your api key",
        "secret": "enter your api secert",
        "pass": "enter your api passphrase"
         }      
    },
    "fees": {
        "OKEX_SWAP": {
            "make": 0.0,
            "take": 0.0002
        }
    },    
    "symbol_info": {
    }, 
    "symbols": [
        {"cid": 1001, "port": ["BTCUSDTSWAP", "OKEX_SWAP"]}        
    ],
    "samplers": [
    ],
    "pricing_models": [
    ],
    "variables": [
        ["BTCUSDTSWAP.OKEX_SWAP_trend30", ["TrendVar", {"port": ["BTCUSDTSWAP", "OKEX_SWAP"], "dur": 1800}]]    
    ],
    "models": [
    ],
    "strategies": [                                 
        ["BTCUSDTSWAP.OKEX_SWAP", [
            "SignalTakeStrategy", {
                "symbol": "BTCUSDTSWAP",
                "trade_market": "OKEX_SWAP",
                "order_size": 0.0001,
                "signal": "BTCUSDTSWAP.OKEX_SWAP_trend30"
            }]]                          
    ]
}

Build

The release contains the complete source code and the premake file used to build it. Follow the steps below to build:

cd algo_sdk/examples/ccc_signal_taker/build_scripts
premake4 gmake
make -j 10 config=release

Run

Apfiny Algo depends on several shared libraries, so you need setup some environment variables first.

Set environment variable ALGO_HOME using the path of your algo_sdk. e.g. /data/cc/algo_sdk:

export ALGO_HOME=YOUR_ALGO_SDK_PATH

Setup other environment variables:

export TZ=UTC
export LD_LIBRARY_PATH=${ALGO_HOME}/bin:$LD_LIBRARY_PATH
export PATH=${ALGO_HOME}/bin:$PATH

You can start the application now. It takes one command line argument, which is the path to a json configuration file.

ccc_signal_taker ${ALGO_HOME}/examples/ccc_signal_taker/cfg/signal_taker_cfg.json