Skip to content

feat: Add interactive trade-ui CLI command#1352

Closed
miohtama wants to merge 31 commits intomasterfrom
feat/trade-ui-command
Closed

feat: Add interactive trade-ui CLI command#1352
miohtama wants to merge 31 commits intomasterfrom
feat/trade-ui-command

Conversation

@miohtama
Copy link
Collaborator

@miohtama miohtama commented Mar 20, 2026

Why

The trade executor needed an interactive CLI command for test trading - selecting pairs, choosing buy/sell direction, and specifying amounts - without manually constructing long CLI invocations. Additionally, Hypercore vault strategies were crashing at startup because the routing system could not correctly identify Hypercore vaults vs ERC-4626 vaults.

Lessons learnt

  • protocol_slug mismatch: The vault API returns protocol_slug=hyperliquid for Hypercore vaults, but export_as_exchange() checked for hypercore. Always verify actual API data before writing string comparisons.
  • other_data unreliable for vault detection: Vault pair exports do not include vault_protocol in other_data. Chain ID is the reliable discriminator - added TradingPairIdentifier.is_hyperliquid_vault() to centralise this logic.
  • Dual chain IDs: Hypercore vaults appear on chain 9999 (synthetic ID from data pipeline) and chain 999 (HyperEVM, used by live Lagoon deployments). Both must be matched.
  • Multi-chain env var fork explosion: When --simulate is set, all configured JSON-RPC env vars spawn Anvil forks. Strategies that only need one chain must filter to their required chain before forking.
  • LOG_LEVEL=info in tests: Left-over debug log levels flood CI output. Tests should use warning or disabled unless they specifically assert on log messages.

Summary

  • Add trade-ui CLI command with Textual-based full-screen TUI for interactive test trade pair selection
  • TUI displays vault pairs with share prices from candle data, supports arrow navigation and Enter to select
  • Add trade direction dialog (buy/sell, buy only, sell only) and amount input
  • Add TradingPairIdentifier.is_hyperliquid_vault() helper, replace 10+ manual other_data.vault_protocol checks across the codebase
  • Add HypercoreVaultPricing.candle_universe support for displaying approximate share prices
  • Fix protocol_slug mismatch in trading-strategy submodule (hyperliquid vs hypercore)
  • Fix default_supported_routers: detect Hypercore vaults by chain_id as fallback for stale cached data
  • Guard create_hypercore_vault_adapter against hot_wallet mode (no vault object)
  • Restrict Anvil fork to strategy-required chains in simulate mode
  • Add unit_testing mode to trade-ui: auto-selects first pair, skips trade execution
  • Add hyper-ai-tui-test.py test strategy for Hypercore vault TUI testing
  • Add tests: test_default_supported_routers, test_hypercore_vault_load, test_hypercore_vault_pricing_candles, test_trade_ui_hypercore
  • Reduce CI log noise: set LOG_LEVEL=warning in 5 tests that had info left from debugging

miohtama and others added 29 commits March 20, 2026 13:27
Rich-based TUI that displays the strategy's trading universe with balances,
lets the user interactively pick a pair, amount and trade mode, then
executes the test trade via the existing make_test_trade() path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Verify the open+close round-trip: buy is a buy, sell is a sell,
same pair, position is closed, reserves still have balance.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No need to start a separate Anvil fork — the simulate flag
handles it via create_web3_config().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…allet log format

- Use getattr() with default in universe_model.py so strategies without
  required_history_period don't crash
- Update check_wallet logging to show "Hot wallet reserve balance" and
  "Vault reserve balance" labels with token names

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace choose_single_chain() with configure_default_chain() so the
  command works when multiple JSON_RPC_* env vars are set
- Update docstring example to use hyper-ai-test.py strategy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Copy strategies/hyper-ai.py to strategies/test_only/hyper-ai-tui-test.py
  for use as the trade-ui example and integration test strategy
- Update docstring example to use the new test strategy
- Use configure_default_chain() instead of choose_single_chain() to fix
  crash when multiple JSON_RPC_* env vars are set

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- HypercoreVaultPricing.get_mid_price() now returns approximate share
  prices from the data pipeline candle universe instead of always 1.0
- Extensively document the pricing model hacks: 1:1 execution pricing,
  approximate candle prices, post-deposit API valuation
- Wire candle_universe through ethereum_protocol_adapters into the
  pricing model
- Add Hypercore->Hyperliquid connection fallback in web3config
- Upgrade TUI to full-screen Textual app with DataTable, modal trade
  dialog, status bar, TVL-sorted pairs, and share price column
- Add textual dependency for Rich-based TUI framework
- Add 7 unit tests for candle-based pricing and TUI price display

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Non-vault pairs trigger RPC calls that retry endlessly on Anvil forks.
Only fetch prices for vault pairs (candle-based, no RPC needed).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- default_supported_routers() now only creates routing for exchanges
  that have actual loaded pairs, preventing RPC errors when vault
  metadata includes exchanges from other chains (e.g. Uniswap on
  Ethereum appearing in a HyperEVM-only vault universe)
- Gate SUPPORTING_PAIRS in hyper-ai-tui-test.py to empty list in
  live trading mode since Uniswap does not exist on HyperEVM

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hypercore vaults use exchange_type=erc_4626_vault in metadata but are
NOT ERC-4626 contracts. When Hypercore vault routing is detected, skip
the ERC-4626 vault routing to prevent empty 0x RPC responses from
calling ERC-4626 methods on Hypercore vault addresses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update all erc_4626_vault type checks to handle the new
hypercore_vault exchange type from the trading-strategy submodule:
- default_supported_routers skips hypercore_vault exchanges cleanly
- trading_strategy_universe uses correct type for Hypercore pairs
- dex_data_translation recognises hypercore_vault as vault kind
- reverse_universe uses correct dex_type string

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Old cached vault data uses erc_4626_vault exchange type for Hypercore
vaults. default_supported_routers() was creating ERC-4626 routing for
these, causing RPC calls to non-existent contracts on HyperEVM.

Fix: skip erc_4626_vault exchanges that belong to known Hypercore
vaults (by exchange_id or chain_id fallback). Genuine ERC-4626 vaults
on other chains are preserved — mixed universes work correctly.

Also fix pre-existing typo: vault_done -> vaults_done flag name.

Add 4 test cases for default_supported_routers covering:
- Hypercore-only universe (no ERC-4626 routing)
- Missing vault_protocol metadata (chain-based fallback)
- Mixed Hypercore + ERC-4626 universe (both routing types)
- New hypercore_vault exchange type

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…smatch

Root cause: Vault.export_as_exchange() checked protocol_slug == 'hypercore'
but the API returns 'hyperliquid', so all Hypercore vaults were exported as
erc_4626_vault. This caused default_supported_routers to create ERC-4626
routing which makes RPC calls to non-existent contracts on HyperEVM.

Fix in trading-strategy submodule: match both 'hypercore' and 'hyperliquid'.

Fix in default_supported_routers: detect Hypercore vaults by chain_id
(9999/999) as fallback for stale cached data, instead of relying on
other_data.vault_protocol which doesn't exist in vault pair exports.

Add test_hypercore_vault_load.py: integration tests with real API data
verifying export types and routing.

Add test_trade_ui_hypercore.py: CLI integration test for trade-ui with
Hypercore strategy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…tection

Replace all other_data.get("vault_protocol") == "hypercore" checks with
the new is_hyperliquid_vault() method that detects by chain_id. The
other_data field doesn't carry vault_protocol for vault-exported pairs,
so the old detection never fired.

Also:
- Skip value_func creation in hot_wallet mode (no vault object)
- Relax assert to warning in create_hypercore_vault_adapter when no
  value_func (allows candle-only pricing for display)
- Guard tx_builder.vault access with hasattr check
- Add unit_testing mode to trade-ui: auto-selects first pair, skips trade
- Working CLI integration test for trade-ui with Hypercore strategy

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both chain IDs are needed:
- 9999 (hypercore): synthetic ID for vault-universe-exported pairs
- 999 (hyperliquid): HyperEVM, used by manually created vault pairs
  in live Lagoon deployments where contracts live on-chain

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The test was using LOG_LEVEL=info which floods CI output with
thousands of lines from data loading. Set to warning and simplify
assertions — command completion without timeout proves success.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These tests had LOG_LEVEL=info left over from debugging, flooding
CI output with thousands of lines from data loading and RPC calls.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hyperliquid silently rejects vault deposits below 5 USDC — no error,
no event, the USDC just stays in the escrow. Add an early assertion in
_create_deposit_or_withdraw_txs() using MINIMUM_VAULT_DEPOSIT from
eth_defi.hyperliquid.core_writer (5_000_000 raw, 6 decimals) to fail
fast with a clear error message.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. _get_share_price_from_candles() now uses the caller's ts instead of
   hard-coding native_datetime_utc_now(). Historical valuation and
   visualisation paths now get the correct price for their timestamp.
   New test verifies a far-past timestamp falls back to 1.0.

2. Add close_only parameter to make_test_trade(). The TUI's "Sell only"
   mode now correctly maps to close_only=True, which skips the buy and
   only closes an existing position. Raises RuntimeError if no open
   position exists, instead of silently opening a new one.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Chain 999 (HyperEVM) is the actual L1 where EVM contracts live — it
does NOT host Hypercore vaults. Only chain 9999 (our synthetic ID for
native Hyperliquid) should match.

Fix create_hypercore_vault_pair() to re-home both base and quote tokens
to chain 9999 so the pair's cross-chain assertion passes and
is_hyperliquid_vault() detects it correctly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix trade_mode string inconsistency: unit_testing path used "open_only"
  but TUI produces "open". Now both use "open".
- Replace broad except (AttributeError, AssertionError) with a pre-check
  for tx_builder.vault presence. AssertionError catch was too broad and
  would suppress legitimate failures.
- Pass single timestamp to _get_price() loop instead of calling
  native_datetime_utc_now() per pair during TUI init.
- Move native_datetime_utc_now import to module level in trade_ui_tui.py.
- Fix misleading docstring: _auto_discover_hypercore_vault only matches
  chain 9999, not "9999 or 999".

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- test_allocation: add is_hyperliquid_vault() to DummyPair mock
- test_hyper_ai_allocation_lockup: use ChainId.hypercore and
  TradingPairKind.vault so is_hyperliquid_vault() detects the pairs
- test_lagoon_e2e: set UNIT_TESTING=false for trade-ui test so the
  mock selection runs and trade execution isn't skipped

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@miohtama miohtama closed this Mar 20, 2026
@miohtama miohtama reopened this Mar 20, 2026
@miohtama miohtama closed this Mar 20, 2026
@miohtama miohtama reopened this Mar 20, 2026
@miohtama miohtama closed this Mar 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant