BackcastPro 0.0.2__tar.gz → 0.0.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of BackcastPro might be problematic. Click here for more details.

Files changed (29) hide show
  1. backcastpro-0.0.4/PKG-INFO +69 -0
  2. backcastpro-0.0.4/README.md +55 -0
  3. {backcastpro-0.0.2 → backcastpro-0.0.4}/pyproject.toml +4 -4
  4. backcastpro-0.0.4/src/BackcastPro/__init__.py +15 -0
  5. backcastpro-0.0.4/src/BackcastPro/_broker.py +415 -0
  6. {backcastpro-0.0.2 → backcastpro-0.0.4}/src/BackcastPro/_stats.py +169 -212
  7. backcastpro-0.0.4/src/BackcastPro/backtest.py +293 -0
  8. backcastpro-0.0.4/src/BackcastPro/data/JapanStock.py +171 -0
  9. backcastpro-0.0.4/src/BackcastPro/data/__init__.py +7 -0
  10. backcastpro-0.0.4/src/BackcastPro/order.py +151 -0
  11. backcastpro-0.0.4/src/BackcastPro/position.py +61 -0
  12. backcastpro-0.0.4/src/BackcastPro/strategy.py +174 -0
  13. backcastpro-0.0.4/src/BackcastPro/trade.py +195 -0
  14. backcastpro-0.0.4/src/BackcastPro.egg-info/PKG-INFO +69 -0
  15. {backcastpro-0.0.2 → backcastpro-0.0.4}/src/BackcastPro.egg-info/SOURCES.txt +8 -7
  16. backcastpro-0.0.2/PKG-INFO +0 -53
  17. backcastpro-0.0.2/README.md +0 -39
  18. backcastpro-0.0.2/src/BackcastPro/__init__.py +0 -90
  19. backcastpro-0.0.2/src/BackcastPro/_plotting.py +0 -785
  20. backcastpro-0.0.2/src/BackcastPro/_util.py +0 -337
  21. backcastpro-0.0.2/src/BackcastPro/backtesting.py +0 -1763
  22. backcastpro-0.0.2/src/BackcastPro/lib.py +0 -646
  23. backcastpro-0.0.2/src/BackcastPro/test/__init__.py +0 -29
  24. backcastpro-0.0.2/src/BackcastPro/test/__main__.py +0 -7
  25. backcastpro-0.0.2/src/BackcastPro/test/_test.py +0 -1174
  26. backcastpro-0.0.2/src/BackcastPro.egg-info/PKG-INFO +0 -53
  27. {backcastpro-0.0.2 → backcastpro-0.0.4}/setup.cfg +0 -0
  28. {backcastpro-0.0.2 → backcastpro-0.0.4}/src/BackcastPro.egg-info/dependency_links.txt +0 -0
  29. {backcastpro-0.0.2 → backcastpro-0.0.4}/src/BackcastPro.egg-info/top_level.txt +0 -0
@@ -0,0 +1,69 @@
1
+ Metadata-Version: 2.4
2
+ Name: BackcastPro
3
+ Version: 0.0.4
4
+ Summary: トレーディング戦略のためのPythonバックテストライブラリ
5
+ Author-email: botterYosuke <yosuke.sasazawa@gmail.com>
6
+ Project-URL: Homepage, https://BackcastPro.github.io/BackcastPro/
7
+ Project-URL: Issues, https://github.com/BackcastPro/BackcastPro/issues
8
+ Project-URL: Logo, https://raw.githubusercontent.com/BackcastPro/BackcastPro/main/docs/img/logo.drawio.svg
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Requires-Python: >=3.9
13
+ Description-Content-Type: text/markdown
14
+
15
+ # <img src="docs/img/logo.drawio.svg" alt="BackcastPro Logo" width="40" height="24"> BackcastPro
16
+
17
+ トレーディング戦略のためのPythonバックテストライブラリ。
18
+
19
+ ## インストール(Windows)
20
+
21
+ ### PyPIから(エンドユーザー向け)
22
+
23
+ ```powershell
24
+ py -m pip install BackcastPro
25
+ ```
26
+
27
+ ### 開発用インストール
28
+
29
+ 開発用に、リポジトリをクローンして開発モードでインストールします。
30
+
31
+ ```powershell
32
+ git clone <repository-url>
33
+ cd BackcastPro
34
+ py -m venv .venv
35
+ .\.venv\Scripts\Activate.ps1
36
+ py -m pip install -e .
37
+ py -m pip install -r requirements.txt
38
+ ```
39
+
40
+ **開発モードインストール(py -m pip install -e .)**
41
+ - プロジェクトを開発モードでインストールします
42
+ - `src` ディレクトリが自動的に Python パスに追加されます
43
+
44
+ ## 使用方法
45
+
46
+ ```python
47
+ from BackcastPro import Strategy, Backtest
48
+ from BackcastPro.data import DataReader, JapanStocks
49
+
50
+ # ここにトレーディング戦略の実装を記述
51
+ ```
52
+
53
+ ## ドキュメント
54
+
55
+ - [ドキュメント一覧](./docs/index.md)
56
+ - [チュートリアル](./docs/tutorial.md)
57
+ - [APIリファレンス](./docs/api-reference.md)
58
+ - [高度な使い方](./docs/advanced-usage.md)
59
+ - [トラブルシューティング](./docs/troubleshooting.md)
60
+ - [開発者ガイド](./docs/developer-guide.md)
61
+ - [PyPIへのデプロイ方法](./docs/how-to-deploy-to-PyPI.md)
62
+ - [サンプル](./docs/examples/)
63
+
64
+ ## バグ報告 / サポート
65
+
66
+ - バグ報告や要望は GitHub Issues へ
67
+ - 質問は Discord コミュニティへ([招待リンク](https://discord.gg/fzJTbpzE))
68
+ - 使い方はドキュメントをご参照ください
69
+
@@ -0,0 +1,55 @@
1
+ # <img src="docs/img/logo.drawio.svg" alt="BackcastPro Logo" width="40" height="24"> BackcastPro
2
+
3
+ トレーディング戦略のためのPythonバックテストライブラリ。
4
+
5
+ ## インストール(Windows)
6
+
7
+ ### PyPIから(エンドユーザー向け)
8
+
9
+ ```powershell
10
+ py -m pip install BackcastPro
11
+ ```
12
+
13
+ ### 開発用インストール
14
+
15
+ 開発用に、リポジトリをクローンして開発モードでインストールします。
16
+
17
+ ```powershell
18
+ git clone <repository-url>
19
+ cd BackcastPro
20
+ py -m venv .venv
21
+ .\.venv\Scripts\Activate.ps1
22
+ py -m pip install -e .
23
+ py -m pip install -r requirements.txt
24
+ ```
25
+
26
+ **開発モードインストール(py -m pip install -e .)**
27
+ - プロジェクトを開発モードでインストールします
28
+ - `src` ディレクトリが自動的に Python パスに追加されます
29
+
30
+ ## 使用方法
31
+
32
+ ```python
33
+ from BackcastPro import Strategy, Backtest
34
+ from BackcastPro.data import DataReader, JapanStocks
35
+
36
+ # ここにトレーディング戦略の実装を記述
37
+ ```
38
+
39
+ ## ドキュメント
40
+
41
+ - [ドキュメント一覧](./docs/index.md)
42
+ - [チュートリアル](./docs/tutorial.md)
43
+ - [APIリファレンス](./docs/api-reference.md)
44
+ - [高度な使い方](./docs/advanced-usage.md)
45
+ - [トラブルシューティング](./docs/troubleshooting.md)
46
+ - [開発者ガイド](./docs/developer-guide.md)
47
+ - [PyPIへのデプロイ方法](./docs/how-to-deploy-to-PyPI.md)
48
+ - [サンプル](./docs/examples/)
49
+
50
+ ## バグ報告 / サポート
51
+
52
+ - バグ報告や要望は GitHub Issues へ
53
+ - 質問は Discord コミュニティへ([招待リンク](https://discord.gg/fzJTbpzE))
54
+ - 使い方はドキュメントをご参照ください
55
+
@@ -1,10 +1,10 @@
1
1
  [project]
2
2
  name = "BackcastPro"
3
- version = "0.0.2"
3
+ version = "0.0.4"
4
4
  authors = [
5
- { name="笹澤ようすけ", email="yosuke.sasazawa@gmail.com" },
5
+ { name="botterYosuke", email="yosuke.sasazawa@gmail.com" },
6
6
  ]
7
- description = "A small example package"
7
+ description = "トレーディング戦略のためのPythonバックテストライブラリ"
8
8
  readme = "README.md"
9
9
  requires-python = ">=3.9"
10
10
  classifiers = [
@@ -16,7 +16,7 @@ classifiers = [
16
16
  [project.urls]
17
17
  Homepage = "https://BackcastPro.github.io/BackcastPro/"
18
18
  Issues = "https://github.com/BackcastPro/BackcastPro/issues"
19
- Logo = "https://raw.githubusercontent.com/BackcastPro/BackcastPro/main/docs/img/2.png"
19
+ Logo = "https://raw.githubusercontent.com/BackcastPro/BackcastPro/main/docs/img/logo.drawio.svg"
20
20
 
21
21
  [tool.setuptools]
22
22
  include-package-data = true
@@ -0,0 +1,15 @@
1
+ """
2
+ BackcastPro をご利用いただきありがとうございます。
3
+
4
+ インストール後のご案内(インストール済みユーザー向け)
5
+
6
+ - ドキュメント総合トップ: https://botteryosuke.github.io/BackcastPro/
7
+ - クイックスタート/チュートリアル: https://botteryosuke.github.io/BackcastPro/tutorial
8
+ - APIリファレンス: https://botteryosuke.github.io/BackcastPro/api-reference
9
+ - 高度な使い方: https://botteryosuke.github.io/BackcastPro/advanced-usage
10
+ - トラブルシューティング: https://botteryosuke.github.io/BackcastPro/troubleshooting
11
+
12
+ ※ 使い始めはチュートリアル → 詳細はAPIリファレンスをご参照ください。
13
+ """
14
+ from .backtest import Backtest
15
+ from .strategy import Strategy
@@ -0,0 +1,415 @@
1
+ """
2
+ ブローカー管理モジュール。
3
+ """
4
+
5
+ import warnings
6
+ from math import copysign
7
+ from typing import List, Optional, TYPE_CHECKING
8
+
9
+ import numpy as np
10
+ import pandas as pd
11
+
12
+ from .order import Order
13
+ from .position import Position
14
+ from .trade import Trade
15
+
16
+ if TYPE_CHECKING:
17
+ pass
18
+
19
+
20
+ class _Broker:
21
+ """
22
+ バックテストにおける証券取引の実行、注文管理、ポジション管理、損益計算を担当します。
23
+ 実際の証券会社のブローカー機能をシミュレートし、リアルな取引環境を提供します。
24
+
25
+ Parameters
26
+ ----------
27
+ data : pd.DataFrame
28
+ 取引対象の価格データ。Open, High, Low, Closeの列を持つ必要があります。
29
+ cash : float
30
+ 初期現金残高。正の値である必要があります。
31
+ spread : float
32
+ ビッドアスクスプレッド(買値と売値の差)。取引コストとして使用されます。
33
+ commission : float or tuple or callable
34
+ 手数料の設定方法:
35
+ - float: 相対手数料(例: 0.001 = 0.1%)
36
+ - tuple: (固定手数料, 相対手数料) の組み合わせ
37
+ - callable: カスタム手数料計算関数 (size, price) -> 手数料
38
+ margin : float
39
+ 必要証拠金率(0 < margin <= 1)。レバレッジ = 1/margin として計算されます。
40
+ trade_on_close : bool
41
+ 取引を終値で実行するかどうか。Trueの場合、次の始値ではなく現在の終値で取引します。
42
+ hedging : bool
43
+ ヘッジングモードの有効化。Trueの場合、反対方向のポジションを同時に保有できます。
44
+ exclusive_orders : bool
45
+ 排他的注文モード。Trueの場合、新しい注文が前のポジションを自動的にクローズします。
46
+ index : pd.Index
47
+ 時系列データのインデックス。エクイティカーブの記録に使用されます。
48
+ """
49
+ # Tips:
50
+ # 関数定義における`*`の意味
51
+ # - `*`以降の引数は、必ずキーワード引数として渡す必要がある
52
+ # - 位置引数として渡すことはできない
53
+ # なぜキーワード専用引数を使うのか?
54
+ # 1. APIの明確性: 引数の意味が明確になる
55
+ # 2. 保守性: 引数の順序を変更しても既存のコードが壊れない
56
+ # 3. 可読性: 関数呼び出し時に何を渡しているかが分かりやすい
57
+ def __init__(self, *, data, cash, spread, commission, margin,
58
+ trade_on_close, hedging, exclusive_orders, index):
59
+ assert cash > 0, f"cash should be > 0, is {cash}"
60
+ assert 0 < margin <= 1, f"margin should be between 0 and 1, is {margin}"
61
+ self._data: pd.DataFrame = data
62
+ self._cash = cash
63
+
64
+ # 手数料の登録
65
+ if callable(commission):
66
+ # 関数`commission`が呼び出し可能な場合
67
+ self._commission = commission
68
+ else:
69
+ try:
70
+ self._commission_fixed, self._commission_relative = commission
71
+ except TypeError:
72
+ self._commission_fixed, self._commission_relative = 0, commission
73
+ assert self._commission_fixed >= 0, 'Need fixed cash commission in $ >= 0'
74
+ assert -.1 <= self._commission_relative < .1, \
75
+ ("commission should be between -10% "
76
+ f"(e.g. market-maker's rebates) and 10% (fees), is {self._commission_relative}")
77
+ self._commission = self._commission_func
78
+
79
+
80
+ self._spread = spread
81
+ self._leverage = 1 / margin
82
+ self._trade_on_close = trade_on_close
83
+ self._hedging = hedging
84
+ self._exclusive_orders = exclusive_orders
85
+
86
+ self._equity = np.tile(np.nan, len(index))
87
+ self.orders: List[Order] = []
88
+ self.trades: List[Trade] = []
89
+ self.position = Position(self)
90
+ self.closed_trades: List[Trade] = []
91
+
92
+ def _commission_func(self, order_size, price):
93
+ return self._commission_fixed + abs(order_size) * price * self._commission_relative
94
+
95
+ def __repr__(self):
96
+ return f'<Broker: {self._cash:.0f}{self.position.pl:+.1f} ({len(self.trades)} trades)>'
97
+
98
+ def new_order(self,
99
+ size: float,
100
+ limit: Optional[float] = None,
101
+ stop: Optional[float] = None,
102
+ sl: Optional[float] = None,
103
+ tp: Optional[float] = None,
104
+ tag: object = None,
105
+ *,
106
+ trade: Optional[Trade] = None) -> Order:
107
+ """
108
+ Argument size indicates whether the order is long or short
109
+ """
110
+ size = float(size)
111
+ stop = stop and float(stop)
112
+ limit = limit and float(limit)
113
+ sl = sl and float(sl)
114
+ tp = tp and float(tp)
115
+
116
+ is_long = size > 0
117
+ assert size != 0, size
118
+ adjusted_price = self._adjusted_price(size)
119
+
120
+ if is_long:
121
+ if not (sl or -np.inf) < (limit or stop or adjusted_price) < (tp or np.inf):
122
+ raise ValueError(
123
+ "Long orders require: "
124
+ f"SL ({sl}) < LIMIT ({limit or stop or adjusted_price}) < TP ({tp})")
125
+ else:
126
+ if not (tp or -np.inf) < (limit or stop or adjusted_price) < (sl or np.inf):
127
+ raise ValueError(
128
+ "Short orders require: "
129
+ f"TP ({tp}) < LIMIT ({limit or stop or adjusted_price}) < SL ({sl})")
130
+
131
+ order = Order(self, size, limit, stop, sl, tp, trade, tag)
132
+
133
+ if not trade:
134
+ # 排他的注文(各新しい注文が前の注文/ポジションを自動クローズ)の場合、
135
+ # 事前にすべての非条件付き注文をキャンセルし、すべてのオープン取引をクローズ
136
+ if self._exclusive_orders:
137
+ for o in self.orders:
138
+ if not o.is_contingent:
139
+ o.cancel()
140
+ for t in self.trades:
141
+ t.close()
142
+
143
+ # 新しい注文を注文キューに配置、SL注文が最初に処理されるようにする
144
+ self.orders.insert(0 if trade and stop else len(self.orders), order)
145
+
146
+ return order
147
+
148
+ @property
149
+ def last_price(self) -> float:
150
+ """ Price at the last (current) close. """
151
+ return self._data.Close.iloc[-1]
152
+
153
+ def _adjusted_price(self, size=None, price=None) -> float:
154
+ """
155
+ Long/short `price`, adjusted for spread.
156
+ In long positions, the adjusted price is a fraction higher, and vice versa.
157
+ """
158
+ return (price or self.last_price) * (1 + copysign(self._spread, size))
159
+
160
+ @property
161
+ def equity(self) -> float:
162
+ return self._cash + sum(trade.pl for trade in self.trades)
163
+
164
+ @property
165
+ def margin_available(self) -> float:
166
+ # https://github.com/QuantConnect/Lean/pull/3768 から
167
+ margin_used = sum(trade.value / self._leverage for trade in self.trades)
168
+ return max(0, self.equity - margin_used)
169
+
170
+ @property
171
+ def cash(self):
172
+ return self._cash
173
+
174
+ @property
175
+ def commission(self):
176
+ return self._commission
177
+
178
+ def next(self):
179
+ i = self._i = len(self._data) - 1
180
+ self._process_orders()
181
+
182
+ # エクイティカーブ用にアカウントエクイティを記録
183
+ equity = self.equity
184
+ self._equity[i] = equity
185
+
186
+ # エクイティが負の場合、すべてを0に設定してシミュレーションを停止
187
+ if equity <= 0:
188
+ assert self.margin_available <= 0
189
+ for trade in self.trades:
190
+ self._close_trade(trade, self._data.Close.iloc[-1], i)
191
+ self._cash = 0
192
+ self._equity[i:] = 0
193
+ raise Exception
194
+
195
+ def _process_orders(self):
196
+ data = self._data
197
+ open, high, low = data.Open.iloc[-1], data.High.iloc[-1], data.Low.iloc[-1]
198
+ reprocess_orders = False
199
+
200
+ # 注文を処理
201
+ for order in list(self.orders): # type: Order
202
+
203
+ # 関連するSL/TP注文は既に削除されている
204
+ if order not in self.orders:
205
+ continue
206
+
207
+ # ストップ条件が満たされたかチェック
208
+ stop_price = order.stop
209
+ if stop_price:
210
+ is_stop_hit = ((high >= stop_price) if order.is_long else (low <= stop_price))
211
+ if not is_stop_hit:
212
+ continue
213
+
214
+ # ストップ価格に達すると、ストップ注文は成行/指値注文になる
215
+ # https://www.sec.gov/fast-answers/answersstopordhtm.html
216
+ order._replace(stop_price=None)
217
+
218
+ # 購入価格を決定
219
+ # 指値注文が約定可能かチェック
220
+ if order.limit:
221
+ is_limit_hit = low <= order.limit if order.is_long else high >= order.limit
222
+ # ストップとリミットが同じバー内で満たされた場合、悲観的に
223
+ # リミットがストップより先に満たされたと仮定する(つまり「カウントされる前に」)
224
+ is_limit_hit_before_stop = (is_limit_hit and
225
+ (order.limit <= (stop_price or -np.inf)
226
+ if order.is_long
227
+ else order.limit >= (stop_price or np.inf)))
228
+ if not is_limit_hit or is_limit_hit_before_stop:
229
+ continue
230
+
231
+ # stop_priceが設定されている場合、このバー内で満たされた
232
+ price = (min(stop_price or open, order.limit)
233
+ if order.is_long else
234
+ max(stop_price or open, order.limit))
235
+ else:
236
+ # 成行注文(Market-if-touched / market order)
237
+ # 条件付き注文は常に次の始値で
238
+ prev_close = data.Close.iloc[-2]
239
+ price = prev_close if self._trade_on_close and not order.is_contingent else open
240
+ if stop_price:
241
+ price = max(price, stop_price) if order.is_long else min(price, stop_price)
242
+
243
+ # エントリー/エグジットバーのインデックスを決定
244
+ is_market_order = not order.limit and not stop_price
245
+ time_index = (
246
+ (self._i - 1)
247
+ if is_market_order and self._trade_on_close and not order.is_contingent else
248
+ self._i)
249
+
250
+ # 注文がSL/TP注文の場合、それが依存していた既存の取引をクローズする必要がある
251
+ if order.parent_trade:
252
+ trade = order.parent_trade
253
+ _prev_size = trade.size
254
+ # order.sizeがtrade.sizeより「大きい」場合、この注文はtrade.close()注文で
255
+ # 取引の一部は事前にクローズされている
256
+ size = copysign(min(abs(_prev_size), abs(order.size)), order.size)
257
+ # この取引がまだクローズされていない場合(例:複数の`trade.close(.5)`呼び出し)
258
+ if trade in self.trades:
259
+ self._reduce_trade(trade, price, size, time_index)
260
+ assert order.size != -_prev_size or trade not in self.trades
261
+ if price == stop_price:
262
+ # 統計用にSLを注文に戻す
263
+ trade._sl_order._replace(stop_price=stop_price)
264
+ if order in (trade._sl_order,
265
+ trade._tp_order):
266
+ assert order.size == -trade.size
267
+ assert order not in self.orders # 取引がクローズされたときに削除される
268
+ else:
269
+ # trade.close()注文で、完了
270
+ assert abs(_prev_size) >= abs(size) >= 1
271
+ self.orders.remove(order)
272
+ continue
273
+
274
+ # そうでなければ、これは独立した取引
275
+
276
+ # 手数料(またはビッドアスクスプレッド)を含むように価格を調整
277
+ # ロングポジションでは調整価格が少し高くなり、その逆も同様
278
+ adjusted_price = self._adjusted_price(order.size, price)
279
+ adjusted_price_plus_commission = \
280
+ adjusted_price + self._commission(order.size, price) / abs(order.size)
281
+
282
+ # 注文サイズが比例的に指定された場合、
283
+ # マージンとスプレッド/手数料を考慮して、単位での真のサイズを事前計算
284
+ size = order.size
285
+ if -1 < size < 1:
286
+ size = copysign(int((self.margin_available * self._leverage * abs(size))
287
+ // adjusted_price_plus_commission), size)
288
+ # 単一ユニットでも十分な現金/マージンがない
289
+ if not size:
290
+ warnings.warn(
291
+ f'time={self._i}: ブローカーは相対サイズの注文を'
292
+ f'不十分なマージンのためキャンセルしました。', category=UserWarning)
293
+ # XXX: 注文はブローカーによってキャンセルされる?
294
+ self.orders.remove(order)
295
+ continue
296
+ assert size == round(size)
297
+ need_size = int(size)
298
+
299
+ if not self._hedging:
300
+ # 既存の反対方向の取引をFIFOでクローズ/削減してポジションを埋める
301
+ # 既存の取引は調整価格でクローズされる(調整は購入時に既に行われているため)
302
+ for trade in list(self.trades):
303
+ if trade.is_long == order.is_long:
304
+ continue
305
+ assert trade.size * order.size < 0
306
+
307
+ # 注文サイズがこの反対方向の既存取引より大きい場合、
308
+ # 完全にクローズされる
309
+ if abs(need_size) >= abs(trade.size):
310
+ self._close_trade(trade, price, time_index)
311
+ need_size += trade.size
312
+ else:
313
+ # 既存の取引が新しい注文より大きい場合、
314
+ # 部分的にのみクローズされる
315
+ self._reduce_trade(trade, price, need_size, time_index)
316
+ need_size = 0
317
+
318
+ if not need_size:
319
+ break
320
+
321
+ # 注文をカバーするのに十分な流動性がない場合、ブローカーはそれをキャンセルする
322
+ if abs(need_size) * adjusted_price_plus_commission > \
323
+ self.margin_available * self._leverage:
324
+ self.orders.remove(order)
325
+ continue
326
+
327
+ # 新しい取引を開始
328
+ if need_size:
329
+ self._open_trade(adjusted_price,
330
+ need_size,
331
+ order.sl,
332
+ order.tp,
333
+ time_index,
334
+ order.tag)
335
+
336
+ # 新しくキューに追加されたSL/TP注文を再処理する必要がある
337
+ # これにより、注文が開かれた同じバーでSLがヒットすることを可能にする
338
+ # https://github.com/kernc/backtesting.py/issues/119 を参照
339
+ if order.sl or order.tp:
340
+ if is_market_order:
341
+ reprocess_orders = True
342
+ # Order.stopとTPが同じバー内でヒットしたが、SLはヒットしなかった。この場合
343
+ # ストップとTPが同じ価格方向に進むため、曖昧ではない
344
+ elif stop_price and not order.limit and order.tp and (
345
+ (order.is_long and order.tp <= high and (order.sl or -np.inf) < low) or
346
+ (order.is_short and order.tp >= low and (order.sl or np.inf) > high)):
347
+ reprocess_orders = True
348
+ elif (low <= (order.sl or -np.inf) <= high or
349
+ low <= (order.tp or -np.inf) <= high):
350
+ warnings.warn(
351
+ f"({data.index[-1]}) 条件付きSL/TP注文が、その親ストップ/リミット注文が取引に"
352
+ "変換された同じバーで実行されることになります。"
353
+ "正確なローソク足内価格変動を断言できないため、"
354
+ "影響を受けるSL/TP注文は代わりに次の(マッチングする)価格/バーで"
355
+ "実行され、結果(この取引の)が幾分疑わしいものになります。"
356
+ "https://github.com/kernc/backtesting.py/issues/119 を参照",
357
+ UserWarning)
358
+
359
+ # 注文処理完了
360
+ self.orders.remove(order)
361
+
362
+ if reprocess_orders:
363
+ self._process_orders()
364
+
365
+ def _reduce_trade(self, trade: Trade, price: float, size: float, time_index: int):
366
+ assert trade.size * size < 0
367
+ assert abs(trade.size) >= abs(size)
368
+
369
+ size_left = trade.size + size
370
+ assert size_left * trade.size >= 0
371
+ if not size_left:
372
+ close_trade = trade
373
+ else:
374
+ # Reduce existing trade ...
375
+ trade._replace(size=size_left)
376
+ if trade._sl_order:
377
+ trade._sl_order._replace(size=-trade.size)
378
+ if trade._tp_order:
379
+ trade._tp_order._replace(size=-trade.size)
380
+
381
+ # ... by closing a reduced copy of it
382
+ close_trade = trade._copy(size=-size, sl_order=None, tp_order=None)
383
+ self.trades.append(close_trade)
384
+
385
+ self._close_trade(close_trade, price, time_index)
386
+
387
+ def _close_trade(self, trade: Trade, price: float, time_index: int):
388
+ self.trades.remove(trade)
389
+ if trade._sl_order:
390
+ self.orders.remove(trade._sl_order)
391
+ if trade._tp_order:
392
+ self.orders.remove(trade._tp_order)
393
+
394
+ closed_trade = trade._replace(exit_price=price, exit_bar=time_index)
395
+ self.closed_trades.append(closed_trade)
396
+ # Apply commission one more time at trade exit
397
+ commission = self._commission(trade.size, price)
398
+ self._cash += trade.pl - commission
399
+ # Save commissions on Trade instance for stats
400
+ trade_open_commission = self._commission(closed_trade.size, closed_trade.entry_price)
401
+ # applied here instead of on Trade open because size could have changed
402
+ # by way of _reduce_trade()
403
+ closed_trade._commissions = commission + trade_open_commission
404
+
405
+ def _open_trade(self, price: float, size: int,
406
+ sl: Optional[float], tp: Optional[float], time_index: int, tag):
407
+ trade = Trade(self, size, price, time_index, tag)
408
+ self.trades.append(trade)
409
+ # Apply broker commission at trade open
410
+ self._cash -= self._commission(size, price)
411
+ # Create SL/TP (bracket) orders.
412
+ if tp:
413
+ trade.tp = tp
414
+ if sl:
415
+ trade.sl = sl