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.
Files changed (21) hide show
  1. {hypertrader-0.1.1 → hypertrader-0.1.2}/PKG-INFO +2 -1
  2. {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/PKG-INFO +2 -1
  3. {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/entry_points.txt +1 -0
  4. {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/requires.txt +1 -0
  5. {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.py +1 -0
  6. {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/auto_trader.py +92 -12
  7. {hypertrader-0.1.1 → hypertrader-0.1.2}/setup.py +3 -1
  8. {hypertrader-0.1.1 → hypertrader-0.1.2}/README.md +0 -0
  9. {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/SOURCES.txt +0 -0
  10. {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/dependency_links.txt +0 -0
  11. {hypertrader-0.1.1 → hypertrader-0.1.2}/hypertrader.egg-info/top_level.txt +0 -0
  12. {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/market_maker.py +0 -0
  13. {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/position_management.py +0 -0
  14. {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/position_watcher.py +0 -0
  15. {hypertrader-0.1.1 → hypertrader-0.1.2}/modes/trailing_stop.py +0 -0
  16. {hypertrader-0.1.1 → hypertrader-0.1.2}/setup.cfg +0 -0
  17. {hypertrader-0.1.1 → hypertrader-0.1.2}/utils/constants.py +0 -0
  18. {hypertrader-0.1.1 → hypertrader-0.1.2}/utils/helpers.py +0 -0
  19. {hypertrader-0.1.1 → hypertrader-0.1.2}/utils/style.py +0 -0
  20. {hypertrader-0.1.1 → hypertrader-0.1.2}/utils/timer.py +0 -0
  21. {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.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.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,2 +1,3 @@
1
1
  [console_scripts]
2
+ ht = hypertrader:main
2
3
  hypertrader = hypertrader:main
@@ -2,6 +2,7 @@ eth-account
2
2
  hyperliquid-python-sdk-async
3
3
  python-dotenv
4
4
  uvloop
5
+ colored
5
6
 
6
7
  [auto]
7
8
  numpy
@@ -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
- candles = await fetch_recent_candles(
1256
- info,
1257
- coin,
1258
- interval,
1259
- periods,
1260
- use_websocket_candles=use_websocket_candles,
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'Sleeping {scan_interval} seconds .. ' + '=' * 40)
2219
- await asyncio.sleep(scan_interval)
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 asyncio.sleep(scan_interval)
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 asyncio.sleep(scan_interval)
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.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