216 lines
8 KiB
Text
216 lines
8 KiB
Text
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 8,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"from backtesting import Strategy\n",
|
|
"from backtesting import Backtest\n",
|
|
"import pandas as pd\n",
|
|
"import numpy as np\n",
|
|
"import talib\n",
|
|
"from talib import MA_Type\n",
|
|
"from datetime import timedelta, datetime\n",
|
|
"import backtesting.lib as bt_lib\n",
|
|
"import math\n",
|
|
"import warnings\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"import warnings\n",
|
|
"warnings.filterwarnings('ignore')\n",
|
|
"import pandas as pd\n",
|
|
"data = pd.read_csv(f'/Users/alex/Dev/MarketAlgorithms/test/data/DOGEUSDT_1.csv', parse_dates=[0], index_col=0)\n",
|
|
"data = data.rename(columns={'timestamp': 'Timestamp',\n",
|
|
" 'open': 'Open',\n",
|
|
" 'high': 'High',\n",
|
|
" 'low': 'Low',\n",
|
|
" 'close': 'Close',\n",
|
|
" 'volume': 'Volume',\n",
|
|
" 'turnover': 'Turnover'})\n",
|
|
"data"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 10,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"class BBDca(Strategy):\n",
|
|
" bb_len = 201\n",
|
|
" bb_mul = 1.8\n",
|
|
" bo_amount = 50\n",
|
|
" so_amount = 270\n",
|
|
" tp = 0.005\n",
|
|
" so_price_deviation = 0.02\n",
|
|
" so_volume_scale = 1.6\n",
|
|
" so_step_scale = 1.6\n",
|
|
" so_count = 5\n",
|
|
" \n",
|
|
" max_days_in_trade = 30000\n",
|
|
" so_enabled = False\n",
|
|
" so_order_policy = \"place_at_start\" #\"trigger_level\"|\"strategy\"\n",
|
|
" LONG = 'long'\n",
|
|
" SHORT = 'short'\n",
|
|
" \n",
|
|
" def safety_order_deviation(self, index):\n",
|
|
" return self.so_price_deviation * math.pow(self.so_step_scale, index - 1)\n",
|
|
" \n",
|
|
" def safety_order_price(self, index, last_safety_order_price, side):\n",
|
|
" qty = self.safety_order_qty(index)\n",
|
|
" if side == 'long':\n",
|
|
" return last_safety_order_price * (1 - self.safety_order_deviation(index)), qty\n",
|
|
" else:\n",
|
|
" return last_safety_order_price * (1 + self.safety_order_deviation(index)), qty\n",
|
|
" \n",
|
|
" def safety_order_qty(self, index):\n",
|
|
" return self.so_amount * math.pow(self.so_volume_scale, index - 1)\n",
|
|
" \n",
|
|
" def place_safety_order(self, bo_level, side):\n",
|
|
" tot_order_size = self.bo_amount\n",
|
|
" last_so_level = bo_level\n",
|
|
" for i in range(1, self.so_count + 1):\n",
|
|
" so_level, amount = self.safety_order_price(i, last_so_level, side)\n",
|
|
" average_price = (tot_order_size * last_so_level + amount * so_level) / (amount + tot_order_size)\n",
|
|
" tot_order_size += amount\n",
|
|
" last_so_level = so_level\n",
|
|
" if side == self.LONG:\n",
|
|
" so_tp_level = average_price * (1 + self.tp)\n",
|
|
" self.buy(tp=so_tp_level, limit=so_level, size=round(amount / so_level))\n",
|
|
" else:\n",
|
|
" so_tp_level = average_price * (1 - self.tp)\n",
|
|
" self.sell(tp=so_tp_level, limit=so_level, size=round(amount / so_level))\n",
|
|
" \n",
|
|
" def init(self):\n",
|
|
" super().init()\n",
|
|
" self.bb_up, self.bb_mid, self.bb_low = self.I(talib.BBANDS, self.data.Close, self.bb_len, self.bb_mul, self.bb_mul, matype=MA_Type.EMA)\n",
|
|
" self.tp_level = self.I(lambda: np.repeat(np.nan, len(self.data)), name='TP level', overlay=True)\n",
|
|
" self.trade_abort = self.I(lambda: np.repeat(np.nan, len(self.data)), name='TA', scatter=True)\n",
|
|
" self.tp_level.flags.writeable = True\n",
|
|
" self.trade_abort.flags.writeable = True\n",
|
|
" \n",
|
|
" def next(self):\n",
|
|
" super().next()\n",
|
|
" \n",
|
|
" if self.position:\n",
|
|
" if self.so_order_policy == \"place_at_start\":\n",
|
|
" if self.trades[-1].entry_bar == len(self.data) - 1:\n",
|
|
" # adjust tp of previous trade\n",
|
|
" #if len(self.trades) == 2:\n",
|
|
" new_tp = self.trades[-1].tp\n",
|
|
" for ti in range(0, len(self.trades) - 1):\n",
|
|
" self.trades[ti].tp = new_tp\n",
|
|
" # update tp level line on chart\n",
|
|
" self.tp_level[-1] = self.trades[0].tp \n",
|
|
" \n",
|
|
" else:\n",
|
|
" self.so_enabled = False\n",
|
|
" for order in self.orders:\n",
|
|
" order.cancel()\n",
|
|
"\n",
|
|
" # handle new trade\n",
|
|
" long_condition = self.data.Close[-1] >= self.bb_low[-1] and self.data.Close[-2] < self.bb_low[-1]\n",
|
|
" short_condition = self.data.Close[-1] <= self.bb_up[-1] and self.data.Close[-2] > self.bb_up[-1]\n",
|
|
"\n",
|
|
" be = self.data.Close[-1]\n",
|
|
" if long_condition and not self.position:\n",
|
|
" tp_level = self.data.Close[-1] * (1 + self.tp)\n",
|
|
" self.buy(tp=tp_level, size=round(self.bo_amount / self.data.Close[-1]))\n",
|
|
" if self.so_order_policy == \"place_at_start\":\n",
|
|
" self.place_safety_order(be, \"long\")\n",
|
|
" elif short_condition and not self.position:\n",
|
|
" tp_level = self.data.Close[-1] * (1 - self.tp)\n",
|
|
" self.sell(tp=tp_level, size=round(self.bo_amount / self.data.Close[-1]))\n",
|
|
" if self.so_order_policy == \"place_at_start\":\n",
|
|
" self.place_safety_order(be, \"short\")\n",
|
|
" "
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"bt = Backtest(data, BBDca, cash=600, margin=0.2, commission=0.0003)\n",
|
|
"stats = bt.run()\n",
|
|
"print(stats)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"bt.plot(show_legend=False, resample=False, plot_pl=True, relative_equity=False)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"bt = Backtest(data, BBDca, cash=600, margin=0.2, commission=0.0003)\n",
|
|
"stats, heatmap = bt.optimize(\n",
|
|
" bb_len = range(190,210,1),\n",
|
|
" bb_mul = list(np.arange(1.8, 3.0, 0.1)),\n",
|
|
" bo_amount = range(50,100,10),\n",
|
|
" so_amount = range(100,300,10),\n",
|
|
" tp = list(np.arange(0.005, 0.01, 0.001)),\n",
|
|
" so_price_deviation = list(np.arange(0.02, 0.06, 0.01)),\n",
|
|
" so_volume_scale = list(np.arange(1.0, 2.0, 0.1)),\n",
|
|
" so_step_scale = list(np.arange(1.0, 2.0, 0.1)),\n",
|
|
" so_count = range(1,5,1),\n",
|
|
" constraint= lambda x: x.bo_amount < x.so_amount / 2, #and x.bo_amount + x.so_amount * x.count ,\n",
|
|
" maximize='Equity Final [$]',\n",
|
|
" method='grid',\n",
|
|
" max_tries=50,\n",
|
|
" random_state=0,\n",
|
|
" return_heatmap=True)\n",
|
|
"\n",
|
|
"print(stats)\n",
|
|
"print(heatmap.sort_values(ascending=False))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"bt.plot(show_legend=False, resample=False, plot_pl=True, relative_equity=False)"
|
|
]
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "venv",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.13.3"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 2
|
|
}
|