Skip to content

Components

Session (Trading Session)

A session begins when you add to or reduce a position within the on_entry method.

A session closes when its position is adjusted to zero.

  • A long session starts by adding to the position
  • A short session starts by reducing the position

Multiple open sessions can exist simultaneously.

Each session contains these attributes:

  • position
  • transactions (read-only)
  • take_profit
  • stop_loss
  • meta

Position adjustments create new transactions (which combine both base and quote asset movements).

take_profit and stop_loss define session brackets.

Brackets usage:

from cipher import percent

session.take_profit = row["close"] * 1.015
session.take_profit = percent("1.5")  # +1.5% of current price for long sessions
session.stop_loss = percent("-1")     # -1% of current price for long sessions
session.stop_loss = None              # disable stop loss

meta is a dictionary-like object where you can store session state information.

Position

Each new session starts with a position of 0.

You can add to or reduce a position (positions can be negative).

Each position change creates a transaction.

There are multiple ways to adjust position:

from cipher import base, percent, quote

session.position = 1
session.position = base(1)           # same as above
session.position = "1"               # int, str, float are converted to Decimal
session.position = quote(100)        # sets position worth 100 quote asset units
session.position += 1                # adds to the position
session.position -= Decimal("1.25")  # reduces position by 1.25
session.position += percent(50)      # adds 50% more to the position
session.position *= 1.5              # same effect as above
session.position = session.position.value + Decimal(1)  # not recommended

Signals

The entry signal is required. You can define as many additional signals as needed.

To add a new signal: - Add a boolean column with the signal name to the dataframe returned by the compose method - Add a signal handler to the strategy: on_<signal_name>

on_entry is called only once for each new session.

on_<signal> is called for each open session.

on_step is similar to on_entry, but is called for every row in the dataframe.

Data Sources

data flow

Cipher supports multiple time series as input. These must be combined into a single dataframe in the compose method.

To add data sources:

cipher.add_source("binance_spot_ohlc", symbol="BTCUSDT", interval="1h")
cipher.add_source("binance_spot_ohlc", symbol="ETHUSDT", interval="1h")

Access data within the compose method:

def compose(self):
    self.datas[0]   # BTCUSDT OHLC data
    self.datas[1]   # ETHUSDT OHLC data
    self.datas.df   # shortcut for self.datas[0]

Sources

Sources read data from APIs, files, etc., in blocks and write them to files.

Several sources are included:

  • binance_futures_ohlc [symbol, interval]
  • binance_spot_ohlc [symbol, interval]
  • csv_file [path, ts_format]
  • gateio_spot_ohlc [symbol, interval]
  • yahoo_finance_ohlc [symbol, interval]

Strategy

The Strategy class tells Cipher when and how to adjust positions.

Interface:

from pandas import DataFrame

from .models import Datas, Wallet
from .proxies import SessionProxy as Session

class Strategy:
    datas: Datas
    wallet: Wallet

    # def __init__(self, param1, param2):
    #     self.param1 = param1
    #     self.param2 = param2

    def compose(self) -> DataFrame:
        return self.datas.df

    def on_entry(self, row: dict, session: Session) -> None:
        pass

    # def on_<signal>(self, row: dict, session: Session) -> None:
    #     pass

    def on_take_profit(self, row: dict, session: Session) -> None:
        session.position = 0

    def on_stop_loss(self, row: dict, session: Session) -> None:
        session.position = 0

    def on_stop(self, row: dict, session: Session) -> None:
        pass

Strategies are stored in files. Generate a new strategy using:

cipher new my_strategy

To run it:

python my_strategy.py

Cipher Instance

The Cipher instance connects all Cipher components together.

# Pass settings as kwargs, otherwise settings load from .env or ENV variables
cipher = Cipher()
cipher.set_strategy(strategy_object)
cipher.set_commission(commission_or_commission_object)
cipher.add_source(source_name_or_source_object, **source_kwargs)

# Process data according to strategy and generate output
cipher.run(start_ts, stop_ts)

cipher.sessions  # returns sessions
cipher.stats     # builds and returns stats object
cipher.output    # raw output containing dataframe and sessions

# If plotter or rows aren't specified, values are automatically selected
cipher.plot(plotter_or_plotter_object_or_none, rows_or_none)

Commission

Commission objects implement this interface:

from abc import ABC, abstractmethod
from decimal import Decimal

from cipher.models.transaction import Transaction

class Commission(ABC):
    @abstractmethod
    def for_transaction(self, transaction: Transaction) -> Decimal:
        pass

The for_transaction method returns how much quote asset should be deducted.

By default, SimpleCommission is used, which returns the specified portion of the quote for each transaction.

Commission is only computed for stats and plotting—it doesn't apply to the output.

Wallet

Access the wallet from a strategy:

self.wallet

The Cipher wallet has two assets: base and quote, both starting at 0.

The wallet has no limits, and assets can go negative.

Transactions are applied to the wallet, adjusting the asset balances.

Stats

Stats is the object returned by the cipher.stats property.

Use stats to evaluate strategy performance.

Plotters

Plotters take Output and generate charts.

Two plotters are currently available:

You can pass a custom plotter to cipher.plot.

Plotters accept rows that describe to the plotter how to group charts. Use this if the default layout doesn't fit your needs.

Rows can contain:

  • ohlc
  • ohlcv
  • signals
  • position
  • balance
  • sessions
  • brackets
  • <indicator_name>

Examples:

rows = [["ohlc"]]                            # show only OHLC
rows = [["ohlc", "ema50"]]                   # show EMA50 as well (must be in dataframe)
rows = [["ohlcv", "sessions"], ["balance"]]  # show OHLCV with session marks on top chart and balance below

plot also accepts limit (number of rows to show) and start (where the plot begins). start can be: datetime, offset, or negative offset.

Indicator names can include markers and colors. See markers here.

Example:

rows = [["ohlc", "my_indicator|^", "another_indicator|s|red"]]

Settings

Pass settings to Cipher as arguments, or use .env file or environment variables.

Available settings: cache_root, log_level.

cache_root: Contains the path to the cache folder. Default: .cache

If you have multiple directories with strategies and want to reuse one cache, specify the same cache_root for both.