Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ Market Data -> Composite Fair Value -> Dynamic Spread -> Inventory Skew -> Multi
| Provider | Models | Env Variable |
|----------|--------|-------------|
| Google Gemini | `gemini-2.0-flash` (default), `gemini-2.5-pro` | `GEMINI_API_KEY` |
| Anthropic Claude | `claude-haiku-4-5-20251001`, `claude-sonnet-4-20250514` | `ANTHROPIC_API_KEY` |
| Anthropic Claude | `claude-haiku-4-5-20251001`, `claude-sonnet-4-20250514` | `ANTHROPIC_API_KEY` or `ANTHROPIC_SESSION_TOKEN` |
| OpenAI | `gpt-4o`, `gpt-4o-mini`, `o3-mini` | `OPENAI_API_KEY` |

---
Expand Down Expand Up @@ -648,7 +648,8 @@ hl run my_strategies.my_strategy:MyStrategy -i ETH-PERP --tick 10
| `HL_TESTNET` | No | `true` (default) or `false` for mainnet |
| `BUILDER_ADDRESS` | No | Override builder fee address |
| `BUILDER_FEE_TENTHS_BPS` | No | Override fee rate (default: 100 = 10 bps) |
| `ANTHROPIC_API_KEY` | No | For `claude_agent` with Claude |
| `ANTHROPIC_API_KEY` | No | For `claude_agent` with Claude (API key) |
| `ANTHROPIC_SESSION_TOKEN` | No | For `claude_agent` with Claude (Claude Max session token) |
| `GEMINI_API_KEY` | No | For `claude_agent` with Gemini |
| `OPENAI_API_KEY` | No | For `claude_agent` with OpenAI |

Expand Down
2 changes: 2 additions & 0 deletions cli/commands/mcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
def mcp_serve(
transport: str = typer.Option("stdio", "--transport", "-t",
help="Transport mode: stdio or sse"),
port: int = typer.Option(18790, "--port", "-p",
help="Port for SSE transport (ignored for stdio)"),
):
"""Start MCP server exposing trading tools for AI agents."""
project_root = str(Path(__file__).resolve().parent.parent.parent)
Expand Down
6 changes: 3 additions & 3 deletions cli/commands/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ def setup_check():
ok_items.append("Builder fee: not configured (optional)")

# 5. LLM key (for claude_agent)
if os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("GEMINI_API_KEY"):
ok_items.append("LLM API key found")
if os.environ.get("ANTHROPIC_API_KEY") or os.environ.get("ANTHROPIC_SESSION_TOKEN") or os.environ.get("GEMINI_API_KEY"):
ok_items.append("LLM API key/session token found")
else:
ok_items.append("LLM API key: not set (only needed for claude_agent strategy)")
ok_items.append("LLM API key/session token: not set (only needed for claude_agent strategy)")

# 6. Data directories
data_dir = Path("data/cli")
Expand Down
32 changes: 20 additions & 12 deletions cli/hl_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ def get_account_state(self) -> Dict:
log.error("Failed to get account state: %s", e)
return {}

# Merge HIP-3 DEX positions (e.g. YEX) so watchdog/reconciliation sees them.
for dex_id in HIP3_DEXS:
# Merge HIP-3 DEX positions (e.g. YEX) — testnet only, not available on mainnet.
for dex_id in (HIP3_DEXS if self._hl.testnet else {}):
try:
dex_state = self._info.post("/info", {
"type": "clearinghouseState", "user": self._address, "dex": dex_id,
Expand Down Expand Up @@ -324,18 +324,25 @@ def place_order(
# Round price to HL tick size (price-dependent, 5 sig figs)
price = self._round_price(price, coin)

# For IOC orders, apply slippage to cross the spread and guarantee fill.
# Strategy prices are often at fair value (inside the spread) which won't
# match any resting orders. Push buys above ask, sells below bid.
if tif == "Ioc":
try:
snap = self._hl.get_snapshot(instrument)
# Price adjustment based on order type:
# - IOC: push past spread to guarantee fill (taker)
# - ALO: sit on maker side of book for rebates
try:
snap = self._hl.get_snapshot(instrument)
if tif == "Ioc":
if is_buy and snap.ask > 0:
price = max(price, self._round_price(snap.ask * SLIPPAGE_FACTOR, coin))
price = max(price, self._round_price(
snap.ask * SLIPPAGE_FACTOR, coin))
elif not is_buy and snap.bid > 0:
price = min(price, self._round_price(snap.bid * (2 - SLIPPAGE_FACTOR), coin))
except Exception:
pass # use original price if snapshot fails
price = min(price, self._round_price(
snap.bid * (2 - SLIPPAGE_FACTOR), coin))
elif tif == "Alo":
if is_buy and snap.bid > 0:
price = self._round_price(snap.bid, coin)
elif not is_buy and snap.ask > 0:
price = self._round_price(snap.ask, coin)
except Exception:
pass # use original price if snapshot fails

fill = self._send_order(coin, instrument, side, is_buy, size, price, tif, builder)

Expand Down Expand Up @@ -508,6 +515,7 @@ def place_trigger_order(self, instrument: str, side: str, size: float, trigger_p
coin = self._to_coin(instrument)
is_buy = side.lower() == "buy"
sz = self._round_size(coin, size)
trigger_price = self._round_price(trigger_price, coin)
try:
result = self._exchange.order(
coin, is_buy, sz, trigger_price,
Expand Down
3 changes: 2 additions & 1 deletion cli/skill.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ hl apex run --mainnet # APEX multi-slot
| `HL_TESTNET` | No | `true` (default) or `false` for mainnet |
| `BUILDER_ADDRESS` | No | Override builder fee address (default: hardcoded) |
| `BUILDER_FEE_TENTHS_BPS` | No | Override fee rate (default: 100 = 10 bps) |
| `ANTHROPIC_API_KEY` | No | For `claude_agent` strategy |
| `ANTHROPIC_API_KEY` | No | For `claude_agent` strategy (API key) |
| `ANTHROPIC_SESSION_TOKEN` | No | For `claude_agent` strategy (Claude Max session token) |
| `GEMINI_API_KEY` | No | For `claude_agent` with Gemini |

\* Either `HL_PRIVATE_KEY` or a keystore with `HL_KEYSTORE_PASSWORD` is required.
Expand Down
17 changes: 17 additions & 0 deletions configs/apex_btc_mainnet.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# APEX config — BTC-PERP only, mainnet, $50 account
# Single parameter focus: radar_score_threshold

# Instrument filter
allowed_instruments:
- BTC-PERP

# Entry threshold — the single parameter to tune
radar_score_threshold: 180

# Scaled for $50 account
total_budget: 50.0
max_slots: 1
leverage: 10.0
daily_loss_limit: 10.0
guard_preset: tight
slot_cooldown_ms: 7200000 # 2 hours — prevents fee churn on re-entry
50 changes: 21 additions & 29 deletions configs/autoresearch_program.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Autoresearch Program: APEX Config Optimization

## Objective
Optimize APEX trading strategy parameters by replaying historical trades through the backtest harness and maximizing net PnL while maintaining trade quality.
Optimize the single entry threshold parameter for BTC-PERP mainnet trading by replaying
historical trades through the backtest harness and maximizing net PnL.

## Mutable File
`apex_config.json`
Expand All @@ -20,42 +21,33 @@ python3 scripts/backtest_apex.py --config apex_config.json --trades data/cli/tra
- `trades` — should stay above 5 (quality gate)
- `profit_factor` — should stay above 1.0

## Parameter Bounds (guardrails)
## Single Parameter: radar_score_threshold

| Parameter | Min | Max | Step | Default |
|------------------------------|-------|-------|------|---------|
| radar_score_threshold | 120 | 280 | 10 | 170 |
| pulse_confidence_threshold | 40.0 | 95.0 | 5.0 | 70.0 |
| daily_loss_limit | 50.0 | 5000.0| 50.0 | 500.0 |
| max_same_direction | 1 | 3 | 1 | 2 |
| Parameter | Min | Max | Step | Default |
|-----------------------|-----|-----|------|---------|
| radar_score_threshold | 120 | 280 | 10 | 170 |

## Research Directions

These are common exploration paths based on REFLECT findings:

1. **High FDR (>30%)**: Raise `radar_score_threshold` in [170, 250] to filter low-quality entries that generate fees without sufficient edge.

2. **Low Win Rate (<40%)**: Sweep `pulse_confidence_threshold` in [70, 95] to require higher conviction before entry.
All other parameters are fixed. Do not modify them.

3. **Direction Imbalance**: If one direction is consistently losing, set `max_same_direction` to 1 to force diversification.

4. **Loss Streaks**: Reduce `daily_loss_limit` by 20% increments to cut drawdowns from consecutive losses.

5. **Healthy Strategy**: If metrics look good (win_rate >50%, FDR <15%), try *lowering* `radar_score_threshold` in [140, 170] to capture more trades without degrading quality.
## Research Directions

6. **Fee Drag Emergency**: If fees exceed gross PnL, simultaneously raise `radar_score_threshold` to [220, 280] and `pulse_confidence_threshold` to [85, 95].
1. **High FDR (>30%)**: Raise `radar_score_threshold` toward 250 — filter low-quality entries.
2. **Low Win Rate (<40%)**: Raise `radar_score_threshold` toward 220 — require higher conviction.
3. **Too few trades**: Lower `radar_score_threshold` toward 140 — loosen entry criteria.
4. **Healthy metrics**: Try lowering `radar_score_threshold` toward 140 to capture more trades.
5. **Fee Drag Emergency**: Raise `radar_score_threshold` to [220, 280].

## Workflow

1. Start with the current `apex_config.json` as baseline
1. Start with current `apex_config.json` as baseline
2. Run backtest to get baseline metrics
3. Pick a research direction based on the metrics
4. Modify one parameter at a time within bounds
5. Re-run backtest and compare
6. If `REJECT: too few trades` appears, the config is too restrictive — back off
7. Keep the config that maximizes `net_pnl` while passing all quality gates
3. Pick a direction based on the metrics
4. Change `radar_score_threshold` by one step (±10)
5. Re-run backtest and compare `net_pnl`
6. Keep if improved, revert if not
7. Repeat until no improvement found

## Quality Gates
- Must produce at least 5 round trips
- `profit_factor` must be > 1.0 (net profitable)
- `fdr` must be < 50% (fees not destroying all edge)
- `profit_factor` must be > 1.0
- `fdr` must be < 50%
8 changes: 4 additions & 4 deletions deploy/openclaw-railway/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,18 @@ RUN mv /usr/bin/rg /usr/bin/rg-real \

# Install agent-cli (our trading CLI)
WORKDIR /agent-cli
COPY ../../ .
COPY . .
RUN pip install --no-cache-dir --break-system-packages -e ".[mcp]"

# Wrapper app
WORKDIR /app
COPY package.json ./
COPY deploy/openclaw-railway/package.json ./
RUN npm install --production

COPY src ./src
COPY deploy/openclaw-railway/src ./src

# Workspace defaults (copied to volume at runtime)
COPY workspace /opt/workspace-defaults
COPY deploy/openclaw-railway/workspace /opt/workspace-defaults

# Vendor mcporter skill
RUN set -eux; \
Expand Down
75 changes: 42 additions & 33 deletions deploy/openclaw-railway/src/bootstrap.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ const PROVIDER_MAP = {
blockrun: { key: "blockrun-wallet-key", provider: "blockrun" },
};

function detectAuthMode(credentialValue, provider) {
// For Anthropic, check if it's an OAuth token (starts with sk-ant-oauth-)
// Otherwise assume API key authentication
if (provider === "anthropic" && credentialValue.startsWith("sk-ant-oauth-")) {
return "oauth";
}
return "token";
}

export async function bootstrap() {
console.log("[bootstrap] Starting auto-configuration...");

Expand All @@ -48,7 +57,7 @@ export async function bootstrap() {
}
}

// 3. Generate openclaw.json with our MCP server
// 3. Generate openclaw.json with MCP server
const config = buildConfig();
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
console.log("[bootstrap] Generated openclaw.json");
Expand Down Expand Up @@ -84,49 +93,49 @@ function buildConfig() {
? (process.env.BLOCKRUN_WALLET_KEY || "")
: aiKey;

const config = {
// Security (headless deployment)
deviceAuth: false,
insecureAuth: true,

// Agent settings
agentConcurrency: 10,
subagentConcurrency: 12,

// AI provider
provider: providerInfo.provider,
[providerInfo.key]: credentialValue,
const profileName = `${providerInfo.provider}:auto`;
const authMode = detectAuthMode(credentialValue, providerInfo.provider);

// MCP servers — our trading CLI is the primary tool provider
mcpServers: {
nunchi_trading: {
command: "python3",
args: ["-m", "cli.main", "mcp", "serve"],
cwd: "/agent-cli",
env: {
HL_PRIVATE_KEY: process.env.HL_PRIVATE_KEY || "",
HL_TESTNET: process.env.HL_TESTNET || "true",
...(isBlockrun ? {
BLOCKRUN_WALLET_KEY: process.env.BLOCKRUN_WALLET_KEY || "",
BLOCKRUN_PROXY_PORT: process.env.BLOCKRUN_PROXY_PORT || "8402",
} : {}),
const config = {
gateway: {
mode: "local",
},
agents: {
defaults: {
maxConcurrent: 10,
subagents: { maxConcurrent: 12 },
},
},
auth: {
profiles: {
[profileName]: {
provider: providerInfo.provider,
mode: authMode,
},
},
},
tools: {
mcp: {
servers: {
"nunchi-trading": {
command: "python3",
args: ["-m", "cli.main", "mcp", "serve", "--transport", "stdio"],
cwd: "/agent-cli",
env: {
...process.env,
PYTHONPATH: "/agent-cli",
},
},
},
},
},

// Workspace
workspaceDir: WORKSPACE_DIR,
stateDir: STATE_DIR,
};

// Telegram integration
if (process.env.TELEGRAM_BOT_TOKEN) {
config.channels = {
telegram: {
botToken: process.env.TELEGRAM_BOT_TOKEN,
allowedUsers: process.env.TELEGRAM_USERNAME
? [process.env.TELEGRAM_USERNAME.replace("@", "")]
: [],
},
};
}
Expand Down
Loading