cipher-bt


Namecipher-bt JSON
Version 0.3.10 PyPI version JSON
download
home_pagehttps://cipher.nanvel.com/
SummaryCipher, a backtesting framework.
upload_time2023-02-11 10:21:37
maintainer
docs_urlNone
authorOleksandr Polieno
requires_python>=3.8,<4.0
license
keywords backtest quant trading crypto framework colab strategy
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Cipher - trading strategy backtesting framework

![Tests](https://github.com/nanvel/cipher-bt/actions/workflows/tests.yaml/badge.svg)

- [Usage](#usage)  
- [Development](#development)
- [Disclaimer](#disclaimer)

Documentation: https://cipher.nanvel.com

Features:

- well-structured, simple to use, extensible
- multiple trading sessions at the same time
- complex exit strategies can be implemented (trailing take profit, etc.)
- multiple data sources support (multiple exchanges, symbols, timeframes, etc.)
- signal generation and signal handlers are splitted
- simple to run, just `python my_strategy.py`
- works in [Google Colab](https://colab.research.google.com/)
- [finplot](https://github.com/highfestiva/finplot) and [mplfinance](https://github.com/matplotlib/mplfinance) plotters
- TA: [pandas-ta](https://github.com/twopirllc/pandas-ta) is included, you can still use your libraries of choice

## Usage

Initialize a new strategies folder and create a strategy:
```shell
pip install 'cipher-bt[finplot]'
mkdir my_strategies
cd my_strategies

cipher init
cipher new my_strategy
python my_strategy.py
```

EMA crossover strategy example:
```python
import numpy as np

from cipher import Cipher, Session, Strategy


class EmaCrossoverStrategy(Strategy):
    def __init__(self, fast_ema_length=9, slow_ema_length=21, trend_ema_length=200):
        self.fast_ema_length = fast_ema_length
        self.slow_ema_length = slow_ema_length
        self.trend_ema_length = trend_ema_length

    def compose(self):
        df = self.datas.df
        df["fast_ema"] = df.ta.ema(length=self.fast_ema_length)
        df["slow_ema"] = df.ta.ema(length=self.slow_ema_length)
        df["trend_ema"] = df.ta.ema(length=self.trend_ema_length)

        df["difference"] = df["fast_ema"] - df["slow_ema"]

        # signal columns have to be boolean type
        df["entry"] = np.sign(df["difference"].shift(1)) != np.sign(df["difference"])

        df["max_6"] = df["high"].rolling(window=6).max()
        df["min_6"] = df["low"].rolling(window=6).min()

        return df

    def on_entry(self, row: dict, session: Session):
        if row["difference"] > 0 and row["close"] > row["trend_ema"]:
            # start a new long session
            session.position += "0.01"
            session.stop_loss = row["min_6"]
            session.take_profit = row["close"] + 1.5 * (row["close"] - row["min_6"])

        elif row["difference"] < 0 and row["close"] < row["trend_ema"]:
            # start a new short session
            session.position -= "0.01"
            session.stop_loss = row["max_6"]
            session.take_profit = row["close"] - 1.5 * (row["max_6"] - row["close"])

    # def on_<signal>(self, row: dict, session: Session) -> None:
    #     """Custom signal handler, called for each open session.
    #     We can adjust or close position or adjust brackets here."""
    #     # session.position = 1
    #     # session.position = base(1)  # same as the one above
    #     # session.position = '1'  # int, str, float are being converted to Decimal
    #     # session.position = quote(100)  # sets position worth 100 quote asset
    #     # session.position += 1  # adds to the position
    #     # session.position -= Decimal('1.25')  # reduces position by 1.25
    #     # session.position += percent(50)  # adds 50% more position
    #     # session.position *= 1.5  # has the same effect as the one above
    #     pass
    #
    # def on_take_profit(self, row: dict, session: Session) -> None:
    #     """Called once take profit hit, default action - close position.
    #     We can adjust the position and brackets here and let the session continue."""
    #     session.position = 0
    #
    # def on_stop_loss(self, row: dict, session: Session) -> None:
    #     """Called once stop loss hit, default action - close position.
    #     We can adjust the position and brackets here and let the session continue."""
    #     session.position = 0
    #
    # def on_stop(self, row: dict, session: Session) -> None:
    #     """Called for each open session when the dataframe end reached.
    #     We have an opportunity to close open sessions, otherwise - they will be ignored."""
    #     session.position = 0


def main():
    cipher = Cipher()
    cipher.add_source("binance_spot_ohlc", symbol="BTCUSDT", interval="1h")
    cipher.set_strategy(EmaCrossoverStrategy())
    cipher.run(start_ts="2020-01-01", stop_ts="2020-04-01")
    cipher.set_commission("0.0025")
    print(cipher.sessions)
    print(cipher.stats)
    cipher.plot()


if __name__ == "__main__":
    main()
```

![ema_crossover_plot](https://github.com/nanvel/cipher-bt/raw/master/docs/plotter.png)

## Development

```shell
brew install poetry
poetry install
poetry shell

pytest tests

cipher --help
```

## Disclaimer

This software is for educational purposes only. Do not risk money which you are afraid to lose.
USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.

            

Raw data

            {
    "_id": null,
    "home_page": "https://cipher.nanvel.com/",
    "name": "cipher-bt",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.8,<4.0",
    "maintainer_email": "",
    "keywords": "backtest,quant,trading,crypto,framework,colab,strategy",
    "author": "Oleksandr Polieno",
    "author_email": "oleksandr@nanvel.com",
    "download_url": "https://files.pythonhosted.org/packages/63/1d/19409c8bba3b07fa0e76ef86f576e00d8316e5174ad1d6a86bf036cf5794/cipher_bt-0.3.10.tar.gz",
    "platform": null,
    "description": "# Cipher - trading strategy backtesting framework\n\n![Tests](https://github.com/nanvel/cipher-bt/actions/workflows/tests.yaml/badge.svg)\n\n- [Usage](#usage)  \n- [Development](#development)\n- [Disclaimer](#disclaimer)\n\nDocumentation: https://cipher.nanvel.com\n\nFeatures:\n\n- well-structured, simple to use, extensible\n- multiple trading sessions at the same time\n- complex exit strategies can be implemented (trailing take profit, etc.)\n- multiple data sources support (multiple exchanges, symbols, timeframes, etc.)\n- signal generation and signal handlers are splitted\n- simple to run, just `python my_strategy.py`\n- works in [Google Colab](https://colab.research.google.com/)\n- [finplot](https://github.com/highfestiva/finplot) and [mplfinance](https://github.com/matplotlib/mplfinance) plotters\n- TA: [pandas-ta](https://github.com/twopirllc/pandas-ta) is included, you can still use your libraries of choice\n\n## Usage\n\nInitialize a new strategies folder and create a strategy:\n```shell\npip install 'cipher-bt[finplot]'\nmkdir my_strategies\ncd my_strategies\n\ncipher init\ncipher new my_strategy\npython my_strategy.py\n```\n\nEMA crossover strategy example:\n```python\nimport numpy as np\n\nfrom cipher import Cipher, Session, Strategy\n\n\nclass EmaCrossoverStrategy(Strategy):\n    def __init__(self, fast_ema_length=9, slow_ema_length=21, trend_ema_length=200):\n        self.fast_ema_length = fast_ema_length\n        self.slow_ema_length = slow_ema_length\n        self.trend_ema_length = trend_ema_length\n\n    def compose(self):\n        df = self.datas.df\n        df[\"fast_ema\"] = df.ta.ema(length=self.fast_ema_length)\n        df[\"slow_ema\"] = df.ta.ema(length=self.slow_ema_length)\n        df[\"trend_ema\"] = df.ta.ema(length=self.trend_ema_length)\n\n        df[\"difference\"] = df[\"fast_ema\"] - df[\"slow_ema\"]\n\n        # signal columns have to be boolean type\n        df[\"entry\"] = np.sign(df[\"difference\"].shift(1)) != np.sign(df[\"difference\"])\n\n        df[\"max_6\"] = df[\"high\"].rolling(window=6).max()\n        df[\"min_6\"] = df[\"low\"].rolling(window=6).min()\n\n        return df\n\n    def on_entry(self, row: dict, session: Session):\n        if row[\"difference\"] > 0 and row[\"close\"] > row[\"trend_ema\"]:\n            # start a new long session\n            session.position += \"0.01\"\n            session.stop_loss = row[\"min_6\"]\n            session.take_profit = row[\"close\"] + 1.5 * (row[\"close\"] - row[\"min_6\"])\n\n        elif row[\"difference\"] < 0 and row[\"close\"] < row[\"trend_ema\"]:\n            # start a new short session\n            session.position -= \"0.01\"\n            session.stop_loss = row[\"max_6\"]\n            session.take_profit = row[\"close\"] - 1.5 * (row[\"max_6\"] - row[\"close\"])\n\n    # def on_<signal>(self, row: dict, session: Session) -> None:\n    #     \"\"\"Custom signal handler, called for each open session.\n    #     We can adjust or close position or adjust brackets here.\"\"\"\n    #     # session.position = 1\n    #     # session.position = base(1)  # same as the one above\n    #     # session.position = '1'  # int, str, float are being converted to Decimal\n    #     # session.position = quote(100)  # sets position worth 100 quote asset\n    #     # session.position += 1  # adds to the position\n    #     # session.position -= Decimal('1.25')  # reduces position by 1.25\n    #     # session.position += percent(50)  # adds 50% more position\n    #     # session.position *= 1.5  # has the same effect as the one above\n    #     pass\n    #\n    # def on_take_profit(self, row: dict, session: Session) -> None:\n    #     \"\"\"Called once take profit hit, default action - close position.\n    #     We can adjust the position and brackets here and let the session continue.\"\"\"\n    #     session.position = 0\n    #\n    # def on_stop_loss(self, row: dict, session: Session) -> None:\n    #     \"\"\"Called once stop loss hit, default action - close position.\n    #     We can adjust the position and brackets here and let the session continue.\"\"\"\n    #     session.position = 0\n    #\n    # def on_stop(self, row: dict, session: Session) -> None:\n    #     \"\"\"Called for each open session when the dataframe end reached.\n    #     We have an opportunity to close open sessions, otherwise - they will be ignored.\"\"\"\n    #     session.position = 0\n\n\ndef main():\n    cipher = Cipher()\n    cipher.add_source(\"binance_spot_ohlc\", symbol=\"BTCUSDT\", interval=\"1h\")\n    cipher.set_strategy(EmaCrossoverStrategy())\n    cipher.run(start_ts=\"2020-01-01\", stop_ts=\"2020-04-01\")\n    cipher.set_commission(\"0.0025\")\n    print(cipher.sessions)\n    print(cipher.stats)\n    cipher.plot()\n\n\nif __name__ == \"__main__\":\n    main()\n```\n\n![ema_crossover_plot](https://github.com/nanvel/cipher-bt/raw/master/docs/plotter.png)\n\n## Development\n\n```shell\nbrew install poetry\npoetry install\npoetry shell\n\npytest tests\n\ncipher --help\n```\n\n## Disclaimer\n\nThis software is for educational purposes only. Do not risk money which you are afraid to lose.\nUSE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.\n",
    "bugtrack_url": null,
    "license": "",
    "summary": "Cipher, a backtesting framework.",
    "version": "0.3.10",
    "split_keywords": [
        "backtest",
        "quant",
        "trading",
        "crypto",
        "framework",
        "colab",
        "strategy"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "2b830dca92cce9a6334f52396a4399c5887866e8801fd8bc056194b6a8675425",
                "md5": "92161aa2bfa8a2b5c655ca20cadba34b",
                "sha256": "0ec9092afadf6e410b9225dee08d4abc6c963596880bc9c1e12ad0c678c0f6d3"
            },
            "downloads": -1,
            "filename": "cipher_bt-0.3.10-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "92161aa2bfa8a2b5c655ca20cadba34b",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8,<4.0",
            "size": 39195,
            "upload_time": "2023-02-11T10:21:36",
            "upload_time_iso_8601": "2023-02-11T10:21:36.218452Z",
            "url": "https://files.pythonhosted.org/packages/2b/83/0dca92cce9a6334f52396a4399c5887866e8801fd8bc056194b6a8675425/cipher_bt-0.3.10-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "631d19409c8bba3b07fa0e76ef86f576e00d8316e5174ad1d6a86bf036cf5794",
                "md5": "3f59a93659e797aa9411d3470248346c",
                "sha256": "6d48c5ae480da676483d640cb0ee60f449da567ce3711e458f079d62cc1319c0"
            },
            "downloads": -1,
            "filename": "cipher_bt-0.3.10.tar.gz",
            "has_sig": false,
            "md5_digest": "3f59a93659e797aa9411d3470248346c",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8,<4.0",
            "size": 26600,
            "upload_time": "2023-02-11T10:21:37",
            "upload_time_iso_8601": "2023-02-11T10:21:37.708722Z",
            "url": "https://files.pythonhosted.org/packages/63/1d/19409c8bba3b07fa0e76ef86f576e00d8316e5174ad1d6a86bf036cf5794/cipher_bt-0.3.10.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-02-11 10:21:37",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "lcname": "cipher-bt"
}
        
Elapsed time: 0.05721s