# Overfitting
A robust and modular backtesting engine designed for crypto futures trading strategies.
Built for speed, simplicity, and accuracy. Overfitting simulates a realistic crypto trading environment — including **liquidation**, **margin**, and **leverage** — for stress-testing your strategies.
## Prerequisites
Before using **Overfitting**, you’ll need to provide your own historical data.
The engine is designed to work with **crypto futures price data**, with **OHLCV format**.
### Required Columns
Your dataset must be a CSV or DataFrame that includes at least the following columns:
- timestamp, open, high, low, close
- `timestamp` should be a **UNIX timestamp in seconds or milliseconds**
## Installation
$ pip install overfitting
## Usage
```python
import pandas as pd
from overfitting import Strategy
def load_data():
df = pd.read_csv('./data/BTCUSDT.csv')
benchamrk_df = pd.read_csv('./data/BTCUSDT.csv') # BTC buy and Hold
df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
df.set_index('timestamp', inplace=True)
start_time = pd.to_datetime('2023-01-01 00:00:00')
df = df.loc[start_time:]
# Compute short and long SMAs
df['sma_short'] = df['close'].rolling(window=20).mean().shift(1)
df['sma_long'] = df['close'].rolling(window=50).mean().shift(1)
return df, benchamrk_df
class MyStrategy(Strategy):
def init(self):
self.asset = 'BTC'
self.set_leverage(self.asset, 1)
def next(self, i):
if i == 0:
return
sma_short = self.val(self.asset, i, "sma_short")
sma_long = self.val(self.asset, i, "sma_long")
previous_sma_short = self.val(self.asset, i - 1, "sma_short")
previous_sma_long = self.val(self.asset, i - 1, "sma_long")
# Also skip if values are not available
if (pd.isna(sma_short) or pd.isna(sma_long) or
pd.isna(previous_sma_short) or pd.isna(previous_sma_long)):
return
# Fetch the current position
position = self.get_position(self.asset)
# Golden cross (entry)
if previous_sma_short <= previous_sma_long and sma_short > sma_long and position.qty == 0:
# First fetch current open price which is the target Price
open_price = self.open(self.asset, i)
# Determine Lot Size
lot_size = self.get_balance() // open_price
# Create LIMIT ORDER
self.limit_order(self.asset, lot_size, open_price)
# Death cross (exit)
if previous_sma_short >= previous_sma_long and sma_short < sma_long and position.qty > 0:
self.market_order(self.asset, -position.qty)
backtest_data, benchmark_data = load_data()
strategy = MyStrategy(
data=backtest_data,
benchmark=benchmark_data, # Default = None Optional
initial_capital=100_000, # Default Optional
commission_rate=0.0002, # Default Optional
maint_maring_rate=0.005, # Default Optional
maint_amount=50 # Default Optional
)
returns = strategy.run()
strategy.plot(returns)
```
Results
-------
```text
Performance Summary
Number of Years 1.66000000
Start Date 2023-01-01 00:00:00
End Date 2024-08-29 00:00:00
Initial Balance 100,000.00000000
Final Balance 205,328.91120000
CAGR 0.52684228
Cumulative Return 2.05328911
Sharpe Ratio 1.24678659
Sortino Ratio 3.54979579
Max Drawdown -0.26332695
Daily Value At Risk -0.04147282
Skew 0.44515551
Kurtosis 2.66444346
Total Trades 182.00000000
Winning Trades 69.00000000
Losing Trades 113.00000000
Win Rate (%) 37.91208791
Gross Profit 399,044.19246000
Gross Loss -293,715.28126000
Net Profit 105,328.91120000
Avg Return (%) 0.38834383
Avg Profit (%) 3.54140613
Avg Loss (%) -1.53697740
Net drawdown in % Peak date Valley date Recovery date Duration
0 26.332695 2024-03-13 2024-06-30 NaT NaN
1 19.678014 2023-03-20 2023-09-07 2023-10-26 159
2 6.297244 2023-12-07 2024-01-24 2024-02-14 50
3 5.585429 2023-01-22 2023-02-14 2023-02-17 20
4 3.898568 2023-02-17 2023-03-11 2023-03-15 19
5 3.336877 2023-11-12 2023-11-18 2023-12-07 19
6 2.699556 2024-02-20 2024-02-26 2024-03-01 9
7 0.767196 2024-03-01 2024-03-03 2024-03-06 4
8 0.324161 2023-01-03 2023-01-07 2023-01-18 12
9 0.019817 2023-11-03 2023-11-04 2023-11-07 3
```
## Performance Visualizations Examples




## Liquidation Handling
Unlike many basic backtesting engines, **overfitting** simulates realistic crypto futures trading, including **forced liquidation** based on margin conditions.
The liquidation logic is based on **isolated margin mode** (similar to Binance Futures):
- **Initial Margin** = Entry Price × Quantity / Leverage
- **Maintenance Margin** = Entry Price × Quantity × Maintenance Margin Rate − Maintenance Amount
- **Liquidation Price** is then calculated based on whether the position is long or short.
When the price crosses the calculated liquidation level, the position is force-closed and the **entire margin is lost**, just like in real crypto markets.
### Liuqidation Calculation
```python
# For long positions
liquid_price = entry_price - (initial_margin - maintenance_margin)
# For short positions
liquid_price = entry_price + (initial_margin - maintenance_margin)
```
## Supported Order Types
Supports four order types: LIMIT, MARKET, STOP LIMIT, and STOP MARKET. Each behaves according to standard trading conventions.
[NOTE] For MAKRET Orders, the system will automatically execute the trade with "open" price.
**The Rules for Stop Order to Trigger is:** <br>
LONG: Price (High) >= Stop Price <br>
SHORT: Price (low) <= Stop Price
```python
# For Long qty > 0 for short qty < 0
# Example 1. if qty == -1. This means Position is Short
# Example 2. if qty == 1. This means Position is Long
limit_order(symbol: str, qty: float, price: float)
market_order(symbol: str, qty: float)
stop_limit_order(symbol: str, qty: float, price: float, stop_price: float)
stop_market_order(symbol: str, qty: float, stop_price: float)
```
### Stop Order Immediate Rejection Rule
If a STOP LIMIT or STOP MARKET order would trigger immediately upon creation (because the current price already breaches the stop price), the system rejects the order with "STOP order would Immediately Trigger" message.
## Multiple Currency Backtesting
You can simply test multiple currencies by passing data as dict[str, pd.DataFrame]. Here key value should be the name of the currency.
### **[IMPORTANT]** When you are running simulations in multi currency mode please make sure that "timestamp" are identical for every symbols
## Helper Functions for Strategy Definitions
```python
class MyStrategy(Strategy):
def init(self):
self.asset = 'BTC'
def next(self, i):
# Fetch the indicator or other custom column from data
val = self.val(self.asset, i, "the indicator value")
# OHLV data
open = self.open(self.asset, i)
high = self.high(self.asset, i)
low = self.low(self.asset, i)
close = self.close(self.asset, i)
o, h, l, c = self.bars(self.asset, i)
# ACCOUNT data
position = self.get_position(self.asset)
balance = self.get_balance()
open_orders = self.get_open_orders() # returns all open orders regardless of symbols
```
## Upcoming Features
- **Parameter Optimizer**
A simple optimizer to help find the best-performing strategy parameters (like SMA windows, thresholds, etc.) based on backtest results.
- **Improved Slippage Modeling**
Dynamic slippage models based on volume, volatility, or order size.
> 💡 Got feedback or suggestions? Feel free to open an issue or contribute via pull request.
Raw data
{
"_id": null,
"home_page": null,
"name": "overfitting",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "algo, bitcoin, ethereum, crypto, cryptocurrency, crypto derivatives, futures, finance, quantitative, liquidation, solana, systematic, quant, trading",
"author": null,
"author_email": "Dohyun Kim <dohyun.k.july@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/ce/ea/5c6f070aef191a23ea144cf23972872dc3f230f88916a957bc57872020f7/overfitting-0.1.8.tar.gz",
"platform": null,
"description": "# Overfitting\n\nA robust and modular backtesting engine designed for crypto futures trading strategies. \nBuilt for speed, simplicity, and accuracy. Overfitting simulates a realistic crypto trading environment \u2014 including **liquidation**, **margin**, and **leverage** \u2014 for stress-testing your strategies.\n\n## Prerequisites\n\nBefore using **Overfitting**, you\u2019ll need to provide your own historical data. \nThe engine is designed to work with **crypto futures price data**, with **OHLCV format**.\n\n### Required Columns\n\nYour dataset must be a CSV or DataFrame that includes at least the following columns:\n- timestamp, open, high, low, close\n - `timestamp` should be a **UNIX timestamp in seconds or milliseconds**\n\n## Installation\n $ pip install overfitting\n\n\n## Usage\n```python\nimport pandas as pd\nfrom overfitting import Strategy\n\ndef load_data():\n df = pd.read_csv('./data/BTCUSDT.csv')\n benchamrk_df = pd.read_csv('./data/BTCUSDT.csv') # BTC buy and Hold\n df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')\n df.set_index('timestamp', inplace=True)\n\n start_time = pd.to_datetime('2023-01-01 00:00:00')\n df = df.loc[start_time:]\n # Compute short and long SMAs\n df['sma_short'] = df['close'].rolling(window=20).mean().shift(1)\n df['sma_long'] = df['close'].rolling(window=50).mean().shift(1)\n\n return df, benchamrk_df\n\nclass MyStrategy(Strategy):\n def init(self):\n self.asset = 'BTC'\n self.set_leverage(self.asset, 1)\n\n def next(self, i):\n if i == 0:\n return\n\n sma_short = self.val(self.asset, i, \"sma_short\")\n sma_long = self.val(self.asset, i, \"sma_long\")\n previous_sma_short = self.val(self.asset, i - 1, \"sma_short\") \n previous_sma_long = self.val(self.asset, i - 1, \"sma_long\")\n\n # Also skip if values are not available\n if (pd.isna(sma_short) or pd.isna(sma_long) or \n pd.isna(previous_sma_short) or pd.isna(previous_sma_long)):\n return\n\n # Fetch the current position\n position = self.get_position(self.asset)\n\n # Golden cross (entry)\n if previous_sma_short <= previous_sma_long and sma_short > sma_long and position.qty == 0:\n # First fetch current open price which is the target Price\n open_price = self.open(self.asset, i)\n # Determine Lot Size\n lot_size = self.get_balance() // open_price\n # Create LIMIT ORDER\n self.limit_order(self.asset, lot_size, open_price)\n\n # Death cross (exit)\n if previous_sma_short >= previous_sma_long and sma_short < sma_long and position.qty > 0:\n self.market_order(self.asset, -position.qty)\n\nbacktest_data, benchmark_data = load_data()\nstrategy = MyStrategy(\n data=backtest_data,\n benchmark=benchmark_data, # Default = None Optional\n initial_capital=100_000, # Default Optional\n commission_rate=0.0002, # Default Optional\n maint_maring_rate=0.005, # Default Optional\n maint_amount=50 # Default Optional\n)\nreturns = strategy.run()\nstrategy.plot(returns)\n```\n\nResults\n-------\n```text\nPerformance Summary\nNumber of Years 1.66000000\nStart Date 2023-01-01 00:00:00\nEnd Date 2024-08-29 00:00:00\nInitial Balance 100,000.00000000\nFinal Balance 205,328.91120000\nCAGR 0.52684228\nCumulative Return 2.05328911\nSharpe Ratio 1.24678659\nSortino Ratio 3.54979579\nMax Drawdown -0.26332695\nDaily Value At Risk -0.04147282\nSkew 0.44515551\nKurtosis 2.66444346\nTotal Trades 182.00000000\nWinning Trades 69.00000000\nLosing Trades 113.00000000\nWin Rate (%) 37.91208791\nGross Profit 399,044.19246000\nGross Loss -293,715.28126000\nNet Profit 105,328.91120000\nAvg Return (%) 0.38834383\nAvg Profit (%) 3.54140613\nAvg Loss (%) -1.53697740\n Net drawdown in % Peak date Valley date Recovery date Duration\n0 26.332695 2024-03-13 2024-06-30 NaT NaN\n1 19.678014 2023-03-20 2023-09-07 2023-10-26 159\n2 6.297244 2023-12-07 2024-01-24 2024-02-14 50\n3 5.585429 2023-01-22 2023-02-14 2023-02-17 20\n4 3.898568 2023-02-17 2023-03-11 2023-03-15 19\n5 3.336877 2023-11-12 2023-11-18 2023-12-07 19\n6 2.699556 2024-02-20 2024-02-26 2024-03-01 9\n7 0.767196 2024-03-01 2024-03-03 2024-03-06 4\n8 0.324161 2023-01-03 2023-01-07 2023-01-18 12\n9 0.019817 2023-11-03 2023-11-04 2023-11-07 3\n```\n\n## Performance Visualizations Examples\n\n\n\n\n\n\n## Liquidation Handling\n\nUnlike many basic backtesting engines, **overfitting** simulates realistic crypto futures trading, including **forced liquidation** based on margin conditions.\n\nThe liquidation logic is based on **isolated margin mode** (similar to Binance Futures):\n\n- **Initial Margin** = Entry Price \u00d7 Quantity / Leverage \n- **Maintenance Margin** = Entry Price \u00d7 Quantity \u00d7 Maintenance Margin Rate \u2212 Maintenance Amount \n- **Liquidation Price** is then calculated based on whether the position is long or short.\n\nWhen the price crosses the calculated liquidation level, the position is force-closed and the **entire margin is lost**, just like in real crypto markets.\n\n### Liuqidation Calculation\n\n```python\n# For long positions\nliquid_price = entry_price - (initial_margin - maintenance_margin)\n\n# For short positions\nliquid_price = entry_price + (initial_margin - maintenance_margin)\n```\n\n## Supported Order Types\nSupports four order types: LIMIT, MARKET, STOP LIMIT, and STOP MARKET. Each behaves according to standard trading conventions.\n\n[NOTE] For MAKRET Orders, the system will automatically execute the trade with \"open\" price.\n\n**The Rules for Stop Order to Trigger is:** <br>\nLONG: Price (High) >= Stop Price <br>\nSHORT: Price (low) <= Stop Price\n```python\n# For Long qty > 0 for short qty < 0\n# Example 1. if qty == -1. This means Position is Short\n# Example 2. if qty == 1. This means Position is Long\nlimit_order(symbol: str, qty: float, price: float)\nmarket_order(symbol: str, qty: float)\nstop_limit_order(symbol: str, qty: float, price: float, stop_price: float)\nstop_market_order(symbol: str, qty: float, stop_price: float)\n```\n\n### Stop Order Immediate Rejection Rule\nIf a STOP LIMIT or STOP MARKET order would trigger immediately upon creation (because the current price already breaches the stop price), the system rejects the order with \"STOP order would Immediately Trigger\" message.\n\n## Multiple Currency Backtesting\nYou can simply test multiple currencies by passing data as dict[str, pd.DataFrame]. Here key value should be the name of the currency.\n\n### **[IMPORTANT]** When you are running simulations in multi currency mode please make sure that \"timestamp\" are identical for every symbols\n\n## Helper Functions for Strategy Definitions\n```python\nclass MyStrategy(Strategy):\n def init(self):\n self.asset = 'BTC'\n \n def next(self, i):\n # Fetch the indicator or other custom column from data\n val = self.val(self.asset, i, \"the indicator value\") \n # OHLV data\n open = self.open(self.asset, i)\n high = self.high(self.asset, i)\n low = self.low(self.asset, i)\n close = self.close(self.asset, i)\n o, h, l, c = self.bars(self.asset, i)\n # ACCOUNT data\n position = self.get_position(self.asset)\n balance = self.get_balance()\n open_orders = self.get_open_orders() # returns all open orders regardless of symbols\n```\n\n## Upcoming Features\n\n- **Parameter Optimizer** \n A simple optimizer to help find the best-performing strategy parameters (like SMA windows, thresholds, etc.) based on backtest results.\n\n- **Improved Slippage Modeling** \n Dynamic slippage models based on volume, volatility, or order size.\n\n> \ud83d\udca1 Got feedback or suggestions? Feel free to open an issue or contribute via pull request.\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A Robust Futures CryptoCurrency Backtesting Library.",
"version": "0.1.8",
"project_urls": {
"Author": "https://github.com/dohyunkjuly",
"Homepage": "https://github.com/dohyunkjuly/overfitting",
"Source": "https://github.com/dohyunkjuly/overfitting"
},
"split_keywords": [
"algo",
" bitcoin",
" ethereum",
" crypto",
" cryptocurrency",
" crypto derivatives",
" futures",
" finance",
" quantitative",
" liquidation",
" solana",
" systematic",
" quant",
" trading"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "46058c17ea4c5cdc41b2905b2ac567e44d6baf58ebb29812a5da7e95fd7dac1e",
"md5": "5091007a1a90b1b4d0974e4e0ea2633f",
"sha256": "5d68c8f050d55325618efe32767f052f3044c39d4b3873e4a3fa39a904319563"
},
"downloads": -1,
"filename": "overfitting-0.1.8-py3-none-any.whl",
"has_sig": false,
"md5_digest": "5091007a1a90b1b4d0974e4e0ea2633f",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 26339,
"upload_time": "2025-09-08T07:52:28",
"upload_time_iso_8601": "2025-09-08T07:52:28.215125Z",
"url": "https://files.pythonhosted.org/packages/46/05/8c17ea4c5cdc41b2905b2ac567e44d6baf58ebb29812a5da7e95fd7dac1e/overfitting-0.1.8-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "ceea5c6f070aef191a23ea144cf23972872dc3f230f88916a957bc57872020f7",
"md5": "086b1f2433be87d1c30dc69572de38c7",
"sha256": "08456dfc41ae2cd4f43579daf346ec308ce3d4f503dac27720e4f4acc825b203"
},
"downloads": -1,
"filename": "overfitting-0.1.8.tar.gz",
"has_sig": false,
"md5_digest": "086b1f2433be87d1c30dc69572de38c7",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 27274,
"upload_time": "2025-09-08T07:52:29",
"upload_time_iso_8601": "2025-09-08T07:52:29.381200Z",
"url": "https://files.pythonhosted.org/packages/ce/ea/5c6f070aef191a23ea144cf23972872dc3f230f88916a957bc57872020f7/overfitting-0.1.8.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-08 07:52:29",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "dohyunkjuly",
"github_project": "overfitting",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "numpy",
"specs": [
[
">=",
"1.17.0"
]
]
},
{
"name": "pandas",
"specs": [
[
">=",
"0.25.0"
]
]
},
{
"name": "seaborn",
"specs": []
},
{
"name": "matplotlib",
"specs": []
},
{
"name": "scipy",
"specs": []
}
],
"lcname": "overfitting"
}