hypertrader 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hypertrader-0.1.0.dist-info/METADATA +416 -0
- hypertrader-0.1.0.dist-info/RECORD +16 -0
- hypertrader-0.1.0.dist-info/WHEEL +5 -0
- hypertrader-0.1.0.dist-info/entry_points.txt +2 -0
- hypertrader-0.1.0.dist-info/top_level.txt +3 -0
- hypertrader.py +741 -0
- modes/auto_trader.py +2141 -0
- modes/market_maker.py +397 -0
- modes/position_management.py +1911 -0
- modes/position_watcher.py +411 -0
- modes/trailing_stop.py +277 -0
- utils/constants.py +31 -0
- utils/helpers.py +988 -0
- utils/style.py +173 -0
- utils/timer.py +14 -0
- utils/worker.py +233 -0
hypertrader.py
ADDED
|
@@ -0,0 +1,741 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Async Hyperliquid trading helper.
|
|
3
|
+
|
|
4
|
+
Modes:
|
|
5
|
+
trailing Manage trailing stops for currently open perp positions.
|
|
6
|
+
market_maker Event-driven maker ladder that rebalances on drift.
|
|
7
|
+
enter Smart limit-entry bracket bot with stop-loss and take-profit logic.
|
|
8
|
+
watch Watch for newly opened positions and automatically attach TP/SL logic.
|
|
9
|
+
auto TA-Lib multi-timeframe signal scanner with automatic entry and TP/SL.
|
|
10
|
+
|
|
11
|
+
Environment:
|
|
12
|
+
HYPERLIQUID_SECRET_KEY Private key for the trading wallet or API wallet.
|
|
13
|
+
HYPERLIQUID_ACCOUNT_ADDRESS Main wallet address / account address.
|
|
14
|
+
|
|
15
|
+
This version is written for darkerego/hyperliquid-python-sdk-async.
|
|
16
|
+
Websocket market data is enabled by default; pass --no-websocket to force HTTP polling.
|
|
17
|
+
"""
|
|
18
|
+
import argparse
|
|
19
|
+
import asyncio
|
|
20
|
+
import decimal
|
|
21
|
+
import logging
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
from typing import Any, Dict, List, Optional
|
|
25
|
+
|
|
26
|
+
import uvloop
|
|
27
|
+
from hyperliquid.info import Info
|
|
28
|
+
|
|
29
|
+
from modes.auto_trader import run_auto_trader
|
|
30
|
+
from modes.market_maker import run_market_maker
|
|
31
|
+
from modes.position_management import run_bracket_entry
|
|
32
|
+
from modes.position_watcher import run_position_watcher
|
|
33
|
+
from modes.trailing_stop import trailing_stop_for_all_positions
|
|
34
|
+
from utils.constants import DEFAULT_LOG_FILE, INTERVAL_TO_MS
|
|
35
|
+
from utils.helpers import parse_fractional_pct
|
|
36
|
+
from utils.style import install_pretty_stdout
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
import numpy as np
|
|
40
|
+
import talib
|
|
41
|
+
except Exception: # TA-Lib is only required for the auto command.
|
|
42
|
+
np = None # type: ignore[assignment]
|
|
43
|
+
talib = None # type: ignore[assignment]
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
os.mkdir("logs")
|
|
47
|
+
except FileExistsError:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
decimal.getcontext().prec = 4
|
|
51
|
+
install_pretty_stdout()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def configure_runtime_logging(log_path: Optional[str] = None) -> str:
|
|
55
|
+
"""Configure file-only runtime logging for exceptions and warnings."""
|
|
56
|
+
resolved_path = os.path.abspath(log_path or DEFAULT_LOG_FILE)
|
|
57
|
+
os.makedirs(os.path.dirname(resolved_path), exist_ok=True)
|
|
58
|
+
|
|
59
|
+
root_logger = logging.getLogger()
|
|
60
|
+
root_logger.handlers.clear()
|
|
61
|
+
root_logger.setLevel(logging.INFO)
|
|
62
|
+
|
|
63
|
+
file_handler = logging.FileHandler(resolved_path, encoding="utf-8")
|
|
64
|
+
file_handler.setLevel(logging.INFO)
|
|
65
|
+
file_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s"))
|
|
66
|
+
root_logger.addHandler(file_handler)
|
|
67
|
+
|
|
68
|
+
runtime_logger = logging.getLogger("hypertrader")
|
|
69
|
+
runtime_logger.setLevel(logging.INFO)
|
|
70
|
+
runtime_logger.propagate = True
|
|
71
|
+
logging.captureWarnings(True)
|
|
72
|
+
|
|
73
|
+
def _log_uncaught_exception(exc_type: type[BaseException], exc_value: BaseException, exc_traceback: Any) -> None:
|
|
74
|
+
if issubclass(exc_type, KeyboardInterrupt):
|
|
75
|
+
runtime_logger.info("[INTERRUPTED] KeyboardInterrupt received.")
|
|
76
|
+
return
|
|
77
|
+
runtime_logger.exception(
|
|
78
|
+
"[UNCAUGHT] Top-level exception",
|
|
79
|
+
exc_info=(exc_type, exc_value, exc_traceback),
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
sys.excepthook = _log_uncaught_exception
|
|
83
|
+
return resolved_path
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def install_asyncio_exception_logging(loop: asyncio.AbstractEventLoop) -> None:
|
|
87
|
+
"""Log unhandled asyncio task/transport exceptions with traceback."""
|
|
88
|
+
|
|
89
|
+
def _handle_asyncio_exception(loop: asyncio.AbstractEventLoop, context: Dict[str, Any]) -> None:
|
|
90
|
+
logger = logging.getLogger("hypertrader")
|
|
91
|
+
exc = context.get("exception")
|
|
92
|
+
message = context.get("message", "Unhandled asyncio exception")
|
|
93
|
+
if exc is not None:
|
|
94
|
+
logger.exception("[ASYNCIO] %s", message, exc_info=(type(exc), exc, exc.__traceback__))
|
|
95
|
+
return
|
|
96
|
+
logger.error("[ASYNCIO] %s | context=%r", message, context)
|
|
97
|
+
|
|
98
|
+
loop.set_exception_handler(_handle_asyncio_exception)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ---------------------------------------------------------------------------
|
|
102
|
+
# Async SDK compatibility
|
|
103
|
+
# ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
async def _compat_name_to_asset(self: Info, name: str) -> int:
|
|
106
|
+
"""Compatibility shim for async SDK revisions missing Info.name_to_asset."""
|
|
107
|
+
if hasattr(self, "_ensure_initialized"):
|
|
108
|
+
await self._ensure_initialized() # type: ignore[attr-defined]
|
|
109
|
+
|
|
110
|
+
name_to_coin = getattr(self, "name_to_coin", {})
|
|
111
|
+
coin_to_asset = getattr(self, "coin_to_asset", {})
|
|
112
|
+
coin = name_to_coin.get(name, name)
|
|
113
|
+
if coin not in coin_to_asset:
|
|
114
|
+
raise KeyError(f"No Hyperliquid asset id found for {name!r}; known names include {list(name_to_coin)[:20]}")
|
|
115
|
+
return int(coin_to_asset[coin])
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def install_async_sdk_compat_shims() -> None:
|
|
119
|
+
"""Install tiny runtime shims for async SDK branches that changed helper methods."""
|
|
120
|
+
if not hasattr(Info, "name_to_asset"):
|
|
121
|
+
setattr(Info, "name_to_asset", _compat_name_to_asset)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
install_async_sdk_compat_shims()
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
# ---------------------------------------------------------------------------
|
|
128
|
+
# CLI
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
def parse_optional_pct(value: Optional[str]) -> Optional[float]:
|
|
132
|
+
"""argparse helper accepting omitted optional decimal fractions."""
|
|
133
|
+
if value is None:
|
|
134
|
+
return None
|
|
135
|
+
return float(value)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def build_arg_parser() -> argparse.ArgumentParser:
|
|
139
|
+
parser = argparse.ArgumentParser(
|
|
140
|
+
description=(
|
|
141
|
+
"Async Hyperliquid CLI: trailing stop manager, bracket entry bot, and event-driven market maker.\n\n"
|
|
142
|
+
"Subcommands:\n"
|
|
143
|
+
" trailing Manage trailing stops for open perp positions.\n"
|
|
144
|
+
" enter Enter a position, place stop-market protection, and manage TP ladder.\n"
|
|
145
|
+
" market_maker Event-driven MM: build a pyramiding ladder around recent volatility."
|
|
146
|
+
),
|
|
147
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
148
|
+
)
|
|
149
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
150
|
+
|
|
151
|
+
trailing_parser = subparsers.add_parser("trailing", help="Trailing stop manager for open perp positions.")
|
|
152
|
+
trailing_parser.add_argument("coin", type=str, nargs="?", default=None, help="Optional coin symbol, e.g. BTC, ETH.")
|
|
153
|
+
trailing_parser.add_argument("--trail-pct", type=float, default=0.01,
|
|
154
|
+
help="Trailing distance fraction, e.g. 0.01 = 1pct.")
|
|
155
|
+
trailing_parser.add_argument("--poll-interval", type=float, default=2.0,
|
|
156
|
+
help="Polling interval in seconds. Default: 2.0.")
|
|
157
|
+
trailing_parser.add_argument("--testnet", action="store_true", help="Use Hyperliquid testnet instead of mainnet.")
|
|
158
|
+
trailing_parser.add_argument("--no-websocket", action="store_true",
|
|
159
|
+
help="Disable websocket market-data cache and use HTTP polling only.")
|
|
160
|
+
trailing_parser.add_argument("--hide-orders", "-ho", action="store_true",
|
|
161
|
+
help="Accepted for consistency; trailing mode already keeps stops local and hidden.")
|
|
162
|
+
|
|
163
|
+
enter_parser = subparsers.add_parser(
|
|
164
|
+
"enter",
|
|
165
|
+
help="Enter a position with limit reposting, market fallback, stop-loss, and TP ladder.",
|
|
166
|
+
)
|
|
167
|
+
enter_parser.add_argument("coin", type=str, help="Perpetual coin symbol, e.g. BTC, ETH, HYPE.")
|
|
168
|
+
enter_parser.add_argument("direction", type=str, choices=["long", "short"], help="Direction to enter.")
|
|
169
|
+
enter_parser.add_argument("--size", type=float, required=True, help="Contract size to enter.")
|
|
170
|
+
enter_parser.add_argument("--take-profit-pct", type=float, default=None,
|
|
171
|
+
help="Take-profit target fraction, e.g. 0.01 = 1pct.")
|
|
172
|
+
enter_parser.add_argument("--stop-loss-pct", type=float, default=None,
|
|
173
|
+
help="Stop-loss fraction. If omitted with TP, defaults to TP * 0.5.")
|
|
174
|
+
enter_parser.add_argument("--take-profit-levels", type=int, default=4,
|
|
175
|
+
help="Number of weighted TP limit levels. Default: 4.")
|
|
176
|
+
enter_parser.add_argument("--trailing-tp", action="store_true",
|
|
177
|
+
help="Place the TP ladder, then after the configured TP level fills cancel the remaining ladder and switch to a local trailing take-profit.")
|
|
178
|
+
enter_parser.add_argument("--trailing-tp-trigger-level", type=int, default=1,
|
|
179
|
+
help="TP ladder level that must fill before trailing TP activates. Default: 1.")
|
|
180
|
+
enter_parser.add_argument("--trailing-tp-profit-pct", type=float, default=0.25,
|
|
181
|
+
help="Trailing distance as a fraction of favorable unrealized profit after the first TP level is hit. Default: 0.25.")
|
|
182
|
+
enter_parser.add_argument("--entry-retries", type=int, default=5,
|
|
183
|
+
help="Number of top-of-book limit repost attempts. Default: 5.")
|
|
184
|
+
enter_parser.add_argument("--entry-repost-interval", type=float, default=0.3,
|
|
185
|
+
help="Seconds between entry reposts. Default: 1.0.")
|
|
186
|
+
enter_parser.add_argument("--poll-interval", type=float, default=1.0,
|
|
187
|
+
help="Seconds between bracket monitor polls. Default: 1.0.")
|
|
188
|
+
enter_parser.add_argument("--tp-reversal-pct", type=float, default=None,
|
|
189
|
+
help="Exit if price reverses this fraction after first TP zone.")
|
|
190
|
+
enter_parser.add_argument("--tp-reversal-limit-exit", action="store_true",
|
|
191
|
+
help="On TP reversal, try a reduce-only limit exit plus protective stop before falling back to market.")
|
|
192
|
+
enter_parser.add_argument("--tp-reversal-stop-buffer-pct", type=float, default=None,
|
|
193
|
+
help="Buffer fraction for the TP-reversal stop trigger; default is tp-reversal-pct * 0.2.")
|
|
194
|
+
enter_parser.add_argument("--entry-tif", type=str, default="Alo", choices=["Alo", "Gtc"],
|
|
195
|
+
help="Entry limit TIF. Default: Alo.")
|
|
196
|
+
enter_parser.add_argument("--tp-tif", type=str, default="Alo", choices=["Alo", "Gtc"],
|
|
197
|
+
help="Take-profit limit TIF. Default: Alo.")
|
|
198
|
+
enter_parser.add_argument("--no-market-fallback", action="store_true",
|
|
199
|
+
help="Do not fall back to market entry after limit retries.")
|
|
200
|
+
enter_parser.add_argument("--market-slippage", type=float, default=0.05,
|
|
201
|
+
help="Slippage fraction for market-style entry/exit. Default: 0.05.")
|
|
202
|
+
enter_parser.add_argument("--keep-existing-tpsl", action="store_true",
|
|
203
|
+
help="Do not cancel existing reduce-only TP/SL orders before entry.")
|
|
204
|
+
enter_parser.add_argument("--testnet", action="store_true", help="Use Hyperliquid testnet instead of mainnet.")
|
|
205
|
+
enter_parser.add_argument("--no-websocket", action="store_true",
|
|
206
|
+
help="Disable websocket market-data cache and use HTTP polling only.")
|
|
207
|
+
enter_parser.add_argument("--hide-orders", "-ho", action="store_true",
|
|
208
|
+
help="Keep TP/SL targets private; no exchange-side bracket orders are placed until targets trigger.")
|
|
209
|
+
|
|
210
|
+
watch_parser = subparsers.add_parser(
|
|
211
|
+
"watch",
|
|
212
|
+
help="Watch for newly opened positions and automatically attach stop-loss and take-profit management.",
|
|
213
|
+
)
|
|
214
|
+
watch_parser.add_argument("coin", type=str, nargs="?", default=None,
|
|
215
|
+
help="Optional coin symbol to watch, e.g. BTC. Omit to watch all coins.")
|
|
216
|
+
watch_parser.add_argument("--take-profit-pct", type=float, default=None,
|
|
217
|
+
help="Take-profit target fraction, e.g. 0.01 = 1pct.")
|
|
218
|
+
watch_parser.add_argument("--stop-loss-pct", type=float, default=None,
|
|
219
|
+
help="Stop-loss fraction. If omitted with TP, defaults to TP * 0.5.")
|
|
220
|
+
watch_parser.add_argument("--take-profit-levels", type=int, default=4,
|
|
221
|
+
help="Number of weighted TP limit levels. Default: 4.")
|
|
222
|
+
watch_parser.add_argument("--trailing-tp", action="store_true",
|
|
223
|
+
help="Place the TP ladder, then after the configured TP level fills cancel the remaining ladder and switch to a local trailing take-profit.")
|
|
224
|
+
watch_parser.add_argument("--trailing-tp-trigger-level", type=int, default=1,
|
|
225
|
+
help="TP ladder level that must fill before trailing TP activates. Default: 1.")
|
|
226
|
+
watch_parser.add_argument("--trailing-tp-profit-pct", type=float, default=0.25,
|
|
227
|
+
help="Trailing distance as a fraction of favorable unrealized profit after the first TP level is hit. Default: 0.25.")
|
|
228
|
+
watch_parser.add_argument("--poll-interval", type=float, default=1.0,
|
|
229
|
+
help="Seconds between watcher/position monitor polls. Default: 1.0.")
|
|
230
|
+
watch_parser.add_argument("--tp-reversal-pct", type=float, default=None,
|
|
231
|
+
help="Exit if price reverses this fraction after first TP zone.")
|
|
232
|
+
watch_parser.add_argument("--tp-reversal-limit-exit", action="store_true",
|
|
233
|
+
help="On TP reversal, try a reduce-only limit exit plus protective stop before falling back to market.")
|
|
234
|
+
watch_parser.add_argument("--tp-reversal-stop-buffer-pct", type=float, default=None,
|
|
235
|
+
help="Buffer fraction for the TP-reversal stop trigger; default is tp-reversal-pct * 0.2.")
|
|
236
|
+
watch_parser.add_argument("--tp-tif", type=str, default="Alo", choices=["Alo", "Gtc"],
|
|
237
|
+
help="Take-profit limit TIF. Default: Alo.")
|
|
238
|
+
watch_parser.add_argument("--market-slippage", type=float, default=0.05,
|
|
239
|
+
help="Slippage fraction for market-style exits. Default: 0.05.")
|
|
240
|
+
watch_parser.add_argument("--keep-existing-tpsl", action="store_true",
|
|
241
|
+
help="Do not cancel existing reduce-only TP/SL orders before attaching new ones.")
|
|
242
|
+
watch_parser.add_argument("--manage-existing", action="store_true",
|
|
243
|
+
help="Also attach TP/SL to matching positions already open at startup.")
|
|
244
|
+
watch_parser.add_argument("--testnet", action="store_true", help="Use Hyperliquid testnet instead of mainnet.")
|
|
245
|
+
watch_parser.add_argument("--no-websocket", action="store_true",
|
|
246
|
+
help="Disable websocket market-data cache and use HTTP polling only.")
|
|
247
|
+
watch_parser.add_argument("--hide-orders", "-ho", action="store_true",
|
|
248
|
+
help="Keep TP/SL targets private; no exchange-side bracket orders are placed until targets trigger.")
|
|
249
|
+
|
|
250
|
+
auto_parser = subparsers.add_parser(
|
|
251
|
+
"auto",
|
|
252
|
+
help="TA-Lib multi-timeframe auto trader using MACD, SAR, ADX, and Bollinger targets.",
|
|
253
|
+
)
|
|
254
|
+
auto_parser.add_argument("coin", type=str, nargs="?", default=None,
|
|
255
|
+
help="Optional perpetual coin symbol, e.g. BTC, ETH, HYPE. Omit to scan the top perp markets by volume.")
|
|
256
|
+
auto_size_group = auto_parser.add_mutually_exclusive_group(required=True)
|
|
257
|
+
auto_size_group.add_argument("--size", type=float, help="Contract size to enter when a signal appears.")
|
|
258
|
+
auto_size_group.add_argument("--size-pct", type=str,
|
|
259
|
+
help="Percent of available collateral to use for sizing. Accepts 10, 10%%, or 0.10.")
|
|
260
|
+
auto_parser.add_argument("--top-markets", type=int, default=10,
|
|
261
|
+
help="When auto coin is omitted, scan the top N perp markets by day notional volume. Default: 10.")
|
|
262
|
+
auto_parser.add_argument("--intervals", type=str, default="1h,15m,5m,1m",
|
|
263
|
+
help="Comma/space separated intervals. Default: 1h,15m,5m,1m.")
|
|
264
|
+
auto_parser.add_argument("--auto-periods", type=int, default=200,
|
|
265
|
+
help="Candles per interval for TA-Lib indicators. Default: 200.")
|
|
266
|
+
auto_parser.add_argument("--scan-interval", type=float, default=30,
|
|
267
|
+
help="Seconds between signal scans. Default: 30.0.")
|
|
268
|
+
auto_parser.add_argument("--max-concurrent-scans", type=int, default=3,
|
|
269
|
+
help="Maximum markets to scan concurrently per auto loop. Default: 3.")
|
|
270
|
+
auto_parser.add_argument("--min-agreement", type=int, default=0,
|
|
271
|
+
help="Intervals required to agree. 0 means all configured intervals. Default: 0.")
|
|
272
|
+
auto_parser.add_argument("--adx-threshold", type=float, default=20.0,
|
|
273
|
+
help="Minimum ADX required for a trend signal. Default: 20.0.")
|
|
274
|
+
auto_parser.add_argument("--macd-fast", type=int, default=12, help="MACD fast period. Default: 12.")
|
|
275
|
+
auto_parser.add_argument("--macd-slow", type=int, default=26, help="MACD slow period. Default: 26.")
|
|
276
|
+
auto_parser.add_argument("--macd-signal", type=int, default=9, help="MACD signal period. Default: 9.")
|
|
277
|
+
auto_parser.add_argument("--sar-acceleration", type=float, default=0.02,
|
|
278
|
+
help="Parabolic SAR acceleration. Default: 0.02.")
|
|
279
|
+
auto_parser.add_argument("--sar-maximum", type=float, default=0.2,
|
|
280
|
+
help="Parabolic SAR maximum acceleration. Default: 0.2.")
|
|
281
|
+
auto_parser.add_argument("--auto-sar-stop-on-shortest-interval", action="store_true",
|
|
282
|
+
help="Use the Parabolic SAR from the shortest configured interval as the stop trigger for auto entries. Default keeps pct-based stop behavior.")
|
|
283
|
+
auto_parser.add_argument("--adx-timeperiod", type=int, default=14, help="ADX timeperiod. Default: 14.")
|
|
284
|
+
auto_parser.add_argument("--bb-timeperiod", type=int, default=20, help="Bollinger Bands timeperiod. Default: 20.")
|
|
285
|
+
auto_parser.add_argument("--bb-dev", type=float, default=2.0,
|
|
286
|
+
help="Bollinger Bands standard-deviation multiplier. Default: 2.0.")
|
|
287
|
+
auto_parser.add_argument("--scalp", action="store_true",
|
|
288
|
+
help="Use scalp-mode Bollinger confirmation on the shortest active interval before auto entry.")
|
|
289
|
+
auto_parser.add_argument("--use-live-candle", action="store_true",
|
|
290
|
+
help="Use the latest candle even if it may still be forming. Default uses last closed candle.")
|
|
291
|
+
auto_parser.add_argument("--take-profit-pct", type=float, default=None,
|
|
292
|
+
help="Override Bollinger-derived TP fraction, e.g. 0.01 = 1pct.")
|
|
293
|
+
auto_parser.add_argument("--min-take-profit-pct", type=float, default=0.002,
|
|
294
|
+
help="Minimum Bollinger-derived TP fraction. Default: 0.002.")
|
|
295
|
+
auto_parser.add_argument("--max-take-profit-pct", type=float, default=0.03,
|
|
296
|
+
help="Maximum Bollinger-derived TP fraction. Default: 0.03.")
|
|
297
|
+
auto_parser.add_argument("--stop-loss-pct", type=float, default=None,
|
|
298
|
+
help="Stop-loss fraction. If omitted, defaults to TP * 0.5.")
|
|
299
|
+
auto_parser.add_argument("--take-profit-levels", type=int, default=4,
|
|
300
|
+
help="Number of weighted TP limit levels. Default: 4.")
|
|
301
|
+
auto_parser.add_argument("--trailing-tp", action="store_true",
|
|
302
|
+
help="Place the TP ladder, then after the configured TP level fills cancel the remaining ladder and switch to a local trailing take-profit.")
|
|
303
|
+
auto_parser.add_argument("--trailing-tp-trigger-level", type=int, default=1,
|
|
304
|
+
help="TP ladder level that must fill before trailing TP activates. Default: 1.")
|
|
305
|
+
auto_parser.add_argument("--trailing-tp-profit-pct", type=float, default=0.25,
|
|
306
|
+
help="Trailing distance as a fraction of favorable unrealized profit after the first TP level is hit. Default: 0.25.")
|
|
307
|
+
auto_parser.add_argument("--entry-retries", type=int, default=5,
|
|
308
|
+
help="Number of top-of-book modify attempts. Default: 5.")
|
|
309
|
+
auto_parser.add_argument("--entry-repost-interval", type=float, default=0.25,
|
|
310
|
+
help="Seconds between entry modify attempts. Default: 1.0.")
|
|
311
|
+
auto_parser.add_argument("--poll-interval", type=float, default=1.0,
|
|
312
|
+
help="Seconds between bracket monitor polls after entry. Default: 1.0.")
|
|
313
|
+
auto_parser.add_argument("--tp-reversal-pct", type=float, default=None,
|
|
314
|
+
help="Exit if price reverses this fraction after first TP zone.")
|
|
315
|
+
auto_parser.add_argument("--tp-reversal-limit-exit", action="store_true",
|
|
316
|
+
help="On TP reversal, try a reduce-only limit exit plus protective stop before falling back to market.")
|
|
317
|
+
auto_parser.add_argument("--tp-reversal-stop-buffer-pct", type=float, default=None,
|
|
318
|
+
help="Buffer fraction for the TP-reversal stop trigger; default is tp-reversal-pct * 0.2.")
|
|
319
|
+
auto_parser.add_argument("--entry-tif", type=str, default="Alo", choices=["Alo", "Gtc"],
|
|
320
|
+
help="Entry limit TIF. Default: Alo.")
|
|
321
|
+
auto_parser.add_argument("--tp-tif", type=str, default="Alo", choices=["Alo", "Gtc"],
|
|
322
|
+
help="Take-profit limit TIF. Default: Alo.")
|
|
323
|
+
auto_parser.add_argument("--no-market-fallback", action="store_true",
|
|
324
|
+
help="Do not fall back to market entry after limit modify attempts.")
|
|
325
|
+
auto_parser.add_argument("--market-slippage", type=float, default=0.05,
|
|
326
|
+
help="Slippage fraction for market-style entry/exit. Default: 0.05.")
|
|
327
|
+
auto_parser.add_argument("--keep-existing-tpsl", action="store_true",
|
|
328
|
+
help="Do not cancel existing reduce-only TP/SL orders before entry.")
|
|
329
|
+
auto_parser.add_argument("--dry-run", action="store_true", help="Print auto signals but do not enter trades.")
|
|
330
|
+
auto_parser.add_argument("--max-trades", type=int, default=0,
|
|
331
|
+
help="Maximum completed auto trades before exit. 0 means unlimited. Default: 0.")
|
|
332
|
+
auto_parser.add_argument("--cooldown-after-trade", type=float, default=60.0,
|
|
333
|
+
help="Seconds to wait after a managed trade exits before scanning again. Default: 60.0.")
|
|
334
|
+
auto_parser.add_argument("--loop-after-trade", dest="loop_after_trade", action="store_true",
|
|
335
|
+
help="After a managed trade finishes, resume scanning for the next signal. Default: enabled.")
|
|
336
|
+
auto_parser.add_argument("--exit-after-trade", dest="loop_after_trade", action="store_false",
|
|
337
|
+
help="Exit auto mode after the first completed managed trade.")
|
|
338
|
+
auto_parser.set_defaults(loop_after_trade=True)
|
|
339
|
+
auto_parser.add_argument("--max-coin-trades-per-session", type=int, default=0,
|
|
340
|
+
help="Per-coin completed trade-cycle limit before cooldown. 0 disables. Default: 0.")
|
|
341
|
+
auto_parser.add_argument("--coin-session-cooldown-seconds", type=float, default=0.0,
|
|
342
|
+
help="Cooldown duration after a per-coin stop condition triggers. 0 disables timed cooldown. Default: 0.")
|
|
343
|
+
auto_parser.add_argument("--coin-session-profit-target", type=float, default=0.0,
|
|
344
|
+
help="Per-coin realized PnL target before cooldown. 0 disables. Default: 0.")
|
|
345
|
+
auto_parser.add_argument("--coin-session-min-profit-to-lock", type=float, default=0.0,
|
|
346
|
+
help="Minimum per-coin peak realized PnL before giveback protection activates. Default: 0.")
|
|
347
|
+
auto_parser.add_argument("--coin-session-giveback-pct", type=float, default=0.0,
|
|
348
|
+
help="Per-coin giveback fraction from peak realized PnL before cooldown. 0 disables. Default: 0.")
|
|
349
|
+
auto_parser.add_argument("--cooldown-after-loss-following-wins", type=int, default=0,
|
|
350
|
+
help="Cooldown a coin after a loss that follows at least N consecutive winning cycles. 0 disables. Default: 0.")
|
|
351
|
+
auto_parser.add_argument("--session-profit-target", type=float, default=0.0,
|
|
352
|
+
help="Stop the entire auto session after total realized PnL reaches this target. 0 disables. Default: 0.")
|
|
353
|
+
auto_parser.add_argument("--session-max-loss", type=float, default=0.0,
|
|
354
|
+
help="Stop the entire auto session if total realized PnL falls to -N or below. 0 disables. Default: 0.")
|
|
355
|
+
auto_parser.add_argument("--session-giveback-pct", type=float, default=0.0,
|
|
356
|
+
help="Stop the entire auto session after giving back this fraction from peak realized PnL. 0 disables. Default: 0.")
|
|
357
|
+
auto_parser.add_argument("--risk-session-log", type=str, default="",
|
|
358
|
+
help="Optional JSONL path for auto risk-session events.")
|
|
359
|
+
auto_parser.add_argument("--testnet", action="store_true", help="Use Hyperliquid testnet instead of mainnet.")
|
|
360
|
+
auto_parser.add_argument("--no-websocket", action="store_true",
|
|
361
|
+
help="Disable websocket market-data cache and use HTTP polling only.")
|
|
362
|
+
auto_parser.add_argument("--disable-ws-candles", action="store_true",
|
|
363
|
+
help="Disable ws for kline data (not recommended): ")
|
|
364
|
+
auto_parser.add_argument("--hide-orders", "-ho", action="store_true",
|
|
365
|
+
help="Keep auto-mode TP/SL targets private; no exchange-side bracket orders are placed until targets trigger.")
|
|
366
|
+
|
|
367
|
+
mm_parser = subparsers.add_parser(
|
|
368
|
+
"market_maker",
|
|
369
|
+
help="Event-driven market maker using async SDK calls.",
|
|
370
|
+
)
|
|
371
|
+
mm_parser.add_argument("coin", type=str, help="Perpetual coin symbol, e.g. BTC, ETH, kPEPE.")
|
|
372
|
+
mm_parser.add_argument("--interval", type=str, default="1m", choices=sorted(INTERVAL_TO_MS.keys()),
|
|
373
|
+
help="Candle interval. Default: 1m.")
|
|
374
|
+
mm_parser.add_argument("--periods", type=int, default=12, help="Recent candle count for stddev. Default: 12.")
|
|
375
|
+
mm_parser.add_argument("--levels", type=int, default=4, help="Number of pyramid levels per side. Default: 4.")
|
|
376
|
+
mm_parser.add_argument("--base-size", type=float, default=0.01,
|
|
377
|
+
help="Base contract size for closest level. Default: 0.01.")
|
|
378
|
+
mm_parser.add_argument("--loop-sleep", type=float, default=1.0, help="Seconds between MM checks. Default: 1.0.")
|
|
379
|
+
mm_parser.add_argument("--min-edge-pct", type=float, default=0.0005,
|
|
380
|
+
help="Minimum edge vs mid as fraction. Default: 0.0005.")
|
|
381
|
+
mm_parser.add_argument("--rebalance-threshold-pct", type=float, default=0.001,
|
|
382
|
+
help="Mid-vs-ladder-center drift threshold. Default: 0.001.")
|
|
383
|
+
mm_parser.add_argument("--protect-close-pct", type=float, default=0.0005,
|
|
384
|
+
help="Do not cancel orders within this mid band. Default: 0.0005.")
|
|
385
|
+
mm_parser.add_argument("--testnet", action="store_true", help="Use Hyperliquid testnet instead of mainnet.")
|
|
386
|
+
mm_parser.add_argument("--no-websocket", action="store_true",
|
|
387
|
+
help="Disable websocket market-data cache and use HTTP polling only.")
|
|
388
|
+
|
|
389
|
+
return parser
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
async def async_main(argv: Optional[List[str]] = None) -> None:
|
|
393
|
+
parser = build_arg_parser()
|
|
394
|
+
args = parser.parse_args(argv)
|
|
395
|
+
|
|
396
|
+
if args.command == "trailing":
|
|
397
|
+
if args.trail_pct <= 0.0:
|
|
398
|
+
print("[ERROR] --trail-pct must be > 0.")
|
|
399
|
+
sys.exit(1)
|
|
400
|
+
if args.poll_interval <= 0.0:
|
|
401
|
+
print("[ERROR] --poll-interval must be > 0.")
|
|
402
|
+
sys.exit(1)
|
|
403
|
+
await trailing_stop_for_all_positions(
|
|
404
|
+
trail_pct=args.trail_pct,
|
|
405
|
+
poll_interval=args.poll_interval,
|
|
406
|
+
use_testnet=args.testnet,
|
|
407
|
+
only_coin=(args.coin.upper() if args.coin is not None else None),
|
|
408
|
+
use_websocket=(not args.no_websocket),
|
|
409
|
+
hide_orders=args.hide_orders,
|
|
410
|
+
)
|
|
411
|
+
return
|
|
412
|
+
|
|
413
|
+
if args.command == "enter":
|
|
414
|
+
if args.size <= 0.0:
|
|
415
|
+
print("[ERROR] --size must be > 0.")
|
|
416
|
+
sys.exit(1)
|
|
417
|
+
if args.take_profit_pct is None and args.stop_loss_pct is None:
|
|
418
|
+
print("[ERROR] Specify --take-profit-pct, --stop-loss-pct, or both.")
|
|
419
|
+
sys.exit(1)
|
|
420
|
+
if args.take_profit_levels <= 0:
|
|
421
|
+
print("[ERROR] --take-profit-levels must be > 0.")
|
|
422
|
+
sys.exit(1)
|
|
423
|
+
if args.trailing_tp_trigger_level <= 0:
|
|
424
|
+
print("[ERROR] --trailing-tp-trigger-level must be > 0.")
|
|
425
|
+
sys.exit(1)
|
|
426
|
+
if args.trailing_tp_trigger_level > args.take_profit_levels:
|
|
427
|
+
print("[ERROR] --trailing-tp-trigger-level cannot exceed --take-profit-levels.")
|
|
428
|
+
sys.exit(1)
|
|
429
|
+
if not (0.0 < args.trailing_tp_profit_pct < 1.0):
|
|
430
|
+
print("[ERROR] --trailing-tp-profit-pct must be between 0 and 1.")
|
|
431
|
+
sys.exit(1)
|
|
432
|
+
if args.entry_retries < 0:
|
|
433
|
+
print("[ERROR] --entry-retries must be >= 0.")
|
|
434
|
+
sys.exit(1)
|
|
435
|
+
if args.entry_repost_interval <= 0.0:
|
|
436
|
+
print("[ERROR] --entry-repost-interval must be > 0.")
|
|
437
|
+
sys.exit(1)
|
|
438
|
+
if args.poll_interval <= 0.0:
|
|
439
|
+
print("[ERROR] --poll-interval must be > 0.")
|
|
440
|
+
sys.exit(1)
|
|
441
|
+
if args.market_slippage < 0.0:
|
|
442
|
+
print("[ERROR] --market-slippage must be >= 0.")
|
|
443
|
+
sys.exit(1)
|
|
444
|
+
if args.tp_reversal_stop_buffer_pct is not None and not (0.0 < args.tp_reversal_stop_buffer_pct < 1.0):
|
|
445
|
+
print("[ERROR] --tp-reversal-stop-buffer-pct must be between 0 and 1.")
|
|
446
|
+
sys.exit(1)
|
|
447
|
+
|
|
448
|
+
await run_bracket_entry(
|
|
449
|
+
coin=args.coin.upper(),
|
|
450
|
+
direction=args.direction,
|
|
451
|
+
size=args.size,
|
|
452
|
+
take_profit_pct=args.take_profit_pct,
|
|
453
|
+
stop_loss_pct=args.stop_loss_pct,
|
|
454
|
+
stop_loss_trigger_px=None,
|
|
455
|
+
take_profit_levels=args.take_profit_levels,
|
|
456
|
+
use_trailing_tp=args.trailing_tp,
|
|
457
|
+
trailing_tp_trigger_level=args.trailing_tp_trigger_level,
|
|
458
|
+
trailing_tp_profit_pct=args.trailing_tp_profit_pct,
|
|
459
|
+
entry_retries=args.entry_retries,
|
|
460
|
+
entry_repost_interval=args.entry_repost_interval,
|
|
461
|
+
poll_interval=args.poll_interval,
|
|
462
|
+
tp_reversal_pct=args.tp_reversal_pct,
|
|
463
|
+
entry_tif=args.entry_tif,
|
|
464
|
+
tp_tif=args.tp_tif,
|
|
465
|
+
market_fallback=(not args.no_market_fallback),
|
|
466
|
+
market_slippage=args.market_slippage,
|
|
467
|
+
cancel_existing_tpsl=(not args.keep_existing_tpsl),
|
|
468
|
+
tp_reversal_limit_exit=args.tp_reversal_limit_exit,
|
|
469
|
+
tp_reversal_stop_buffer_pct=args.tp_reversal_stop_buffer_pct,
|
|
470
|
+
use_testnet=args.testnet,
|
|
471
|
+
use_websocket=(not args.no_websocket),
|
|
472
|
+
hide_orders=args.hide_orders,
|
|
473
|
+
)
|
|
474
|
+
return
|
|
475
|
+
|
|
476
|
+
if args.command == "watch":
|
|
477
|
+
if args.take_profit_pct is None and args.stop_loss_pct is None:
|
|
478
|
+
print("[ERROR] Specify --take-profit-pct, --stop-loss-pct, or both.")
|
|
479
|
+
sys.exit(1)
|
|
480
|
+
if args.take_profit_levels <= 0:
|
|
481
|
+
print("[ERROR] --take-profit-levels must be > 0.")
|
|
482
|
+
sys.exit(1)
|
|
483
|
+
if args.trailing_tp_trigger_level <= 0:
|
|
484
|
+
print("[ERROR] --trailing-tp-trigger-level must be > 0.")
|
|
485
|
+
sys.exit(1)
|
|
486
|
+
if args.trailing_tp_trigger_level > args.take_profit_levels:
|
|
487
|
+
print("[ERROR] --trailing-tp-trigger-level cannot exceed --take-profit-levels.")
|
|
488
|
+
sys.exit(1)
|
|
489
|
+
if not (0.0 < args.trailing_tp_profit_pct < 1.0):
|
|
490
|
+
print("[ERROR] --trailing-tp-profit-pct must be between 0 and 1.")
|
|
491
|
+
sys.exit(1)
|
|
492
|
+
if args.poll_interval <= 0.0:
|
|
493
|
+
print("[ERROR] --poll-interval must be > 0.")
|
|
494
|
+
sys.exit(1)
|
|
495
|
+
if args.market_slippage < 0.0:
|
|
496
|
+
print("[ERROR] --market-slippage must be >= 0.")
|
|
497
|
+
sys.exit(1)
|
|
498
|
+
if args.tp_reversal_stop_buffer_pct is not None and not (0.0 < args.tp_reversal_stop_buffer_pct < 1.0):
|
|
499
|
+
print("[ERROR] --tp-reversal-stop-buffer-pct must be between 0 and 1.")
|
|
500
|
+
sys.exit(1)
|
|
501
|
+
|
|
502
|
+
await run_position_watcher(
|
|
503
|
+
only_coin=(args.coin.upper() if args.coin is not None else None),
|
|
504
|
+
take_profit_pct=args.take_profit_pct,
|
|
505
|
+
stop_loss_pct=args.stop_loss_pct,
|
|
506
|
+
take_profit_levels=args.take_profit_levels,
|
|
507
|
+
use_trailing_tp=args.trailing_tp,
|
|
508
|
+
trailing_tp_trigger_level=args.trailing_tp_trigger_level,
|
|
509
|
+
trailing_tp_profit_pct=args.trailing_tp_profit_pct,
|
|
510
|
+
poll_interval=args.poll_interval,
|
|
511
|
+
tp_reversal_pct=args.tp_reversal_pct,
|
|
512
|
+
tp_tif=args.tp_tif,
|
|
513
|
+
market_slippage=args.market_slippage,
|
|
514
|
+
cancel_existing_tpsl=(not args.keep_existing_tpsl),
|
|
515
|
+
manage_existing=args.manage_existing,
|
|
516
|
+
tp_reversal_limit_exit=args.tp_reversal_limit_exit,
|
|
517
|
+
tp_reversal_stop_buffer_pct=args.tp_reversal_stop_buffer_pct,
|
|
518
|
+
use_testnet=args.testnet,
|
|
519
|
+
use_websocket=(not args.no_websocket),
|
|
520
|
+
hide_orders=args.hide_orders,
|
|
521
|
+
)
|
|
522
|
+
return
|
|
523
|
+
|
|
524
|
+
if args.command == "auto":
|
|
525
|
+
if args.size is not None and args.size <= 0.0:
|
|
526
|
+
print("[ERROR] --size must be > 0.")
|
|
527
|
+
sys.exit(1)
|
|
528
|
+
if args.size_pct is not None:
|
|
529
|
+
try:
|
|
530
|
+
parse_fractional_pct(args.size_pct, field_name="--size-pct")
|
|
531
|
+
except RuntimeError as exc:
|
|
532
|
+
print(f"[ERROR] {exc}")
|
|
533
|
+
sys.exit(1)
|
|
534
|
+
|
|
535
|
+
if args.disable_ws_candles:
|
|
536
|
+
setattr(args, 'ws_candles', False)
|
|
537
|
+
else:
|
|
538
|
+
setattr(args, 'ws_candles', True)
|
|
539
|
+
|
|
540
|
+
if args.top_markets <= 0:
|
|
541
|
+
print("[ERROR] --top-markets must be > 0.")
|
|
542
|
+
sys.exit(1)
|
|
543
|
+
if args.auto_periods <= 0:
|
|
544
|
+
print("[ERROR] --auto-periods must be > 0.")
|
|
545
|
+
sys.exit(1)
|
|
546
|
+
if args.scan_interval <= 0.0:
|
|
547
|
+
print("[ERROR] --scan-interval must be > 0.")
|
|
548
|
+
sys.exit(1)
|
|
549
|
+
if args.max_concurrent_scans <= 0:
|
|
550
|
+
print("[ERROR] --max-concurrent-scans must be > 0.")
|
|
551
|
+
sys.exit(1)
|
|
552
|
+
if args.min_agreement < 0:
|
|
553
|
+
print("[ERROR] --min-agreement must be >= 0.")
|
|
554
|
+
sys.exit(1)
|
|
555
|
+
if args.take_profit_levels <= 0:
|
|
556
|
+
print("[ERROR] --take-profit-levels must be > 0.")
|
|
557
|
+
sys.exit(1)
|
|
558
|
+
if args.trailing_tp_trigger_level <= 0:
|
|
559
|
+
print("[ERROR] --trailing-tp-trigger-level must be > 0.")
|
|
560
|
+
sys.exit(1)
|
|
561
|
+
if args.trailing_tp_trigger_level > args.take_profit_levels:
|
|
562
|
+
print("[ERROR] --trailing-tp-trigger-level cannot exceed --take-profit-levels.")
|
|
563
|
+
sys.exit(1)
|
|
564
|
+
if not (0.0 < args.trailing_tp_profit_pct < 1.0):
|
|
565
|
+
print("[ERROR] --trailing-tp-profit-pct must be between 0 and 1.")
|
|
566
|
+
sys.exit(1)
|
|
567
|
+
if args.entry_retries < 0:
|
|
568
|
+
print("[ERROR] --entry-retries must be >= 0.")
|
|
569
|
+
sys.exit(1)
|
|
570
|
+
if args.entry_repost_interval <= 0.0:
|
|
571
|
+
print("[ERROR] --entry-repost-interval must be > 0.")
|
|
572
|
+
sys.exit(1)
|
|
573
|
+
if args.poll_interval <= 0.0:
|
|
574
|
+
print("[ERROR] --poll-interval must be > 0.")
|
|
575
|
+
sys.exit(1)
|
|
576
|
+
if args.market_slippage < 0.0:
|
|
577
|
+
print("[ERROR] --market-slippage must be >= 0.")
|
|
578
|
+
sys.exit(1)
|
|
579
|
+
if args.max_trades < 0:
|
|
580
|
+
print("[ERROR] --max-trades must be >= 0.")
|
|
581
|
+
sys.exit(1)
|
|
582
|
+
if args.cooldown_after_trade < 0.0:
|
|
583
|
+
print("[ERROR] --cooldown-after-trade must be >= 0.")
|
|
584
|
+
sys.exit(1)
|
|
585
|
+
if args.max_coin_trades_per_session < 0:
|
|
586
|
+
print("[ERROR] --max-coin-trades-per-session must be >= 0.")
|
|
587
|
+
sys.exit(1)
|
|
588
|
+
if args.coin_session_cooldown_seconds < 0.0:
|
|
589
|
+
print("[ERROR] --coin-session-cooldown-seconds must be >= 0.")
|
|
590
|
+
sys.exit(1)
|
|
591
|
+
if args.coin_session_profit_target < 0.0:
|
|
592
|
+
print("[ERROR] --coin-session-profit-target must be >= 0.")
|
|
593
|
+
sys.exit(1)
|
|
594
|
+
if args.coin_session_min_profit_to_lock < 0.0:
|
|
595
|
+
print("[ERROR] --coin-session-min-profit-to-lock must be >= 0.")
|
|
596
|
+
sys.exit(1)
|
|
597
|
+
if not (0.0 <= args.coin_session_giveback_pct < 1.0):
|
|
598
|
+
print("[ERROR] --coin-session-giveback-pct must be >= 0 and < 1.")
|
|
599
|
+
sys.exit(1)
|
|
600
|
+
if args.cooldown_after_loss_following_wins < 0:
|
|
601
|
+
print("[ERROR] --cooldown-after-loss-following-wins must be >= 0.")
|
|
602
|
+
sys.exit(1)
|
|
603
|
+
if args.session_profit_target < 0.0:
|
|
604
|
+
print("[ERROR] --session-profit-target must be >= 0.")
|
|
605
|
+
sys.exit(1)
|
|
606
|
+
if args.session_max_loss < 0.0:
|
|
607
|
+
print("[ERROR] --session-max-loss must be >= 0.")
|
|
608
|
+
sys.exit(1)
|
|
609
|
+
if not (0.0 <= args.session_giveback_pct < 1.0):
|
|
610
|
+
print("[ERROR] --session-giveback-pct must be >= 0 and < 1.")
|
|
611
|
+
sys.exit(1)
|
|
612
|
+
if args.tp_reversal_stop_buffer_pct is not None and not (0.0 < args.tp_reversal_stop_buffer_pct < 1.0):
|
|
613
|
+
print("[ERROR] --tp-reversal-stop-buffer-pct must be between 0 and 1.")
|
|
614
|
+
sys.exit(1)
|
|
615
|
+
if args.ws_candles and args.no_websocket:
|
|
616
|
+
print("[ERROR] --ws-candles requires websocket market data; remove --no-websocket.")
|
|
617
|
+
sys.exit(1)
|
|
618
|
+
|
|
619
|
+
await run_auto_trader(
|
|
620
|
+
coin=(args.coin.upper() if args.coin is not None else None),
|
|
621
|
+
size=args.size,
|
|
622
|
+
size_pct=args.size_pct,
|
|
623
|
+
top_markets=args.top_markets,
|
|
624
|
+
intervals_value=args.intervals,
|
|
625
|
+
periods=args.auto_periods,
|
|
626
|
+
scan_interval=args.scan_interval,
|
|
627
|
+
max_concurrent_scans=args.max_concurrent_scans,
|
|
628
|
+
min_agreement=args.min_agreement,
|
|
629
|
+
adx_threshold=args.adx_threshold,
|
|
630
|
+
take_profit_pct=args.take_profit_pct,
|
|
631
|
+
stop_loss_pct=args.stop_loss_pct,
|
|
632
|
+
min_take_profit_pct=args.min_take_profit_pct,
|
|
633
|
+
max_take_profit_pct=args.max_take_profit_pct,
|
|
634
|
+
take_profit_levels=args.take_profit_levels,
|
|
635
|
+
use_trailing_tp=args.trailing_tp,
|
|
636
|
+
trailing_tp_trigger_level=args.trailing_tp_trigger_level,
|
|
637
|
+
trailing_tp_profit_pct=args.trailing_tp_profit_pct,
|
|
638
|
+
entry_retries=args.entry_retries,
|
|
639
|
+
entry_repost_interval=args.entry_repost_interval,
|
|
640
|
+
poll_interval=args.poll_interval,
|
|
641
|
+
tp_reversal_pct=args.tp_reversal_pct,
|
|
642
|
+
entry_tif=args.entry_tif,
|
|
643
|
+
tp_tif=args.tp_tif,
|
|
644
|
+
market_fallback=(not args.no_market_fallback),
|
|
645
|
+
market_slippage=args.market_slippage,
|
|
646
|
+
cancel_existing_tpsl=(not args.keep_existing_tpsl),
|
|
647
|
+
tp_reversal_limit_exit=args.tp_reversal_limit_exit,
|
|
648
|
+
tp_reversal_stop_buffer_pct=args.tp_reversal_stop_buffer_pct,
|
|
649
|
+
macd_fast=args.macd_fast,
|
|
650
|
+
macd_slow=args.macd_slow,
|
|
651
|
+
macd_signal_period=args.macd_signal,
|
|
652
|
+
sar_acceleration=args.sar_acceleration,
|
|
653
|
+
sar_maximum=args.sar_maximum,
|
|
654
|
+
adx_timeperiod=args.adx_timeperiod,
|
|
655
|
+
bb_timeperiod=args.bb_timeperiod,
|
|
656
|
+
bb_dev=args.bb_dev,
|
|
657
|
+
scalp=args.scalp,
|
|
658
|
+
use_last_closed_candle=(not args.use_live_candle),
|
|
659
|
+
use_sar_stop_on_shortest_interval=args.auto_sar_stop_on_shortest_interval,
|
|
660
|
+
dry_run=args.dry_run,
|
|
661
|
+
max_trades=args.max_trades,
|
|
662
|
+
cooldown_after_trade=args.cooldown_after_trade,
|
|
663
|
+
loop_after_trade=args.loop_after_trade,
|
|
664
|
+
max_coin_trades_per_session=args.max_coin_trades_per_session,
|
|
665
|
+
coin_session_cooldown_seconds=args.coin_session_cooldown_seconds,
|
|
666
|
+
coin_session_profit_target=args.coin_session_profit_target,
|
|
667
|
+
coin_session_min_profit_to_lock=args.coin_session_min_profit_to_lock,
|
|
668
|
+
coin_session_giveback_pct=args.coin_session_giveback_pct,
|
|
669
|
+
cooldown_after_loss_following_wins=args.cooldown_after_loss_following_wins,
|
|
670
|
+
session_profit_target=args.session_profit_target,
|
|
671
|
+
session_max_loss=args.session_max_loss,
|
|
672
|
+
session_giveback_pct=args.session_giveback_pct,
|
|
673
|
+
use_testnet=args.testnet,
|
|
674
|
+
use_websocket=(not args.no_websocket),
|
|
675
|
+
use_websocket_candles=args.ws_candles,
|
|
676
|
+
hide_orders=args.hide_orders,
|
|
677
|
+
risk_session_log=args.risk_session_log,
|
|
678
|
+
)
|
|
679
|
+
return
|
|
680
|
+
|
|
681
|
+
if args.command == "market_maker":
|
|
682
|
+
if args.periods <= 0:
|
|
683
|
+
print("[ERROR] --periods must be > 0.")
|
|
684
|
+
sys.exit(1)
|
|
685
|
+
if args.levels <= 0:
|
|
686
|
+
print("[ERROR] --levels must be > 0.")
|
|
687
|
+
sys.exit(1)
|
|
688
|
+
if args.base_size <= 0:
|
|
689
|
+
print("[ERROR] --base-size must be > 0.")
|
|
690
|
+
sys.exit(1)
|
|
691
|
+
if args.min_edge_pct <= 0:
|
|
692
|
+
print("[ERROR] --min-edge-pct must be > 0.")
|
|
693
|
+
sys.exit(1)
|
|
694
|
+
if args.rebalance_threshold_pct <= 0:
|
|
695
|
+
print("[ERROR] --rebalance-threshold-pct must be > 0.")
|
|
696
|
+
sys.exit(1)
|
|
697
|
+
await run_market_maker(
|
|
698
|
+
coin=args.coin.upper(),
|
|
699
|
+
interval=args.interval,
|
|
700
|
+
periods=args.periods,
|
|
701
|
+
levels=args.levels,
|
|
702
|
+
base_size=args.base_size,
|
|
703
|
+
use_testnet=args.testnet,
|
|
704
|
+
loop_sleep=args.loop_sleep,
|
|
705
|
+
min_edge_pct=args.min_edge_pct,
|
|
706
|
+
rebalance_threshold_pct=args.rebalance_threshold_pct,
|
|
707
|
+
protect_close_pct=args.protect_close_pct,
|
|
708
|
+
use_websocket=(not args.no_websocket),
|
|
709
|
+
)
|
|
710
|
+
return
|
|
711
|
+
|
|
712
|
+
parser.print_help()
|
|
713
|
+
sys.exit(1)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def main(argv: Optional[List[str]] = None) -> None:
|
|
717
|
+
log_path = configure_runtime_logging()
|
|
718
|
+
try:
|
|
719
|
+
loop = asyncio.new_event_loop()
|
|
720
|
+
asyncio.set_event_loop(loop)
|
|
721
|
+
install_asyncio_exception_logging(loop)
|
|
722
|
+
print(f"[LOG] Writing runtime logs to {log_path}")
|
|
723
|
+
print(f"[START] argv={argv if argv is not None else sys.argv[1:]}")
|
|
724
|
+
loop.run_until_complete(async_main(argv))
|
|
725
|
+
except KeyboardInterrupt:
|
|
726
|
+
print("\n[!] Interrupted.")
|
|
727
|
+
except Exception:
|
|
728
|
+
logging.getLogger("hypertrader").exception("[FATAL] Unhandled main() exception.")
|
|
729
|
+
sys.exit(1)
|
|
730
|
+
finally:
|
|
731
|
+
try:
|
|
732
|
+
loop = asyncio.get_event_loop()
|
|
733
|
+
except RuntimeError:
|
|
734
|
+
loop = None
|
|
735
|
+
if loop is not None and not loop.is_closed():
|
|
736
|
+
loop.close()
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
if __name__ == "__main__":
|
|
740
|
+
uvloop.install()
|
|
741
|
+
main()
|