Skip to content

Instantly share code, notes, and snippets.

@dogwood008
Created January 16, 2021 12:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dogwood008/fda98e9645b0f1f82fc9c034b809874d to your computer and use it in GitHub Desktop.
Save dogwood008/fda98e9645b0f1f82fc9c034b809874d to your computer and use it in GitHub Desktop.
「株のシステムトレードをしよう - 1から始める株自動取引システムの作り方」で使用しているバックテスト https://bit.ly/3qvOehD
Display the source blob
Display the rendered blob
Raw
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"name": "read_kabu_plus_csv_on_backtrader",
"provenance": [],
"collapsed_sections": [
"ikhFsyP6tRV_",
"7DjGXkBKf0_O",
"Vt3cDCUatPQV",
"X7iwc8uBtLPI",
"OscgK3cl2Bbn",
"btPaVVBztI2Z"
]
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
}
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "ikhFsyP6tRV_"
},
"source": [
"## Pip install"
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "beyRsNDRzhF5",
"outputId": "e4645180-1beb-48b4-942b-b70c66294f4d"
},
"source": [
"#!pip install git+https://github.com/dogwood008/backtrader.git@bugfix_order_day_doesnt_works backtrader_plotting\n",
"!pip install backtrader==1.9.76.123 backtrader_plotting"
],
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"text": [
"Collecting backtrader==1.9.76.123\n",
"\u001b[?25l Downloading https://files.pythonhosted.org/packages/1a/bf/78aadd993e2719d6764603465fde163ba6ec15cf0e81f13e39ca13451348/backtrader-1.9.76.123-py2.py3-none-any.whl (410kB)\n",
"\u001b[K |████████████████████████████████| 419kB 6.0MB/s \n",
"\u001b[?25hCollecting backtrader_plotting\n",
"\u001b[?25l Downloading https://files.pythonhosted.org/packages/77/c3/5e2913a965e27b9fe4d45092dca3a2e87bd528032496583fd96e54ced136/backtrader_plotting-1.1.0-py3-none-any.whl (62kB)\n",
"\u001b[K |████████████████████████████████| 71kB 8.8MB/s \n",
"\u001b[?25hCollecting bokeh~=2.0.0\n",
"\u001b[?25l Downloading https://files.pythonhosted.org/packages/30/cf/9bef843880084b646cf5e6988e8f0aa081dccdf09f1617cfc6755f9a3353/bokeh-2.0.2.tar.gz (8.6MB)\n",
"\u001b[K |████████████████████████████████| 8.6MB 9.9MB/s \n",
"\u001b[?25hRequirement already satisfied: matplotlib in /usr/local/lib/python3.6/dist-packages (from backtrader_plotting) (3.2.2)\n",
"Collecting markdown2\n",
" Downloading https://files.pythonhosted.org/packages/a2/d2/6e6ab0d9387c332bf1de205522a8386b48e2dddf332ba6ad71b2ec371110/markdown2-2.3.10-py2.py3-none-any.whl\n",
"Requirement already satisfied: jinja2 in /usr/local/lib/python3.6/dist-packages (from backtrader_plotting) (2.11.2)\n",
"Requirement already satisfied: pandas in /usr/local/lib/python3.6/dist-packages (from backtrader_plotting) (1.1.5)\n",
"Requirement already satisfied: PyYAML>=3.10 in /usr/local/lib/python3.6/dist-packages (from bokeh~=2.0.0->backtrader_plotting) (3.13)\n",
"Requirement already satisfied: python-dateutil>=2.1 in /usr/local/lib/python3.6/dist-packages (from bokeh~=2.0.0->backtrader_plotting) (2.8.1)\n",
"Requirement already satisfied: numpy>=1.11.3 in /usr/local/lib/python3.6/dist-packages (from bokeh~=2.0.0->backtrader_plotting) (1.19.5)\n",
"Requirement already satisfied: pillow>=4.0 in /usr/local/lib/python3.6/dist-packages (from bokeh~=2.0.0->backtrader_plotting) (7.0.0)\n",
"Requirement already satisfied: packaging>=16.8 in /usr/local/lib/python3.6/dist-packages (from bokeh~=2.0.0->backtrader_plotting) (20.8)\n",
"Requirement already satisfied: tornado>=5 in /usr/local/lib/python3.6/dist-packages (from bokeh~=2.0.0->backtrader_plotting) (5.1.1)\n",
"Requirement already satisfied: typing_extensions>=3.7.4 in /usr/local/lib/python3.6/dist-packages (from bokeh~=2.0.0->backtrader_plotting) (3.7.4.3)\n",
"Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.1 in /usr/local/lib/python3.6/dist-packages (from matplotlib->backtrader_plotting) (2.4.7)\n",
"Requirement already satisfied: kiwisolver>=1.0.1 in /usr/local/lib/python3.6/dist-packages (from matplotlib->backtrader_plotting) (1.3.1)\n",
"Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.6/dist-packages (from matplotlib->backtrader_plotting) (0.10.0)\n",
"Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.6/dist-packages (from jinja2->backtrader_plotting) (1.1.1)\n",
"Requirement already satisfied: pytz>=2017.2 in /usr/local/lib/python3.6/dist-packages (from pandas->backtrader_plotting) (2018.9)\n",
"Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.6/dist-packages (from python-dateutil>=2.1->bokeh~=2.0.0->backtrader_plotting) (1.15.0)\n",
"Building wheels for collected packages: bokeh\n",
" Building wheel for bokeh (setup.py) ... \u001b[?25l\u001b[?25hdone\n",
" Created wheel for bokeh: filename=bokeh-2.0.2-cp36-none-any.whl size=9072534 sha256=9e2b2e936c39c4ad8149edb02ca50e22a9ec5b97211790e84c2b079abda70a11\n",
" Stored in directory: /root/.cache/pip/wheels/a3/5e/7d/49405a043a4714a1b66bb7a16dfe16a1f0db9217baac7c4fe3\n",
"Successfully built bokeh\n",
"\u001b[31mERROR: panel 0.9.7 has requirement bokeh>=2.1, but you'll have bokeh 2.0.2 which is incompatible.\u001b[0m\n",
"Installing collected packages: backtrader, bokeh, markdown2, backtrader-plotting\n",
" Found existing installation: bokeh 2.1.1\n",
" Uninstalling bokeh-2.1.1:\n",
" Successfully uninstalled bokeh-2.1.1\n",
"Successfully installed backtrader-1.9.76.123 backtrader-plotting-1.1.0 bokeh-2.0.2 markdown2-2.3.10\n"
],
"name": "stdout"
}
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "7DjGXkBKf0_O"
},
"source": [
"## Consts"
]
},
{
"cell_type": "code",
"metadata": {
"id": "HrNGCeJFfw5W"
},
"source": [
"USE_BOKEH = False"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "Vt3cDCUatPQV"
},
"source": [
"## Load CSV"
]
},
{
"cell_type": "code",
"metadata": {
"id": "LXFvPl1bzi8D"
},
"source": [
"path_to_csv = '/content/drive/MyDrive/Project/kabu-plus/japan-stock-prices-2_2020_9143_adjc.csv'"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "7sHlE2Ci1AUY"
},
"source": [
"import pandas as pd\n",
"import backtrader as bt\n",
"csv = pd.read_csv(path_to_csv)\n",
"pd.set_option('display.max_rows', 500)\n",
"csv"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "X7iwc8uBtLPI"
},
"source": [
"## FeedsData"
]
},
{
"cell_type": "code",
"metadata": {
"id": "AQvZjzz27dBU"
},
"source": [
"#############################################################\n",
"# Copyright (C) 2020 dogwood008 (original author: Daniel Rodriguez; https://github.com/mementum/backtraders)\n",
"#\n",
"# This program is free software: you can redistribute it and/or modify\n",
"# it under the terms of the GNU General Public License as published by\n",
"# the Free Software Foundation, either version 3 of the License, or\n",
"# (at your option) any later version.\n",
"#\n",
"# This program is distributed in the hope that it will be useful,\n",
"# but WITHOUT ANY WARRANTY; without even the implied warranty of\n",
"# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n",
"# GNU General Public License for more details.\n",
"#\n",
"# You should have received a copy of the GNU General Public License\n",
"# along with this program. If not, see <https://www.gnu.org/licenses/>.\n",
"#############################################################\n",
"\n",
"import csv\n",
"import itertools\n",
"import io\n",
"import pytz\n",
"from datetime import date, datetime\n",
"from backtrader.utils import date2num\n",
"from typing import Any\n",
"\n",
"class KabuPlusJPCSVData(bt.feeds.YahooFinanceCSVData):\n",
" '''\n",
" Parses pre-downloaded KABU+ CSV Data Feeds (or locally generated if they\n",
" comply to the Yahoo formatg)\n",
" Specific parameters:\n",
" - ``dataname``: The filename to parse or a file-like object\n",
" - ``reverse`` (default: ``True``)\n",
" It is assumed that locally stored files have already been reversed\n",
" during the download process\n",
" - ``round`` (default: ``True``)\n",
" Whether to round the values to a specific number of decimals after\n",
" having adjusted the close\n",
" - ``roundvolume`` (default: ``0``)\n",
" Round the resulting volume to the given number of decimals after having\n",
" adjusted it\n",
" - ``decimals`` (default: ``2``)\n",
" Number of decimals to round to\n",
" - ``swapcloses`` (default: ``False``)\n",
" [2018-11-16] It would seem that the order of *close* and *adjusted\n",
" close* is now fixed. The parameter is retained, in case the need to\n",
" swap the columns again arose.\n",
" '''\n",
" DATE = 'date'\n",
" OPEN = 'open'\n",
" HIGH = 'high'\n",
" LOW = 'low'\n",
" CLOSE = 'close'\n",
" VOLUME = 'volume'\n",
" ADJUSTED_CLOSE = 'adjusted_close'\n",
" \n",
" params = (\n",
" ('reverse', True),\n",
" ('round', True),\n",
" ('decimals', 2),\n",
" ('roundvolume', False),\n",
" ('swapcloses', False),\n",
" ('headers', True),\n",
" ('header_names', { # CSVのカラム名と内部的なキーを変換する辞書\n",
" DATE: 'date',\n",
" OPEN: 'open',\n",
" HIGH: 'high',\n",
" LOW: 'low',\n",
" CLOSE: 'close',\n",
" VOLUME: 'volumes',\n",
" ADJUSTED_CLOSE: 'adj_close',\n",
" }),\n",
" ('tz', pytz.timezone('Asia/Tokyo'))\n",
" )\n",
"\n",
" def _fetch_value(self, values: dict, column_name: str) -> Any:\n",
" '''\n",
" パラメタで指定された変換辞書を使用して、\n",
" CSVで定義されたカラム名に沿って値を取得する。\n",
" '''\n",
" index = self._column_index(self.p.header_names[column_name])\n",
" return values[index]\n",
"\n",
" \n",
" def _column_index(self, column_name: str) -> int:\n",
" '''\n",
" 与えたカラム名に対するインデックス番号を返す。\n",
" 見つからなければ ValueError を投げる。\n",
" '''\n",
" return self._csv_headers.index(column_name)\n",
"\n",
" # copied from https://github.com/mementum/backtrader/blob/0426c777b0abdfafbb0988f5c31347553256a2de/backtrader/feed.py#L666-L679\n",
" def start(self):\n",
" super(bt.feed.CSVDataBase, self).start()\n",
"\n",
" if self.f is None:\n",
" if hasattr(self.p.dataname, 'readline'):\n",
" self.f = self.p.dataname\n",
" else:\n",
" # Let an exception propagate to let the caller know\n",
" self.f = io.open(self.p.dataname, 'r')\n",
"\n",
" if self.p.headers and self.p.header_names:\n",
" _csv_reader = csv.reader([self.f.readline()])\n",
" self._csv_headers = next(_csv_reader)\n",
"\n",
" self.separator = self.p.separator\n",
"\n",
"\n",
" def _loadline(self, linetokens):\n",
" while True:\n",
" nullseen = False\n",
" for tok in linetokens[1:]:\n",
" if tok == 'null':\n",
" nullseen = True\n",
" linetokens = self._getnextline() # refetch tokens\n",
" if not linetokens:\n",
" return False # cannot fetch, go away\n",
"\n",
" # out of for to carry on wiwth while True logic\n",
" break\n",
"\n",
" if not nullseen:\n",
" break # can proceed\n",
"\n",
" dttxt = self._fetch_value(linetokens, self.DATE)\n",
" dt = date(int(dttxt[0:4]), int(dttxt[5:7]), int(dttxt[8:10]))\n",
" dtnum = date2num(datetime.combine(dt, self.p.sessionend))\n",
" #dtnum = date2num(datetime.combine(dt, self.p.sessionend), tz=pytz.timezone('Asia/Tokyo'))\n",
"\n",
" self.lines.datetime[0] = dtnum\n",
" o = float(self._fetch_value(linetokens, self.OPEN))\n",
" h = float(self._fetch_value(linetokens, self.HIGH))\n",
" l = float(self._fetch_value(linetokens, self.LOW))\n",
" rawc = float(self._fetch_value(linetokens, self.CLOSE))\n",
" self.lines.openinterest[0] = 0.0\n",
"\n",
" adjustedclose = float(self._fetch_value(linetokens, self.ADJUSTED_CLOSE))\n",
" v = float(self._fetch_value(linetokens, self.VOLUME))\n",
"\n",
" if self.p.swapcloses: # swap closing prices if requested\n",
" rawc, adjustedclose = adjustedclose, rawc\n",
"\n",
" adjfactor = rawc / adjustedclose\n",
"\n",
" o /= adjfactor\n",
" h /= adjfactor\n",
" l /= adjfactor\n",
" v *= adjfactor\n",
"\n",
" if self.p.round:\n",
" decimals = self.p.decimals\n",
" o = round(o, decimals)\n",
" h = round(h, decimals)\n",
" l = round(l, decimals)\n",
" rawc = round(rawc, decimals)\n",
"\n",
" v = round(v, self.p.roundvolume)\n",
"\n",
" self.lines.open[0] = o\n",
" self.lines.high[0] = h\n",
" self.lines.low[0] = l\n",
" self.lines.close[0] = adjustedclose\n",
" self.lines.volume[0] = v\n",
"\n",
" return True"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "OscgK3cl2Bbn"
},
"source": [
"## Sizer"
]
},
{
"cell_type": "code",
"metadata": {
"id": "w8Xc0esf2BG2"
},
"source": [
"# import backtrader as bt\n",
"# from bt.comminfo import CommInfoBase\n",
"# from bt.feeds import AbstractDataBase\n",
"# \n",
"# class RangeSizer(bt.Sizer):\n",
"# '''\n",
"# 決められた注文額に納まるように、注文数を調整する。\n",
"# '''\n",
"# \n",
"# params = (\n",
"# ('min_order_price', 10 * 10000), # 最低購入金額(円)\n",
"# ('max_order_price', 50 * 10000), # 最高購入金額(円) \n",
"# )\n",
"# \n",
"# def _getsizing(self, comminfo: CommInfoBase,\n",
"# cash: float, data: AbstractDataBase, isbuy: bool) -> int:\n",
"# '''\n",
"# base: https://github.com/mementum/backtrader/blob/master/backtrader/sizer.py\n",
"# Parameters\n",
"# ----------------------\n",
"# comminfo : bt.comminfo.CommInfoBase\n",
"# The CommissionInfo instance that contains\n",
"# information about the commission for the data and allows\n",
"# calculation of position value, operation cost, commision for the\n",
"# operation\n",
"# \n",
"# cash : float\n",
"# current available cash in the *broker*\n",
"# \n",
"# data : Any\n",
"# target of the operation\n",
"# \n",
"# isbuy : bool\n",
"# will be `True` for *buy* operations and `False`\n",
"# for *sell* operations\n",
"# \n",
"# \n",
"# Returns\n",
"# -----------------------\n",
"# size : int\n",
"# The size of an order\n",
"# '''\n",
"# \n",
"# issell = not isbuy\n",
"# \n",
"# if issell:\n",
"# # 売り注文なら、全量を指定\n",
"# position = self.broker.getposition(data)\n",
"# if not position.size:\n",
"# return 0 # do not sell if nothing is open\n",
"# else:\n",
"# return position.size\n",
"# \n",
"# else:\n",
"# # TODO\n",
"# "
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "btPaVVBztI2Z"
},
"source": [
"## StrategyFetcher"
]
},
{
"cell_type": "code",
"metadata": {
"id": "KvXZMT1bimpy"
},
"source": [
"# # https://www.backtrader.com/blog/posts/2017-05-16-stsel-revisited/stsel-revisited/\n",
"# class StFetcher(object):\n",
"# _STRATS = []\n",
"# \n",
"# @classmethod\n",
"# def register(cls, target):\n",
"# cls._STRATS.append(target)\n",
"# \n",
"# @classmethod\n",
"# def COUNT(cls):\n",
"# return range(len(cls._STRATS))\n",
"# \n",
"# def __new__(cls, *args, **kwargs):\n",
"# idx = kwargs.pop('idx')\n",
"# \n",
"# obj = cls._STRATS[idx](*args, **kwargs)\n",
"# return obj"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "bRUcKzOTILaf"
},
"source": [
"## Strategy"
]
},
{
"cell_type": "code",
"metadata": {
"id": "mDFMtmEw1DDf"
},
"source": [
"import backtrader as bt\n",
"from logging import getLogger, StreamHandler, Formatter, DEBUG, INFO, WARN\n",
"\n",
"if USE_BOKEH:\n",
" from backtrader_plotting import Bokeh\n",
" from backtrader_plotting.schemes import Tradimo\n",
"\n",
"# for jupyter\n",
"if 'PercentageBuySellStrategyWithLogger' in globals():\n",
" del PercentageBuySellStrategyWithLogger\n",
"\n",
"\n",
"from typing import Callable, Union, Optional\n",
"LazyString = Callable[[str], str]\n",
"LazyableString = Union[LazyString, str]\n",
"\n",
"# Create a Stratey\n",
"# @StFetcher.register\n",
"class PercentageBuySellStrategyWithLogger(bt.Strategy):\n",
" params = (\n",
" ('default_unit_size', 100), # デフォルトの単元株の株数\n",
" ('buy_under_percentage', 5), # 前日終値と比較し本日始値が▲x%の場合に買い注文\n",
" ('sell_over_percentage', 5), # 前日終値と比較し本日始値が+y%の場合に売り注文\n",
" ('min_order_price', 10 * 10000), # 最低購入金額(円)\n",
" ('max_order_price', 50 * 10000), # 最高購入金額(円)\n",
" ('smaperiod', 5),\n",
" # ('sizer', RangeSizer), # 数量指定ロジック\n",
" )\n",
"\n",
" # def _log(self, txt: LazyLazyableString, dt=None):\n",
" # ''' Logging function for this strategy '''\n",
" # dt = dt or self.datas[0].datetime.date(0)\n",
" # self._logger.debug('%s, %s' % (dt.isoformat(), txt))\n",
"\n",
" def __init__(self, loglevel):\n",
" # Keep a reference to the \"close\" line in the data[0] dataseries\n",
" self._dataopen = self.datas[0].open\n",
" self._datahigh = self.datas[0].high\n",
" self._datalow = self.datas[0].low\n",
" self._dataclose = self.datas[0].close\n",
" self._dataadjclose = self.datas[0].adjclose\n",
" self._datavolume = self.datas[0].volume\n",
" self._logger = getLogger(__name__)\n",
" self.handler = StreamHandler()\n",
" self.handler.setLevel(loglevel)\n",
" self._logger.setLevel(loglevel)\n",
" self._logger.addHandler(self.handler)\n",
" self._logger.propagate = False\n",
" self.handler.setFormatter(\n",
" Formatter('[%(levelname)s] %(message)s'))\n",
" self.sma = bt.indicators.SimpleMovingAverage(\n",
" self.datas[0], period=self.params.smaperiod) \n",
"\n",
"\n",
" def notify_order(self, order):\n",
" if order.status in [order.Submitted, order.Accepted]:\n",
" return\n",
"\n",
" if order.status in [order.Completed]:\n",
" if order.isbuy():\n",
" buy_or_sell = 'BUY'\n",
" elif order.issell():\n",
" buy_or_sell = 'SELL'\n",
" else:\n",
" buy_or_sell = 'UNDEFINED'\n",
" self._log(lambda: '{b_s:4s} EXECUTED, {price:7.2f}'.format(\n",
" b_s=buy_or_sell,\n",
" price=order.executed.price))\n",
"\n",
" elif order.status in [order.Canceled, order.Margin, order.Rejected]:\n",
" self._debug(lambda: 'Order Canceled/Margin/Rejected')\n",
" elif order.status in [order.Expired]:\n",
" # from IPython.core.debugger import Pdb; Pdb().set_trace()\n",
" self._debug(lambda: 'Expired: {b_s:s} ¥{sum:,d} (@{price:.2f} * {unit:4d}), valid: ({valid:s}, {valid_raw:f})'.format(\n",
" sum=int(order.price * order.size),\n",
" b_s=order.ordtypename(), price=order.price, unit=order.size,\n",
" valid=str(bt.utils.dateintern.num2date(order.valid, tz=pytz.timezone('Asia/Tokyo'))),\n",
" valid_raw=order.valid)\n",
" )\n",
" else:\n",
" self._debug(order.getstatusname())\n",
" \n",
"\n",
" \n",
" def _log(self, txt: LazyableString, loglevel=INFO, dt=None):\n",
" ''' Logging function for this strategy '''\n",
" dt = dt or self.datas[0].datetime.date(0)\n",
" logtext = txt() if callable(txt) else str(txt)\n",
" self._logger.log(loglevel, '%s, %s' % (dt.isoformat(), logtext))\n",
"\n",
" def _debug(self, txt: LazyableString, dt=None):\n",
" self._log(txt, DEBUG, dt)\n",
"\n",
" def _info(self, txt: LazyableString, dt=None):\n",
" self._log(txt, INFO, dt)\n",
"\n",
" def _size(self, unit_price: float, is_buy: bool) -> Optional[int]:\n",
" '''\n",
" Params\n",
" ------------------\n",
" unit_price : flat\n",
" ある銘柄の価格\n",
" is_buy : bool\n",
" 買い注文なら True, 売り注文なら False\n",
" '''\n",
" if not is_buy:\n",
" # 売り注文なら、全量を指定する\n",
" return None\n",
" min_size = 100\n",
" max_size = 2000\n",
" for i in range(int(min_size / 100), int(max_size / 100 + 1)):\n",
" unit = i * 100\n",
" order_value = unit_price * unit\n",
" if order_value < self.p.min_order_price:\n",
" # 安すぎる場合、購入量を増やす\n",
" continue\n",
" elif self.p.min_order_price <= order_value <= self.p.max_order_price:\n",
" return unit\n",
" else:\n",
" # 高すぎて買えない場合、買わない\n",
" return 0\n",
"\n",
" def _is_to_buy(self, open_today: float, close_yesterday: float, high_today: Optional[float] = None) -> bool:\n",
" # 本日始値*閾値% <= 前日終値\n",
" to_buy_condition: bool = open_today * (100.0 - self.p.buy_under_percentage) / 100.0 <= close_yesterday\n",
" if high_today:\n",
" # バックテスト実行時に、始値より高値が高いことを確認する。\n",
" # これにより、実際にこの戦略をリアルタイムで動かした場合にも、動作可能であることを確認する。\n",
" return to_buy_condition and open_today <= high_today\n",
" else:\n",
" # リアルタイムで動かした場合は、high_today = None\n",
" return to_buy_condition\n",
" \n",
" def _is_to_close(self, open_today: float, close_yesterday: float, low_today: Optional[float] = None) -> bool:\n",
" # 前日終値*閾値% <= 本日始値\n",
" to_sell_condition: bool = close_yesterday * (100.0 + self.p.sell_over_percentage) / 100.0 <= open_today\n",
" if low_today:\n",
" # バックテスト実行時に、始値より安値が低いことを確認する。\n",
" # これにより、実際にこの戦略をリアルタイムで動かした場合にも、動作可能であることを確認する。\n",
" return to_sell_condition and low_today <= low_today\n",
" else:\n",
" # リアルタイムで動かした場合は、low_today = None\n",
" return to_sell_condition\n",
" \n",
" def next(self):\n",
" # 当日の始値を見るためにチートする\n",
" return\n",
"\n",
" def next_open(self): # 当日の始値を見るためにチートする\n",
" open_today = self._dataopen[0]\n",
" high_today = self._datahigh[0]\n",
" low_today = self._datalow[0]\n",
" close_yesterday = self._dataclose[-1]\n",
"\n",
" if self._is_to_buy(open_today, close_yesterday, high_today):\n",
" size = self._size(unit_price=open_today, is_buy=True)\n",
" self._info(lambda: 'BUY CREATE @{price:.2f}, #{unit:4d} (open_today, close_yesterday)=({open_today:f}, {close_yesterday:f})'.format(\n",
" # FIXME: fix unit size\n",
" price=open_today, unit=-100, open_today=open_today, close_yesterday=close_yesterday)\n",
" )\n",
" self._debug(lambda: '(o, h, l, c) = ({o:}, {h:}, {l:}, {c:})'.format(\n",
" o=self._dataopen[0], h=self._datahigh[0], l=self._datalow[0], c=self._dataclose[0]))\n",
" self.buy(size=size, price=open_today, exectype=bt.Order.Limit, valid=bt.Order.DAY)\n",
"\n",
" elif self._is_to_close(open_today, close_yesterday, low_today):\n",
" size = self._size(unit_price=open_today, is_buy=False)\n",
" self._info(lambda: 'CLOSE (SELL) CREATE @{price:.2f}, #all (open_today, close_yesterday)=({open_today:f}, {close_yesterday:f})'.format(\n",
" price=open_today, open_today=open_today, close_yesterday=close_yesterday)\n",
" )\n",
" self.close(size=size, price=open_today, exectype=bt.Order.Limit, valid=bt.Order.DAY)\n",
"\n",
"\n",
"if USE_BOKEH:\n",
" del PercentageBuySellStrategyWithLogger\n",
" from percentage_buy_sell_strategy_with_logger import PercentageBuySellStrategyWithLogger"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "iyvN-BEOtG4Q"
},
"source": [
"## main"
]
},
{
"cell_type": "code",
"metadata": {
"id": "JMT1MRKQ3e0M"
},
"source": [
"import backtrader.analyzers as btanalyzers\n",
"class BackTest:\n",
" def __init__(self, strategy: bt.Strategy, cheat_on_open=True):\n",
" self.cerebro = bt.Cerebro(tz='Asia/Tokyo', cheat_on_open=cheat_on_open)\n",
" # Set cheat-on-close\n",
" # self.cerebro.brokerj.set_coc(True)\n",
"\n",
" data = KabuPlusJPCSVData(\n",
" dataname=path_to_csv,\n",
" fromdate=datetime(2020, 1, 1),\n",
" todate=datetime(2020, 11, 30),\n",
" reverse=False)\n",
"\n",
" self.cerebro.adddata(data)\n",
" # Add a strategy\n",
" IN_DEVELOPMENT = False # このフラグにより、ログレベルを切り替えることで、本番ではWARN以上のみをログに出すようにする。\n",
" # フラグの切り替えは、環境変数で行う事が望ましいが今は一旦先送りする。\n",
" loglevel = DEBUG if IN_DEVELOPMENT else WARN\n",
" self.cerebro.broker.setcash(100 * 10000 * 3) # 信用取引なので3倍\n",
" self.cerebro.addstrategy(strategy, loglevel)\n",
" self.cerebro.addanalyzer(btanalyzers.DrawDown, _name='drawdown')\n",
" self.cerebro.addanalyzer(btanalyzers.AnnualReturn, _name='annualreturn')\n",
" # self.cerebro.optstrategy(StFetcher, idx=StFetcher.COUNT())\n",
"\n",
" def run(self):\n",
" initial_cash = self.cerebro.broker.getvalue()\n",
" thestrats = self.cerebro.run()\n",
" thestrat = thestrats[0]\n",
"\n",
" print('Initial Portfolio Value: {val:,}'.format(val=initial_cash))\n",
" print('Final Portfolio Value: {val:,}'.format(val=int(self.cerebro.broker.getvalue())))\n",
" print('DrawDown:', thestrat.analyzers.drawdown.get_analysis())\n",
" print('Annual Return:', thestrat.analyzers.annualreturn.get_analysis())\n",
"\n",
" save_file = False\n",
" if USE_BOKEH:\n",
" if save_file:\n",
" b = Bokeh(style='bar', plot_mode='single', scheme=Tradimo(), output_mode='save', filename='chart.html')\n",
" else:\n",
" b = Bokeh(style='bar', plot_mode='single', scheme=Tradimo())\n",
" return self.cerebro.plot(b, iplot=not save_file)\n",
" else:\n",
" return self.cerebro.plot(use='agg')\n",
"\n"
],
"execution_count": 24,
"outputs": []
},
{
"cell_type": "code",
"metadata": {
"id": "MpfYaBh7S38I"
},
"source": [
"# def num2date(val: float, tz=pytz.timezone('Asia/Tokyo')):\n",
"# return bt.utils.dateintern.num2date(val, tz=tz)\n",
"# \n",
"# def p_order(order: bt.Order):\n",
"# print(str(order))"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {
"id": "v3E7KaB9mISO"
},
"source": [
"## [Order Execution Logic](https://www.backtrader.com/docu/order-creation-execution/order-creation-execution/)\n",
"\n",
"- https://www.backtrader.com/blog/posts/2017-05-01-cheat-on-open/cheat-on-open/"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "NrT1nqW-mIBX"
},
"source": [
""
]
},
{
"cell_type": "code",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/",
"height": 421
},
"id": "AzQeOrGCPa56",
"outputId": "25e9fee4-a814-46c8-8ce2-869e00815cae"
},
"source": [
"if __name__ == '__main__':\n",
" backtest = BackTest(strategy=PercentageBuySellStrategyWithLogger)\n",
" chart = backtest.run()\n",
" from IPython.display import display\n",
" display(chart[0][0])\n",
" backtest.cerebro.strats = []\n"
],
"execution_count": 25,
"outputs": [
{
"output_type": "stream",
"text": [
"Initial Portfolio Value: 3,000,000\n",
"Final Portfolio Value: 5,385,350\n",
"DrawDown: AutoOrderedDict([('len', 23), ('drawdown', 4.487128326549434), ('moneydown', 253000.0), ('max', AutoOrderedDict([('len', 27), ('drawdown', 15.976233973567702), ('moneydown', 655500.0)]))])\n",
"Sharpe Ratio: OrderedDict([('sharperatio', None)])\n",
"Annual Return: OrderedDict([(2020, 0.7951166666666667)])\n"
],
"name": "stdout"
},
{
"output_type": "display_data",
"data": {
"image/png": "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sW7tOjZ98y2BTb0x1XLlwIEDgCWbVYsWLSpMFo1b5G5r9OLHrcxQr169Kp07dxadTidvv/22BAYGCiDVq1eX119/XTw8PKRmzZoSHR1d5jatWGeIWVlZatnAgQMlIiJCRERsbGzkwoULYjab5cEHH5SoqCjJzs4Wk8kku3btkk8++URmzpwpIiKBgYHy/fffi4jIyJEj5aeffirSV3JysnTt2lVNZG4ymUrMUPv27av2PXPmTFm0aJEcOXJEAgICJD8/X0REwsLCRMQyQ12yZIm88MIL6sytMNOnT5cXXnhBRET27Nkjffv2VeVct26dWs86M5swYYIsXbpULTeZTDJp0iR15hAdHS39+/dXx7Fz50613vUYOXKkrFix4rrnrcyePVumFSQ9Xrt2rUyfPl09FxAQIHFxcer7v/76Szp06CA5OTkiItK0aVP1CXzFihXy7rvvltrHxIkTZfHixSJimXn27NmzyPniM9TnnntOjh8/XiJR/KZNm8THx0e8vLzk9OnTpfZlMBjUGdXmzZtl+PDhIiLi4+NT5PtknaFa+/74449l8uTJajvFP9f3339fPv/88yJl1u9QWWaoIpYZ/f79+4t85ydOnChffvmlmEwmad++veTm5sq5c+ekV69eRep89913RdqaO3duqbOn0mQxGo3Su3dv2b59e5HyjRs3yjPPPFOifmhoaJFyo9EoAQEBUqNGDdW6UJiRI0eq/e7cuVOaN28uIiLNmjVT/+Z27typ/k2UF/n5+RIZGan2YeVms8Lvv/9eAJk3b56YTCbJ+f2YDKlaT5wVvZysGSBxr88VO51eAKnvWeeOZNRmqOV73LNrqJcvXyYgIIA//viDb7/9llmzZhEeHs6+ffsIDg7mo48+wsHBgV9//bXUWVJZ2b17N8HBwQQGBrJ//371ibh27drcd999KIpCp06dOHnyJLGxsfTt25exY8eybNmyIk/PPj4+ANx3331cvXqVb7/9lqCgIB555BFq1KjByJEjefrpp3nhhRdKXcs6deoUXbt2BaBr166cOHGC48eP061bN2xsLIYGa27Vq1evsnDhQubNm4der+fMmTPqmpQ1E06nTp0A8PX15dSpU2o/1j4Kc/ToUTXZOYBOp+PIkSMsWrSIoKAgxo4dS1pamjqOd999Vx3HkiVLCAoK4vnnn1evnzVrFlWrVmX48OE3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\n",
"text/plain": [
"<Figure size 432x288 with 4 Axes>"
]
},
"metadata": {
"tags": []
}
}
]
}
]
}
# -*- coding: utf-8 -*-
"""read_kabu_plus_csv_on_backtrader
Automatically generated by Colaboratory.
Original file is located at
https://colab.research.google.com/drive/1eHqULDGmmM6bQLaxJyt8DSB_qcwsTkk-
## Pip install
"""
#!pip install git+https://github.com/dogwood008/backtrader.git@bugfix_order_day_doesnt_works backtrader_plotting
!pip install backtrader==1.9.76.123 backtrader_plotting
"""## Consts"""
USE_BOKEH = False
"""## Load CSV"""
path_to_csv = '/content/drive/MyDrive/Project/kabu-plus/japan-stock-prices-2_2020_9143_adjc.csv'
import pandas as pd
import backtrader as bt
csv = pd.read_csv(path_to_csv)
pd.set_option('display.max_rows', 500)
csv
"""## FeedsData"""
#############################################################
# Copyright (C) 2020 dogwood008 (original author: Daniel Rodriguez; https://github.com/mementum/backtraders)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#############################################################
import csv
import itertools
import io
import pytz
from datetime import date, datetime
from backtrader.utils import date2num
from typing import Any
class KabuPlusJPCSVData(bt.feeds.YahooFinanceCSVData):
'''
Parses pre-downloaded KABU+ CSV Data Feeds (or locally generated if they
comply to the Yahoo formatg)
Specific parameters:
- ``dataname``: The filename to parse or a file-like object
- ``reverse`` (default: ``True``)
It is assumed that locally stored files have already been reversed
during the download process
- ``round`` (default: ``True``)
Whether to round the values to a specific number of decimals after
having adjusted the close
- ``roundvolume`` (default: ``0``)
Round the resulting volume to the given number of decimals after having
adjusted it
- ``decimals`` (default: ``2``)
Number of decimals to round to
- ``swapcloses`` (default: ``False``)
[2018-11-16] It would seem that the order of *close* and *adjusted
close* is now fixed. The parameter is retained, in case the need to
swap the columns again arose.
'''
DATE = 'date'
OPEN = 'open'
HIGH = 'high'
LOW = 'low'
CLOSE = 'close'
VOLUME = 'volume'
ADJUSTED_CLOSE = 'adjusted_close'
params = (
('reverse', True),
('round', True),
('decimals', 2),
('roundvolume', False),
('swapcloses', False),
('headers', True),
('header_names', { # CSVのカラム名と内部的なキーを変換する辞書
DATE: 'date',
OPEN: 'open',
HIGH: 'high',
LOW: 'low',
CLOSE: 'close',
VOLUME: 'volumes',
ADJUSTED_CLOSE: 'adj_close',
}),
('tz', pytz.timezone('Asia/Tokyo'))
)
def _fetch_value(self, values: dict, column_name: str) -> Any:
'''
パラメタで指定された変換辞書を使用して、
CSVで定義されたカラム名に沿って値を取得する。
'''
index = self._column_index(self.p.header_names[column_name])
return values[index]
def _column_index(self, column_name: str) -> int:
'''
与えたカラム名に対するインデックス番号を返す。
見つからなければ ValueError を投げる。
'''
return self._csv_headers.index(column_name)
# copied from https://github.com/mementum/backtrader/blob/0426c777b0abdfafbb0988f5c31347553256a2de/backtrader/feed.py#L666-L679
def start(self):
super(bt.feed.CSVDataBase, self).start()
if self.f is None:
if hasattr(self.p.dataname, 'readline'):
self.f = self.p.dataname
else:
# Let an exception propagate to let the caller know
self.f = io.open(self.p.dataname, 'r')
if self.p.headers and self.p.header_names:
_csv_reader = csv.reader([self.f.readline()])
self._csv_headers = next(_csv_reader)
self.separator = self.p.separator
def _loadline(self, linetokens):
while True:
nullseen = False
for tok in linetokens[1:]:
if tok == 'null':
nullseen = True
linetokens = self._getnextline() # refetch tokens
if not linetokens:
return False # cannot fetch, go away
# out of for to carry on wiwth while True logic
break
if not nullseen:
break # can proceed
dttxt = self._fetch_value(linetokens, self.DATE)
dt = date(int(dttxt[0:4]), int(dttxt[5:7]), int(dttxt[8:10]))
dtnum = date2num(datetime.combine(dt, self.p.sessionend))
#dtnum = date2num(datetime.combine(dt, self.p.sessionend), tz=pytz.timezone('Asia/Tokyo'))
self.lines.datetime[0] = dtnum
o = float(self._fetch_value(linetokens, self.OPEN))
h = float(self._fetch_value(linetokens, self.HIGH))
l = float(self._fetch_value(linetokens, self.LOW))
rawc = float(self._fetch_value(linetokens, self.CLOSE))
self.lines.openinterest[0] = 0.0
adjustedclose = float(self._fetch_value(linetokens, self.ADJUSTED_CLOSE))
v = float(self._fetch_value(linetokens, self.VOLUME))
if self.p.swapcloses: # swap closing prices if requested
rawc, adjustedclose = adjustedclose, rawc
adjfactor = rawc / adjustedclose
o /= adjfactor
h /= adjfactor
l /= adjfactor
v *= adjfactor
if self.p.round:
decimals = self.p.decimals
o = round(o, decimals)
h = round(h, decimals)
l = round(l, decimals)
rawc = round(rawc, decimals)
v = round(v, self.p.roundvolume)
self.lines.open[0] = o
self.lines.high[0] = h
self.lines.low[0] = l
self.lines.close[0] = adjustedclose
self.lines.volume[0] = v
return True
"""## Sizer"""
# import backtrader as bt
# from bt.comminfo import CommInfoBase
# from bt.feeds import AbstractDataBase
#
# class RangeSizer(bt.Sizer):
# '''
# 決められた注文額に納まるように、注文数を調整する。
# '''
#
# params = (
# ('min_order_price', 10 * 10000), # 最低購入金額(円)
# ('max_order_price', 50 * 10000), # 最高購入金額(円)
# )
#
# def _getsizing(self, comminfo: CommInfoBase,
# cash: float, data: AbstractDataBase, isbuy: bool) -> int:
# '''
# base: https://github.com/mementum/backtrader/blob/master/backtrader/sizer.py
# Parameters
# ----------------------
# comminfo : bt.comminfo.CommInfoBase
# The CommissionInfo instance that contains
# information about the commission for the data and allows
# calculation of position value, operation cost, commision for the
# operation
#
# cash : float
# current available cash in the *broker*
#
# data : Any
# target of the operation
#
# isbuy : bool
# will be `True` for *buy* operations and `False`
# for *sell* operations
#
#
# Returns
# -----------------------
# size : int
# The size of an order
# '''
#
# issell = not isbuy
#
# if issell:
# # 売り注文なら、全量を指定
# position = self.broker.getposition(data)
# if not position.size:
# return 0 # do not sell if nothing is open
# else:
# return position.size
#
# else:
# # TODO
#
"""## StrategyFetcher"""
# # https://www.backtrader.com/blog/posts/2017-05-16-stsel-revisited/stsel-revisited/
# class StFetcher(object):
# _STRATS = []
#
# @classmethod
# def register(cls, target):
# cls._STRATS.append(target)
#
# @classmethod
# def COUNT(cls):
# return range(len(cls._STRATS))
#
# def __new__(cls, *args, **kwargs):
# idx = kwargs.pop('idx')
#
# obj = cls._STRATS[idx](*args, **kwargs)
# return obj
"""## Strategy"""
import backtrader as bt
from logging import getLogger, StreamHandler, Formatter, DEBUG, INFO, WARN
if USE_BOKEH:
from backtrader_plotting import Bokeh
from backtrader_plotting.schemes import Tradimo
# for jupyter
if 'PercentageBuySellStrategyWithLogger' in globals():
del PercentageBuySellStrategyWithLogger
from typing import Callable, Union, Optional
LazyString = Callable[[str], str]
LazyableString = Union[LazyString, str]
# Create a Stratey
# @StFetcher.register
class PercentageBuySellStrategyWithLogger(bt.Strategy):
params = (
('default_unit_size', 100), # デフォルトの単元株の株数
('buy_under_percentage', 5), # 前日終値と比較し本日始値が▲x%の場合に買い注文
('sell_over_percentage', 5), # 前日終値と比較し本日始値が+y%の場合に売り注文
('min_order_price', 10 * 10000), # 最低購入金額(円)
('max_order_price', 50 * 10000), # 最高購入金額(円)
('smaperiod', 5),
# ('sizer', RangeSizer), # 数量指定ロジック
)
# def _log(self, txt: LazyLazyableString, dt=None):
# ''' Logging function for this strategy '''
# dt = dt or self.datas[0].datetime.date(0)
# self._logger.debug('%s, %s' % (dt.isoformat(), txt))
def __init__(self, loglevel):
# Keep a reference to the "close" line in the data[0] dataseries
self._dataopen = self.datas[0].open
self._datahigh = self.datas[0].high
self._datalow = self.datas[0].low
self._dataclose = self.datas[0].close
self._dataadjclose = self.datas[0].adjclose
self._datavolume = self.datas[0].volume
self._logger = getLogger(__name__)
self.handler = StreamHandler()
self.handler.setLevel(loglevel)
self._logger.setLevel(loglevel)
self._logger.addHandler(self.handler)
self._logger.propagate = False
self.handler.setFormatter(
Formatter('[%(levelname)s] %(message)s'))
self.sma = bt.indicators.SimpleMovingAverage(
self.datas[0], period=self.params.smaperiod)
def notify_order(self, order):
if order.status in [order.Submitted, order.Accepted]:
return
if order.status in [order.Completed]:
if order.isbuy():
buy_or_sell = 'BUY'
elif order.issell():
buy_or_sell = 'SELL'
else:
buy_or_sell = 'UNDEFINED'
self._log(lambda: '{b_s:4s} EXECUTED, {price:7.2f}'.format(
b_s=buy_or_sell,
price=order.executed.price))
elif order.status in [order.Canceled, order.Margin, order.Rejected]:
self._debug(lambda: 'Order Canceled/Margin/Rejected')
elif order.status in [order.Expired]:
# from IPython.core.debugger import Pdb; Pdb().set_trace()
self._debug(lambda: 'Expired: {b_s:s} ¥{sum:,d} (@{price:.2f} * {unit:4d}), valid: ({valid:s}, {valid_raw:f})'.format(
sum=int(order.price * order.size),
b_s=order.ordtypename(), price=order.price, unit=order.size,
valid=str(bt.utils.dateintern.num2date(order.valid, tz=pytz.timezone('Asia/Tokyo'))),
valid_raw=order.valid)
)
else:
self._debug(order.getstatusname())
def _log(self, txt: LazyableString, loglevel=INFO, dt=None):
''' Logging function for this strategy '''
dt = dt or self.datas[0].datetime.date(0)
logtext = txt() if callable(txt) else str(txt)
self._logger.log(loglevel, '%s, %s' % (dt.isoformat(), logtext))
def _debug(self, txt: LazyableString, dt=None):
self._log(txt, DEBUG, dt)
def _info(self, txt: LazyableString, dt=None):
self._log(txt, INFO, dt)
def _size(self, unit_price: float, is_buy: bool) -> Optional[int]:
'''
Params
------------------
unit_price : flat
ある銘柄の価格
is_buy : bool
買い注文なら True, 売り注文なら False
'''
if not is_buy:
# 売り注文なら、全量を指定する
return None
min_size = 100
max_size = 2000
for i in range(int(min_size / 100), int(max_size / 100 + 1)):
unit = i * 100
order_value = unit_price * unit
if order_value < self.p.min_order_price:
# 安すぎる場合、購入量を増やす
continue
elif self.p.min_order_price <= order_value <= self.p.max_order_price:
return unit
else:
# 高すぎて買えない場合、買わない
return 0
def _is_to_buy(self, open_today: float, close_yesterday: float, high_today: Optional[float] = None) -> bool:
# 本日始値*閾値% <= 前日終値
to_buy_condition: bool = open_today * (100.0 - self.p.buy_under_percentage) / 100.0 <= close_yesterday
if high_today:
# バックテスト実行時に、始値より高値が高いことを確認する。
# これにより、実際にこの戦略をリアルタイムで動かした場合にも、動作可能であることを確認する。
return to_buy_condition and open_today <= high_today
else:
# リアルタイムで動かした場合は、high_today = None
return to_buy_condition
def _is_to_close(self, open_today: float, close_yesterday: float, low_today: Optional[float] = None) -> bool:
# 前日終値*閾値% <= 本日始値
to_sell_condition: bool = close_yesterday * (100.0 + self.p.sell_over_percentage) / 100.0 <= open_today
if low_today:
# バックテスト実行時に、始値より安値が低いことを確認する。
# これにより、実際にこの戦略をリアルタイムで動かした場合にも、動作可能であることを確認する。
return to_sell_condition and low_today <= low_today
else:
# リアルタイムで動かした場合は、low_today = None
return to_sell_condition
def next(self):
# 当日の始値を見るためにチートする
return
def next_open(self): # 当日の始値を見るためにチートする
open_today = self._dataopen[0]
high_today = self._datahigh[0]
low_today = self._datalow[0]
close_yesterday = self._dataclose[-1]
if self._is_to_buy(open_today, close_yesterday, high_today):
size = self._size(unit_price=open_today, is_buy=True)
self._info(lambda: 'BUY CREATE @{price:.2f}, #{unit:4d} (open_today, close_yesterday)=({open_today:f}, {close_yesterday:f})'.format(
# FIXME: fix unit size
price=open_today, unit=-100, open_today=open_today, close_yesterday=close_yesterday)
)
self._debug(lambda: '(o, h, l, c) = ({o:}, {h:}, {l:}, {c:})'.format(
o=self._dataopen[0], h=self._datahigh[0], l=self._datalow[0], c=self._dataclose[0]))
self.buy(size=size, price=open_today, exectype=bt.Order.Limit, valid=bt.Order.DAY)
elif self._is_to_close(open_today, close_yesterday, low_today):
size = self._size(unit_price=open_today, is_buy=False)
self._info(lambda: 'CLOSE (SELL) CREATE @{price:.2f}, #all (open_today, close_yesterday)=({open_today:f}, {close_yesterday:f})'.format(
price=open_today, open_today=open_today, close_yesterday=close_yesterday)
)
self.close(size=size, price=open_today, exectype=bt.Order.Limit, valid=bt.Order.DAY)
if USE_BOKEH:
del PercentageBuySellStrategyWithLogger
from percentage_buy_sell_strategy_with_logger import PercentageBuySellStrategyWithLogger
"""## main"""
import backtrader.analyzers as btanalyzers
class BackTest:
def __init__(self, strategy: bt.Strategy, cheat_on_open=True):
self.cerebro = bt.Cerebro(tz='Asia/Tokyo', cheat_on_open=cheat_on_open)
# Set cheat-on-close
# self.cerebro.brokerj.set_coc(True)
data = KabuPlusJPCSVData(
dataname=path_to_csv,
fromdate=datetime(2020, 1, 1),
todate=datetime(2020, 11, 30),
reverse=False)
self.cerebro.adddata(data)
# Add a strategy
IN_DEVELOPMENT = False # このフラグにより、ログレベルを切り替えることで、本番ではWARN以上のみをログに出すようにする。
# フラグの切り替えは、環境変数で行う事が望ましいが今は一旦先送りする。
loglevel = DEBUG if IN_DEVELOPMENT else WARN
self.cerebro.broker.setcash(100 * 10000 * 3) # 信用取引なので3倍
self.cerebro.addstrategy(strategy, loglevel)
self.cerebro.addanalyzer(btanalyzers.DrawDown, _name='drawdown')
self.cerebro.addanalyzer(btanalyzers.AnnualReturn, _name='annualreturn')
# self.cerebro.optstrategy(StFetcher, idx=StFetcher.COUNT())
def run(self):
initial_cash = self.cerebro.broker.getvalue()
thestrats = self.cerebro.run()
thestrat = thestrats[0]
print('Initial Portfolio Value: {val:,}'.format(val=initial_cash))
print('Final Portfolio Value: {val:,}'.format(val=int(self.cerebro.broker.getvalue())))
print('DrawDown:', thestrat.analyzers.drawdown.get_analysis())
print('Annual Return:', thestrat.analyzers.annualreturn.get_analysis())
save_file = False
if USE_BOKEH:
if save_file:
b = Bokeh(style='bar', plot_mode='single', scheme=Tradimo(), output_mode='save', filename='chart.html')
else:
b = Bokeh(style='bar', plot_mode='single', scheme=Tradimo())
return self.cerebro.plot(b, iplot=not save_file)
else:
return self.cerebro.plot(use='agg')
# def num2date(val: float, tz=pytz.timezone('Asia/Tokyo')):
# return bt.utils.dateintern.num2date(val, tz=tz)
#
# def p_order(order: bt.Order):
# print(str(order))
"""## [Order Execution Logic](https://www.backtrader.com/docu/order-creation-execution/order-creation-execution/)
- https://www.backtrader.com/blog/posts/2017-05-01-cheat-on-open/cheat-on-open/
"""
if __name__ == '__main__':
backtest = BackTest(strategy=PercentageBuySellStrategyWithLogger)
chart = backtest.run()
from IPython.display import display
display(chart[0][0])
backtest.cerebro.strats = []
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment