AlgoTradeKit 0.7.2__tar.gz → 0.7.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.
- {algotradekit-0.7.2 → algotradekit-0.7.4}/CHANGELOG.md +171 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/PKG-INFO +80 -3
- {algotradekit-0.7.2 → algotradekit-0.7.4}/README.md +79 -2
- {algotradekit-0.7.2 → algotradekit-0.7.4}/pyproject.toml +1 -1
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/__init__.py +1 -1
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/simulate/__init__.py +2 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/simulate/_config.py +52 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/simulate/_engine.py +335 -86
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/simulate/_position.py +61 -7
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/simulate/_report.py +15 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/simulate/_runner.py +12 -9
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/strategy/_types.py +24 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/visual/chart.py +222 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/visual/indicator_renderer.py +230 -21
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/visual/models.py +18 -16
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/visual/static/index.html +267 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/tests/test_simulate.py +680 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/tests/test_strategy.py +21 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/.github/workflows/publish.yml +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/.gitignore +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/LICENSE +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/__init__.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/_utils.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/collector.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/converter.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/normalizer.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/sources/__init__.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/sources/base.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/sources/binance.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/storage/__init__.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/data/storage/csv_handler.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/indicator/__init__.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/indicator/_base.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/indicator/atr.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/indicator/ichimoku.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/indicator/ma.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/indicator/macd.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/indicator/rsi.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/report/__init__.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/report/_builder.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/report/_display.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/report/_server.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/report/static/report.html +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/simulate/_lot.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/simulate/_session.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/strategy/__init__.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/strategy/_base.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/strategy/builtin/__init__.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/strategy/builtin/macd.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/visual/__init__.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/src/AlgoTradeKit/visual/server.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/tests/test_data.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/tests/test_data_converter.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/tests/test_data_normalizer.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/tests/test_indicator.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/tests/test_report.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/tests/test_visual.py +0 -0
- {algotradekit-0.7.2 → algotradekit-0.7.4}/tests/test_visual__0_4_1.py +0 -0
|
@@ -7,6 +7,177 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.7.4] — 2026-06-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
#### `simulate` module — dynamic SL/TP visualisation
|
|
15
|
+
|
|
16
|
+
- **`ClosedTrade.sl_history`** — new `tuple` field recording every SL-state
|
|
17
|
+
change throughout a trade's lifetime as an ordered sequence of dicts
|
|
18
|
+
`{"time": int_ms, "sl": float, "next_tp": float | None}`. An entry is
|
|
19
|
+
appended at position open (initial state) and again every time the SL
|
|
20
|
+
advances: after each `multi_rr` TP level hit (SL walks to break-even /
|
|
21
|
+
previous TP) or whenever the trailing SL moves to a new high-water mark.
|
|
22
|
+
An empty history means the SL never moved; a single entry means the
|
|
23
|
+
position opened but the SL never advanced before closing.
|
|
24
|
+
|
|
25
|
+
- **`ClosedTrade.final_next_tp`** — new `float | None` field that records
|
|
26
|
+
the TP target that was active at the exact moment the trade closed. For
|
|
27
|
+
a `multi_rr` position that hit TP3 (moving next target to TP4) and was
|
|
28
|
+
then stopped out at the TP3-advanced SL, this is TP4. `None` when all
|
|
29
|
+
levels were consumed (position closed at the final TP) or no TP was
|
|
30
|
+
configured.
|
|
31
|
+
|
|
32
|
+
- **`ClosedTrade.peak_price`** — new `float` field storing the most
|
|
33
|
+
favourable price reached during the trade (highest high for longs, lowest
|
|
34
|
+
low for shorts). Used by the visual module as the top of the position box
|
|
35
|
+
for trailing-SL trades (the peak marks the last price that drove the
|
|
36
|
+
trailing SL to its tightest level).
|
|
37
|
+
|
|
38
|
+
- **`SimulateReport.trade_markers`** now includes `sl_history`, `final_next_tp`,
|
|
39
|
+
`peak_price`, and `rr_levels_hit` for every marker, enabling the
|
|
40
|
+
chart-side rendering of dynamic SL/TP lines.
|
|
41
|
+
|
|
42
|
+
#### `visual` module — dynamic SL/TP lines on chart
|
|
43
|
+
|
|
44
|
+
- **`add_simulation_positions`** (in `indicator_renderer.py`) enhanced with:
|
|
45
|
+
- **Dynamic SL line segments** — for `multi_rr` and `trailing` positions,
|
|
46
|
+
coloured horizontal `TrendLine` drawings are added alongside each position
|
|
47
|
+
box showing where the stop-loss was at each stage of the trade:
|
|
48
|
+
- **Red** — SL is in the loss zone (below entry for long)
|
|
49
|
+
- **Amber/yellow** — SL is at break-even (entry price)
|
|
50
|
+
- **Cyan** — SL is in profit territory (above entry for long)
|
|
51
|
+
- **Green TP lines** (`multi_rr` only) — for each SL segment, a matching
|
|
52
|
+
green horizontal line shows the next TP target that was being aimed at.
|
|
53
|
+
- **Posbox TP fix** — the profit-zone boundary of the position box now
|
|
54
|
+
shows `final_next_tp` (the TP at closing time) rather than the initial
|
|
55
|
+
TP at entry. After hitting TP3 with next target TP4, the box correctly
|
|
56
|
+
displays TP4 instead of staying stuck at TP1.
|
|
57
|
+
- **Trailing posbox top** — for `sl_mode="trailing"`, the top of the
|
|
58
|
+
position box is set to `peak_price` (the highest price the trailing SL
|
|
59
|
+
ever chased), giving a natural view of the profit zone that was available.
|
|
60
|
+
- **Partial-close grouping** — multiple `ClosedTrade` rows sharing a
|
|
61
|
+
`trade_id` (from `tp_level_close_fractions`) are now collapsed into a
|
|
62
|
+
single position box (one box per unique trade, not one per slice).
|
|
63
|
+
- New `config` parameter on `add_simulation_positions` — pass the
|
|
64
|
+
`SimulateConfig` used for the simulation to enable automatic mode
|
|
65
|
+
detection (`tp_mode`/`sl_mode`) for the correct line style.
|
|
66
|
+
|
|
67
|
+
- **Indicator toolbar** — a new 📈 button in the chart toolbar opens an
|
|
68
|
+
"Add Indicator" panel where the user can add technical indicators to the
|
|
69
|
+
live candle chart without writing any code:
|
|
70
|
+
- **Moving averages**: EMA, SMA, WMA, SMMA, DEMA, TEMA, HMA, VWMA, VWAP
|
|
71
|
+
- **Oscillators**: RSI (with optional MA), MACD, ATR (pure Python — no
|
|
72
|
+
third-party TA library)
|
|
73
|
+
- **Trend**: Ichimoku Cloud
|
|
74
|
+
- Each indicator type shows its own parameter form (period, source, MA
|
|
75
|
+
type, fast/slow/signal periods, Tenkan/Kijun/Senkou B, etc.).
|
|
76
|
+
- A colour picker lets the user override the default series colour.
|
|
77
|
+
- The "Add to Chart" button sends a `compute_indicator` WebSocket message
|
|
78
|
+
to the `Chart` server, which computes the indicator from the stored OHLCV
|
|
79
|
+
`DataFrame` and broadcasts the result as one or more `add_indicator`
|
|
80
|
+
messages; the chart renders them immediately.
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- **Trailing SL false gap-open trigger** — the trailing SL was previously
|
|
85
|
+
advanced in step A (before close checks), which could cause a "gap open
|
|
86
|
+
past SL" close on the very candle that caused the SL to jump, when the
|
|
87
|
+
candle's open price was between the old and new SL levels. The trailing
|
|
88
|
+
SL is now advanced inside `_check_close` *after* the gap-open test, so
|
|
89
|
+
the gap check always uses the SL from the end of the previous candle.
|
|
90
|
+
This fix applies to both the single-pair `Simulate` engine and the
|
|
91
|
+
multi-pair `run_multi` engine (`_runner.py`).
|
|
92
|
+
|
|
93
|
+
- **Chart and report tabs disconnecting immediately** — the
|
|
94
|
+
`ChartServer` and `ReportServer` both run in daemon threads, which Python
|
|
95
|
+
kills the instant the script's main thread exits. Previously,
|
|
96
|
+
`Simulate.run()` started both servers non-blocking and returned, so all
|
|
97
|
+
user code after the call (printing stats, saving CSV history, etc.) ran
|
|
98
|
+
normally — but as soon as `main()` returned the process died, closing both
|
|
99
|
+
browser tabs before the user could interact with them. A one-time `atexit`
|
|
100
|
+
handler is now registered whenever a browser UI is opened; it blocks with
|
|
101
|
+
a `while True: sleep(1)` loop (identical to `chart.show(block=True)`) after
|
|
102
|
+
all user code has finished, keeping the process alive until the user presses
|
|
103
|
+
**Ctrl+C**. The handler registers only once per process invocation, so
|
|
104
|
+
multiple `Simulate.run()` calls (batch/multi-symbol) produce a single
|
|
105
|
+
blocking handler at exit.
|
|
106
|
+
|
|
107
|
+
### Tests
|
|
108
|
+
|
|
109
|
+
- `tests/test_simulate.py` — new `TestSlHistory` class (13 tests) covering:
|
|
110
|
+
- `sl_history` length and content after 0, 1, and 2 `multi_rr` TP hits
|
|
111
|
+
- timestamp presence and ordering in `sl_history`
|
|
112
|
+
- `final_next_tp` value after partial and final TP consumption
|
|
113
|
+
- `peak_price` captured correctly for trailing-SL trades
|
|
114
|
+
- backward-compatibility: plain `signal` SL mode produces exactly one
|
|
115
|
+
`sl_history` entry (no SL movement)
|
|
116
|
+
- `add_simulation_positions` smoke test: one posbox per unique `trade_id`
|
|
117
|
+
- dynamic SL `TrendLine` drawings added when `sl_history` length > 1
|
|
118
|
+
- posbox `take_profit` uses `final_next_tp` not initial TP
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## [0.7.3] — 2026-06-21
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
|
|
126
|
+
#### `strategy` module
|
|
127
|
+
- **`Signal.risk_multiplier`** — new optional field (default `1.0`) that scales
|
|
128
|
+
the size/risk a single signal receives relative to the run's global
|
|
129
|
+
`SimulateConfig` sizing (`risk_percent` / `fixed_amount` / `fixed_lot`).
|
|
130
|
+
Lets a strategy vary risk per signal instead of using one fixed sizing
|
|
131
|
+
rule for every trade in a backtest — e.g. half size on a tentative
|
|
132
|
+
"limit" entry vs. full size on a confirmed one, or splitting one trade
|
|
133
|
+
idea into several sub-positions whose sizes sum to one risk unit
|
|
134
|
+
(three signals at `1/3` each, targeting 1R/2R/3R, to emulate scaling out
|
|
135
|
+
of a position). Default `1.0` is fully backward compatible.
|
|
136
|
+
|
|
137
|
+
#### `simulate` module
|
|
138
|
+
- **`SimulateConfig.tp_level_close_fractions`** — optional list, parallel to
|
|
139
|
+
`tp_levels`, that turns on **true partial closes** for `tp_mode="multi_rr"`.
|
|
140
|
+
Each entry is the fraction of the position's *original* size to realise
|
|
141
|
+
when the corresponding level is reached, instead of only moving the SL.
|
|
142
|
+
`None` (default) preserves the exact pre-0.7.3 behaviour (SL-only
|
|
143
|
+
advancement, single full close at the final level). When set:
|
|
144
|
+
- fractions summing to `1.0` give a full progressive scale-out
|
|
145
|
+
(e.g. `[1/3, 1/3, 1/3]`) — profit is banked level by level instead of
|
|
146
|
+
all-or-nothing at the end.
|
|
147
|
+
- all-zero fractions give a "let it run" position: the SL just walks
|
|
148
|
+
through every level and the full size stays open, trailing at the
|
|
149
|
+
last level's price, until eventually stopped out.
|
|
150
|
+
- any mix is valid — partially bank some levels, let others purely
|
|
151
|
+
advance the SL.
|
|
152
|
+
|
|
153
|
+
Each partial close produces its own `ClosedTrade` sharing the position's
|
|
154
|
+
`trade_id`, so `SimulateReport` statistics (win rate, profit factor,
|
|
155
|
+
equity curve) count every realised slice on its own line, the same way
|
|
156
|
+
a real scaled-out position would appear on a broker statement.
|
|
157
|
+
Commission (pre-charged once, in full, at entry) is split proportionally
|
|
158
|
+
across every slice so the total charged across one position's full
|
|
159
|
+
lifetime never changes.
|
|
160
|
+
- **`CLOSE_REASON_TP_PARTIAL`** is now exported from `AlgoTradeKit.simulate`.
|
|
161
|
+
It existed on `ClosedTrade` since an earlier release (with `is_tp`
|
|
162
|
+
already wired to recognise it) but no code path had ever produced it
|
|
163
|
+
until `tp_level_close_fractions` above.
|
|
164
|
+
- `run_multi()` gained the identical partial-close handling as the
|
|
165
|
+
single-pair `Simulate` engine, so shared-wallet multi-symbol portfolios
|
|
166
|
+
behave the same way; `run_batch()` needed no changes (it only wraps
|
|
167
|
+
`Simulate(cfg).run()`).
|
|
168
|
+
|
|
169
|
+
### Tests
|
|
170
|
+
- `tests/test_strategy.py` — `TestSignal` extended with 5 tests for
|
|
171
|
+
`risk_multiplier` (default, custom value, `<= 0` validation, positional-
|
|
172
|
+
construction backward compatibility).
|
|
173
|
+
- `tests/test_simulate.py` — new `TestEngineRiskMultiplier` (4 tests) and
|
|
174
|
+
`TestEngineMultiRRPartialClose` (8 tests, including a hand-derived PnL
|
|
175
|
+
ground truth and an end-to-end wallet-conservation check) and
|
|
176
|
+
`TestSimulateConfigPartialTPValidation` (8 tests). All 171 pre-existing
|
|
177
|
+
tests in these two files continue to pass unmodified.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
10
181
|
## [0.7.2] — 2026-06-20
|
|
11
182
|
|
|
12
183
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: AlgoTradeKit
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.4
|
|
4
4
|
Summary: Algorithmic Trading Toolkit — data collection, indicators, strategy, simulation, and live trading
|
|
5
5
|
Project-URL: Homepage, https://github.com/AmirMohammadBazdar/AlgoTradeKit
|
|
6
6
|
Project-URL: Repository, https://github.com/AmirMohammadBazdar/AlgoTradeKit
|
|
@@ -308,12 +308,48 @@ report = Simulate(config).run(strategy_result)
|
|
|
308
308
|
| `tp_mode` | `"signal"` | `"signal"/"fixed_rr"/"multi_rr"/"none"` |
|
|
309
309
|
| `tp_rr` | 2.0 | R:R ratio for `fixed_rr` mode |
|
|
310
310
|
| `tp_levels` | [1,2,3] | R levels for `multi_rr` mode |
|
|
311
|
+
| `tp_level_close_fractions` | `None` | **v0.7.3** Fraction of original size to realise at each `tp_levels` entry (`multi_rr` only) — see below |
|
|
311
312
|
| `sl_mode` | `"signal"` | `"signal"` or `"trailing"` |
|
|
312
313
|
| `risk_free_enabled` | False | Move SL to break-even at `risk_free_at_rr` |
|
|
313
314
|
| `show_chart` | **False** | **v0.7.0** Open candle chart after run |
|
|
314
315
|
| `report_mode` | **`"none"`** | **v0.7.0** Post-run report rendering |
|
|
315
316
|
| `report_save_path` | `"report.html"` | **v0.7.0** HTML save path |
|
|
316
317
|
|
|
318
|
+
### Per-Signal Sizing & Partial Take-Profit (v0.7.3)
|
|
319
|
+
|
|
320
|
+
`Signal.risk_multiplier` scales one signal's size relative to the run's
|
|
321
|
+
sizing config — useful for varying risk by context, or for splitting one
|
|
322
|
+
trade idea into several sub-positions whose sizes sum to one risk unit:
|
|
323
|
+
|
|
324
|
+
```python
|
|
325
|
+
# Three signals at the same candle, each risking 1/3 of a normal trade,
|
|
326
|
+
# targeting 1R / 2R / 3R — equivalent to scaling out of one position.
|
|
327
|
+
for i, tp_r in enumerate([1, 2, 3], start=1):
|
|
328
|
+
signals.append(Signal(
|
|
329
|
+
direction="long", entry_price=entry, stop_loss=sl,
|
|
330
|
+
take_profit=entry + tp_r * (entry - sl),
|
|
331
|
+
timestamp=ts, candle_index=idx, timeframe="1h",
|
|
332
|
+
risk_multiplier=1 / 3,
|
|
333
|
+
))
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
`SimulateConfig.tp_level_close_fractions` turns `multi_rr` into a true
|
|
337
|
+
scale-out — each level realises a real, partial close instead of only
|
|
338
|
+
moving the SL:
|
|
339
|
+
|
|
340
|
+
```python
|
|
341
|
+
config = SimulateConfig(
|
|
342
|
+
...,
|
|
343
|
+
tp_mode="multi_rr",
|
|
344
|
+
tp_levels=[1.0, 2.0, 3.0],
|
|
345
|
+
tp_level_close_fractions=[1 / 3, 1 / 3, 1 / 3], # bank 1/3 at each level
|
|
346
|
+
)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Set every fraction to `0.0` instead to get the opposite pattern — SL walks
|
|
350
|
+
through every level but nothing closes until the position is eventually
|
|
351
|
+
stopped out, letting winners run with a trailing stop and no fixed target.
|
|
352
|
+
|
|
317
353
|
### Batch Sweep
|
|
318
354
|
|
|
319
355
|
```python
|
|
@@ -425,10 +461,25 @@ TradingView-style position boxes showing SL/TP zones with R:R labels:
|
|
|
425
461
|
from AlgoTradeKit.visual.indicator_renderer import add_simulation_positions
|
|
426
462
|
|
|
427
463
|
chart = Chart.from_csv("data/binance-futures_BTCUSDT_1h.csv")
|
|
428
|
-
add_simulation_positions(chart, report, opacity=0.15)
|
|
464
|
+
add_simulation_positions(chart, report, opacity=0.15, config=sim_config)
|
|
429
465
|
chart.show(block=True)
|
|
430
466
|
```
|
|
431
467
|
|
|
468
|
+
**Dynamic SL/TP lines *(v0.7.4)*** — when `config` is passed, coloured
|
|
469
|
+
horizontal lines are drawn on the chart for every trade where the SL moved:
|
|
470
|
+
|
|
471
|
+
| Colour | Meaning |
|
|
472
|
+
|--------|---------|
|
|
473
|
+
| 🔴 Red | SL is in the loss zone (below entry for long) |
|
|
474
|
+
| 🟡 Amber | SL is at break-even (entry price) |
|
|
475
|
+
| 🔵 Cyan | SL is in profit territory |
|
|
476
|
+
| 🟢 Green | Next TP target (`multi_rr` only) |
|
|
477
|
+
|
|
478
|
+
The position box's profit-zone boundary is also corrected to show the TP
|
|
479
|
+
target that was active at the *moment of closing* rather than the initial TP
|
|
480
|
+
at entry. For trailing-SL trades the box top is set to `peak_price` (the
|
|
481
|
+
highest price the trailing SL ever chased).
|
|
482
|
+
|
|
432
483
|
Manual single box:
|
|
433
484
|
```python
|
|
434
485
|
chart.add_position_box(
|
|
@@ -439,6 +490,21 @@ chart.add_position_box(
|
|
|
439
490
|
)
|
|
440
491
|
```
|
|
441
492
|
|
|
493
|
+
### Indicator Toolbar *(v0.7.4)*
|
|
494
|
+
|
|
495
|
+
The chart toolbar now has an **Add Indicator** (📈) button. Clicking it
|
|
496
|
+
opens a panel where indicators can be added interactively at runtime — no
|
|
497
|
+
code required:
|
|
498
|
+
|
|
499
|
+
- **Moving averages**: EMA, SMA, WMA, SMMA, DEMA, TEMA, HMA, VWMA, VWAP
|
|
500
|
+
- **Oscillators**: RSI (with optional MA), MACD, ATR
|
|
501
|
+
- **Trend**: Ichimoku Cloud
|
|
502
|
+
|
|
503
|
+
Each type shows its own parameter form. After clicking *Add to Chart*, the
|
|
504
|
+
server computes the indicator from the OHLCV data and the chart updates
|
|
505
|
+
immediately. Indicators added this way work exactly like those added via
|
|
506
|
+
`add_ma()` / `add_rsi()` / etc. in Python code.
|
|
507
|
+
|
|
442
508
|
### Strategy Drawings (v0.7.0)
|
|
443
509
|
|
|
444
510
|
```python
|
|
@@ -562,9 +628,20 @@ Two browser tabs open:
|
|
|
562
628
|
|---|---|---|
|
|
563
629
|
| `TP_MODE_SIGNAL` | `"signal"` | Use Signal's take_profit |
|
|
564
630
|
| `TP_MODE_FIXED_RR` | `"fixed_rr"` | entry ± `tp_rr` × SL distance |
|
|
565
|
-
| `TP_MODE_MULTI_RR` | `"multi_rr"` | Multiple levels, SL trails |
|
|
631
|
+
| `TP_MODE_MULTI_RR` | `"multi_rr"` | Multiple levels, SL trails (and optionally partially closes — see `tp_level_close_fractions` *(v0.7.3)*) |
|
|
566
632
|
| `TP_MODE_NONE` | `"none"` | No TP |
|
|
567
633
|
|
|
634
|
+
### Close Reason Constants
|
|
635
|
+
|
|
636
|
+
| Constant | Value | Meaning |
|
|
637
|
+
|---|---|---|
|
|
638
|
+
| `CLOSE_REASON_SL` | `"sl"` | Stop loss hit |
|
|
639
|
+
| `CLOSE_REASON_TP` | `"tp"` | Take profit hit — nothing left open afterwards |
|
|
640
|
+
| `CLOSE_REASON_TP_PARTIAL` | `"tp_rr"` | **v0.7.3** Intermediate `multi_rr` level partially realised — position still open with reduced size |
|
|
641
|
+
| `CLOSE_REASON_RF` | `"rf"` | Risk-free / trailed SL hit |
|
|
642
|
+
| `CLOSE_REASON_FC` | `"force_close"` | Closed by an `ExitSignal` |
|
|
643
|
+
| `CLOSE_REASON_EOD` | `"end_of_data"` | Still open when data ran out |
|
|
644
|
+
|
|
568
645
|
### SL Mode Constants
|
|
569
646
|
|
|
570
647
|
| Constant | Value | Behaviour |
|
|
@@ -252,12 +252,48 @@ report = Simulate(config).run(strategy_result)
|
|
|
252
252
|
| `tp_mode` | `"signal"` | `"signal"/"fixed_rr"/"multi_rr"/"none"` |
|
|
253
253
|
| `tp_rr` | 2.0 | R:R ratio for `fixed_rr` mode |
|
|
254
254
|
| `tp_levels` | [1,2,3] | R levels for `multi_rr` mode |
|
|
255
|
+
| `tp_level_close_fractions` | `None` | **v0.7.3** Fraction of original size to realise at each `tp_levels` entry (`multi_rr` only) — see below |
|
|
255
256
|
| `sl_mode` | `"signal"` | `"signal"` or `"trailing"` |
|
|
256
257
|
| `risk_free_enabled` | False | Move SL to break-even at `risk_free_at_rr` |
|
|
257
258
|
| `show_chart` | **False** | **v0.7.0** Open candle chart after run |
|
|
258
259
|
| `report_mode` | **`"none"`** | **v0.7.0** Post-run report rendering |
|
|
259
260
|
| `report_save_path` | `"report.html"` | **v0.7.0** HTML save path |
|
|
260
261
|
|
|
262
|
+
### Per-Signal Sizing & Partial Take-Profit (v0.7.3)
|
|
263
|
+
|
|
264
|
+
`Signal.risk_multiplier` scales one signal's size relative to the run's
|
|
265
|
+
sizing config — useful for varying risk by context, or for splitting one
|
|
266
|
+
trade idea into several sub-positions whose sizes sum to one risk unit:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
# Three signals at the same candle, each risking 1/3 of a normal trade,
|
|
270
|
+
# targeting 1R / 2R / 3R — equivalent to scaling out of one position.
|
|
271
|
+
for i, tp_r in enumerate([1, 2, 3], start=1):
|
|
272
|
+
signals.append(Signal(
|
|
273
|
+
direction="long", entry_price=entry, stop_loss=sl,
|
|
274
|
+
take_profit=entry + tp_r * (entry - sl),
|
|
275
|
+
timestamp=ts, candle_index=idx, timeframe="1h",
|
|
276
|
+
risk_multiplier=1 / 3,
|
|
277
|
+
))
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
`SimulateConfig.tp_level_close_fractions` turns `multi_rr` into a true
|
|
281
|
+
scale-out — each level realises a real, partial close instead of only
|
|
282
|
+
moving the SL:
|
|
283
|
+
|
|
284
|
+
```python
|
|
285
|
+
config = SimulateConfig(
|
|
286
|
+
...,
|
|
287
|
+
tp_mode="multi_rr",
|
|
288
|
+
tp_levels=[1.0, 2.0, 3.0],
|
|
289
|
+
tp_level_close_fractions=[1 / 3, 1 / 3, 1 / 3], # bank 1/3 at each level
|
|
290
|
+
)
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Set every fraction to `0.0` instead to get the opposite pattern — SL walks
|
|
294
|
+
through every level but nothing closes until the position is eventually
|
|
295
|
+
stopped out, letting winners run with a trailing stop and no fixed target.
|
|
296
|
+
|
|
261
297
|
### Batch Sweep
|
|
262
298
|
|
|
263
299
|
```python
|
|
@@ -369,10 +405,25 @@ TradingView-style position boxes showing SL/TP zones with R:R labels:
|
|
|
369
405
|
from AlgoTradeKit.visual.indicator_renderer import add_simulation_positions
|
|
370
406
|
|
|
371
407
|
chart = Chart.from_csv("data/binance-futures_BTCUSDT_1h.csv")
|
|
372
|
-
add_simulation_positions(chart, report, opacity=0.15)
|
|
408
|
+
add_simulation_positions(chart, report, opacity=0.15, config=sim_config)
|
|
373
409
|
chart.show(block=True)
|
|
374
410
|
```
|
|
375
411
|
|
|
412
|
+
**Dynamic SL/TP lines *(v0.7.4)*** — when `config` is passed, coloured
|
|
413
|
+
horizontal lines are drawn on the chart for every trade where the SL moved:
|
|
414
|
+
|
|
415
|
+
| Colour | Meaning |
|
|
416
|
+
|--------|---------|
|
|
417
|
+
| 🔴 Red | SL is in the loss zone (below entry for long) |
|
|
418
|
+
| 🟡 Amber | SL is at break-even (entry price) |
|
|
419
|
+
| 🔵 Cyan | SL is in profit territory |
|
|
420
|
+
| 🟢 Green | Next TP target (`multi_rr` only) |
|
|
421
|
+
|
|
422
|
+
The position box's profit-zone boundary is also corrected to show the TP
|
|
423
|
+
target that was active at the *moment of closing* rather than the initial TP
|
|
424
|
+
at entry. For trailing-SL trades the box top is set to `peak_price` (the
|
|
425
|
+
highest price the trailing SL ever chased).
|
|
426
|
+
|
|
376
427
|
Manual single box:
|
|
377
428
|
```python
|
|
378
429
|
chart.add_position_box(
|
|
@@ -383,6 +434,21 @@ chart.add_position_box(
|
|
|
383
434
|
)
|
|
384
435
|
```
|
|
385
436
|
|
|
437
|
+
### Indicator Toolbar *(v0.7.4)*
|
|
438
|
+
|
|
439
|
+
The chart toolbar now has an **Add Indicator** (📈) button. Clicking it
|
|
440
|
+
opens a panel where indicators can be added interactively at runtime — no
|
|
441
|
+
code required:
|
|
442
|
+
|
|
443
|
+
- **Moving averages**: EMA, SMA, WMA, SMMA, DEMA, TEMA, HMA, VWMA, VWAP
|
|
444
|
+
- **Oscillators**: RSI (with optional MA), MACD, ATR
|
|
445
|
+
- **Trend**: Ichimoku Cloud
|
|
446
|
+
|
|
447
|
+
Each type shows its own parameter form. After clicking *Add to Chart*, the
|
|
448
|
+
server computes the indicator from the OHLCV data and the chart updates
|
|
449
|
+
immediately. Indicators added this way work exactly like those added via
|
|
450
|
+
`add_ma()` / `add_rsi()` / etc. in Python code.
|
|
451
|
+
|
|
386
452
|
### Strategy Drawings (v0.7.0)
|
|
387
453
|
|
|
388
454
|
```python
|
|
@@ -506,9 +572,20 @@ Two browser tabs open:
|
|
|
506
572
|
|---|---|---|
|
|
507
573
|
| `TP_MODE_SIGNAL` | `"signal"` | Use Signal's take_profit |
|
|
508
574
|
| `TP_MODE_FIXED_RR` | `"fixed_rr"` | entry ± `tp_rr` × SL distance |
|
|
509
|
-
| `TP_MODE_MULTI_RR` | `"multi_rr"` | Multiple levels, SL trails |
|
|
575
|
+
| `TP_MODE_MULTI_RR` | `"multi_rr"` | Multiple levels, SL trails (and optionally partially closes — see `tp_level_close_fractions` *(v0.7.3)*) |
|
|
510
576
|
| `TP_MODE_NONE` | `"none"` | No TP |
|
|
511
577
|
|
|
578
|
+
### Close Reason Constants
|
|
579
|
+
|
|
580
|
+
| Constant | Value | Meaning |
|
|
581
|
+
|---|---|---|
|
|
582
|
+
| `CLOSE_REASON_SL` | `"sl"` | Stop loss hit |
|
|
583
|
+
| `CLOSE_REASON_TP` | `"tp"` | Take profit hit — nothing left open afterwards |
|
|
584
|
+
| `CLOSE_REASON_TP_PARTIAL` | `"tp_rr"` | **v0.7.3** Intermediate `multi_rr` level partially realised — position still open with reduced size |
|
|
585
|
+
| `CLOSE_REASON_RF` | `"rf"` | Risk-free / trailed SL hit |
|
|
586
|
+
| `CLOSE_REASON_FC` | `"force_close"` | Closed by an `ExitSignal` |
|
|
587
|
+
| `CLOSE_REASON_EOD` | `"end_of_data"` | Still open when data ran out |
|
|
588
|
+
|
|
512
589
|
### SL Mode Constants
|
|
513
590
|
|
|
514
591
|
| Constant | Value | Behaviour |
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "AlgoTradeKit"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.4"
|
|
8
8
|
description = "Algorithmic Trading Toolkit — data collection, indicators, strategy, simulation, and live trading"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { file = "LICENSE" }
|
|
@@ -99,6 +99,7 @@ from ._position import (
|
|
|
99
99
|
CLOSE_REASON_RF,
|
|
100
100
|
CLOSE_REASON_SL,
|
|
101
101
|
CLOSE_REASON_TP,
|
|
102
|
+
CLOSE_REASON_TP_PARTIAL,
|
|
102
103
|
ClosedTrade,
|
|
103
104
|
)
|
|
104
105
|
from ._report import (
|
|
@@ -139,6 +140,7 @@ __all__ = [
|
|
|
139
140
|
"ClosedTrade",
|
|
140
141
|
"CLOSE_REASON_SL",
|
|
141
142
|
"CLOSE_REASON_TP",
|
|
143
|
+
"CLOSE_REASON_TP_PARTIAL",
|
|
142
144
|
"CLOSE_REASON_RF",
|
|
143
145
|
"CLOSE_REASON_FC",
|
|
144
146
|
"CLOSE_REASON_EOD",
|
|
@@ -121,6 +121,37 @@ class SimulateConfig:
|
|
|
121
121
|
For ``"multi_rr"`` mode: list of R-multiples at which TP levels sit
|
|
122
122
|
(default: ``[1.0, 2.0, 3.0]``). After each level is hit, SL moves
|
|
123
123
|
to the previous level (0.0 = entry, 1.0 = first TP, etc.).
|
|
124
|
+
tp_level_close_fractions : list[float] | None
|
|
125
|
+
For ``"multi_rr"`` mode only. Optional list, same length as
|
|
126
|
+
``tp_levels``, giving the fraction of the position's **original**
|
|
127
|
+
size to actually realise (partially close) when each corresponding
|
|
128
|
+
level is reached. Each value must be in ``[0.0, 1.0]`` and the
|
|
129
|
+
values must sum to ``<= 1.0``.
|
|
130
|
+
|
|
131
|
+
``None`` (default) preserves the exact pre-v0.7.3 behaviour: no
|
|
132
|
+
level closes anything — the position's SL simply walks forward
|
|
133
|
+
through the levels, and only the *final* level fully closes the
|
|
134
|
+
position. This default is unchanged for full backward compatibility.
|
|
135
|
+
|
|
136
|
+
When provided, every level (including the last) closes **only**
|
|
137
|
+
the fraction you specify — there is no automatic "close everything"
|
|
138
|
+
special-case for the final level anymore. Two common patterns:
|
|
139
|
+
|
|
140
|
+
* **Scale out completely** — fractions summing to ``1.0``
|
|
141
|
+
(e.g. ``[1/3, 1/3, 1/3]`` for ``tp_levels=[1, 2, 3]``) realises
|
|
142
|
+
profit progressively and leaves nothing open after the last level.
|
|
143
|
+
* **Trail forever, never auto-realise** — all-zero fractions
|
|
144
|
+
(e.g. ``[0.0] * 20`` for ``tp_levels=[1, 2, ..., 20]``) never
|
|
145
|
+
partially closes anything; the SL just keeps stepping up through
|
|
146
|
+
every level and the full size stays open, trailing at the last
|
|
147
|
+
level's price, until it is eventually stopped out, force-closed,
|
|
148
|
+
or hits end-of-data.
|
|
149
|
+
|
|
150
|
+
Each partial close produces its own ``ClosedTrade`` (sharing the
|
|
151
|
+
same ``trade_id`` as the rest of that position's closes), so
|
|
152
|
+
``SimulateReport`` statistics count each realised slice separately
|
|
153
|
+
— matching how a real scaled-out position would show up on a
|
|
154
|
+
broker statement.
|
|
124
155
|
sl_mode : str
|
|
125
156
|
Stop-loss mode: ``"signal"`` (use SL from Signal) or ``"trailing"``
|
|
126
157
|
(default: ``"signal"``).
|
|
@@ -199,6 +230,7 @@ class SimulateConfig:
|
|
|
199
230
|
tp_mode: str = TP_MODE_SIGNAL
|
|
200
231
|
tp_rr: float = 2.0
|
|
201
232
|
tp_levels: list[float] = field(default_factory=lambda: [1.0, 2.0, 3.0])
|
|
233
|
+
tp_level_close_fractions: list[float] | None = None
|
|
202
234
|
|
|
203
235
|
# ------------------------------------------------------------------
|
|
204
236
|
# Stop-loss
|
|
@@ -291,6 +323,26 @@ class SimulateConfig:
|
|
|
291
323
|
raise ValueError(
|
|
292
324
|
"SimulateConfig.tp_levels must be a non-empty list when tp_mode='multi_rr'."
|
|
293
325
|
)
|
|
326
|
+
if self.tp_level_close_fractions is not None:
|
|
327
|
+
if self.tp_mode != TP_MODE_MULTI_RR:
|
|
328
|
+
raise ValueError(
|
|
329
|
+
"SimulateConfig.tp_level_close_fractions requires tp_mode='multi_rr'."
|
|
330
|
+
)
|
|
331
|
+
if len(self.tp_level_close_fractions) != len(self.tp_levels):
|
|
332
|
+
raise ValueError(
|
|
333
|
+
"SimulateConfig.tp_level_close_fractions must be the same length as "
|
|
334
|
+
f"tp_levels ({len(self.tp_levels)}), got "
|
|
335
|
+
f"{len(self.tp_level_close_fractions)}."
|
|
336
|
+
)
|
|
337
|
+
if any(not (0.0 <= f <= 1.0) for f in self.tp_level_close_fractions):
|
|
338
|
+
raise ValueError(
|
|
339
|
+
"SimulateConfig.tp_level_close_fractions values must all be in [0.0, 1.0]."
|
|
340
|
+
)
|
|
341
|
+
if sum(self.tp_level_close_fractions) > 1.0 + 1e-9:
|
|
342
|
+
raise ValueError(
|
|
343
|
+
"SimulateConfig.tp_level_close_fractions must sum to <= 1.0, got "
|
|
344
|
+
f"{sum(self.tp_level_close_fractions)!r}."
|
|
345
|
+
)
|
|
294
346
|
if not self.config_id:
|
|
295
347
|
self.config_id = self._auto_id()
|
|
296
348
|
if self.report_mode not in (
|