|
12 | 12 | import warnings |
13 | 13 | from abc import ABCMeta, abstractmethod |
14 | 14 | from copy import copy |
| 15 | +from difflib import get_close_matches |
15 | 16 | from functools import lru_cache, partial |
16 | 17 | from itertools import chain, product, repeat |
17 | 18 | from math import copysign |
@@ -64,10 +65,12 @@ def __str__(self): |
64 | 65 | def _check_params(self, params): |
65 | 66 | for k, v in params.items(): |
66 | 67 | if not hasattr(self, k): |
| 68 | + suggestions = get_close_matches(k, (attr for attr in dir(self) if not attr.startswith('_'))) |
| 69 | + hint = f" Did you mean: {', '.join(suggestions)}?" if suggestions else "" |
67 | 70 | raise AttributeError( |
68 | | - f"Strategy '{self.__class__.__name__}' is missing parameter '{k}'." |
| 71 | + f"Strategy '{self.__class__.__name__}' is missing parameter '{k}'. " |
69 | 72 | "Strategy class should define parameters as class variables before they " |
70 | | - "can be optimized or run with.") |
| 73 | + "can be optimized or run with." + hint) |
71 | 74 | setattr(self, k, v) |
72 | 75 | return params |
73 | 76 |
|
@@ -964,8 +967,9 @@ def _process_orders(self): |
964 | 967 | # Not enough cash/margin even for a single unit |
965 | 968 | if not size: |
966 | 969 | warnings.warn( |
967 | | - f'time={self._i}: Broker canceled the relative-sized ' |
968 | | - f'order due to insufficient margin.', category=UserWarning) |
| 970 | + f'time={self._i}: Broker canceled the relative-sized order due to insufficient margin ' |
| 971 | + f'(equity={self.equity:.2f}, margin_available={self.margin_available:.2f}).', |
| 972 | + category=UserWarning) |
969 | 973 | # XXX: The order is canceled by the broker? |
970 | 974 | self.orders.remove(order) |
971 | 975 | continue |
@@ -998,6 +1002,10 @@ def _process_orders(self): |
998 | 1002 | # If we don't have enough liquidity to cover for the order, the broker CANCELS it |
999 | 1003 | if abs(need_size) * adjusted_price_plus_commission > \ |
1000 | 1004 | self.margin_available * self._leverage: |
| 1005 | + warnings.warn( |
| 1006 | + f'time={self._i}: Broker canceled the order due to insufficient margin ' |
| 1007 | + f'(equity={self.equity:.2f}, margin_available={self.margin_available:.2f}).', |
| 1008 | + category=UserWarning) |
1001 | 1009 | self.orders.remove(order) |
1002 | 1010 | continue |
1003 | 1011 |
|
@@ -1121,7 +1129,7 @@ class Backtest: |
1121 | 1129 |
|
1122 | 1130 | `cash` is the initial cash to start with. |
1123 | 1131 |
|
1124 | | - `spread` is the the constant bid-ask spread rate (relative to the price). |
| 1132 | + `spread` is the constant bid-ask spread rate (relative to the price). |
1125 | 1133 | E.g. set it to `0.0002` for commission-less forex |
1126 | 1134 | trading where the average spread is roughly 0.2‰ of the asking price. |
1127 | 1135 |
|
@@ -1151,7 +1159,7 @@ class Backtest: |
1151 | 1159 |
|
1152 | 1160 | `margin` is the required margin (ratio) of a leveraged account. |
1153 | 1161 | No difference is made between initial and maintenance margins. |
1154 | | - To run the backtest using e.g. 50:1 leverge that your broker allows, |
| 1162 | + To run the backtest using e.g. 50:1 leverage that your broker allows, |
1155 | 1163 | set margin to `0.02` (1 / leverage). |
1156 | 1164 |
|
1157 | 1165 | If `trade_on_close` is `True`, market orders will be filled |
|
0 commit comments