{"id":13532627,"url":"https://github.com/nanvel/cipher-bt","last_synced_at":"2026-02-10T12:37:24.840Z","repository":{"id":65809015,"uuid":"584058734","full_name":"nanvel/cipher-bt","owner":"nanvel","description":"Trading strategy backtesting framework with focus on position adjustment in a session scope.","archived":false,"fork":false,"pushed_at":"2023-12-08T16:13:40.000Z","size":4166,"stargazers_count":16,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-28T02:49:58.573Z","etag":null,"topics":["backtest","backtester","backtesting","backtesting-frameworks","framework","python","quant","trading","trading-simulator"],"latest_commit_sha":null,"homepage":"https://cipher.nanvel.com","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nanvel.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-01-01T06:15:53.000Z","updated_at":"2025-02-19T17:25:16.000Z","dependencies_parsed_at":"2023-12-08T17:26:58.906Z","dependency_job_id":"c5181793-03ee-4d0a-8575-7a54bfd7543e","html_url":"https://github.com/nanvel/cipher-bt","commit_stats":{"total_commits":34,"total_committers":2,"mean_commits":17.0,"dds":0.4117647058823529,"last_synced_commit":"d44b70b2e7550474c3d581fb1106673766eb4e0c"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanvel%2Fcipher-bt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanvel%2Fcipher-bt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanvel%2Fcipher-bt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nanvel%2Fcipher-bt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nanvel","download_url":"https://codeload.github.com/nanvel/cipher-bt/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246712931,"owners_count":20821819,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backtest","backtester","backtesting","backtesting-frameworks","framework","python","quant","trading","trading-simulator"],"created_at":"2024-08-01T07:01:12.431Z","updated_at":"2026-02-10T12:37:24.834Z","avatar_url":"https://github.com/nanvel.png","language":"Python","funding_links":[],"categories":["Backtest + live trading"],"sub_categories":["General purpose"],"readme":"# Cipher - Trading Strategy Backtesting Framework\n\n![Tests](https://github.com/nanvel/cipher-bt/actions/workflows/tests.yaml/badge.svg)\n[![PyPI version](https://badge.fury.io/py/cipher-bt.svg)](https://badge.fury.io/py/cipher-bt)\n[![Python Versions](https://img.shields.io/pypi/pyversions/cipher-bt.svg)](https://pypi.python.org/pypi/cipher-bt/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\n![cipher](https://github.com/nanvel/cipher-bt/raw/master/docs/cipher.jpeg)\n\n- [Usage](#usage)  \n- [Development](#development)\n- [Disclaimer](#disclaimer)\n\nDocumentation: https://cipher.nanvel.com\n\n**Features:**\n\n- Well-structured, intuitive, and easily extensible design\n- Support for multiple concurrent trading sessions\n- Sophisticated exit strategies including trailing take profits\n- Multi-source data integration (exchanges, symbols, timeframes)\n- Clean separation between signal generation and handling\n- Simple execution - just run `python my_strategy.py`\n- Compatibility with [Google Colab](https://colab.research.google.com/)\n- Built-in visualization with [finplot](https://github.com/highfestiva/finplot) and [mplfinance](https://github.com/matplotlib/mplfinance) plotters\n\n## Usage\n\nSet up a new strategies workspace and create your first strategy:\n```shell\nmkdir strategies\ncd strategies\nuv init\nuv add 'cipher-bt[finplot,talib]'\n\nuv run cipher init\nuv run cipher new my_strategy\nuv run python my_strategy.py\n```\n\nComplete EMA crossover strategy example:\n```python\nimport numpy as np\nimport talib\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\"] = talib.EMA(df[\"close\"], timeperiod=self.fast_ema_length)\n        df[\"slow_ema\"] = talib.EMA(df[\"close\"], timeperiod=self.slow_ema_length)\n        df[\"trend_ema\"] = talib.EMA(df[\"close\"], timeperiod=self.trend_ema_length)\n\n        df[\"difference\"] = df[\"fast_ema\"] - df[\"slow_ema\"]\n\n        # Signal columns must 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\"] \u003e 0 and row[\"close\"] \u003e row[\"trend_ema\"]:\n            # Open a new long position\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\"] \u003c 0 and row[\"close\"] \u003c row[\"trend_ema\"]:\n            # Open a new short position\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_\u003csignal\u003e(self, row: dict, session: Session) -\u003e None:\n    #     \"\"\"Custom signal handler called for each active session.\n    #     Adjust or close positions and modify brackets here.\"\"\"\n    #     # session.position = 1\n    #     # session.position = base(1)  # equivalent to the above\n    #     # session.position = '1'  # int, str, float are converted to Decimal\n    #     # session.position = quote(100)  # position worth 100 quote asset\n    #     # session.position += 1  # add to the position\n    #     # session.position -= Decimal('1.25')  # reduce position by 1.25\n    #     # session.position += percent(50)  # add 50% more to position\n    #     # session.position *= 1.5  # equivalent to the above\n    #     pass\n    #\n    # def on_take_profit(self, row: dict, session: Session) -\u003e None:\n    #     \"\"\"Called when take profit is hit. Default action closes the position.\n    #     Modify position and brackets here to continue the session.\"\"\"\n    #     session.position = 0\n    #\n    # def on_stop_loss(self, row: dict, session: Session) -\u003e None:\n    #     \"\"\"Called when stop loss is hit. Default action closes the position.\n    #     Modify position and brackets here to continue the session.\"\"\"\n    #     session.position = 0\n    #\n    # def on_stop(self, row: dict, session: Session) -\u003e None:\n    #     \"\"\"Called for each active session when dataframe ends.\n    #     Close open sessions here, 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=\"2025-01-01\", stop_ts=\"2025-04-01\")\n    cipher.set_commission(\"0.00075\")\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 uv\nuv sync --all-extras\nsource .venv/bin/activate\n\npytest tests\ncipher --help\n```\n\n## Disclaimer\n\nThis software is for educational purposes only. Do not risk money you cannot afford to lose.\nUSE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnanvel%2Fcipher-bt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnanvel%2Fcipher-bt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnanvel%2Fcipher-bt/lists"}