
Hl Copy Trader
- 2 installs
- 1 repo stars
- Updated July 29, 2026
- starchild-ai-agent/community-skills
Copy-trade a Hyperliquid address by mirroring its positions scaled to your capital, with a stop-loss line, leverage cap, and periodic sync.
About
A skill that copy-trades any Hyperliquid trader by mirroring their positions and orders scaled to your capital, with a stop-loss line, leverage cap, and periodic sync. A developer uses it to auto-follow a target HL wallet while funds stay in the user's own wallet.
- Mirrors a target HL address scaled to your capital with a scale ratio
- Risk stop-loss line, leverage cap, and periodic sync loop
Hl Copy Trader by the numbers
- 2 all-time installs (skills.sh)
- Ranked #870 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Jul 30, 2026 (Skillselion catalog sync)
npx skills add https://github.com/starchild-ai-agent/community-skills --skill hl-copy-traderAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 1 |
| Last updated | July 29, 2026 |
| Repository | starchild-ai-agent/community-skills ↗ |
What it does
Copy-trade a Hyperliquid address by mirroring its positions scaled to your capital, with a stop-loss line, leverage cap, and periodic sync.
Files
HL Copy Trader
名称 / Name: HL Copy Trader
描述(中文): 一键跟单 Hyperliquid 上的优质交易员。自动读取目标持仓,按你的资金比例等比缩放同步。资金始终在你自己的钱包中。
Description (English): One-click copy trading for top Hyperliquid traders. Automatically mirrors target positions scaled to your capital. Your funds stay in your own wallet.
---
Prerequisites — Wallet Policy
Before any trade, wallet policy must be active. Load the wallet-policy skill and propose the standard wildcard policy (deny key export + allow *). Required for HL deposit, order signing, and cancellation.
---
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
target_address | string | — | Required. Target trader's HL wallet address |
my_capital | number | — | Required. Your capital in USDC |
risk_stop_pct | number | 40 | Stop loss %. Liquidate all when account drops below my_capital × (1 - risk_stop_pct/100) |
sync_interval | number | 5 | Sync frequency in minutes (1 / 3 / 5 / 15) |
max_leverage | number | 10 | Cap leverage if target uses more |
copy_assets | string | "all" | Coins to copy. "all" or comma-separated e.g. "BTC,ETH" |
min_order_size | number | 10 | Skip scaled orders below this USDC notional |
---
Initialization Flow
Run scripts/setup.py to initialize. It: 1. Reads the target's current HL account value 2. Computes scale_ratio = my_capital / target_account_value 3. Shows a confirmation summary to the user (see below) 4. On confirm: deposits if needed, sets leverage, mirrors current positions + orders 5. Registers the sync monitor scheduled task (every sync_interval minutes)
Confirmation summary to show user before starting:
Target trader: {address} (truncated)
Target capital: ${target_value:,.0f}
Your capital: ${my_capital:,.0f}
Scale ratio: 1 : {ratio:.0f}
Stop-loss line: ${stop_value:,.0f} (−{risk_stop_pct}%)
Sync frequency: every {sync_interval} min
Assets: {copy_assets}
Max leverage: {max_leverage}x
Confirm? (yes/no)---
Sync Loop Logic
Run scripts/sync.py on every scheduled trigger. Steps:
1. Risk check first — if account_value < stop_value: close all positions, cancel all orders, pause task, notify user. 2. Fetch target state — positions + open orders via HL info API. 3. Fetch my state — positions + open orders + account value. 4. Sync positions — for each target position: scale size, cap leverage, open/adjust/close as needed. Close any position I hold that target no longer holds. 5. Sync orders — cancel orders target no longer has (via saved paul_oid→my_oid mapping). Place orders target added. Skip if scaled notional < min_order_size or asset not in copy_assets. 6. Notify — only if something changed. Silent on no-op runs.
State is stored in tasks/{job_id}/state.json:
{
"paused": false,
"target_address": "0x...",
"my_capital": 1000,
"scale_ratio": 0.01,
"stop_value": 600,
"sync_interval": 5,
"max_leverage": 10,
"copy_assets": "all",
"min_order_size": 10,
"paul_orders": {"target_oid": "my_oid"},
"lang": "zh"
}---
Notifications & Reports
Language rule: detect language from the user's setup command. Store in state.json as lang: "zh" or lang: "en". All notifications and reports follow that language. User can switch anytime by saying "report in English" or "以后用中文汇报".
Real-time (on change only):
- zh:
[时间] 同步完成:新增 X 笔,取消 X 笔,调整 X 笔仓位 - en:
[time] Synced: +X orders, −X cancelled, X positions adjusted
Daily (UTC 00:00):
- Account value vs starting capital
- Current positions summary (coin, side, size, unrealized PnL)
- Distance to stop-loss line
- Target trader's day performance
Weekly (Sunday UTC 00:00):
- Total PnL ($ and %)
- Target trader's week PnL
- Copy deviation analysis (why my % ≠ target %)
- Operation stats
- Parameter tuning suggestions
---
Risk Warning (always show on setup)
⚠️ Copy trading carries risk. Past performance ≠ future results.
- 5-min sync delay means your fills differ from the target's
- Small scaled orders may be skipped (below
min_order_size) - Start with small capital to validate before scaling up
- You can stop anytime — your funds stay in your own HL account
---
Key Implementation Notes
- Use
HyperliquidClientfromskills/hyperliquid/client.pyfor all HL calls - BTC minimum order size on HL is 0.001 BTC — always
max(scaled_size, 0.001) - After opening position, verify fill via
get_account_statebefore confirming - Deduplication: run cleanup pass on my orders before first sync to avoid doubling
- Scale ratio must be recomputed if target's account value changes significantly (>20%)
- For the weekly report, compare realized PnL from fills, not mark-to-market
---
Files
| File | Purpose |
|---|---|
scripts/setup.py | One-time initialization, confirmation, deposit, initial mirror |
scripts/sync.py | Sync loop — called by scheduled task every N minutes |
references/api.md | HL API notes and client method reference |
HL Copy Trader — API & Client Reference
HyperliquidClient Methods Used
from skills.hyperliquid.client import HyperliquidClient
c = HyperliquidClient()
# Address
my_addr = await c._get_address()
# Read state
acct = await c.get_account_state(address) # marginSummary, assetPositions
orders = await c.get_open_orders(address) # list of orders
fills = await c._info('userFills', user=address) # list of fills
# Trade
await c.update_leverage(coin, leverage, is_cross=True)
await c.market_open(coin, is_buy, size) # IoC market order
await c.market_close(coin, address) # close full position
await c.place_order(coin, is_buy, size, price, order_type='limit')
await c.cancel_order(coin, oid) # cancel by oid
await c.cancel_all_orders(coin)
# Deposit
await c.deposit_usdc(amount) # min 5 USDC, from Arbitrum walletOrder Status Shapes
r = await c.place_order(...)
statuses = r['response']['data']['statuses']
st = statuses[0]
# Resting (limit order sitting in book)
my_oid = st['resting']['oid']
# Filled (market order or limit crossed)
avg_px = st['filled']['avgPx']
my_oid = st['filled']['oid']
# Error
err = st.get('error') # string describing what went wrongHL Info API Shape
# clearinghouseState
{
"marginSummary": {
"accountValue": "1000.00",
"totalMarginUsed": "50.00",
"withdrawable": "950.00"
},
"assetPositions": [
{
"position": {
"coin": "BTC",
"szi": "-0.001", # negative = short
"entryPx": "80000",
"leverage": {"type": "cross", "value": 3},
"unrealizedPnl": "5.00",
"liquidationPx": "120000",
"marginUsed": "25.00"
}
}
]
}
# openOrders
[
{
"coin": "BTC",
"side": "B", # B=buy, A=sell/ask
"sz": "0.001",
"limitPx": "75000",
"oid": 12345678,
"timestamp": 1234567890000
}
]Minimum Sizes
| Coin | Min size | Size decimals |
|---|---|---|
| BTC | 0.001 | 3 |
| ETH | 0.01 | 2 |
| SOL | 0.1 | 1 |
Always max(scaled_size, min_size) before placing orders.
State JSON Schema
{
"paused": false,
"target_address": "0x...",
"my_capital": 1000.0,
"scale_ratio": 0.01,
"stop_value": 600.0,
"sync_interval": 5,
"max_leverage": 10,
"copy_assets": "all",
"min_order_size": 10.0,
"lang": "zh",
"paul_orders": {
"target_oid_string": "my_oid_string"
},
"started_at": "2025-01-01T00:00:00",
"job_id": "interval_abc123",
"last_sync": "2025-01-07T10:00:00",
"last_account_value": 985.0
}#!/usr/bin/env python3
"""
HL Copy Trader — Setup Script
Initializes copy trade: reads target, computes scale, deposits if needed,
mirrors current positions+orders, registers the sync scheduled task.
Usage (called by agent after user confirms):
python3 setup.py \
--target 0xdAe4... \
--capital 1000 \
--risk-stop 40 \
--interval 5 \
--max-leverage 10 \
--assets all \
--min-order 10 \
--lang zh \
--job-id <scheduled_task_job_id>
"""
import asyncio, sys, json, argparse, os
from pathlib import Path
from datetime import datetime
sys.path.insert(0, '/data/workspace')
from skills.hyperliquid.client import HyperliquidClient
MIN_BTC_SIZE = 0.001
HL_INFO = 'https://api.hyperliquid.xyz/info'
# ── Args ──────────────────────────────────────────────────────────────────────
parser = argparse.ArgumentParser()
parser.add_argument('--target', required=True)
parser.add_argument('--capital', type=float, required=True)
parser.add_argument('--risk-stop', type=float, default=40)
parser.add_argument('--interval', type=int, default=5)
parser.add_argument('--max-leverage', type=int, default=10)
parser.add_argument('--assets', default='all')
parser.add_argument('--min-order', type=float, default=10)
parser.add_argument('--lang', default='zh')
parser.add_argument('--job-id', default='')
parser.add_argument('--dry-run', action='store_true')
args = parser.parse_args()
STOP_VALUE = args.capital * (1 - args.risk_stop / 100)
# ── Helpers ───────────────────────────────────────────────────────────────────
def fmtUSD(v): return f"${float(v):,.2f}"
def scale_size(sz, ratio): return max(round(float(sz) * ratio, 3), MIN_BTC_SIZE)
def asset_allowed(coin):
if args.assets == 'all': return True
return coin.upper() in [a.strip().upper() for a in args.assets.split(',')]
# ── Main ──────────────────────────────────────────────────────────────────────
async def main():
c = HyperliquidClient()
my_addr = await c._get_address()
# 1. Read target state
print(f"\nReading target trader: {args.target[:10]}…{args.target[-6:]}")
target_acct = await c.get_account_state(args.target)
target_margin = target_acct.get('marginSummary', {})
target_val = float(target_margin.get('accountValue', 0))
if target_val == 0:
print("ERROR: Target account not found or has zero value.")
sys.exit(1)
scale_ratio = args.capital / target_val
target_positions = target_acct.get('assetPositions', [])
target_orders = await c.get_open_orders(args.target)
# 2. Print confirmation summary
print(f"""
╔══════════════════════════════════════════════╗
║ HL COPY TRADER — Setup ║
╠══════════════════════════════════════════════╣
║ Target: {args.target[:12]}…{args.target[-6:]} ║
║ Target value: {fmtUSD(target_val):<36} ║
║ Your capital: {fmtUSD(args.capital):<36} ║
║ Scale ratio: 1 : {1/scale_ratio:.1f:<31} ║
║ Stop-loss: {fmtUSD(STOP_VALUE)} (−{args.risk_stop}%){'':<22} ║
║ Sync: every {args.interval} min{'':<29} ║
║ Assets: {args.assets:<36} ║
║ Max leverage: {args.max_leverage}x{'':<35} ║
║ Positions: {len(target_positions)} open{'':<32} ║
║ Orders: {len(target_orders)} open{'':<32} ║
╚══════════════════════════════════════════════╝
""")
if args.dry_run:
print("[DRY RUN] Stopping here — no trades executed.")
return
# 3. Read my current HL balance
my_acct = await c.get_account_state(my_addr)
my_val = float(my_acct.get('marginSummary', {}).get('accountValue', 0))
if my_val < args.capital * 0.95:
needed = args.capital - my_val
print(f"\nDepositing ${needed:.2f} USDC to Hyperliquid…")
dep = await c.deposit_usdc(needed)
print(f"Deposit: {dep}")
await asyncio.sleep(15) # wait for bridge
# 4. Mirror positions
print("\n── Mirroring positions ──")
for p in target_positions:
pos = p.get('position', {})
coin = pos.get('coin')
if not asset_allowed(coin): continue
szi = float(pos.get('szi', 0))
if szi == 0: continue
lev = min(int(pos.get('leverage', {}).get('value', 1)), args.max_leverage)
my_sz = scale_size(abs(szi), scale_ratio)
is_buy = szi > 0
# Set leverage
try:
await c.update_leverage(coin, lev, is_cross=True)
except Exception as e:
print(f" Leverage warn ({coin}): {e}")
# Open position
try:
r = await c.market_open(coin, is_buy=is_buy, size=my_sz)
statuses = r.get('response', {}).get('data', {}).get('statuses', [{}])
st = statuses[0]
if 'filled' in st:
side = 'LONG' if is_buy else 'SHORT'
print(f" ✅ {side} {my_sz} {coin} @ avg ${st['filled']['avgPx']}")
else:
print(f" ⚠️ {coin}: {st}")
except Exception as e:
print(f" ❌ {coin}: {e}")
await asyncio.sleep(0.4)
# 5. Mirror orders
print("\n── Mirroring orders ──")
paul_orders_map = {} # target_oid → my_oid
placed = 0
skipped = 0
for o in target_orders:
coin = o.get('coin')
if not asset_allowed(coin): continue
side = o.get('side')
sz = float(o.get('sz', 0))
px = float(o.get('limitPx', 0))
target_oid = str(o.get('oid'))
my_sz = scale_size(sz, scale_ratio)
notional = my_sz * px
if notional < args.min_order:
print(f" ⏭ Skip {side} {my_sz} {coin} @ {px:.0f} — notional ${notional:.1f} < ${args.min_order}")
skipped += 1
continue
is_buy = side == 'B'
try:
r = await c.place_order(coin, is_buy=is_buy, size=my_sz, price=px, order_type='limit')
statuses = r.get('response', {}).get('data', {}).get('statuses', [{}])
st = statuses[0]
if 'resting' in st:
my_oid = str(st['resting']['oid'])
paul_orders_map[target_oid] = my_oid
side_label = 'BUY ' if is_buy else 'SELL'
print(f" ✅ {side_label} {my_sz} {coin} @ ${px:,.0f}")
placed += 1
elif 'filled' in st:
my_oid = str(st['filled']['oid'])
paul_orders_map[target_oid] = my_oid
print(f" ✅ FILLED {my_sz} {coin} @ ${px:,.0f}")
placed += 1
else:
print(f" ⚠️ {coin}: {st}")
except Exception as e:
print(f" ❌ {coin} @ {px:.0f}: {e}")
await asyncio.sleep(0.3)
print(f"\nOrders: {placed} placed, {skipped} skipped (below min size)")
# 6. Save state
state_dir = Path(f'/data/workspace/tasks/{args.job_id}') if args.job_id else Path('/data/workspace/tasks/hl-copy-trader-state')
state_dir.mkdir(parents=True, exist_ok=True)
state = {
'paused': False,
'target_address': args.target,
'my_capital': args.capital,
'scale_ratio': scale_ratio,
'stop_value': STOP_VALUE,
'sync_interval': args.interval,
'max_leverage': args.max_leverage,
'copy_assets': args.assets,
'min_order_size': args.min_order,
'lang': args.lang,
'paul_orders': paul_orders_map,
'started_at': datetime.utcnow().isoformat(),
'job_id': args.job_id,
}
(state_dir / 'state.json').write_text(json.dumps(state, indent=2))
print(f"\n✅ State saved to {state_dir}/state.json")
print(f"✅ Setup complete. Sync monitor will run every {args.interval} minutes.")
asyncio.run(main())
#!/usr/bin/env python3
"""
HL Copy Trader — Sync Script
Called by scheduled task every N minutes.
Reads state.json, syncs positions & orders, enforces risk controls.
Usage:
python3 sync.py --state-dir /data/workspace/tasks/{job_id}
"""
import asyncio, sys, json, argparse
from pathlib import Path
from datetime import datetime, timezone
sys.path.insert(0, '/data/workspace')
from skills.hyperliquid.client import HyperliquidClient
MIN_BTC_SIZE = 0.001
parser = argparse.ArgumentParser()
parser.add_argument('--state-dir', required=True, help='Path to task state directory')
args = parser.parse_args()
STATE_FILE = Path(args.state_dir) / 'state.json'
def load_state():
return json.loads(STATE_FILE.read_text())
def save_state(s):
STATE_FILE.write_text(json.dumps(s, indent=2))
def scale_size(sz, ratio):
return max(round(float(sz) * ratio, 3), MIN_BTC_SIZE)
def asset_allowed(coin, copy_assets):
if copy_assets == 'all': return True
return coin.upper() in [a.strip().upper() for a in copy_assets.split(',')]
def notify(msg, lang):
"""Print notification — task system will push if non-empty stdout."""
now = datetime.now(timezone.utc).strftime('%H:%M UTC')
print(f"[{now}] {msg}")
async def main():
if not STATE_FILE.exists():
print("ERROR: state.json not found. Run setup.py first.")
sys.exit(1)
state = load_state()
lang = state.get('lang', 'zh')
# ── Paused check ──────────────────────────────────────────────
if state.get('paused'):
# Silent — don't push noise when already paused
return
c = HyperliquidClient()
my_addr = await c._get_address()
target_addr = state['target_address']
scale_ratio = state['scale_ratio']
stop_value = state['stop_value']
max_leverage = state.get('max_leverage', 10)
min_order = state.get('min_order_size', 10)
copy_assets = state.get('copy_assets', 'all')
paul_orders_map = state.get('paul_orders', {}) # target_oid → my_oid
# ── Fetch states ──────────────────────────────────────────────
my_acct, target_acct = await asyncio.gather(
c.get_account_state(my_addr),
c.get_account_state(target_addr),
)
my_val = float(my_acct.get('marginSummary', {}).get('accountValue', 0))
# ── Risk check ────────────────────────────────────────────────
if my_val < stop_value:
if lang == 'zh':
msg = f"⚠️ 风控熔断触发!账户余额 ${my_val:.2f} 低于安全线 ${stop_value:.2f}。正在平仓所有仓位并停止跟单…"
else:
msg = f"⚠️ Risk stop triggered! Account ${my_val:.2f} below stop ${stop_value:.2f}. Closing all positions and stopping copy trade…"
print(msg)
try: await c.cancel_all_orders('BTC')
except Exception as e: print(f"Cancel error: {e}")
try: await c.market_close('BTC', my_addr)
except Exception as e: print(f"Close error: {e}")
state['paused'] = True
save_state(state)
if lang == 'zh':
print(f"✅ 已停止跟单。当前余额 ${my_val:.2f}")
else:
print(f"✅ Copy trade stopped. Current balance ${my_val:.2f}")
return
# ── Fetch target orders ───────────────────────────────────────
target_orders_raw = await c.get_open_orders(target_addr)
target_orders = {str(o['oid']): o for o in target_orders_raw if asset_allowed(o.get('coin',''), copy_assets)}
target_positions = {
p['position']['coin']: p['position']
for p in target_acct.get('assetPositions', [])
if asset_allowed(p['position']['coin'], copy_assets)
}
# My orders
my_orders_raw = await c.get_open_orders(my_addr)
my_orders = {str(o['oid']): o for o in my_orders_raw}
# My positions
my_positions = {
p['position']['coin']: p['position']
for p in my_acct.get('assetPositions', [])
}
actions = []
# ── Sync orders ───────────────────────────────────────────────
# 1. Cancel orders target no longer has
for target_oid, my_oid in list(paul_orders_map.items()):
if target_oid not in target_orders:
if my_oid in my_orders:
try:
await c.cancel_order('BTC', int(my_oid))
if lang == 'zh':
actions.append(f"🗑 取消挂单 oid={my_oid}(目标已取消)")
else:
actions.append(f"🗑 Cancelled order oid={my_oid} (target cancelled)")
except Exception as e:
actions.append(f"❌ Cancel failed {my_oid}: {e}")
await asyncio.sleep(0.2)
del paul_orders_map[target_oid]
# 2. Place new orders target has that I don't
for target_oid, o in target_orders.items():
if target_oid not in paul_orders_map:
coin = o.get('coin')
is_buy = o.get('side') == 'B'
my_sz = scale_size(float(o['sz']), scale_ratio)
px = float(o['limitPx'])
notional = my_sz * px
if notional < min_order:
continue # silent skip
try:
r = await c.place_order(coin, is_buy=is_buy, size=my_sz, price=px, order_type='limit')
statuses = r.get('response', {}).get('data', {}).get('statuses', [{}])
st = statuses[0]
if 'resting' in st:
my_oid = str(st['resting']['oid'])
paul_orders_map[target_oid] = my_oid
side_label = '买入' if is_buy else '卖出'
if lang == 'zh':
actions.append(f"➕ 新增挂单 {side_label} {my_sz} {coin} @ ${px:,.0f}")
else:
side_label = 'BUY' if is_buy else 'SELL'
actions.append(f"➕ New order {side_label} {my_sz} {coin} @ ${px:,.0f}")
elif 'filled' in st:
my_oid = str(st['filled']['oid'])
paul_orders_map[target_oid] = my_oid
if lang == 'zh':
actions.append(f"✅ 成交 {my_sz} {coin} @ ${px:,.0f}")
else:
actions.append(f"✅ Filled {my_sz} {coin} @ ${px:,.0f}")
except Exception as e:
actions.append(f"❌ Order failed: {e}")
await asyncio.sleep(0.3)
# ── Sync positions ────────────────────────────────────────────
# Close positions I have that target no longer has
for coin, my_pos in my_positions.items():
if coin not in target_positions:
try:
await c.market_close(coin, my_addr)
if lang == 'zh':
actions.append(f"📉 平仓 {coin}(目标已平仓)")
else:
actions.append(f"📉 Closed {coin} (target closed)")
except Exception as e:
actions.append(f"❌ Close {coin} failed: {e}")
# Adjust positions that differ
for coin, t_pos in target_positions.items():
t_szi = float(t_pos.get('szi', 0))
if t_szi == 0: continue
t_lev = min(int(t_pos.get('leverage', {}).get('value', 1)), max_leverage)
target_sz = scale_size(abs(t_szi), scale_ratio)
my_pos = my_positions.get(coin)
my_szi = float(my_pos.get('szi', 0)) if my_pos else 0.0
diff = round(target_sz * (1 if t_szi > 0 else -1) - my_szi, 3)
if abs(diff) >= MIN_BTC_SIZE:
is_buy = diff > 0
try:
await c.update_leverage(coin, t_lev, is_cross=True)
r = await c.market_open(coin, is_buy=is_buy, size=abs(diff))
side_label = '加仓' if is_buy else '减仓'
if lang == 'zh':
actions.append(f"📐 {side_label} {abs(diff)} {coin}")
else:
side_label = 'Added' if is_buy else 'Reduced'
actions.append(f"📐 {side_label} {abs(diff)} {coin}")
except Exception as e:
actions.append(f"❌ Position adj {coin}: {e}")
# ── Save state & report ───────────────────────────────────────
state['paul_orders'] = paul_orders_map
state['last_sync'] = datetime.utcnow().isoformat()
state['last_account_value'] = my_val
save_state(state)
if actions:
if lang == 'zh':
print(f"💰 账户余额: ${my_val:.2f} | 距熔断线: ${my_val - stop_value:.2f}")
else:
print(f"💰 Account: ${my_val:.2f} | Buffer to stop: ${my_val - stop_value:.2f}")
for a in actions:
print(f" {a}")
# else: no output → no push notification (save cost)
asyncio.run(main())
#!/usr/bin/env python3
"""
HL Copy Trader — Weekly Report
Called by a separate scheduled task every Sunday UTC 00:00.
Usage:
python3 weekly_report.py --state-dir /data/workspace/tasks/{job_id}
"""
import asyncio, sys, json, argparse
from pathlib import Path
from datetime import datetime, timezone, timedelta
sys.path.insert(0, '/data/workspace')
from skills.hyperliquid.client import HyperliquidClient
parser = argparse.ArgumentParser()
parser.add_argument('--state-dir', required=True)
args = parser.parse_args()
STATE_FILE = Path(args.state_dir) / 'state.json'
async def main():
if not STATE_FILE.exists():
print("No state file found.")
return
state = json.loads(STATE_FILE.read_text())
lang = state.get('lang', 'zh')
c = HyperliquidClient()
my_addr = await c._get_address()
target_addr = state['target_address']
my_capital = state['my_capital']
stop_value = state['stop_value']
# Fetch current states
my_acct, target_acct = await asyncio.gather(
c.get_account_state(my_addr),
c.get_account_state(target_addr),
)
my_val = float(my_acct.get('marginSummary', {}).get('accountValue', 0))
target_val = float(target_acct.get('marginSummary', {}).get('accountValue', 0))
# Fills this week
now = datetime.now(timezone.utc)
week_ago = int((now - timedelta(days=7)).timestamp() * 1000)
my_fills = await c._info('userFills', user=my_addr)
if isinstance(my_fills, list):
week_fills = [f for f in my_fills if f.get('time', 0) >= week_ago]
week_pnl = sum(float(f.get('closedPnl', 0)) for f in week_fills)
week_trades = len(week_fills)
else:
week_pnl = 0
week_trades = 0
my_pct = ((my_val - my_capital) / my_capital * 100)
week_pct = (week_pnl / my_capital * 100)
if lang == 'zh':
print(f"""
📊 每周跟单报告 — {now.strftime('%Y-%m-%d')}
{'='*45}
本周收益: ${week_pnl:+.2f} ({week_pct:+.2f}%)
本周交易次数: {week_trades} 笔
{'─'*45}
账户总值: ${my_val:,.2f}
起始本金: ${my_capital:,.2f}
总收益率: {my_pct:+.2f}%
距熔断线: ${my_val - stop_value:,.2f}(安全线 ${stop_value:,.2f})
{'─'*45}
目标交易员账户: ${target_val:,.2f}
缩放比例: 1 : {1/state['scale_ratio']:.0f}
{'='*45}
💡 建议: {'策略运行正常,继续监控' if my_pct > -10 else '注意回撤,考虑减少仓位或暂停跟单'}
""")
else:
print(f"""
📊 Weekly Copy Trade Report — {now.strftime('%Y-%m-%d')}
{'='*45}
Week PnL: ${week_pnl:+.2f} ({week_pct:+.2f}%)
Week trades: {week_trades}
{'─'*45}
Account value: ${my_val:,.2f}
Starting cap: ${my_capital:,.2f}
Total return: {my_pct:+.2f}%
Buffer to stop: ${my_val - stop_value:,.2f} (stop at ${stop_value:,.2f})
{'─'*45}
Target account: ${target_val:,.2f}
Scale ratio: 1 : {1/state['scale_ratio']:.0f}
{'='*45}
💡 Suggestion: {'Strategy running well, continue monitoring' if my_pct > -10 else 'Watch drawdown — consider reducing size or pausing'}
""")
asyncio.run(main())