hypertrader 0.2.0__tar.gz → 0.2.1__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.
- {hypertrader-0.2.0 → hypertrader-0.2.1}/PKG-INFO +22 -1
- {hypertrader-0.2.0 → hypertrader-0.2.1}/README.md +21 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/hypertrader.egg-info/PKG-INFO +22 -1
- {hypertrader-0.2.0 → hypertrader-0.2.1}/hypertrader.egg-info/SOURCES.txt +4 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/hypertrader.py +14 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/modes/auto_trader.py +152 -39
- {hypertrader-0.2.0 → hypertrader-0.2.1}/modes/position_management.py +85 -24
- {hypertrader-0.2.0 → hypertrader-0.2.1}/setup.py +1 -1
- {hypertrader-0.2.0 → hypertrader-0.2.1}/strategies/base.py +31 -1
- {hypertrader-0.2.0 → hypertrader-0.2.1}/strategies/fib_reversal.py +6 -0
- hypertrader-0.2.1/tests/test_session_recovery.py +224 -0
- hypertrader-0.2.1/tests/test_trade_journal.py +202 -0
- hypertrader-0.2.1/utils/session_state.py +51 -0
- hypertrader-0.2.1/utils/trade_journal.py +186 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/hypertrader.egg-info/dependency_links.txt +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/hypertrader.egg-info/entry_points.txt +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/hypertrader.egg-info/requires.txt +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/hypertrader.egg-info/top_level.txt +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/modes/market_maker.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/modes/position_watcher.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/modes/trailing_stop.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/setup.cfg +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/strategies/__init__.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/strategies/default.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/strategies/orderflow_metrics.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/strategies/orderflow_models.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/strategies/orderflow_pullback.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/strategies/registry.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/strategies/reversal.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_api_retry.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_async_hyperliquid.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_auto_dry_run.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_auto_strategy_registry.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_auto_trader_logging.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_cli_shutdown.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_fib_reversal_strategy.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_market_ids.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_orderflow_metrics.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_orderflow_pullback_lifecycle.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_reversal_strategy.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_simulated_logging.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/tests/test_trailing_stop.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/utils/constants.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/utils/helpers.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/utils/retry.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/utils/simulated_logging.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/utils/style.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/utils/timer.py +0 -0
- {hypertrader-0.2.0 → hypertrader-0.2.1}/utils/worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypertrader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Async Hyperliquid trading helper built on the async SDK.
|
|
5
5
|
Author: darkerego
|
|
6
6
|
License: MIT
|
|
@@ -324,6 +324,7 @@ Key options:
|
|
|
324
324
|
- `--scalp`
|
|
325
325
|
- `--use-live-candle`
|
|
326
326
|
- `--take-profit-pct`: override Bollinger-derived TP
|
|
327
|
+
- `--take-profit-scale`: multiply target distances from entry (default `1.0`; `0.5` halves them)
|
|
327
328
|
- `--min-take-profit-pct`
|
|
328
329
|
- `--max-take-profit-pct`
|
|
329
330
|
- `--stop-loss-pct`
|
|
@@ -364,6 +365,8 @@ Key options:
|
|
|
364
365
|
|
|
365
366
|
`auto` derives TP from Bollinger Bands unless `--take-profit-pct` overrides it. If TP is set and SL is omitted, SL defaults to half TP. Executions route through the same bracket-entry path used by `enter`.
|
|
366
367
|
|
|
368
|
+
`--take-profit-scale 0.5` moves each planned target halfway toward entry, for both longs and shorts: `new_target = entry + 0.5 * (target - entry)`. For example, an entry of 100 with targets 110, 120, and 130 becomes 105, 110, and 115. Scaling applies after strategy signal selection and before exchange price rounding, including Fibonacci targets and dry runs. Stops and allocation percentages stay the same. The multiplier must be finite and positive; `1.0` preserves the original distances.
|
|
369
|
+
|
|
367
370
|
Fibonacci pullback strategy:
|
|
368
371
|
|
|
369
372
|
```bash
|
|
@@ -567,6 +570,24 @@ The websocket cache is used as the fast path for market data such as mids, best
|
|
|
567
570
|
|
|
568
571
|
## Logging
|
|
569
572
|
|
|
573
|
+
Each new `auto` session (live or dry-run) creates a unique folder under `logs/sessions/` and prints its location. Restarting an unfinished session with the same account and trading settings automatically resumes that folder and appends to its journal. Use `--session-log-dir PATH` to choose another parent directory. Each folder contains:
|
|
574
|
+
|
|
575
|
+
- `session.json`: the original CLI arguments, parsed CLI settings including defaults, and the arguments passed to the auto trader. Environment variables and exchange clients are not serialized.
|
|
576
|
+
- `closed_trades.csv`: one row per completed position cycle, with UTC start/close times, direction, strategy, requested and filled sizes, size-weighted entry/exit prices, PnL, fees, and links to trade details. Rows are saved as trades complete; cancelled or failed tasks are not counted as closed trades.
|
|
577
|
+
- A JSON file per trade with the signal snapshot and raw exchange fills. Missing fill/PnL data produces blank CSV values with an explanation in `pnl_status`. Live PnL subtracts fill fees but excludes funding; simulated PnL excludes fees, funding, and slippage. Start/close times describe the managed cycle, including entry attempts and closure detection.
|
|
578
|
+
|
|
579
|
+
Atomic `.active-*.json` checkpoints in the parent directory retain session PnL and peaks, completed-trade counts, per-coin streaks and cooldown deadlines, pending trades, and Fibonacci impulses already traded. Live monitors restore saved hidden targets, order IDs, and trailing-stop state; interrupted entry attempts reconcile the existing position without submitting a new entry. Simulations restore entry price, remaining size, partial profits, and the next target. Completed results awaiting accounting are replayed without duplicating CSV rows.
|
|
580
|
+
|
|
581
|
+
Keep the same session directory and trading settings when restarting after an error. Accounts, networks, strategies, settings, and live/dry-run modes have separate checkpoints; simultaneous processes cannot share one checkpoint. `--new-session` deliberately resets recovery state and starts a new journal. A session that exits normally after its configured trade or risk limit is complete, so the next invocation starts a new one. Recovery begins with sessions created by this version; older logs alone cannot restore open-trade state. Local stops and simulated fills only run while the bot is running, and resume from the last saved state using current market prices.
|
|
582
|
+
|
|
583
|
+
Add `--trade-charts` to save an SVG price chart for each completed trade:
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
python3 hypertrader.py auto SOL --size 10 --dry-run --trade-charts
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Charts plot candle prices across the trade with size-weighted entry/exit markers. The candle interval adapts to the trade duration, and the latest candle may be incomplete. Charts require no extra plotting packages. The CSV is written before chart generation; the per-trade JSON records chart success, failure, or interruption. `chart_file` is the intended relative path and may not exist if chart generation fails. Charting is off by default and adds no candle requests when disabled.
|
|
590
|
+
|
|
570
591
|
Dry-run console output, runtime messages, risk events, and simulated trades are written to `logs/simulated.log`. Simulation fills at observed market mid prices. Plans with explicit take-profit allocations, including `fib_reversal`, close the allocated portion of the original size at each reached target (40% at TP1, 35% at TP2, and 25% at TP3 for Fibonacci). Each fill logs a `simulated_take_profit_filled` event with its size and PnL. Position records track remaining size, cumulative realized PnL, and unrealized PnL on the remaining position; a stop closes only that remainder. Completed-trade PnL includes all partial exits. Plans without explicit allocations exit the full position at the stop or final target. Fees, funding, slippage, and dynamic stop adjustments are not modeled. Open positions at shutdown are logged with realized partial profits and their remaining unrealized PnL.
|
|
571
592
|
|
|
572
593
|
Runtime logs for live runs are written to [`logs/hypertrader.log`](/media/anon/development/anon/PycharmProjects/hypertrader/logs/hypertrader.log).
|
|
@@ -293,6 +293,7 @@ Key options:
|
|
|
293
293
|
- `--scalp`
|
|
294
294
|
- `--use-live-candle`
|
|
295
295
|
- `--take-profit-pct`: override Bollinger-derived TP
|
|
296
|
+
- `--take-profit-scale`: multiply target distances from entry (default `1.0`; `0.5` halves them)
|
|
296
297
|
- `--min-take-profit-pct`
|
|
297
298
|
- `--max-take-profit-pct`
|
|
298
299
|
- `--stop-loss-pct`
|
|
@@ -333,6 +334,8 @@ Key options:
|
|
|
333
334
|
|
|
334
335
|
`auto` derives TP from Bollinger Bands unless `--take-profit-pct` overrides it. If TP is set and SL is omitted, SL defaults to half TP. Executions route through the same bracket-entry path used by `enter`.
|
|
335
336
|
|
|
337
|
+
`--take-profit-scale 0.5` moves each planned target halfway toward entry, for both longs and shorts: `new_target = entry + 0.5 * (target - entry)`. For example, an entry of 100 with targets 110, 120, and 130 becomes 105, 110, and 115. Scaling applies after strategy signal selection and before exchange price rounding, including Fibonacci targets and dry runs. Stops and allocation percentages stay the same. The multiplier must be finite and positive; `1.0` preserves the original distances.
|
|
338
|
+
|
|
336
339
|
Fibonacci pullback strategy:
|
|
337
340
|
|
|
338
341
|
```bash
|
|
@@ -536,6 +539,24 @@ The websocket cache is used as the fast path for market data such as mids, best
|
|
|
536
539
|
|
|
537
540
|
## Logging
|
|
538
541
|
|
|
542
|
+
Each new `auto` session (live or dry-run) creates a unique folder under `logs/sessions/` and prints its location. Restarting an unfinished session with the same account and trading settings automatically resumes that folder and appends to its journal. Use `--session-log-dir PATH` to choose another parent directory. Each folder contains:
|
|
543
|
+
|
|
544
|
+
- `session.json`: the original CLI arguments, parsed CLI settings including defaults, and the arguments passed to the auto trader. Environment variables and exchange clients are not serialized.
|
|
545
|
+
- `closed_trades.csv`: one row per completed position cycle, with UTC start/close times, direction, strategy, requested and filled sizes, size-weighted entry/exit prices, PnL, fees, and links to trade details. Rows are saved as trades complete; cancelled or failed tasks are not counted as closed trades.
|
|
546
|
+
- A JSON file per trade with the signal snapshot and raw exchange fills. Missing fill/PnL data produces blank CSV values with an explanation in `pnl_status`. Live PnL subtracts fill fees but excludes funding; simulated PnL excludes fees, funding, and slippage. Start/close times describe the managed cycle, including entry attempts and closure detection.
|
|
547
|
+
|
|
548
|
+
Atomic `.active-*.json` checkpoints in the parent directory retain session PnL and peaks, completed-trade counts, per-coin streaks and cooldown deadlines, pending trades, and Fibonacci impulses already traded. Live monitors restore saved hidden targets, order IDs, and trailing-stop state; interrupted entry attempts reconcile the existing position without submitting a new entry. Simulations restore entry price, remaining size, partial profits, and the next target. Completed results awaiting accounting are replayed without duplicating CSV rows.
|
|
549
|
+
|
|
550
|
+
Keep the same session directory and trading settings when restarting after an error. Accounts, networks, strategies, settings, and live/dry-run modes have separate checkpoints; simultaneous processes cannot share one checkpoint. `--new-session` deliberately resets recovery state and starts a new journal. A session that exits normally after its configured trade or risk limit is complete, so the next invocation starts a new one. Recovery begins with sessions created by this version; older logs alone cannot restore open-trade state. Local stops and simulated fills only run while the bot is running, and resume from the last saved state using current market prices.
|
|
551
|
+
|
|
552
|
+
Add `--trade-charts` to save an SVG price chart for each completed trade:
|
|
553
|
+
|
|
554
|
+
```bash
|
|
555
|
+
python3 hypertrader.py auto SOL --size 10 --dry-run --trade-charts
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Charts plot candle prices across the trade with size-weighted entry/exit markers. The candle interval adapts to the trade duration, and the latest candle may be incomplete. Charts require no extra plotting packages. The CSV is written before chart generation; the per-trade JSON records chart success, failure, or interruption. `chart_file` is the intended relative path and may not exist if chart generation fails. Charting is off by default and adds no candle requests when disabled.
|
|
559
|
+
|
|
539
560
|
Dry-run console output, runtime messages, risk events, and simulated trades are written to `logs/simulated.log`. Simulation fills at observed market mid prices. Plans with explicit take-profit allocations, including `fib_reversal`, close the allocated portion of the original size at each reached target (40% at TP1, 35% at TP2, and 25% at TP3 for Fibonacci). Each fill logs a `simulated_take_profit_filled` event with its size and PnL. Position records track remaining size, cumulative realized PnL, and unrealized PnL on the remaining position; a stop closes only that remainder. Completed-trade PnL includes all partial exits. Plans without explicit allocations exit the full position at the stop or final target. Fees, funding, slippage, and dynamic stop adjustments are not modeled. Open positions at shutdown are logged with realized partial profits and their remaining unrealized PnL.
|
|
540
561
|
|
|
541
562
|
Runtime logs for live runs are written to [`logs/hypertrader.log`](/media/anon/development/anon/PycharmProjects/hypertrader/logs/hypertrader.log).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypertrader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Async Hyperliquid trading helper built on the async SDK.
|
|
5
5
|
Author: darkerego
|
|
6
6
|
License: MIT
|
|
@@ -324,6 +324,7 @@ Key options:
|
|
|
324
324
|
- `--scalp`
|
|
325
325
|
- `--use-live-candle`
|
|
326
326
|
- `--take-profit-pct`: override Bollinger-derived TP
|
|
327
|
+
- `--take-profit-scale`: multiply target distances from entry (default `1.0`; `0.5` halves them)
|
|
327
328
|
- `--min-take-profit-pct`
|
|
328
329
|
- `--max-take-profit-pct`
|
|
329
330
|
- `--stop-loss-pct`
|
|
@@ -364,6 +365,8 @@ Key options:
|
|
|
364
365
|
|
|
365
366
|
`auto` derives TP from Bollinger Bands unless `--take-profit-pct` overrides it. If TP is set and SL is omitted, SL defaults to half TP. Executions route through the same bracket-entry path used by `enter`.
|
|
366
367
|
|
|
368
|
+
`--take-profit-scale 0.5` moves each planned target halfway toward entry, for both longs and shorts: `new_target = entry + 0.5 * (target - entry)`. For example, an entry of 100 with targets 110, 120, and 130 becomes 105, 110, and 115. Scaling applies after strategy signal selection and before exchange price rounding, including Fibonacci targets and dry runs. Stops and allocation percentages stay the same. The multiplier must be finite and positive; `1.0` preserves the original distances.
|
|
369
|
+
|
|
367
370
|
Fibonacci pullback strategy:
|
|
368
371
|
|
|
369
372
|
```bash
|
|
@@ -567,6 +570,24 @@ The websocket cache is used as the fast path for market data such as mids, best
|
|
|
567
570
|
|
|
568
571
|
## Logging
|
|
569
572
|
|
|
573
|
+
Each new `auto` session (live or dry-run) creates a unique folder under `logs/sessions/` and prints its location. Restarting an unfinished session with the same account and trading settings automatically resumes that folder and appends to its journal. Use `--session-log-dir PATH` to choose another parent directory. Each folder contains:
|
|
574
|
+
|
|
575
|
+
- `session.json`: the original CLI arguments, parsed CLI settings including defaults, and the arguments passed to the auto trader. Environment variables and exchange clients are not serialized.
|
|
576
|
+
- `closed_trades.csv`: one row per completed position cycle, with UTC start/close times, direction, strategy, requested and filled sizes, size-weighted entry/exit prices, PnL, fees, and links to trade details. Rows are saved as trades complete; cancelled or failed tasks are not counted as closed trades.
|
|
577
|
+
- A JSON file per trade with the signal snapshot and raw exchange fills. Missing fill/PnL data produces blank CSV values with an explanation in `pnl_status`. Live PnL subtracts fill fees but excludes funding; simulated PnL excludes fees, funding, and slippage. Start/close times describe the managed cycle, including entry attempts and closure detection.
|
|
578
|
+
|
|
579
|
+
Atomic `.active-*.json` checkpoints in the parent directory retain session PnL and peaks, completed-trade counts, per-coin streaks and cooldown deadlines, pending trades, and Fibonacci impulses already traded. Live monitors restore saved hidden targets, order IDs, and trailing-stop state; interrupted entry attempts reconcile the existing position without submitting a new entry. Simulations restore entry price, remaining size, partial profits, and the next target. Completed results awaiting accounting are replayed without duplicating CSV rows.
|
|
580
|
+
|
|
581
|
+
Keep the same session directory and trading settings when restarting after an error. Accounts, networks, strategies, settings, and live/dry-run modes have separate checkpoints; simultaneous processes cannot share one checkpoint. `--new-session` deliberately resets recovery state and starts a new journal. A session that exits normally after its configured trade or risk limit is complete, so the next invocation starts a new one. Recovery begins with sessions created by this version; older logs alone cannot restore open-trade state. Local stops and simulated fills only run while the bot is running, and resume from the last saved state using current market prices.
|
|
582
|
+
|
|
583
|
+
Add `--trade-charts` to save an SVG price chart for each completed trade:
|
|
584
|
+
|
|
585
|
+
```bash
|
|
586
|
+
python3 hypertrader.py auto SOL --size 10 --dry-run --trade-charts
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Charts plot candle prices across the trade with size-weighted entry/exit markers. The candle interval adapts to the trade duration, and the latest candle may be incomplete. Charts require no extra plotting packages. The CSV is written before chart generation; the per-trade JSON records chart success, failure, or interruption. `chart_file` is the intended relative path and may not exist if chart generation fails. Charting is off by default and adds no candle requests when disabled.
|
|
590
|
+
|
|
570
591
|
Dry-run console output, runtime messages, risk events, and simulated trades are written to `logs/simulated.log`. Simulation fills at observed market mid prices. Plans with explicit take-profit allocations, including `fib_reversal`, close the allocated portion of the original size at each reached target (40% at TP1, 35% at TP2, and 25% at TP3 for Fibonacci). Each fill logs a `simulated_take_profit_filled` event with its size and PnL. Position records track remaining size, cumulative realized PnL, and unrealized PnL on the remaining position; a stop closes only that remainder. Completed-trade PnL includes all partial exits. Plans without explicit allocations exit the full position at the stop or final target. Fees, funding, slippage, and dynamic stop adjustments are not modeled. Open positions at shutdown are logged with realized partial profits and their remaining unrealized PnL.
|
|
571
592
|
|
|
572
593
|
Runtime logs for live runs are written to [`logs/hypertrader.log`](/media/anon/development/anon/PycharmProjects/hypertrader/logs/hypertrader.log).
|
|
@@ -32,12 +32,16 @@ tests/test_market_ids.py
|
|
|
32
32
|
tests/test_orderflow_metrics.py
|
|
33
33
|
tests/test_orderflow_pullback_lifecycle.py
|
|
34
34
|
tests/test_reversal_strategy.py
|
|
35
|
+
tests/test_session_recovery.py
|
|
35
36
|
tests/test_simulated_logging.py
|
|
37
|
+
tests/test_trade_journal.py
|
|
36
38
|
tests/test_trailing_stop.py
|
|
37
39
|
utils/constants.py
|
|
38
40
|
utils/helpers.py
|
|
39
41
|
utils/retry.py
|
|
42
|
+
utils/session_state.py
|
|
40
43
|
utils/simulated_logging.py
|
|
41
44
|
utils/style.py
|
|
42
45
|
utils/timer.py
|
|
46
|
+
utils/trade_journal.py
|
|
43
47
|
utils/worker.py
|
|
@@ -300,6 +300,8 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|
|
300
300
|
help="Use the latest candle even if it may still be forming. Default uses last closed candle.")
|
|
301
301
|
auto_parser.add_argument("--take-profit-pct", type=float, default=None,
|
|
302
302
|
help="Override Bollinger-derived TP fraction, e.g. 0.01 = 1pct.")
|
|
303
|
+
auto_parser.add_argument("--take-profit-scale", type=float, default=1.0,
|
|
304
|
+
help="Multiply each target's distance from entry; 0.5 halves it. Default: 1.")
|
|
303
305
|
auto_parser.add_argument("--min-take-profit-pct", type=float, default=0.002,
|
|
304
306
|
help="Minimum Bollinger-derived TP fraction. Default: 0.002.")
|
|
305
307
|
auto_parser.add_argument("--max-take-profit-pct", type=float, default=0.03,
|
|
@@ -337,6 +339,12 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|
|
337
339
|
auto_parser.add_argument("--keep-existing-tpsl", action="store_true",
|
|
338
340
|
help="Do not cancel existing reduce-only TP/SL orders before entry.")
|
|
339
341
|
auto_parser.add_argument("--dry-run", action="store_true", help="Simulate positions using 10,000 cUSDC assumed collateral and log to logs/simulated.log.")
|
|
342
|
+
auto_parser.add_argument("--session-log-dir", default="logs/sessions",
|
|
343
|
+
help="Parent directory for per-run settings and closed-trade CSV files. Default: logs/sessions.")
|
|
344
|
+
auto_parser.add_argument("--new-session", action="store_true",
|
|
345
|
+
help="Start fresh instead of resuming the matching unfinished auto session.")
|
|
346
|
+
auto_parser.add_argument("--trade-charts", action="store_true",
|
|
347
|
+
help="Save an SVG price chart when each auto position closes.")
|
|
340
348
|
auto_parser.add_argument("--max-trades", type=int, default=0,
|
|
341
349
|
help="Maximum completed auto trades before exit. 0 means unlimited. Default: 0.")
|
|
342
350
|
auto_parser.add_argument("--cooldown-after-trade", type=float, default=60.0,
|
|
@@ -877,6 +885,12 @@ async def async_main(argv: Optional[List[str]] = None) -> None:
|
|
|
877
885
|
use_last_closed_candle=(not args.use_live_candle),
|
|
878
886
|
use_sar_stop_on_shortest_interval=args.auto_sar_stop_on_shortest_interval,
|
|
879
887
|
dry_run=args.dry_run,
|
|
888
|
+
session_log_dir=args.session_log_dir,
|
|
889
|
+
new_session=args.new_session,
|
|
890
|
+
take_profit_scale=args.take_profit_scale,
|
|
891
|
+
trade_charts=args.trade_charts,
|
|
892
|
+
session_argv=list(sys.argv[1:] if argv is None else argv),
|
|
893
|
+
session_cli_settings=vars(args).copy(),
|
|
880
894
|
max_trades=args.max_trades,
|
|
881
895
|
cooldown_after_trade=args.cooldown_after_trade,
|
|
882
896
|
loop_after_trade=args.loop_after_trade,
|
|
@@ -6,7 +6,7 @@ import math
|
|
|
6
6
|
import os
|
|
7
7
|
import sys
|
|
8
8
|
import time
|
|
9
|
-
from dataclasses import dataclass, field
|
|
9
|
+
from dataclasses import asdict, dataclass, field
|
|
10
10
|
from types import SimpleNamespace
|
|
11
11
|
from typing import Optional, List, Dict, Any, Tuple
|
|
12
12
|
|
|
@@ -14,11 +14,13 @@ from hyperliquid.exchange import Exchange
|
|
|
14
14
|
from hyperliquid.info import Info
|
|
15
15
|
|
|
16
16
|
from modes.position_management import run_bracket_entry
|
|
17
|
-
from strategies.base import AutoStrategy, StrategyContext, StrategySignal
|
|
17
|
+
from strategies.base import AutoStrategy, StrategyContext, StrategySignal, StrategyExecutionPlan, scale_take_profit_signal
|
|
18
18
|
from strategies.registry import create_strategy
|
|
19
19
|
from strategies.reversal import normalize_signal_prices
|
|
20
20
|
from utils.constants import AUTO_TRADES_LOG_FILE, SIMULATED_LOG_FILE, INTERVAL_TO_MS, PRICE_EPS, cp
|
|
21
21
|
from utils.retry import retry_api_read
|
|
22
|
+
from utils.trade_journal import TradeJournal
|
|
23
|
+
from utils.session_state import SessionState
|
|
22
24
|
from utils.helpers import parse_interval_list, init_clients, _try_float, parse_fractional_pct, \
|
|
23
25
|
extract_account_balance_from_user_state, round_size_for_hyperliquid, get_user_state_with_retry, get_all_mids, \
|
|
24
26
|
fetch_recent_candles, compute_position_unrealized_pnl, close_clients, compute_default_stop_loss_pct, \
|
|
@@ -248,7 +250,7 @@ class AutoManagedTradeResult:
|
|
|
248
250
|
simulated_pnl: Optional[float] = None
|
|
249
251
|
|
|
250
252
|
|
|
251
|
-
async def run_simulated_trade(info, plan, auto_logger, poll_interval):
|
|
253
|
+
async def run_simulated_trade(info, plan, auto_logger, poll_interval, *, state=None, checkpoint=None):
|
|
252
254
|
"""Paper fill at observed mids, honoring explicit TP allocations without orders.
|
|
253
255
|
|
|
254
256
|
PnL is gross quote-currency PnL: fees, funding, and slippage are not modeled.
|
|
@@ -271,12 +273,21 @@ async def run_simulated_trade(info, plan, auto_logger, poll_interval):
|
|
|
271
273
|
targets = list(enumerate(zip(plan.take_profit_prices, allocations), start=1))
|
|
272
274
|
else:
|
|
273
275
|
targets = [(len(plan.take_profit_prices), (plan.take_profit_prices[-1], 100.0))]
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
276
|
+
state = state if state is not None else {}
|
|
277
|
+
if "result" in state:
|
|
278
|
+
return AutoManagedTradeResult(**state["result"])
|
|
279
|
+
started_ms = state.get("started_ms", now_ms())
|
|
280
|
+
entry = state.get("entry")
|
|
281
|
+
last_price = state.get("last_price")
|
|
282
|
+
remaining_size = state.get("remaining_size", plan.size)
|
|
283
|
+
realized_pnl = state.get("realized_pnl", 0.0)
|
|
284
|
+
next_target = state.get("next_target", 0)
|
|
285
|
+
|
|
286
|
+
def save():
|
|
287
|
+
state.update(started_ms=started_ms, entry=entry, last_price=last_price,
|
|
288
|
+
remaining_size=remaining_size, realized_pnl=realized_pnl, next_target=next_target)
|
|
289
|
+
if checkpoint is not None:
|
|
290
|
+
checkpoint()
|
|
280
291
|
|
|
281
292
|
def record(event, **payload):
|
|
282
293
|
log_auto_trade_event(
|
|
@@ -324,14 +335,23 @@ async def run_simulated_trade(info, plan, auto_logger, poll_interval):
|
|
|
324
335
|
closed_ms = now_ms()
|
|
325
336
|
record("simulated_position_exited", exit_price=price,
|
|
326
337
|
reason="stop_loss" if stop_hit else "take_profit", closed_ms=closed_ms)
|
|
327
|
-
|
|
338
|
+
result = AutoManagedTradeResult(
|
|
328
339
|
coin=plan.coin, direction=plan.direction, size=plan.size,
|
|
329
340
|
take_profit_pct=abs(plan.take_profit_prices[-1] - entry) / entry,
|
|
330
341
|
stop_loss_pct=abs(plan.stop_price - entry) / entry,
|
|
331
342
|
started_ms=started_ms, closed_ms=closed_ms, simulated_pnl=realized_pnl,
|
|
332
343
|
log_context={"simulated": True, "entry_price": entry,
|
|
333
|
-
"exit_price": price, "realized_pnl": realized_pnl
|
|
344
|
+
"exit_price": price, "realized_pnl": realized_pnl,
|
|
345
|
+
"average_exit_price": entry + direction * realized_pnl / plan.size,
|
|
346
|
+
"planned_entry_price": plan.expected_entry,
|
|
347
|
+
"stop_price": plan.stop_price,
|
|
348
|
+
"take_profit_prices": plan.take_profit_prices,
|
|
349
|
+
"exit_reason": "stop_loss" if stop_hit else "take_profit"},
|
|
334
350
|
)
|
|
351
|
+
state["result"] = asdict(result)
|
|
352
|
+
save()
|
|
353
|
+
return result
|
|
354
|
+
save()
|
|
335
355
|
record("simulated_position_mark", mark_price=price,
|
|
336
356
|
unrealized_pnl=direction * (price - entry) * remaining_size)
|
|
337
357
|
await asyncio.sleep(poll_interval)
|
|
@@ -763,7 +783,8 @@ async def fetch_trade_cycle_net_pnl(
|
|
|
763
783
|
if str(fill.get("coin", "")).upper() != coin_upper:
|
|
764
784
|
continue
|
|
765
785
|
fill_time_ms = int(_try_float(fill.get("time")) or _try_float(fill.get("timestamp")) or 0.0)
|
|
766
|
-
|
|
786
|
+
# The request has a buffer, but adjacent trade cycles must not share fills.
|
|
787
|
+
if fill_time_ms < start_time_ms or fill_time_ms > end_time_ms:
|
|
767
788
|
continue
|
|
768
789
|
closed_pnl = _try_float(fill.get("closedPnl"))
|
|
769
790
|
if closed_pnl is None:
|
|
@@ -2277,10 +2298,22 @@ async def run_auto_trader(
|
|
|
2277
2298
|
account_address: Optional[str] = None,
|
|
2278
2299
|
info: Optional[Info] = None,
|
|
2279
2300
|
exchange: Optional[Exchange] = None,
|
|
2301
|
+
session_log_dir: str = "logs/sessions",
|
|
2302
|
+
trade_charts: bool = False,
|
|
2303
|
+
session_argv: Optional[List[str]] = None,
|
|
2304
|
+
session_cli_settings: Optional[Dict[str, Any]] = None,
|
|
2305
|
+
take_profit_scale: float = 1.0,
|
|
2306
|
+
new_session: bool = False,
|
|
2280
2307
|
) -> None:
|
|
2281
2308
|
"""Automatically scan TA-Lib signals, enter positions, and hand off to bracket management."""
|
|
2282
2309
|
|
|
2310
|
+
session_settings = {key: value for key, value in locals().items()
|
|
2311
|
+
if key not in {"info", "exchange", "account_address", "session_argv", "session_cli_settings"}}
|
|
2312
|
+
if session_cli_settings is not None:
|
|
2313
|
+
session_settings["cli_settings"] = dict(session_cli_settings)
|
|
2283
2314
|
require_talib_available()
|
|
2315
|
+
if not math.isfinite(take_profit_scale) or take_profit_scale <= 0:
|
|
2316
|
+
raise RuntimeError("--take-profit-scale must be finite and > 0.")
|
|
2284
2317
|
coin = normalize_hyperliquid_market_id(coin) if coin is not None else None
|
|
2285
2318
|
intervals = parse_interval_list(intervals_value)
|
|
2286
2319
|
if (size is None) == (size_pct is None):
|
|
@@ -2493,13 +2526,34 @@ async def run_auto_trader(
|
|
|
2493
2526
|
try:
|
|
2494
2527
|
if owns_clients:
|
|
2495
2528
|
account_address, info, exchange = await init_clients(use_testnet, use_websocket=use_websocket)
|
|
2529
|
+
session_store = SessionState(session_log_dir, session_settings, account_address, new_session=new_session)
|
|
2530
|
+
saved = session_store.data
|
|
2531
|
+
if isinstance(strategy, AutoStrategy):
|
|
2532
|
+
strategy.restore_session_state(saved.get("strategy_state", {}))
|
|
2533
|
+
journal = TradeJournal(session_log_dir, session_settings, argv=session_argv, charts=trade_charts,
|
|
2534
|
+
resume_directory=saved.get("journal_directory"))
|
|
2535
|
+
print(f"[AUTO] {'Resumed' if saved else 'New'} session journal: {journal.directory}")
|
|
2496
2536
|
await strategy.start(info=info, account_address=account_address)
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
coin_sessions:
|
|
2537
|
+
risk_session = AutoRiskSessionState(**saved.get("risk_session", {"started_ms": now_ms()}))
|
|
2538
|
+
metrics_start_time_ms = risk_session.started_ms
|
|
2539
|
+
coin_sessions = {key: CoinTradeSessionState(**value) for key, value in saved.get("coin_sessions", {}).items()}
|
|
2540
|
+
completed_trades = saved.get("completed_trades", 0)
|
|
2541
|
+
pending_trades = saved.get("pending_trades", {})
|
|
2500
2542
|
active_trade_tasks: Dict[str, asyncio.Task[AutoManagedTradeResult]] = {}
|
|
2501
|
-
|
|
2502
|
-
|
|
2543
|
+
shutting_down = False
|
|
2544
|
+
stop_requested = risk_session.stopped or (0 < max_trades <= completed_trades) or (completed_trades > 0 and not loop_after_trade)
|
|
2545
|
+
stop_reason = risk_session.stop_reason or "saved session trade limit reached"
|
|
2546
|
+
|
|
2547
|
+
def checkpoint():
|
|
2548
|
+
session_store.data.update(
|
|
2549
|
+
journal_directory=str(journal.directory), risk_session=asdict(risk_session),
|
|
2550
|
+
coin_sessions={key: asdict(value) for key, value in coin_sessions.items()},
|
|
2551
|
+
completed_trades=completed_trades, pending_trades=pending_trades,
|
|
2552
|
+
strategy_state=strategy.session_state() if isinstance(strategy, AutoStrategy) else {},
|
|
2553
|
+
)
|
|
2554
|
+
session_store.save()
|
|
2555
|
+
|
|
2556
|
+
checkpoint()
|
|
2503
2557
|
print("============================================================")
|
|
2504
2558
|
print(" Hyperliquid Async Auto Trader")
|
|
2505
2559
|
print("============================================================")
|
|
@@ -2559,8 +2613,13 @@ async def run_auto_trader(
|
|
|
2559
2613
|
managed_market_fallback: Optional[bool] = None,
|
|
2560
2614
|
managed_tp_prices: Optional[tuple[float, ...]] = None,
|
|
2561
2615
|
managed_tp_allocations: Optional[tuple[float, ...]] = None,
|
|
2616
|
+
recovery_state=None,
|
|
2562
2617
|
) -> AutoManagedTradeResult:
|
|
2563
|
-
|
|
2618
|
+
recovery_state = recovery_state if recovery_state is not None else pending_trades[managed_coin]["state"]
|
|
2619
|
+
if "result" in recovery_state:
|
|
2620
|
+
return AutoManagedTradeResult(**recovery_state["result"])
|
|
2621
|
+
trade_cycle_started_ms = recovery_state.setdefault("started_ms", now_ms())
|
|
2622
|
+
checkpoint()
|
|
2564
2623
|
await run_bracket_entry(
|
|
2565
2624
|
coin=managed_coin,
|
|
2566
2625
|
direction=managed_direction,
|
|
@@ -2600,8 +2659,10 @@ async def run_auto_trader(
|
|
|
2600
2659
|
account_address=account_address,
|
|
2601
2660
|
info=info,
|
|
2602
2661
|
exchange=exchange,
|
|
2662
|
+
recovery_state=recovery_state,
|
|
2663
|
+
checkpoint=checkpoint,
|
|
2603
2664
|
)
|
|
2604
|
-
|
|
2665
|
+
result = AutoManagedTradeResult(
|
|
2605
2666
|
coin=managed_coin,
|
|
2606
2667
|
direction=managed_direction,
|
|
2607
2668
|
size=managed_size,
|
|
@@ -2611,23 +2672,52 @@ async def run_auto_trader(
|
|
|
2611
2672
|
closed_ms=now_ms(),
|
|
2612
2673
|
log_context=dict(managed_log_context or {}),
|
|
2613
2674
|
)
|
|
2675
|
+
recovery_state["result"] = asdict(result)
|
|
2676
|
+
checkpoint()
|
|
2677
|
+
return result
|
|
2678
|
+
|
|
2679
|
+
def start_live_trade(**kwargs):
|
|
2680
|
+
trade_coin = kwargs["managed_coin"]
|
|
2681
|
+
# Store JSON data before the task can submit any orders.
|
|
2682
|
+
kwargs = dict(kwargs)
|
|
2683
|
+
snapshot = kwargs.pop("managed_shortest_snapshot")
|
|
2684
|
+
pending_trades[trade_coin] = {"kwargs": kwargs, "shortest_interval": getattr(snapshot, "interval", None), "state": {}}
|
|
2685
|
+
checkpoint()
|
|
2686
|
+
active_trade_tasks[trade_coin] = asyncio.create_task(
|
|
2687
|
+
_run_managed_auto_trade(**kwargs, managed_shortest_snapshot=snapshot))
|
|
2688
|
+
|
|
2689
|
+
for saved_coin, pending in pending_trades.items():
|
|
2690
|
+
if dry_run:
|
|
2691
|
+
active_trade_tasks[saved_coin] = asyncio.create_task(run_simulated_trade(
|
|
2692
|
+
info, StrategyExecutionPlan(**pending["plan"]), auto_trades_logger, poll_interval,
|
|
2693
|
+
state=pending["state"], checkpoint=checkpoint))
|
|
2694
|
+
else:
|
|
2695
|
+
pending["state"]["resuming"] = True
|
|
2696
|
+
active_trade_tasks[saved_coin] = asyncio.create_task(_run_managed_auto_trade(
|
|
2697
|
+
**pending["kwargs"], recovery_state=pending["state"],
|
|
2698
|
+
managed_shortest_snapshot=(SimpleNamespace(interval=pending["shortest_interval"])
|
|
2699
|
+
if pending["shortest_interval"] else None)))
|
|
2614
2700
|
|
|
2615
2701
|
async def _drain_completed_trade_tasks() -> None:
|
|
2616
2702
|
nonlocal completed_trades, stop_requested, stop_reason
|
|
2617
2703
|
finished_coins = [task_coin for task_coin, task in active_trade_tasks.items() if task.done()]
|
|
2618
2704
|
for finished_coin in finished_coins:
|
|
2619
|
-
task = active_trade_tasks
|
|
2705
|
+
task = active_trade_tasks[finished_coin]
|
|
2620
2706
|
try:
|
|
2621
2707
|
trade_result = task.result()
|
|
2622
2708
|
except asyncio.CancelledError:
|
|
2709
|
+
active_trade_tasks.pop(finished_coin)
|
|
2623
2710
|
print(f"[AUTO] Managed trade task for {finished_coin} was cancelled.")
|
|
2624
2711
|
log_auto_trade_event(
|
|
2625
2712
|
auto_trades_logger,
|
|
2626
2713
|
"auto_trade_task_cancelled",
|
|
2627
2714
|
coin=finished_coin,
|
|
2628
2715
|
)
|
|
2716
|
+
if not shutting_down:
|
|
2717
|
+
raise
|
|
2629
2718
|
continue
|
|
2630
2719
|
except Exception as exc:
|
|
2720
|
+
active_trade_tasks.pop(finished_coin)
|
|
2631
2721
|
print(f"[AUTO-WARN] Managed trade task failed for {finished_coin}: {exc}")
|
|
2632
2722
|
logger.exception("[AUTO] Managed trade task failed for %s", finished_coin)
|
|
2633
2723
|
log_auto_trade_event(
|
|
@@ -2636,9 +2726,10 @@ async def run_auto_trader(
|
|
|
2636
2726
|
coin=finished_coin,
|
|
2637
2727
|
error=str(exc),
|
|
2638
2728
|
)
|
|
2639
|
-
|
|
2729
|
+
# Preserve the checkpoint and stop; a restart must recover this trade
|
|
2730
|
+
# before scanning can place another entry for the same coin.
|
|
2731
|
+
raise RuntimeError(f"Managed trade failed for {finished_coin}; session saved for recovery") from exc
|
|
2640
2732
|
|
|
2641
|
-
completed_trades += 1
|
|
2642
2733
|
log_auto_trade_event(
|
|
2643
2734
|
auto_trades_logger,
|
|
2644
2735
|
"auto_trade_complete",
|
|
@@ -2647,7 +2738,7 @@ async def run_auto_trader(
|
|
|
2647
2738
|
size=trade_result.size,
|
|
2648
2739
|
take_profit_pct=trade_result.take_profit_pct,
|
|
2649
2740
|
stop_loss_pct=trade_result.stop_loss_pct,
|
|
2650
|
-
completed_trades=completed_trades,
|
|
2741
|
+
completed_trades=completed_trades + 1,
|
|
2651
2742
|
started_ms=trade_result.started_ms,
|
|
2652
2743
|
closed_ms=trade_result.closed_ms,
|
|
2653
2744
|
signal_snapshot=trade_result.log_context,
|
|
@@ -2666,6 +2757,9 @@ async def run_auto_trader(
|
|
|
2666
2757
|
start_time_ms=trade_result.started_ms,
|
|
2667
2758
|
end_time_ms=trade_result.closed_ms,
|
|
2668
2759
|
)
|
|
2760
|
+
await journal.record(trade_result, cycle_pnl, cycle_fills, pnl_reason, info, deduplicate=True)
|
|
2761
|
+
active_trade_tasks.pop(finished_coin)
|
|
2762
|
+
completed_trades += 1
|
|
2669
2763
|
if pnl_reason:
|
|
2670
2764
|
print(
|
|
2671
2765
|
f"[AUTO-WARN] {trade_result.coin} post-trade PnL unavailable; "
|
|
@@ -2775,6 +2869,8 @@ async def run_auto_trader(
|
|
|
2775
2869
|
elif not loop_after_trade:
|
|
2776
2870
|
stop_requested = True
|
|
2777
2871
|
stop_reason = f"completed {completed_trades} trade(s) and auto looping is disabled"
|
|
2872
|
+
pending_trades.pop(finished_coin, None)
|
|
2873
|
+
checkpoint()
|
|
2778
2874
|
|
|
2779
2875
|
async def _sleep_until_next_scan_batch() -> None:
|
|
2780
2876
|
if startup_backfill_state.enabled and startup_backfill_state.pause_between_batches and not startup_backfill_state.complete:
|
|
@@ -2789,6 +2885,7 @@ async def run_auto_trader(
|
|
|
2789
2885
|
while True:
|
|
2790
2886
|
_iter +=1
|
|
2791
2887
|
await _drain_completed_trade_tasks()
|
|
2888
|
+
checkpoint()
|
|
2792
2889
|
if stop_requested:
|
|
2793
2890
|
if active_trade_tasks:
|
|
2794
2891
|
print(
|
|
@@ -2804,6 +2901,8 @@ async def run_auto_trader(
|
|
|
2804
2901
|
await asyncio.sleep(min(scan_interval, 1.0))
|
|
2805
2902
|
continue
|
|
2806
2903
|
print(f"[AUTO] {stop_reason}; exiting auto mode.")
|
|
2904
|
+
session_store.data["complete"] = True
|
|
2905
|
+
checkpoint()
|
|
2807
2906
|
return
|
|
2808
2907
|
|
|
2809
2908
|
if coin is not None:
|
|
@@ -3046,12 +3145,16 @@ async def run_auto_trader(
|
|
|
3046
3145
|
|
|
3047
3146
|
for launch_coin, launch_signal, launch_context, launch_size in launch_specs:
|
|
3048
3147
|
try:
|
|
3148
|
+
launch_signal = scale_take_profit_signal(launch_signal, take_profit_scale)
|
|
3049
3149
|
if launch_signal.strategy in ("reversal", "fib_reversal"):
|
|
3050
3150
|
launch_signal = await normalize_signal_prices(info, launch_signal)
|
|
3051
3151
|
plan = strategy.build_execution_plan(launch_context, launch_signal, launch_size)
|
|
3052
3152
|
if dry_run:
|
|
3153
|
+
pending_trades[launch_coin] = {"plan": asdict(plan), "state": {}}
|
|
3154
|
+
checkpoint()
|
|
3053
3155
|
active_trade_tasks[launch_coin] = asyncio.create_task(
|
|
3054
|
-
run_simulated_trade(info, plan, auto_trades_logger, poll_interval
|
|
3156
|
+
run_simulated_trade(info, plan, auto_trades_logger, poll_interval,
|
|
3157
|
+
state=pending_trades[launch_coin]["state"], checkpoint=checkpoint)
|
|
3055
3158
|
)
|
|
3056
3159
|
continue
|
|
3057
3160
|
if plan.kind == "bracket_entry":
|
|
@@ -3082,8 +3185,7 @@ async def run_auto_trader(
|
|
|
3082
3185
|
size=launch_size,
|
|
3083
3186
|
execution_plan=log_context,
|
|
3084
3187
|
)
|
|
3085
|
-
|
|
3086
|
-
_run_managed_auto_trade(
|
|
3188
|
+
start_live_trade(
|
|
3087
3189
|
managed_coin=launch_coin,
|
|
3088
3190
|
managed_direction=plan.direction,
|
|
3089
3191
|
managed_size=launch_size,
|
|
@@ -3099,7 +3201,6 @@ async def run_auto_trader(
|
|
|
3099
3201
|
managed_entry_retries=plan_entry_retries,
|
|
3100
3202
|
managed_market_fallback=plan_market_fallback,
|
|
3101
3203
|
)
|
|
3102
|
-
)
|
|
3103
3204
|
else:
|
|
3104
3205
|
if len(plan.take_profit_prices) < 3:
|
|
3105
3206
|
raise RuntimeError(
|
|
@@ -3135,8 +3236,7 @@ async def run_auto_trader(
|
|
|
3135
3236
|
size=launch_size,
|
|
3136
3237
|
execution_plan=log_context,
|
|
3137
3238
|
)
|
|
3138
|
-
|
|
3139
|
-
_run_managed_auto_trade(
|
|
3239
|
+
start_live_trade(
|
|
3140
3240
|
managed_coin=launch_coin,
|
|
3141
3241
|
managed_direction=plan.direction,
|
|
3142
3242
|
managed_size=launch_size,
|
|
@@ -3148,8 +3248,9 @@ async def run_auto_trader(
|
|
|
3148
3248
|
managed_shortest_snapshot=None,
|
|
3149
3249
|
managed_log_context=log_context,
|
|
3150
3250
|
)
|
|
3151
|
-
)
|
|
3152
3251
|
except Exception as exc:
|
|
3252
|
+
if isinstance(exc, OSError):
|
|
3253
|
+
raise
|
|
3153
3254
|
print(f"[AUTO-WARN] Failed to launch {launch_signal.strategy} signal for {launch_coin}: {exc}")
|
|
3154
3255
|
logger.exception("[AUTO] Failed to launch %s signal for %s", launch_signal.strategy, launch_coin)
|
|
3155
3256
|
log_auto_trade_event(
|
|
@@ -3176,16 +3277,28 @@ async def run_auto_trader(
|
|
|
3176
3277
|
)
|
|
3177
3278
|
continue
|
|
3178
3279
|
|
|
3179
|
-
metrics_start_time_ms = int(time.time() * 1000)
|
|
3180
3280
|
await _sleep_until_next_scan_batch()
|
|
3181
3281
|
except KeyboardInterrupt:
|
|
3182
3282
|
print("\n[!] Caught Ctrl+C, stopping auto trader.")
|
|
3183
3283
|
finally:
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3284
|
+
shutting_down = True
|
|
3285
|
+
tasks = list(locals().get("active_trade_tasks", {}).values())
|
|
3286
|
+
for task in tasks:
|
|
3287
|
+
task.cancel()
|
|
3288
|
+
await asyncio.gather(*tasks, return_exceptions=True)
|
|
3289
|
+
try:
|
|
3290
|
+
# Completed tasks can otherwise be lost when shutdown interrupts a scan sleep.
|
|
3291
|
+
if "_drain_completed_trade_tasks" in locals():
|
|
3292
|
+
await _drain_completed_trade_tasks()
|
|
3293
|
+
finally:
|
|
3294
|
+
try:
|
|
3295
|
+
if "checkpoint" in locals():
|
|
3296
|
+
checkpoint()
|
|
3297
|
+
finally:
|
|
3298
|
+
if "session_store" in locals():
|
|
3299
|
+
session_store.close()
|
|
3300
|
+
try:
|
|
3301
|
+
await strategy.shutdown()
|
|
3302
|
+
finally:
|
|
3303
|
+
if owns_clients:
|
|
3304
|
+
await close_clients(info, exchange)
|