应用止损
止损是用于在证券达到指定价格水平时自动买入或卖出的工具。它们对于通过让交易者自动退出糟糕的交易来限制潜在损失非常有用,同时也可以在证券达到某个价格水平时自动卖出以获利。
PyBroker 支持止损的模拟,本笔记本将详细介绍这一特性:
[1]:
import pybroker
from pybroker import Strategy, YFinance
pybroker.enable_data_source_cache('stops')
strategy = Strategy(YFinance(), '1/1/2018', '1/1/2023')
止损
止损单用于在证券价格达到或跌破指定水平时自动退出交易。例如,以下代码展示了一个在入场价格下跌 20%
处设置的止损单示例:
[2]:
def buy_with_stop_loss(ctx):
if not ctx.long_pos():
ctx.buy_shares = ctx.calc_target_shares(1)
ctx.stop_loss_pct = 20
strategy.add_execution(buy_with_stop_loss, ['TSLA'])
result = strategy.backtest()
result.trades
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 1 of 1 completed
Loaded bar data: 0:00:00
Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:02
[2]:
type | symbol | entry_date | exit_date | entry | exit | shares | pnl | return_pct | agg_pnl | bars | pnl_per_bar | stop | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||
1 | long | TSLA | 2018-01-03 | 2018-03-28 | 21.36 | 17.09 | 4679 | -19988.69 | -20.00 | -19988.69 | 58 | -344.63 | loss |
2 | long | TSLA | 2018-03-29 | 2019-05-20 | 17.31 | 13.73 | 4622 | -16531.36 | -20.66 | -36520.04 | 286 | -57.80 | loss |
锁定利润
类似地,可以使用获利单来锁定交易的利润。以下代码在入场价格上涨 10%
处添加了一个获利单:
[3]:
def buy_with_stop_loss_and_profit(ctx):
if not ctx.long_pos():
ctx.buy_shares = ctx.calc_target_shares(1)
ctx.stop_loss_pct = 20
ctx.stop_profit_pct = 10
strategy.clear_executions()
strategy.add_execution(buy_with_stop_loss_and_profit, ['TSLA'])
result = strategy.backtest()
result.trades
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00
Loaded cached bar data.
Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[3]:
type | symbol | entry_date | exit_date | entry | exit | shares | pnl | return_pct | agg_pnl | bars | pnl_per_bar | stop | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||
1 | long | TSLA | 2018-01-03 | 2018-01-22 | 21.36 | 23.50 | 4679 | 9994.34 | 10.0 | 9994.34 | 12 | 832.86 | profit |
2 | long | TSLA | 2018-01-23 | 2018-03-27 | 23.72 | 18.98 | 4637 | -21997.93 | -20.0 | -12003.58 | 44 | -499.95 | loss |
3 | long | TSLA | 2018-03-28 | 2018-04-04 | 17.36 | 19.10 | 4727 | 8206.07 | 10.0 | -3797.51 | 4 | 2051.52 | profit |
4 | long | TSLA | 2018-04-05 | 2018-06-07 | 19.82 | 21.80 | 4853 | 9618.65 | 10.0 | 5821.13 | 44 | 218.61 | profit |
5 | long | TSLA | 2018-06-08 | 2018-06-12 | 21.39 | 23.53 | 4947 | 10581.63 | 10.0 | 16402.77 | 2 | 5290.82 | profit |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
85 | long | TSLA | 2022-07-29 | 2022-10-07 | 288.71 | 230.97 | 1010 | -58319.42 | -20.0 | 133480.29 | 49 | -1190.19 | loss |
86 | long | TSLA | 2022-10-10 | 2022-11-09 | 222.68 | 178.14 | 1046 | -46584.66 | -20.0 | 86895.63 | 22 | -2117.48 | loss |
87 | long | TSLA | 2022-11-10 | 2022-12-19 | 185.51 | 148.41 | 1007 | -37361.71 | -20.0 | 49533.92 | 26 | -1436.99 | loss |
88 | long | TSLA | 2022-12-20 | 2022-12-27 | 143.07 | 114.46 | 997 | -28528.16 | -20.0 | 21005.76 | 4 | -7132.04 | loss |
89 | long | TSLA | 2022-12-28 | 2022-12-29 | 112.25 | 123.48 | 1078 | 12100.55 | 10.0 | 33106.31 | 1 | 12100.55 | profit |
89 rows × 13 columns
移动止损
移动止损单用于在证券价格跌破最高市场价格的一定百分比或现金金额时退出交易。以下是在最高市场价格下跌 20%
处设置移动止损的示例:
[4]:
def buy_with_trailing_stop_loss_and_profit(ctx):
if not ctx.long_pos():
ctx.buy_shares = ctx.calc_target_shares(1)
ctx.stop_trailing_pct = 20
ctx.stop_profit_pct = 10
strategy.clear_executions()
strategy.add_execution(buy_with_trailing_stop_loss_and_profit, ['TSLA'])
result = strategy.backtest()
result.trades
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00
Loaded cached bar data.
Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[4]:
type | symbol | entry_date | exit_date | entry | exit | shares | pnl | return_pct | agg_pnl | bars | pnl_per_bar | stop | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||
1 | long | TSLA | 2018-01-03 | 2018-01-22 | 21.36 | 23.50 | 4679 | 9994.34 | 10.00 | 9994.34 | 12 | 832.86 | profit |
2 | long | TSLA | 2018-01-23 | 2018-03-27 | 23.72 | 19.20 | 4637 | -20961.72 | -19.06 | -10967.37 | 44 | -476.40 | trailing |
3 | long | TSLA | 2018-03-28 | 2018-04-04 | 17.36 | 19.10 | 4783 | 8303.29 | 10.00 | -2664.08 | 4 | 2075.82 | profit |
4 | long | TSLA | 2018-04-05 | 2018-06-07 | 19.82 | 21.80 | 4910 | 9731.62 | 10.00 | 7067.54 | 44 | 221.17 | profit |
5 | long | TSLA | 2018-06-08 | 2018-06-12 | 21.39 | 23.53 | 5005 | 10705.70 | 10.00 | 17773.23 | 2 | 5352.85 | profit |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
102 | long | TSLA | 2022-08-02 | 2022-10-03 | 300.25 | 251.73 | 1095 | -53125.76 | -16.16 | 175768.16 | 43 | -1235.48 | trailing |
103 | long | TSLA | 2022-10-04 | 2022-10-24 | 249.75 | 199.80 | 1104 | -55144.80 | -20.00 | 120623.36 | 14 | -3938.91 | trailing |
104 | long | TSLA | 2022-10-25 | 2022-11-08 | 217.18 | 189.92 | 1015 | -27668.90 | -12.55 | 92954.46 | 10 | -2766.89 | trailing |
105 | long | TSLA | 2022-11-09 | 2022-12-13 | 186.50 | 160.66 | 1008 | -26050.75 | -13.86 | 66903.71 | 23 | -1132.64 | trailing |
106 | long | TSLA | 2022-12-14 | 2022-12-22 | 158.46 | 128.79 | 1036 | -30736.04 | -18.72 | 36167.67 | 6 | -5122.67 | trailing |
106 rows × 13 columns
设定限价
止损单可以与限价结合使用,以确保订单仅在特定价格水平执行。下面是在止损单上设置限价的示例:
[5]:
def buy_with_trailing_stop_loss_and_profit(ctx):
if not ctx.long_pos():
ctx.buy_shares = ctx.calc_target_shares(1)
ctx.stop_trailing_pct = 20
ctx.stop_trailing_limit = ctx.close[-1] + 1
ctx.stop_profit_pct = 10
ctx.stop_profit_limit = ctx.close[-1] - 1
strategy.clear_executions()
strategy.add_execution(buy_with_trailing_stop_loss_and_profit, ['TSLA'])
result = strategy.backtest()
result.trades.head()
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00
Loaded cached bar data.
Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[5]:
type | symbol | entry_date | exit_date | entry | exit | shares | pnl | return_pct | agg_pnl | bars | pnl_per_bar | stop | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||
1 | long | TSLA | 2018-01-03 | 2018-01-22 | 21.36 | 23.50 | 4679 | 9994.34 | 10.0 | 9994.34 | 12 | 832.86 | profit |
2 | long | TSLA | 2018-01-23 | 2019-12-18 | 23.72 | 26.09 | 4637 | 10998.96 | 10.0 | 20993.31 | 480 | 22.91 | profit |
3 | long | TSLA | 2019-12-19 | 2020-01-03 | 26.78 | 29.46 | 4518 | 12099.20 | 10.0 | 33092.51 | 9 | 1344.36 | profit |
4 | long | TSLA | 2020-01-06 | 2020-01-08 | 29.72 | 32.69 | 4478 | 13308.62 | 10.0 | 46401.13 | 2 | 6654.31 | profit |
5 | long | TSLA | 2020-01-09 | 2020-01-14 | 32.39 | 35.63 | 4462 | 14452.42 | 10.0 | 60853.55 | 3 | 4817.47 | profit |
取消止损
以下代码展示了取消止损单的示例:
[6]:
def buy_with_stop_trailing_and_cancel(ctx):
pos = ctx.long_pos()
if not pos:
ctx.buy_shares = ctx.calc_target_shares(1)
ctx.stop_trailing_pct = 20
elif pos.bars > 60:
ctx.cancel_stops(ctx.symbol)
strategy.clear_executions()
strategy.add_execution(buy_with_stop_trailing_and_cancel, ['TSLA'])
result = strategy.backtest()
result.trades
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00
Loaded cached bar data.
Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[6]:
type | symbol | entry_date | exit_date | entry | exit | shares | pnl | return_pct | agg_pnl | bars | pnl_per_bar | stop | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||
1 | long | TSLA | 2018-01-03 | 2018-03-27 | 21.36 | 19.23 | 4679 | -9981.87 | -9.99 | -9981.87 | 57 | -175.12 | trailing |
设定限价
By default, stops are checked against the bar’s low and high prices, and they are exited at the stop’s threshold (e.g., -2%) on the same bar when the stop is triggered.
To set a custom exit price, the “exit_price” fields for each stop type can be used. When these fields are set, the stop will be checked against the exit_price
, and it will exit at the exit_price
when triggered. For example, the code below sets the stop_trailing_exit_price to the open price on the bar that triggers the stop:
[7]:
from pybroker import PriceType
def buy_with_stop_trailing_and_exit_price(ctx):
pos = ctx.long_pos()
if not pos:
ctx.buy_shares = ctx.calc_target_shares(1)
ctx.stop_trailing_pct = 20
ctx.stop_trailing_exit_price = PriceType.OPEN
strategy.clear_executions()
strategy.add_execution(buy_with_stop_trailing_and_exit_price, ['TSLA'])
result = strategy.backtest()
result.trades.head()
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00
Loaded cached bar data.
Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[7]:
type | symbol | entry_date | exit_date | entry | exit | shares | pnl | return_pct | agg_pnl | bars | pnl_per_bar | stop | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||
1 | long | TSLA | 2018-01-03 | 2018-03-28 | 21.36 | 17.64 | 4679 | -17412.12 | -17.42 | -17412.12 | 58 | -300.21 | trailing |
2 | long | TSLA | 2018-03-29 | 2018-07-25 | 17.31 | 19.78 | 4771 | 11797.10 | 14.28 | -5615.03 | 81 | 145.64 | trailing |
3 | long | TSLA | 2018-07-26 | 2018-08-20 | 20.48 | 19.45 | 4585 | -4737.83 | -5.05 | -10352.86 | 17 | -278.70 | trailing |
4 | long | TSLA | 2018-08-21 | 2018-09-07 | 21.13 | 17.34 | 4242 | -16077.18 | -17.94 | -26430.04 | 12 | -1339.76 | trailing |
5 | long | TSLA | 2018-09-10 | 2018-12-26 | 18.57 | 20.00 | 3961 | 5664.23 | 7.70 | -20765.81 | 74 | 76.54 | trailing |