hypertrader 0.1.1__tar.gz → 0.1.2__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.1.1 → hypertrader-0.1.2}/PKG-INFO +2 -1
- {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/PKG-INFO +2 -1
- {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/entry_points.txt +1 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/requires.txt +1 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.py +1 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/auto_trader.py +92 -12
- {hypertrader-0.1.1 → hypertrader-0.1.2}/setup.py +3 -1
- {hypertrader-0.1.1 → hypertrader-0.1.2}/README.md +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/SOURCES.txt +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/dependency_links.txt +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/top_level.txt +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/market_maker.py +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/position_management.py +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/position_watcher.py +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/trailing_stop.py +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/setup.cfg +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/utils/constants.py +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/utils/helpers.py +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/utils/style.py +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/utils/timer.py +0 -0
- {hypertrader-0.1.1 → hypertrader-0.1.2}/utils/worker.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypertrader
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Async Hyperliquid trading helper built on the async SDK.
|
|
5
5
|
Author: darkerego
|
|
6
6
|
License: MIT
|
|
@@ -15,6 +15,7 @@ Requires-Dist: eth-account
|
|
|
15
15
|
Requires-Dist: hyperliquid-python-sdk-async
|
|
16
16
|
Requires-Dist: python-dotenv
|
|
17
17
|
Requires-Dist: uvloop
|
|
18
|
+
Requires-Dist: colored
|
|
18
19
|
Provides-Extra: auto
|
|
19
20
|
Requires-Dist: numpy; extra == "auto"
|
|
20
21
|
Requires-Dist: TA-Lib; extra == "auto"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hypertrader
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Async Hyperliquid trading helper built on the async SDK.
|
|
5
5
|
Author: darkerego
|
|
6
6
|
License: MIT
|
|
@@ -15,6 +15,7 @@ Requires-Dist: eth-account
|
|
|
15
15
|
Requires-Dist: hyperliquid-python-sdk-async
|
|
16
16
|
Requires-Dist: python-dotenv
|
|
17
17
|
Requires-Dist: uvloop
|
|
18
|
+
Requires-Dist: colored
|
|
18
19
|
Provides-Extra: auto
|
|
19
20
|
Requires-Dist: numpy; extra == "auto"
|
|
20
21
|
Requires-Dist: TA-Lib; extra == "auto"
|
|
@@ -145,6 +145,7 @@ def build_arg_parser() -> argparse.ArgumentParser:
|
|
|
145
145
|
" market_maker Event-driven MM: build a pyramiding ladder around recent volatility."
|
|
146
146
|
),
|
|
147
147
|
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
148
|
+
usage="Full documentation located at https://github.com/darkerego/hypertrader.",
|
|
148
149
|
)
|
|
149
150
|
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
150
151
|
|
|
@@ -18,7 +18,7 @@ from utils.constants import AUTO_TRADES_LOG_FILE, INTERVAL_TO_MS, cp
|
|
|
18
18
|
from utils.helpers import parse_interval_list, init_clients, _try_float, parse_fractional_pct, \
|
|
19
19
|
extract_account_balance_from_user_state, round_size_for_hyperliquid, get_user_state_with_retry, get_all_mids, \
|
|
20
20
|
fetch_recent_candles, compute_position_unrealized_pnl, close_clients, compute_default_stop_loss_pct, \
|
|
21
|
-
extract_closed_pnl_from_fill
|
|
21
|
+
extract_closed_pnl_from_fill, is_rate_limit_error
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
24
24
|
_AUTO_TRADES_LOGGER: Optional[logging.Logger] = None
|
|
@@ -202,6 +202,53 @@ class AutoManagedTradeResult:
|
|
|
202
202
|
closed_ms: int
|
|
203
203
|
|
|
204
204
|
|
|
205
|
+
@dataclass
|
|
206
|
+
class AutoStartupBackfillState:
|
|
207
|
+
"""Track initial REST candle backfill progress and temporary 429 pacing."""
|
|
208
|
+
enabled: bool
|
|
209
|
+
pause_seconds: float = 3.0
|
|
210
|
+
pause_between_batches: bool = False
|
|
211
|
+
complete: bool = False
|
|
212
|
+
pending_pairs: set[Tuple[str, str]] | None = None
|
|
213
|
+
|
|
214
|
+
def __post_init__(self) -> None:
|
|
215
|
+
if self.pending_pairs is None:
|
|
216
|
+
self.pending_pairs = set()
|
|
217
|
+
|
|
218
|
+
def register_pending(self, coin: str, interval: str) -> None:
|
|
219
|
+
if not self.enabled or self.complete:
|
|
220
|
+
return
|
|
221
|
+
self.pending_pairs.add((str(coin).upper(), str(interval)))
|
|
222
|
+
|
|
223
|
+
def mark_success(self, coin: str, interval: str) -> None:
|
|
224
|
+
if not self.enabled or self.complete:
|
|
225
|
+
return
|
|
226
|
+
self.pending_pairs.discard((str(coin).upper(), str(interval)))
|
|
227
|
+
if not self.pending_pairs:
|
|
228
|
+
self.complete = True
|
|
229
|
+
if self.pause_between_batches:
|
|
230
|
+
print("[AUTO] Initial REST candle backfill completed; resuming normal scan cadence.")
|
|
231
|
+
|
|
232
|
+
def mark_non_rate_limit_failure(self, coin: str, interval: str) -> None:
|
|
233
|
+
if not self.enabled or self.complete:
|
|
234
|
+
return
|
|
235
|
+
self.pending_pairs.discard((str(coin).upper(), str(interval)))
|
|
236
|
+
if not self.pending_pairs:
|
|
237
|
+
self.complete = True
|
|
238
|
+
if self.pause_between_batches:
|
|
239
|
+
print("[AUTO] Initial REST candle backfill completed; resuming normal scan cadence.")
|
|
240
|
+
|
|
241
|
+
def mark_rate_limit(self) -> None:
|
|
242
|
+
if not self.enabled or self.complete:
|
|
243
|
+
return
|
|
244
|
+
if not self.pause_between_batches:
|
|
245
|
+
print(
|
|
246
|
+
f"[AUTO] Initial REST candle backfill hit rate limits; pausing "
|
|
247
|
+
f"{self.pause_seconds:.1f}s between scan batches until startup backfill finishes."
|
|
248
|
+
)
|
|
249
|
+
self.pause_between_batches = True
|
|
250
|
+
|
|
251
|
+
|
|
205
252
|
def now_ms() -> int:
|
|
206
253
|
return int(time.time() * 1000)
|
|
207
254
|
|
|
@@ -1244,6 +1291,7 @@ async def compute_auto_interval_signal(
|
|
|
1244
1291
|
bb_dev: float,
|
|
1245
1292
|
use_last_closed_candle: bool,
|
|
1246
1293
|
use_websocket_candles: bool = False,
|
|
1294
|
+
startup_backfill_state: Optional[AutoStartupBackfillState] = None,
|
|
1247
1295
|
) -> AutoIntervalSignal:
|
|
1248
1296
|
"""Fetch candles for one interval and compute MACD/SAR/ADX/Bollinger signal state."""
|
|
1249
1297
|
require_talib_available()
|
|
@@ -1252,13 +1300,26 @@ async def compute_auto_interval_signal(
|
|
|
1252
1300
|
raise RuntimeError(f"--auto-periods must be at least {minimum_periods} for the chosen indicator settings.")
|
|
1253
1301
|
# TODO: Determine - is it possible to retrieve candles for multiple coins with one API call? Or is this data \
|
|
1254
1302
|
# TODO: that is available over the websocket?
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1303
|
+
if startup_backfill_state is not None:
|
|
1304
|
+
startup_backfill_state.register_pending(coin, interval)
|
|
1305
|
+
try:
|
|
1306
|
+
candles = await fetch_recent_candles(
|
|
1307
|
+
info,
|
|
1308
|
+
coin,
|
|
1309
|
+
interval,
|
|
1310
|
+
periods,
|
|
1311
|
+
use_websocket_candles=use_websocket_candles,
|
|
1312
|
+
)
|
|
1313
|
+
except Exception as exc:
|
|
1314
|
+
if startup_backfill_state is not None:
|
|
1315
|
+
if is_rate_limit_error(exc):
|
|
1316
|
+
startup_backfill_state.mark_rate_limit()
|
|
1317
|
+
else:
|
|
1318
|
+
startup_backfill_state.mark_non_rate_limit_failure(coin, interval)
|
|
1319
|
+
raise
|
|
1320
|
+
else:
|
|
1321
|
+
if startup_backfill_state is not None:
|
|
1322
|
+
startup_backfill_state.mark_success(coin, interval)
|
|
1262
1323
|
usable_candles = candles[:-1] if use_last_closed_candle and len(candles) > 1 else candles
|
|
1263
1324
|
|
|
1264
1325
|
highs: List[float] = []
|
|
@@ -1391,6 +1452,7 @@ async def evaluate_auto_trade_decision(
|
|
|
1391
1452
|
use_sar_stop_on_shortest_interval: bool,
|
|
1392
1453
|
use_websocket_candles: bool = False,
|
|
1393
1454
|
current_px: Optional[float] = None,
|
|
1455
|
+
startup_backfill_state: Optional[AutoStartupBackfillState] = None,
|
|
1394
1456
|
) -> AutoTradeDecision:
|
|
1395
1457
|
"""Evaluate all configured intervals and return an aggregated trade decision."""
|
|
1396
1458
|
snapshots: List[AutoIntervalSignal] = []
|
|
@@ -1414,6 +1476,7 @@ async def evaluate_auto_trade_decision(
|
|
|
1414
1476
|
bb_dev=bb_dev,
|
|
1415
1477
|
use_last_closed_candle=use_last_closed_candle,
|
|
1416
1478
|
use_websocket_candles=use_websocket_candles,
|
|
1479
|
+
startup_backfill_state=startup_backfill_state,
|
|
1417
1480
|
)
|
|
1418
1481
|
)
|
|
1419
1482
|
except Exception as exc:
|
|
@@ -1585,6 +1648,7 @@ async def scan_auto_trade_candidate(
|
|
|
1585
1648
|
use_sar_stop_on_shortest_interval: bool,
|
|
1586
1649
|
snapshot: AutoScanLoopSnapshot,
|
|
1587
1650
|
use_websocket_candles: bool = False,
|
|
1651
|
+
startup_backfill_state: Optional[AutoStartupBackfillState] = None,
|
|
1588
1652
|
) -> AutoScanCandidate:
|
|
1589
1653
|
"""Scan one market using shared loop snapshots to avoid redundant REST calls."""
|
|
1590
1654
|
existing_pos = snapshot.positions_by_coin.get(scan_coin.upper())
|
|
@@ -1614,6 +1678,7 @@ async def scan_auto_trade_candidate(
|
|
|
1614
1678
|
use_sar_stop_on_shortest_interval=use_sar_stop_on_shortest_interval,
|
|
1615
1679
|
use_websocket_candles=use_websocket_candles,
|
|
1616
1680
|
current_px=snapshot.mids.get(scan_coin.upper()),
|
|
1681
|
+
startup_backfill_state=startup_backfill_state,
|
|
1617
1682
|
)
|
|
1618
1683
|
print_auto_decision(decision, scan_coin)
|
|
1619
1684
|
|
|
@@ -1785,7 +1850,11 @@ async def run_auto_trader(
|
|
|
1785
1850
|
if not owns_clients and (account_address is None or info is None or exchange is None):
|
|
1786
1851
|
raise RuntimeError("Pass account_address, info, and exchange together when reusing initialized clients.")
|
|
1787
1852
|
completed_trades = 0
|
|
1853
|
+
_iter = 0
|
|
1788
1854
|
auto_trades_logger = get_auto_trades_logger()
|
|
1855
|
+
startup_backfill_state = AutoStartupBackfillState(
|
|
1856
|
+
enabled=(coin is None and not use_websocket_candles),
|
|
1857
|
+
)
|
|
1789
1858
|
|
|
1790
1859
|
try:
|
|
1791
1860
|
if owns_clients:
|
|
@@ -2044,7 +2113,17 @@ async def run_auto_trader(
|
|
|
2044
2113
|
stop_requested = True
|
|
2045
2114
|
stop_reason = f"completed {completed_trades} trade(s) and auto looping is disabled"
|
|
2046
2115
|
|
|
2116
|
+
async def _sleep_until_next_scan_batch() -> None:
|
|
2117
|
+
if startup_backfill_state.enabled and startup_backfill_state.pause_between_batches and not startup_backfill_state.complete:
|
|
2118
|
+
print(
|
|
2119
|
+
f"[AUTO] Startup REST candle backfill still in progress; "
|
|
2120
|
+
f"sleeping {startup_backfill_state.pause_seconds:.1f}s before the normal scan interval."
|
|
2121
|
+
)
|
|
2122
|
+
await asyncio.sleep(startup_backfill_state.pause_seconds)
|
|
2123
|
+
await asyncio.sleep(scan_interval)
|
|
2124
|
+
|
|
2047
2125
|
while True:
|
|
2126
|
+
_iter +=1
|
|
2048
2127
|
await _drain_completed_trade_tasks()
|
|
2049
2128
|
if stop_requested:
|
|
2050
2129
|
if active_trade_tasks:
|
|
@@ -2159,6 +2238,7 @@ async def run_auto_trader(
|
|
|
2159
2238
|
use_sar_stop_on_shortest_interval=use_sar_stop_on_shortest_interval,
|
|
2160
2239
|
snapshot=shared_snapshot,
|
|
2161
2240
|
use_websocket_candles=use_websocket_candles,
|
|
2241
|
+
startup_backfill_state=startup_backfill_state,
|
|
2162
2242
|
)
|
|
2163
2243
|
|
|
2164
2244
|
scan_tasks = [asyncio.create_task(_scan_with_limit(scan_coin)) for scan_coin in eligible_scan_coins]
|
|
@@ -2215,14 +2295,14 @@ async def run_auto_trader(
|
|
|
2215
2295
|
await asyncio.gather(*scan_tasks, return_exceptions=True)
|
|
2216
2296
|
|
|
2217
2297
|
if not launch_specs:
|
|
2218
|
-
print('=' * 40 + f'
|
|
2219
|
-
await
|
|
2298
|
+
print('=' * 40 + f'Iteration: {_iter}, sleeping {scan_interval} seconds (--scan-interval). ' + '=' * 40)
|
|
2299
|
+
await _sleep_until_next_scan_batch()
|
|
2220
2300
|
continue
|
|
2221
2301
|
|
|
2222
2302
|
if dry_run:
|
|
2223
2303
|
for launch_coin, _launch_decision, _launch_size in launch_specs:
|
|
2224
2304
|
print(f"[AUTO] Dry run enabled; {launch_coin} signal will not be traded.")
|
|
2225
|
-
await
|
|
2305
|
+
await _sleep_until_next_scan_batch()
|
|
2226
2306
|
continue
|
|
2227
2307
|
|
|
2228
2308
|
for launch_coin, launch_decision, launch_size in launch_specs:
|
|
@@ -2256,7 +2336,7 @@ async def run_auto_trader(
|
|
|
2256
2336
|
)
|
|
2257
2337
|
|
|
2258
2338
|
metrics_start_time_ms = int(time.time() * 1000)
|
|
2259
|
-
await
|
|
2339
|
+
await _sleep_until_next_scan_batch()
|
|
2260
2340
|
except KeyboardInterrupt:
|
|
2261
2341
|
print("\n[!] Caught Ctrl+C, stopping auto trader.")
|
|
2262
2342
|
finally:
|
|
@@ -9,7 +9,7 @@ README = ROOT / "README.md"
|
|
|
9
9
|
|
|
10
10
|
setup(
|
|
11
11
|
name="hypertrader",
|
|
12
|
-
version="0.1.
|
|
12
|
+
version="0.1.2",
|
|
13
13
|
description="Async Hyperliquid trading helper built on the async SDK.",
|
|
14
14
|
long_description=README.read_text(encoding="utf-8"),
|
|
15
15
|
long_description_content_type="text/markdown",
|
|
@@ -26,6 +26,7 @@ setup(
|
|
|
26
26
|
"hyperliquid-python-sdk-async",
|
|
27
27
|
"python-dotenv",
|
|
28
28
|
"uvloop",
|
|
29
|
+
"colored",
|
|
29
30
|
],
|
|
30
31
|
extras_require={
|
|
31
32
|
"auto": [
|
|
@@ -36,6 +37,7 @@ setup(
|
|
|
36
37
|
entry_points={
|
|
37
38
|
"console_scripts": [
|
|
38
39
|
"hypertrader=hypertrader:main",
|
|
40
|
+
"ht=hypertrader:main",
|
|
39
41
|
],
|
|
40
42
|
},
|
|
41
43
|
classifiers=[
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|