BackcastPro 0.0.5__py3-none-any.whl → 0.1.1__py3-none-any.whl
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.
- BackcastPro/__init__.py +4 -5
- BackcastPro/_broker.py +160 -156
- BackcastPro/_stats.py +22 -14
- BackcastPro/backtest.py +94 -70
- BackcastPro/order.py +10 -0
- BackcastPro/position.py +1 -2
- BackcastPro/strategy.py +12 -6
- BackcastPro/trade.py +60 -28
- {backcastpro-0.0.5.dist-info → backcastpro-0.1.1.dist-info}/METADATA +4 -5
- backcastpro-0.1.1.dist-info/RECORD +12 -0
- BackcastPro/data/JapanStock.py +0 -171
- BackcastPro/data/__init__.py +0 -7
- backcastpro-0.0.5.dist-info/RECORD +0 -14
- {backcastpro-0.0.5.dist-info → backcastpro-0.1.1.dist-info}/WHEEL +0 -0
- {backcastpro-0.0.5.dist-info → backcastpro-0.1.1.dist-info}/top_level.txt +0 -0
BackcastPro/backtest.py
CHANGED
|
@@ -76,8 +76,9 @@ class Backtest:
|
|
|
76
76
|
まだ[アクティブで継続中]の取引は最後のバーでクローズされ、
|
|
77
77
|
計算されたバックテスト統計に貢献します。
|
|
78
78
|
"""
|
|
79
|
+
|
|
79
80
|
def __init__(self,
|
|
80
|
-
data: pd.DataFrame,
|
|
81
|
+
data: dict[str, pd.DataFrame],
|
|
81
82
|
strategy: Type,
|
|
82
83
|
*,
|
|
83
84
|
cash: float = 10_000,
|
|
@@ -94,8 +95,7 @@ class Backtest:
|
|
|
94
95
|
|
|
95
96
|
if not (isinstance(strategy, type) and issubclass(strategy, Strategy)):
|
|
96
97
|
raise TypeError('`strategy` must be a Strategy sub-type')
|
|
97
|
-
|
|
98
|
-
raise TypeError("`data` must be a pandas.DataFrame with columns")
|
|
98
|
+
|
|
99
99
|
if not isinstance(spread, Number):
|
|
100
100
|
raise TypeError('`spread` must be a float value, percent of '
|
|
101
101
|
'entry order price')
|
|
@@ -105,47 +105,63 @@ class Backtest:
|
|
|
105
105
|
'or a function that takes `(order_size, price)`'
|
|
106
106
|
'and returns commission dollar value')
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
108
|
+
k0, v0 = next(iter(data.items()))
|
|
109
|
+
len0 = len(v0)
|
|
110
|
+
for k, v in list(data.items())[1:]:
|
|
111
|
+
if not len0 == len(v):
|
|
112
|
+
raise TypeError(f"`data[{k}]` 数が、{k0}と合致していません。")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
data = data.copy()
|
|
116
|
+
|
|
117
|
+
for code, df in data.items():
|
|
118
|
+
if not isinstance(df, pd.DataFrame):
|
|
119
|
+
raise TypeError(f"`data[{code}]` must be a pandas.DataFrame with columns")
|
|
120
|
+
|
|
121
|
+
# インデックスをdatetimeインデックスに変換
|
|
122
|
+
if (not isinstance(df.index, pd.DatetimeIndex) and
|
|
123
|
+
not isinstance(df.index, pd.RangeIndex) and
|
|
124
|
+
# 大部分が大きな数値の数値インデックス
|
|
125
|
+
(df.index.is_numeric() and
|
|
126
|
+
(df.index > pd.Timestamp('1975').timestamp()).mean() > .8)):
|
|
127
|
+
try:
|
|
128
|
+
df.index = pd.to_datetime(df.index, infer_datetime_format=True)
|
|
129
|
+
except ValueError:
|
|
130
|
+
pass
|
|
131
|
+
|
|
132
|
+
if 'Volume' not in df:
|
|
133
|
+
df['Volume'] = np.nan
|
|
134
|
+
|
|
135
|
+
if len(df) == 0:
|
|
136
|
+
raise ValueError(f'OHLC `data[{code}]` is empty')
|
|
137
|
+
if len(df.columns.intersection({'Open', 'High', 'Low', 'Close', 'Volume'})) != 5:
|
|
138
|
+
raise ValueError(f"`data[{code}]` must be a pandas.DataFrame with columns "
|
|
139
|
+
"'Open', 'High', 'Low', 'Close', and (optionally) 'Volume'")
|
|
140
|
+
if df[['Open', 'High', 'Low', 'Close']].isnull().values.any():
|
|
141
|
+
raise ValueError('Some OHLC values are missing (NaN). '
|
|
142
|
+
'Please strip those lines with `df.dropna()` or '
|
|
143
|
+
'fill them in with `df.interpolate()` or whatever.')
|
|
144
|
+
if np.any(df['Close'] > cash):
|
|
145
|
+
warnings.warn('Some prices are larger than initial cash value. Note that fractional '
|
|
146
|
+
'trading is not supported by this class. If you want to trade Bitcoin, '
|
|
147
|
+
'increase initial cash, or trade μBTC or satoshis instead (see e.g. class '
|
|
148
|
+
'`backtesting.lib.FractionalBacktest`.',
|
|
149
|
+
stacklevel=2)
|
|
150
|
+
if not df.index.is_monotonic_increasing:
|
|
151
|
+
warnings.warn(f'data[{code}] index is not sorted in ascending order. Sorting.',
|
|
152
|
+
stacklevel=2)
|
|
153
|
+
df = df.sort_index()
|
|
154
|
+
data[code] = df # 重要:ソート後のDataFrameを辞書に再代入
|
|
155
|
+
if not isinstance(df.index, pd.DatetimeIndex):
|
|
156
|
+
warnings.warn(f'data[{code}] index is not datetime. Assuming simple periods, '
|
|
157
|
+
'but `pd.DateTimeIndex` is advised.',
|
|
158
|
+
stacklevel=2)
|
|
159
|
+
|
|
160
|
+
# 辞書dataに含まれる全てのdf.index一覧を作成
|
|
161
|
+
# df.indexが不一致の場合のために、どれかに固有値があれば抽出しておくため
|
|
162
|
+
self.index: pd.DatetimeIndex = pd.DatetimeIndex(sorted({idx for df in data.values() for idx in df.index}))
|
|
163
|
+
|
|
164
|
+
self._data: dict[str, pd.DataFrame] = data
|
|
149
165
|
|
|
150
166
|
# partialとは、関数の一部の引数を事前に固定して、新しい関数を作成します。
|
|
151
167
|
# これにより、後で残りの引数だけを渡せば関数を実行できるようになります。
|
|
@@ -155,16 +171,13 @@ class Backtest:
|
|
|
155
171
|
self._broker = partial(
|
|
156
172
|
_Broker, cash=cash, spread=spread, commission=commission, margin=margin,
|
|
157
173
|
trade_on_close=trade_on_close, hedging=hedging,
|
|
158
|
-
exclusive_orders=exclusive_orders
|
|
174
|
+
exclusive_orders=exclusive_orders
|
|
159
175
|
)
|
|
160
176
|
|
|
161
177
|
self._strategy = strategy
|
|
162
178
|
self._results: Optional[pd.Series] = None
|
|
163
179
|
self._finalize_trades = bool(finalize_trades)
|
|
164
180
|
|
|
165
|
-
# 初期化パラメータを保存(実行後に振り返るため)
|
|
166
|
-
self._cash = cash
|
|
167
|
-
self._commission = commission
|
|
168
181
|
|
|
169
182
|
def run(self) -> pd.Series:
|
|
170
183
|
"""
|
|
@@ -219,49 +232,50 @@ class Backtest:
|
|
|
219
232
|
# 循環インポートを避けるためにここでインポート
|
|
220
233
|
from .strategy import Strategy
|
|
221
234
|
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
strategy: Strategy = self._strategy(broker, data)
|
|
235
|
+
broker: _Broker = self._broker(data=self._data)
|
|
236
|
+
strategy: Strategy = self._strategy(broker, self._data)
|
|
225
237
|
|
|
226
238
|
strategy.init()
|
|
227
239
|
|
|
228
|
-
# strategy.init()で加工されたdata
|
|
229
|
-
self._data
|
|
240
|
+
# strategy.init()で加工されたdataを登録
|
|
241
|
+
data = self._data.copy()
|
|
230
242
|
|
|
231
|
-
# インジケーターがまだ「ウォームアップ」中の最初の数本のキャンドルをスキップ
|
|
232
|
-
# 少なくとも2つのエントリが利用可能になるように+1
|
|
233
|
-
start = 1
|
|
234
|
-
|
|
235
243
|
# "invalid value encountered in ..."警告を無効化。比較
|
|
236
244
|
# np.nan >= 3は無効ではない;Falseです。
|
|
237
245
|
with np.errstate(invalid='ignore'):
|
|
238
246
|
|
|
239
247
|
# プログレスバーを表示
|
|
240
|
-
progress_bar = tqdm(
|
|
248
|
+
progress_bar = tqdm(self.index,
|
|
241
249
|
desc="バックテスト実行中",
|
|
242
250
|
unit="step",
|
|
243
251
|
ncols=120,
|
|
244
252
|
leave=True,
|
|
245
253
|
dynamic_ncols=True)
|
|
246
|
-
|
|
247
|
-
for
|
|
254
|
+
count = 0
|
|
255
|
+
for current_time in progress_bar:
|
|
256
|
+
|
|
248
257
|
# 注文処理とブローカー関連の処理
|
|
249
|
-
|
|
258
|
+
for k, value in self._data.items():
|
|
259
|
+
# time以前のデータをフィルタリング
|
|
260
|
+
data[k] = value[value.index <= current_time]
|
|
261
|
+
|
|
262
|
+
# brokerに更新したdateを再登録
|
|
250
263
|
try:
|
|
251
264
|
broker._data = data
|
|
252
|
-
broker.next()
|
|
265
|
+
broker.next(current_time)
|
|
253
266
|
except:
|
|
254
267
|
break
|
|
255
268
|
|
|
256
269
|
# 次のティック、バークローズ直前
|
|
257
270
|
strategy._data = data
|
|
258
|
-
strategy.next()
|
|
271
|
+
strategy.next(current_time)
|
|
259
272
|
|
|
273
|
+
count += 1
|
|
274
|
+
|
|
260
275
|
# プログレスバーの説明を更新(現在の日付を表示)
|
|
261
|
-
if
|
|
276
|
+
if data:
|
|
262
277
|
try:
|
|
263
|
-
|
|
264
|
-
progress_bar.set_postfix({"日付": current_date})
|
|
278
|
+
progress_bar.set_postfix({"日付": current_time.strftime('%Y-%m-%d')})
|
|
265
279
|
except:
|
|
266
280
|
pass
|
|
267
281
|
else:
|
|
@@ -272,8 +286,7 @@ class Backtest:
|
|
|
272
286
|
|
|
273
287
|
# HACK: 最後の戦略イテレーションで配置されたクローズ注文を処理するために
|
|
274
288
|
# ブローカーを最後にもう一度実行。最後のブローカーイテレーションと同じOHLC値を使用。
|
|
275
|
-
|
|
276
|
-
broker.next()
|
|
289
|
+
broker.next(self.index[count-1])
|
|
277
290
|
elif len(broker.trades):
|
|
278
291
|
warnings.warn(
|
|
279
292
|
'バックテスト終了時に一部の取引がオープンのままです。'
|
|
@@ -283,11 +296,22 @@ class Backtest:
|
|
|
283
296
|
equity = pd.Series(broker._equity).bfill().fillna(broker._cash).values
|
|
284
297
|
self._results = compute_stats(
|
|
285
298
|
trades=broker.closed_trades,
|
|
286
|
-
equity=equity,
|
|
287
|
-
|
|
299
|
+
equity=np.array(equity),
|
|
300
|
+
index=self.index,
|
|
288
301
|
strategy_instance=strategy,
|
|
289
302
|
risk_free_rate=0.0,
|
|
290
303
|
)
|
|
291
304
|
|
|
292
305
|
return self._results
|
|
293
306
|
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def cash(self):
|
|
310
|
+
# partialで初期化されている場合、初期化時のcash値を返す
|
|
311
|
+
return self._broker.keywords.get('cash', 0)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
@property
|
|
315
|
+
def commission(self):
|
|
316
|
+
# partialで初期化されている場合、初期化時のcommission値を返す
|
|
317
|
+
return self._broker.keywords.get('commission', 0)
|
BackcastPro/order.py
CHANGED
|
@@ -21,7 +21,9 @@ class Order:
|
|
|
21
21
|
|
|
22
22
|
すべての出された注文は[取消注文まで有効]です。
|
|
23
23
|
"""
|
|
24
|
+
|
|
24
25
|
def __init__(self, broker: '_Broker',
|
|
26
|
+
code: str,
|
|
25
27
|
size: float,
|
|
26
28
|
limit_price: Optional[float] = None,
|
|
27
29
|
stop_price: Optional[float] = None,
|
|
@@ -29,6 +31,7 @@ class Order:
|
|
|
29
31
|
tp_price: Optional[float] = None,
|
|
30
32
|
parent_trade: Optional['Trade'] = None,
|
|
31
33
|
tag: object = None):
|
|
34
|
+
self.__code = code
|
|
32
35
|
self.__broker = broker
|
|
33
36
|
assert size != 0
|
|
34
37
|
self.__size = size
|
|
@@ -59,6 +62,13 @@ class Order:
|
|
|
59
62
|
|
|
60
63
|
# Fields getters
|
|
61
64
|
|
|
65
|
+
@property
|
|
66
|
+
def code(self) -> str:
|
|
67
|
+
"""
|
|
68
|
+
注文対象の銘柄コード。
|
|
69
|
+
"""
|
|
70
|
+
return self.__code
|
|
71
|
+
|
|
62
72
|
@property
|
|
63
73
|
def size(self) -> float:
|
|
64
74
|
"""
|
BackcastPro/position.py
CHANGED
|
@@ -18,6 +18,7 @@ class Position:
|
|
|
18
18
|
if self.position:
|
|
19
19
|
... # ポジションがあります(ロングまたはショート)
|
|
20
20
|
"""
|
|
21
|
+
|
|
21
22
|
def __init__(self, broker: '_Broker'):
|
|
22
23
|
self.__broker = broker
|
|
23
24
|
|
|
@@ -57,5 +58,3 @@ class Position:
|
|
|
57
58
|
for trade in self.__broker.trades:
|
|
58
59
|
trade.close(portion)
|
|
59
60
|
|
|
60
|
-
def __repr__(self):
|
|
61
|
-
return f'<Position: {self.size} ({len(self.__broker.trades)} trades)>'
|
BackcastPro/strategy.py
CHANGED
|
@@ -6,6 +6,7 @@ from __future__ import annotations
|
|
|
6
6
|
|
|
7
7
|
import sys
|
|
8
8
|
from abc import ABCMeta, abstractmethod
|
|
9
|
+
from tkinter import NO
|
|
9
10
|
from typing import Optional, Tuple
|
|
10
11
|
|
|
11
12
|
import pandas as pd
|
|
@@ -35,12 +36,12 @@ class Strategy(metaclass=ABCMeta):
|
|
|
35
36
|
オーバーライドして独自の戦略を定義してください。
|
|
36
37
|
"""
|
|
37
38
|
|
|
38
|
-
def __init__(self, broker: _Broker, data: pd.DataFrame):
|
|
39
|
+
def __init__(self, broker: _Broker, data: dict[str, pd.DataFrame]):
|
|
39
40
|
"""
|
|
40
41
|
これは Backtestクラスで初期化するためユーザーは不要
|
|
41
42
|
"""
|
|
42
43
|
self._broker: _Broker = broker
|
|
43
|
-
self._data: pd.DataFrame = data
|
|
44
|
+
self._data: dict[str, pd.DataFrame] = data
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
@abstractmethod
|
|
@@ -58,7 +59,7 @@ class Strategy(metaclass=ABCMeta):
|
|
|
58
59
|
"""
|
|
59
60
|
|
|
60
61
|
@abstractmethod
|
|
61
|
-
def next(self):
|
|
62
|
+
def next(self, current_time: pd.Timestamp):
|
|
62
63
|
"""
|
|
63
64
|
メインのストラテジー実行メソッド。新しい
|
|
64
65
|
`backtesting.backtesting.Strategy.data`
|
|
@@ -88,6 +89,7 @@ class Strategy(metaclass=ABCMeta):
|
|
|
88
89
|
_FULL_EQUITY = __FULL_EQUITY(1 - sys.float_info.epsilon)
|
|
89
90
|
|
|
90
91
|
def buy(self, *,
|
|
92
|
+
code: str,
|
|
91
93
|
size: float = _FULL_EQUITY,
|
|
92
94
|
limit: Optional[float] = None,
|
|
93
95
|
stop: Optional[float] = None,
|
|
@@ -108,9 +110,11 @@ class Strategy(metaclass=ABCMeta):
|
|
|
108
110
|
"""
|
|
109
111
|
assert 0 < size < 1 or round(size) == size >= 1, \
|
|
110
112
|
"sizeは正の資産割合または正の整数単位である必要があります"
|
|
111
|
-
|
|
113
|
+
|
|
114
|
+
return self._broker.new_order(code, size, limit, stop, sl, tp, tag)
|
|
112
115
|
|
|
113
116
|
def sell(self, *,
|
|
117
|
+
code: str,
|
|
114
118
|
size: float = _FULL_EQUITY,
|
|
115
119
|
limit: Optional[float] = None,
|
|
116
120
|
stop: Optional[float] = None,
|
|
@@ -139,7 +143,8 @@ class Strategy(metaclass=ABCMeta):
|
|
|
139
143
|
"""
|
|
140
144
|
assert 0 < size < 1 or round(size) == size >= 1, \
|
|
141
145
|
"sizeは正の資産割合または正の整数単位である必要があります"
|
|
142
|
-
|
|
146
|
+
|
|
147
|
+
return self._broker.new_order(code, -size, limit, stop, sl, tp, tag)
|
|
143
148
|
|
|
144
149
|
@property
|
|
145
150
|
def equity(self) -> float:
|
|
@@ -147,7 +152,7 @@ class Strategy(metaclass=ABCMeta):
|
|
|
147
152
|
return self._broker.equity
|
|
148
153
|
|
|
149
154
|
@property
|
|
150
|
-
def data(self) -> pd.DataFrame:
|
|
155
|
+
def data(self) -> dict[str, pd.DataFrame]:
|
|
151
156
|
"""
|
|
152
157
|
価格データは、`Backtest.__init__`に渡されるものと同じ
|
|
153
158
|
"""
|
|
@@ -172,3 +177,4 @@ class Strategy(metaclass=ABCMeta):
|
|
|
172
177
|
def closed_trades(self) -> 'Tuple[Trade, ...]':
|
|
173
178
|
"""決済済みトレードリスト(`Trade` を参照)。"""
|
|
174
179
|
return tuple(self._broker.closed_trades)
|
|
180
|
+
|
BackcastPro/trade.py
CHANGED
|
@@ -18,23 +18,19 @@ class Trade:
|
|
|
18
18
|
`Order`が約定されると、アクティブな`Trade`が発生します。
|
|
19
19
|
アクティブな取引は`Strategy.trades`で、クローズされた決済済み取引は`Strategy.closed_trades`で見つけることができます。
|
|
20
20
|
"""
|
|
21
|
-
def __init__(self, broker: '_Broker', size: int, entry_price: float,
|
|
21
|
+
def __init__(self, broker: '_Broker', code: str, size: int, entry_price: float, entry_time: Union[pd.Timestamp, int], tag):
|
|
22
22
|
self.__broker = broker
|
|
23
|
+
self.__code = code
|
|
23
24
|
self.__size = size
|
|
24
25
|
self.__entry_price = entry_price
|
|
25
26
|
self.__exit_price: Optional[float] = None
|
|
26
|
-
self.
|
|
27
|
-
self.
|
|
27
|
+
self.__entry_time: Union[pd.Timestamp, int] = entry_time
|
|
28
|
+
self.__exit_time: Optional[Union[pd.Timestamp, int]] = None
|
|
28
29
|
self.__sl_order: Optional[Order] = None
|
|
29
30
|
self.__tp_order: Optional[Order] = None
|
|
30
31
|
self.__tag = tag
|
|
31
32
|
self._commissions = 0
|
|
32
33
|
|
|
33
|
-
def __repr__(self):
|
|
34
|
-
return f'<Trade size={self.__size} time={self.__entry_bar}-{self.__exit_bar or ""} ' \
|
|
35
|
-
f'price={self.__entry_price}-{self.__exit_price or ""} pl={self.pl:.0f}' \
|
|
36
|
-
f'{" tag=" + str(self.__tag) if self.__tag is not None else ""}>'
|
|
37
|
-
|
|
38
34
|
def _replace(self, **kwargs):
|
|
39
35
|
for k, v in kwargs.items():
|
|
40
36
|
setattr(self, f'_{self.__class__.__qualname__}__{k}', v)
|
|
@@ -49,11 +45,16 @@ class Trade:
|
|
|
49
45
|
# Ensure size is an int to avoid rounding errors on 32-bit OS
|
|
50
46
|
size = copysign(max(1, int(round(abs(self.__size) * portion))), -self.__size)
|
|
51
47
|
from .order import Order
|
|
52
|
-
order = Order(self.__broker, size, parent_trade=self, tag=self.__tag)
|
|
48
|
+
order = Order(self.__broker, self.__code, size, parent_trade=self, tag=self.__tag)
|
|
53
49
|
self.__broker.orders.insert(0, order)
|
|
54
50
|
|
|
55
51
|
# Fields getters
|
|
56
52
|
|
|
53
|
+
@property
|
|
54
|
+
def code(self):
|
|
55
|
+
"""取引対象銘柄"""
|
|
56
|
+
return self.__code
|
|
57
|
+
|
|
57
58
|
@property
|
|
58
59
|
def size(self):
|
|
59
60
|
"""取引サイズ(ボリューム;ショート取引の場合は負の値)。"""
|
|
@@ -71,16 +72,59 @@ class Trade:
|
|
|
71
72
|
|
|
72
73
|
@property
|
|
73
74
|
def entry_bar(self) -> int:
|
|
74
|
-
"""
|
|
75
|
-
|
|
75
|
+
"""
|
|
76
|
+
取引がエントリーされた時のローソク足バーのインデックス。
|
|
77
|
+
|
|
78
|
+
.. deprecated:: 0.1.0
|
|
79
|
+
`entry_time`プロパティを使用してください。
|
|
80
|
+
"""
|
|
81
|
+
import warnings
|
|
82
|
+
warnings.warn(
|
|
83
|
+
"entry_barプロパティは非推奨です。entry_timeプロパティを使用してください。",
|
|
84
|
+
DeprecationWarning,
|
|
85
|
+
stacklevel=2
|
|
86
|
+
)
|
|
87
|
+
# データからインデックスを逆算
|
|
88
|
+
try:
|
|
89
|
+
data_index = self.__broker._data[self.__code].index
|
|
90
|
+
return data_index.get_loc(self.__entry_time)
|
|
91
|
+
except (KeyError, TypeError):
|
|
92
|
+
# インデックスが見つからない場合は0を返す
|
|
93
|
+
return 0
|
|
76
94
|
|
|
77
95
|
@property
|
|
78
96
|
def exit_bar(self) -> Optional[int]:
|
|
79
97
|
"""
|
|
80
98
|
取引がエグジットされた時のローソク足バーのインデックス
|
|
81
99
|
(取引がまだアクティブな場合はNone)。
|
|
100
|
+
|
|
101
|
+
.. deprecated:: 0.1.0
|
|
102
|
+
`exit_time`プロパティを使用してください。
|
|
82
103
|
"""
|
|
83
|
-
|
|
104
|
+
import warnings
|
|
105
|
+
warnings.warn(
|
|
106
|
+
"exit_barプロパティは非推奨です。exit_timeプロパティを使用してください。",
|
|
107
|
+
DeprecationWarning,
|
|
108
|
+
stacklevel=2
|
|
109
|
+
)
|
|
110
|
+
if self.__exit_time is None:
|
|
111
|
+
return None
|
|
112
|
+
try:
|
|
113
|
+
data_index = self.__broker._data[self.__code].index
|
|
114
|
+
return data_index.get_loc(self.__exit_time)
|
|
115
|
+
except (KeyError, TypeError):
|
|
116
|
+
# インデックスが見つからない場合はNoneを返す
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def entry_time(self) -> Union[pd.Timestamp, int]:
|
|
121
|
+
"""取引がエントリーされた日時。"""
|
|
122
|
+
return self.__entry_time
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def exit_time(self) -> Optional[Union[pd.Timestamp, int]]:
|
|
126
|
+
"""取引がエグジットされた日時。"""
|
|
127
|
+
return self.__exit_time
|
|
84
128
|
|
|
85
129
|
@property
|
|
86
130
|
def tag(self):
|
|
@@ -103,18 +147,6 @@ class Trade:
|
|
|
103
147
|
|
|
104
148
|
# Extra properties
|
|
105
149
|
|
|
106
|
-
@property
|
|
107
|
-
def entry_time(self) -> Union[pd.Timestamp, int]:
|
|
108
|
-
"""取引がエントリーされた日時。"""
|
|
109
|
-
return self.__broker._data.index[self.__entry_bar]
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
def exit_time(self) -> Optional[Union[pd.Timestamp, int]]:
|
|
113
|
-
"""取引がエグジットされた日時。"""
|
|
114
|
-
if self.__exit_bar is None:
|
|
115
|
-
return None
|
|
116
|
-
return self.__broker._data.index[self.__exit_bar]
|
|
117
|
-
|
|
118
150
|
@property
|
|
119
151
|
def is_long(self):
|
|
120
152
|
"""取引がロングの場合True(取引サイズが正の値)。"""
|
|
@@ -131,13 +163,13 @@ class Trade:
|
|
|
131
163
|
取引の利益(正の値)または損失(負の値)を現金単位で表示。
|
|
132
164
|
手数料は取引がクローズされた後にのみ反映されます。
|
|
133
165
|
"""
|
|
134
|
-
price = self.__exit_price or self.__broker.last_price
|
|
166
|
+
price = self.__exit_price or self.__broker.last_price(self.__code)
|
|
135
167
|
return (self.__size * (price - self.__entry_price)) - self._commissions
|
|
136
168
|
|
|
137
169
|
@property
|
|
138
170
|
def pl_pct(self):
|
|
139
171
|
"""取引の利益(正の値)または損失(負の値)をパーセントで表示。"""
|
|
140
|
-
price = self.__exit_price or self.__broker.last_price
|
|
172
|
+
price = self.__exit_price or self.__broker.last_price(self.__code)
|
|
141
173
|
gross_pl_pct = copysign(1, self.__size) * (price / self.__entry_price - 1)
|
|
142
174
|
|
|
143
175
|
# 取引全体のサイズに対する手数料を個別単位に換算
|
|
@@ -147,7 +179,7 @@ class Trade:
|
|
|
147
179
|
@property
|
|
148
180
|
def value(self):
|
|
149
181
|
"""取引の総価値を現金単位で表示(ボリューム × 価格)。"""
|
|
150
|
-
price = self.__exit_price or self.__broker.last_price
|
|
182
|
+
price = self.__exit_price or self.__broker.last_price(self.__code)
|
|
151
183
|
return abs(self.__size) * price
|
|
152
184
|
|
|
153
185
|
# SL/TP management API
|
|
@@ -191,5 +223,5 @@ class Trade:
|
|
|
191
223
|
order.cancel()
|
|
192
224
|
if price:
|
|
193
225
|
kwargs = {'stop': price} if type == 'sl' else {'limit': price}
|
|
194
|
-
order = self.__broker.new_order(-self.size, trade=self, tag=self.tag, **kwargs)
|
|
226
|
+
order = self.__broker.new_order(self.code, -self.size, trade=self, tag=self.tag, **kwargs)
|
|
195
227
|
setattr(self, attr, order)
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: BackcastPro
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.1.1
|
|
4
4
|
Summary: トレーディング戦略のためのPythonバックテストライブラリ
|
|
5
|
-
Author
|
|
5
|
+
Author: botterYosuke
|
|
6
6
|
Project-URL: Homepage, https://github.com/botterYosuke/BackcastPro/
|
|
7
|
-
Project-URL: Issues, https://github.com/
|
|
8
|
-
Project-URL: Logo, https://raw.githubusercontent.com/
|
|
7
|
+
Project-URL: Issues, https://github.com/botterYosuke/BackcastPro/issues
|
|
8
|
+
Project-URL: Logo, https://raw.githubusercontent.com/botterYosuke/BackcastPro/main/docs/img/logo.drawio.svg
|
|
9
9
|
Classifier: Programming Language :: Python :: 3
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -45,7 +45,6 @@ py -m pip install -r requirements.txt
|
|
|
45
45
|
|
|
46
46
|
```python
|
|
47
47
|
from BackcastPro import Strategy, Backtest
|
|
48
|
-
from BackcastPro.data import DataReader, JapanStocks
|
|
49
48
|
|
|
50
49
|
# ここにトレーディング戦略の実装を記述
|
|
51
50
|
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
BackcastPro/__init__.py,sha256=VS0jRp1UqgrHufWzx_mq_TuULtoX_DSxwEOXp2by4tQ,870
|
|
2
|
+
BackcastPro/_broker.py,sha256=KaZUxCisbB_5RRMSpIGhus3-gTurgZK-ETd7eo3JANo,20562
|
|
3
|
+
BackcastPro/_stats.py,sha256=swVWo2ZEP7Qt_neVuEXBw9GbshLU28oc4EyPedW3XlI,8437
|
|
4
|
+
BackcastPro/backtest.py,sha256=nI-X7DKSIahiJf3PTWjwjvc8qG7pt6FNDCgrhR9nKYY,15952
|
|
5
|
+
BackcastPro/order.py,sha256=S_2j6zjSFNgujJJDcaQw1-cXU9jgwE-PTCMLZvx6EAI,5706
|
|
6
|
+
BackcastPro/position.py,sha256=xm8umqpJDHqLEmQ_qyq_K0VR1rKO6mY2J10c0wlzYCU,2137
|
|
7
|
+
BackcastPro/strategy.py,sha256=zHCYMjfE6RA4Y-ANvGBp7dEpxFsSbSzf4Vtv1vN0jc8,7359
|
|
8
|
+
BackcastPro/trade.py,sha256=fXB2YKdDzgnc895-PGdad2_sh5uH6X6HoEeFpD5CeCc,8340
|
|
9
|
+
backcastpro-0.1.1.dist-info/METADATA,sha256=NnynVoy3YDAEIY8E4rYuLrcom0dmOpNk-xX9wLcL2_Q,2061
|
|
10
|
+
backcastpro-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
11
|
+
backcastpro-0.1.1.dist-info/top_level.txt,sha256=GiP-TX_Bc2jjwHS9cx0VCrW27e9JPbhWWnqGxa5B4Fs,12
|
|
12
|
+
backcastpro-0.1.1.dist-info/RECORD,,
|