Python
Overview

Overview

Starting your Strategy

In order to create a strategy on cybotrade you only have to understand two things:

  1. A set of handlers for market events
  2. How to interact with the Cybotrade runtime

Let's take a deeper look on how to do them in code.

File Structure

You need to have three files, namely requirements.txt, exchange-keys.json and main.py.

    • requirements.txt
    • exchange-keys.json
    • main.py
  • requirements.txt

    This is the file where you include the dependencies of your Python script, which means you can use any external libraries as long as they are published on PyPi eg. TA-lib, numpy, pandas, etc. An example is shown as follows

    cybotrade>=1.4.20
    colorlog>=6.7.0
    TA-Lib>=0.4.26
    pandas>=2.0.2
    matplotlib>=3.7.1

    exchange-keys.json

    This is a required file that should be placed in the directory where you run the strategy, containing the API key and secrets for your preferred exchange.

    Kindly refer to the documentation of your preferred exchange on how to acquire these keys.

    The exchange-keys.json schema can be seen with the example below:

    [
      {
        "exchange": "{EXCHANGE}",
        "api_key": "{YOUR_EXCHANGE_API_KEY}",
        "api_secret": "{YOUR_EXCHANGE_API_SECRET}",
        "api_passphrase": "{YOUR_EXCHANGE_API_PASSPHRASE}",
        "environment": "{EXCHANGE_ENVIRONMENT}"
      }
    ]

    As of right now the only supported exchange value is bybit_linear.
    The table below shows the current supported exchanges.

    ExchangeSupported
    bitget_linear
    bitget_spot
    bybit_linear
    bybit_spot
    binance_linear
    binance_spot
    okx_linear
    okx_spot

    The exchange environment denoted here refers to the either mainnet or testnet. An example of this exchange-keys.json would be

    [
      {
        "exchange": "bybit_linear",
        "api_key": "my_bybit_api_key",
        "api_secret": "my_bybit_api_secret",
        "environment": "testnet"
      }
    ]
     

    Do note that some exchanges (such as OKX and Bitget that are currently supported) has a 'Passphrase' for their api-keys, this api_passphrase is optional and is only required when passing credentials for exchanges mentioned prior.

    [
      {
        "exchange": "bitget_linear",
        "api_key": "my_bitget_api_key",
        "api_secret": "my_bitget_api_secret",
        "api_passphrase": "my_bitget_api_passphrase"
        "environment": "mainnet"
      }
    ]

    For an example if your current working directory is strategies the file should be placed as such:

    • requirements.txt
    • exchange-keys.json
    • main.py
  • if your current working directory is before strategies then place the exchange-keys.json as such:

    • requirements.txt
    • exchange-keys.json
  • main.py

    This is the file where you write your Python code, the most important thing is that you must

    • Define your own 'strategy' class that extends the Strategy class from Cybotrade.
    from cybotrade.strategy import Strategy
     
    class MyStrategy(Strategy):
      pass

    Your 'strategy' class (MyStrategy) will be the main way to interact with the Cybotrade runtime by defining and overriding the handlers provided by the Strategy class from cybotrade.

    Strategy Implementation

    Handlers for market events

    The runtime subscribes market data stream such as candle, public trades etc. from exchanges and it is up to strategy developers to implement the handlers taken for these events. It can be done by overriding the Strategy class' methods.

    The most basic event handler is on_candle_closed, which is a handler for when a candle closes.

    from cybotrade.strategy import Strategy
     
    class MyStrategy(Strategy):
      # This method will be invoked every time a candle closes, if the strategy
      # subscribed to a 1 hour candle then this method will be invoked every hour.
      async def on_candle_closed(self, strategy, topic, symbol):
        # Here you can define the actions to be taken such as calculating indicators when
        # a candle has closed.
        print("A Candle has just closed!")
        ...

    There are also event handlers like on_order_update as well as on_datasource_interval that would be commonly used.

    from cybotrade.strategy import Strategy
     
    class MyStrategy(Strategy):
        async def on_candle_closed(self, strategy, topic, symbol):
            print("A Candle has just closed!")
            ...
     
        async def on_datasource_interval(self, strategy, topic, data_list):
            print("CryptoQuant open-interest data!")
     
        async def on_order_update(self, strategy, udpate):
            print("Take Profit!")
     

    There are a few additional event handlers provided by the Strategy class that can be overriden that is documented here.

    StrategyTrader

    The main way to interact with the Cybotrade runtime, in order to perform actions on the exchange of your choice, is to use the strategy parameter from the handler function. This parameter has a type of StrategyTrader.

    from cybotrade.strategy import Strategy
     
    class MyStrategy(Strategy):
        async def on_candle_closed(self, strategy, topic, symbol):
            # Retrieved available balance from exchange.
            bal = await strategy.get_current_available_balance(
                exchange=Exchange.BybitLinear,
                symbol=symbol
            )
            # Print available balance
            print(bal)

    The StrategyTrader is also how you are able to place orders:

    from cybotrade.strategy import Strategy
    from cybotrade.models import OrderSide, Exchange
     
    class MyStrategy(Strategy):
        async def on_candle_closed(self, strategy, topic, symbol):
            # Open a Buy order.
            await strategy.open(
                side=OrderSide.Buy,
                exchange=Exchange.BybitLinear,
                symbol=symbol,
                quantity=0.001,
            )
            print(f"Placed order for 0.001 of {symbol}")

    There are many APIs exposed by the StrategyTrader that is documented here.

    RuntimeConfig

    The RuntimeConfig struct provided by cybotrade is the self-explanatory it is the config settings for the Cybotrade Runtime.

    Data selection, such as candle data, as well as Cybotrade Datasource data will be defined here:

    from cybotrade.models RuntimeConfig, RuntimeMode
    from datetime import datetime, timezone
    ...
     
    config = RuntimeConfig(
        mode=RuntimeMode.LiveTestnet,
        datasource_topics=[],
        candle_topics=["candles-1d-BTC/USDT-bybit"],
        active_order_interval=1,
        start_time=datetime(2020, 4, 1, 0, 0, 0, tzinfo=timezone.utc),
        end_time=datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
        data_count=100,
        exchange_keys="path/to/your/exchange-keys.json",
    )

    This is the simplest possible RuntimeConfig setting.

    • The mode parameter value is the RuntimeMode enum provided by cybotrade and has two possible values, Live and LiveTestnet.
    • The datasource_topics parameter value is an array of strings that accepts datasource topics to retrieve Cybotrade Datasource data, more on this later.
    • The candle_topics parameter value is an array of strings, candle data is provided for free and can be referred to here.
    • The active_order_interval parameter is only relevant in RuntimeMode.Live and RuntimeMode.LiveTestnet, so this can be ignored for now.
    • The start_time and end_time parameters is the timeframe in which you would like to run your backtest. In this example it would be backtesting a strategy logic from 01-04-2020 to 01-01-2024.
    • The data_count parameter is not necessary to be understood for this guide, but it will be helpful to take a look at this documentation after running the guide.
    • The exchange_keys parameter value is the file path to your exchange-keys.json. Note that the path is based off your current working directory when you run the main.py script.

    There is an option for RuntimeConfig which is api_key and api_secret, this value is not required when requesting for candle data.

    There are a few additional options available for the RuntimeConfig that is described here.

    Permutation and Hyper Parameter Tuning

    Permutation

    As of the time of writing this, the current Cybotrade can only start the runtime through the Permutation class. This will be changed in the future.

    Currently in order to actually start and run the strategy, you will have to wrap the RuntimeConfig in the Permutation class provided by cybotrade.

    from cybotrade.permutation import Permutation
    ...
    permutation = Permutation(config)

    The Permutation class allows the user to run the same strategy multiple times with different parameters through Hyper Parameter Tuning.

    Hyper Parameters

    At face value, the Hyper Parameters is just a dictionary:

    hyper_parameters = {}

    Temporarily, for this example, we can leave the hyper_parameters empty.
    The use case for Permutations and Hyper Parameter Tuning is explored further here.

    Running the Strategy

    As of the time of writing, this is the only way to start a strategy, this will be updated in the future.

    In order to start the strategy, you will have to define an async function, this async function will then be passed into asyncio.run().

    If you're unsure as to what asyncio is, there is no need to worry. You can imagine it as an engine to run asynchronous functions upon. However, the only time it is used is in the starting of the strategy and nowhere else.

    In the async function, you will have to call upon the .run() method of the Permutation class passing in your overidden Strategy class (MyStrategy) as well as the hyper_parameters:

    import asyncio
    ...
    async def start_strategy():
        await permutation.run(hyper_parameters, MyStrategy)
     
    asyncio.run(start_strategy())

    Great job reaching here!

    Backtesting

    🚫

    As of writing, the latest Cybotrade no longer actively maintains Backtesting mode. We suggest performing backtesting with alternative methods to ensure accuracy.

    Deprecated Documentation

    You are all set and ready to run a simple backtest that places an order everytime a candle closes by combining all the logic above into one simple python file:

    from cybotrade.strategy import Strategy
    from cybotrade.models import OrderSide, Exchange, RuntimeConfig, RuntimeMode
    from datetime import datetime, timezone
    from cybotrade.permutation import Permutation
    import asyncio
     
    class MyStrategy(Strategy):
        async def on_candle_closed(self, strategy, topic, symbol):
            # Open a Buy order.
            await strategy.open(
                side=OrderSide.Buy,
                exchange=Exchange.BybitLinear,
                symbol=symbol,
                quantity=0.001,
            )
            print(f"Placed order for 0.001 of {symbol}")
     
    config = RuntimeConfig(
        mode=RuntimeMode.Backtest,
        datasource_topics=[],
        candle_topics=["candles-1d-BTC/USDT-bybit"],
        active_order_interval=1,
        start_time=datetime(2020, 4, 1, 0, 0, 0, tzinfo=timezone.utc),
        end_time=datetime(2024, 1, 1, 0, 0, 0, tzinfo=timezone.utc),
        data_count=100,
    )
     
    permutation = Permutation(config)
    hyper_parameters = {}
     
    async def start_runtime():
        await permutation.run(hyper_parameters, MyStrategy)
     
    asyncio.run(start_runtime())

    You should be able to run the following command to start your strategy:

    python main.py

    You will then be greeted with logs that should look similar to this: output of example strategy

    Congratulations! You just ran a backtest for a strategy!

    Backtest Performance

    Now you may notice that if you run the command

    ls

    you should be able to see a new json file generated like such: performance json in ls

    This performance_*.json can be plugged into our comprehensive Cybotrade Analysis (opens in a new tab) platform to perform performance analysis on your strategy!

    Moving Forward

    It is recommended to go through the following sequence to ensure your strategy is robust.

    LiveTestnet

    Execute your strategy on Testnet with RuntimeMode.LiveTestnet, to filter for any unexpected scenarios when running your automated strategy live!

    Live on Cybotrade Cloud

    Run your strategy without worries 24/7 on our Cybotrade Cloud (opens in a new tab) platform and start hitting your Take Profits!

    As simple as this example has made using cybotrade look, it is still much more flexible and powerful than what is presented here.

    There are more guides following this simple overview, that will extend upon what was used in this guide.

    A recommendation to start your approach is to go through these additional documentations: