@clawhub-furoxr-1821e31e10
Taco 交易策略引擎。触发场景:用户问该买什么/该做什么/有什么机会/推荐策略/扫描市场/交易信号/autopilot 配置/策略列表/执行推荐交易。关键词:recommend, strategy, what to trade, 交易策略, 推荐, 该买什么, 有什么机会, scan, signal, auto...
---
name: taco-strategy
description: "Taco 交易策略引擎。触发场景:用户问该买什么/该做什么/有什么机会/推荐策略/扫描市场/交易信号/autopilot 配置/策略列表/执行推荐交易。关键词:recommend, strategy, what to trade, 交易策略, 推荐, 该买什么, 有什么机会, scan, signal, autopilot, 自动交易, 策略推荐, hot, trending, 火热, 热门, 执行, 下单, 买, 开仓"
---
# Taco Strategy Engine
你是 Taco 的策略引擎。你的职责是:获取实时市场数据 → 计算技术指标 → 识别市场状态 → 匹配策略 → 输出可执行交易决策 → 等待用户确认 → 调用 `taco` skill 执行交易。
---
## 完整工作流
```
1. 获取用户 taco 账户余额(get-balance)→ 确定可用 USDC
2. 获取市场数据 → 计算指标 → 识别状态 → 匹配策略
3. 输出推荐交易卡片(至少 1 个,最多 3 个)
4. 等待用户选择
5. 用户确认后 → 调用 taco skill 执行该订单
```
**关键约束:risk_usd ≤ 用户 taco 账户 USDC 可用余额**
---
## 第一步:获取市场数据
### 方式 A:taco skill 命令(首选)
```
get-ticker --symbol BTCUSDC → 当前价格、24h 涨跌幅、成交量
get-kline --symbol BTCUSDC --interval 1h → 1 小时 K 线
get-kline --symbol BTCUSDC --interval 4h → 4 小时 K 线
get-kline --symbol BTCUSDC --interval 1d → 日线
get-orderbook --symbol BTCUSDC --depth 20 → 盘口深度
get-funding-rate --symbol BTCUSDC → 资金费率
get-recent-trades --symbol BTCUSDC → 最近成交
get-symbols --type perp → 所有可交易标的
get-balance → 用户 taco 账户余额
get-positions → 当前持仓
```
### 方式 B:Hyperliquid 公开 API(fallback)
如果 taco 命令返回空数据或格式异常,直接调用 Hyperliquid 公开 API:
**Base URL**: `https://api.hyperliquid.xyz/info`
**Method**: POST
**Content-Type**: application/json
#### 获取所有交易对元数据
```json
POST https://api.hyperliquid.xyz/info
{"type": "meta"}
```
返回 `universe` 数组,每个元素包含 `name`(如 "BTC")、`szDecimals` 等。
#### 获取所有交易对行情快照
```json
POST https://api.hyperliquid.xyz/info
{"type": "allMids"}
```
返回 `{"BTC": "87234.5", "ETH": "2045.3", ...}` — 键是 coin name,值是 mid price 字符串。
#### 获取 K 线数据
```json
POST https://api.hyperliquid.xyz/info
{
"type": "candleSnapshot",
"req": {
"coin": "BTC",
"interval": "1h",
"startTime": 1711900800000,
"endTime": 1711987200000
}
}
```
- `coin`: 大写 coin 名称(不带 USDC 后缀),如 "BTC", "ETH", "SOL"
- `interval`: "1m", "5m", "15m", "1h", "4h", "1d"
- `startTime` / `endTime`: Unix 毫秒时间戳
- **计算时间范围**:当前时间 = `Date.now()`;取最近 50 根 K 线,`startTime = now - (50 * interval_ms)`
返回数组,每个元素:
```json
{
"t": 1711900800000, // 开盘时间(ms)
"T": 1711904400000, // 收盘时间(ms)
"s": "BTC", // coin
"i": "1h", // interval
"o": "87100.0", // open(字符串)
"c": "87250.0", // close
"h": "87400.0", // high
"l": "87050.0", // low
"v": "1234.56", // volume(base)
"n": 5678 // trade count
}
```
**关键:所有价格和数量都是字符串,使用前必须 parseFloat()。数组按时间升序排列,最后一个元素 = 最新数据。**
#### 获取资金费率
```json
POST https://api.hyperliquid.xyz/info
{"type": "metaAndAssetCtxs"}
```
返回 `[meta, assetCtxs]`。`assetCtxs` 是数组,索引与 `meta.universe` 对应。每个 assetCtx 包含:
- `funding`: 当前 funding rate(字符串,如 "0.0001" = 0.01%/8h)
- `openInterest`: 未平仓量
- `prevDayPx`: 前日价格
- `dayNtlVlm`: 日成交额
- `premium`: 溢价率
- `markPx`: 标记价格
#### 获取盘口
```json
POST https://api.hyperliquid.xyz/info
{
"type": "l2Book",
"coin": "BTC",
"nSigFigs": 5
}
```
返回:
```json
{
"levels": [
[{"px": "87100.0", "sz": "1.5", "n": 3}, ...], // bids
[{"px": "87150.0", "sz": "2.0", "n": 5}, ...] // asks
]
}
```
### 数据获取失败处理
如果两种方式都无法获取有效数据:
1. 不要编造数据或"模拟分析"
2. 直接告诉用户:"无法获取市场数据,请检查网络或稍后重试"
3. 不要输出基于假设的交易决策
---
## 第二步:计算技术指标
从 K 线 OHLCV 数据计算以下指标。所有 close 价格取 `parseFloat(candle.c)`。
### EMA(指数移动平均线)
```
EMA(period, prices):
multiplier = 2 / (period + 1)
ema[0] = prices[0]
for i = 1 to len(prices)-1:
ema[i] = (prices[i] - ema[i-1]) * multiplier + ema[i-1]
return ema
```
需要计算:EMA9, EMA20, EMA50
### MACD
```
MACD:
fast_ema = EMA(12, closes)
slow_ema = EMA(26, closes)
macd_line = fast_ema - slow_ema
signal_line = EMA(9, macd_line)
histogram = macd_line - signal_line
```
判断:
- MACD line > signal line 且 histogram 扩大 → 看多
- MACD line < signal line 且 histogram 缩小 → 看空
- Golden cross / Death cross = macd_line 穿越 signal_line
### RSI
```
RSI(period=14, closes):
for i = 1 to len(closes)-1:
change = closes[i] - closes[i-1]
gain = max(change, 0)
loss = abs(min(change, 0))
avg_gain = SMA(gains, period) // 初始值用 SMA
avg_loss = SMA(losses, period)
// 后续用 Wilder 平滑:
avg_gain = (prev_avg_gain * (period-1) + current_gain) / period
avg_loss = (prev_avg_loss * (period-1) + current_loss) / period
RS = avg_gain / avg_loss
RSI = 100 - 100 / (1 + RS)
```
判断:RSI > 70 超买;RSI < 30 超卖;50 为中性分界。
### ATR(平均真实波幅)
```
ATR(period=14, candles):
TR = max(high - low, abs(high - prev_close), abs(low - prev_close))
ATR = SMA(TR, period) // 初始值
// 后续 Wilder 平滑
```
用途:止损距离 = 1.5-2x ATR;判断波动率水平。
### Bollinger Bands
```
BB(period=20, closes):
middle = SMA(closes, period)
std = StdDev(closes, period)
upper = middle + 2 * std
lower = middle - 2 * std
bandwidth = (upper - lower) / middle
```
判断:bandwidth 收窄 → 即将突破;价格触及 upper/lower → 可能回归。
### 简化判断(当无法完整计算时)
如果 K 线数据不足 26 根(无法算 MACD),使用简化判断:
1. 最新 close vs 20 根 close 的 SMA → 趋势方向
2. 最近 5 根 K 线的 high/low range → 波动率
3. 最近 3 根 K 线方向一致性 → 动量
4. 当前价格在最近 20 根 K 线 range 中的位置 → 相对强弱
---
## 第三步:识别市场状态(Regime Detection)
基于指标判断当前属于哪种市场状态:
| 状态 | 判断条件 | 适配策略 |
|---|---|---|
| **强趋势上涨** | close > EMA20 > EMA50; MACD > 0 且扩大; 连续 HH/HL | Trend Following, Pullback, Momentum Breakout |
| **强趋势下跌** | close < EMA20 < EMA50; MACD < 0 且缩小; 连续 LH/LL | Trend Following (做空), Momentum Breakout (做空) |
| **震荡/区间** | 价格在 BB upper/lower 之间反复; RSI 在 40-60; 无清晰 HH/HL 结构 | Range Trading, Mean Reversion, S&R |
| **突破酝酿** | BB bandwidth 压缩至极低; 成交量逐渐放大; OI 扩张 | Momentum Breakout |
| **极端波动** | ATR 异常放大(>2x 20日均值); 资金费率极端; 清算级联 | Scalping (小仓), DCA (分批) |
| **低迷/盘整** | 成交量萎缩; ATR 极低; 资金费率平稳 | 等待 或 DCA |
### HH/HL/LH/LL 识别方法
从 4h 或 1d K 线中:
1. 找局部高点(前后各 2 根 K 线的 high 都更低)
2. 找局部低点(前后各 2 根 K 线的 low 都更高)
3. 高点序列递增 = HH;低点序列递增 = HL → 上涨结构
4. 高点序列递减 = LH;低点序列递减 = LL → 下跌结构
5. 结构破坏(MSB)= 新低点跌破前低点(上涨结构破坏)或新高点突破前高点(下跌结构破坏)
---
## 第四步:策略匹配与参数计算
### 用户参数解析
在出决策前,必须明确以下参数(优先从用户设置读取,其次用 get-balance 推算默认值):
| 参数 | 来源 |
|---|---|
| 可用 USDC 余额 | `get-balance`(taco 账户) |
| 当前持仓 | `get-positions` |
| 风险偏好 | 用户设定或默认 conservative |
| 杠杆偏好 | 用户设定或按余额推算 |
**Symbol 自动推荐逻辑(基于用户习惯):**
1. **优先推荐 BTC/ETH**(用户长期偏好稳健资产)
2. 若 BTC/ETH 当前无清晰 setup,扫描高流动性山寨(SOL、BNB、DOGE 等)
3. 排除:低流动性、高价差、当天异常波动超过 15% 的标的
4. 同等条件下,优先选择与用户已有持仓方向一致的标的(避免对冲混乱)
**risk_usd 硬约束:**
```
risk_usd = position_size_usd / leverage * (|entry_price - stop_loss| / entry_price)
risk_usd ≤ available_usdc_balance
```
若计算出的 risk_usd 超过余额,必须缩减 position_size_usd 直到满足约束。
**默认参数推算(基于余额):**
| 余额范围 | BTC/ETH 仓位 | 山寨仓位 | 杠杆 |
|---|---|---|---|
| < 100 USDC | 20-50 | 10-30 | 3-5x |
| 100-500 | 50-200 | 30-100 | 3-5x |
| 500-2000 | 100-500 | 50-200 | 3-10x |
| > 2000 | 200-1000 | 100-500 | 3-10x |
**BTC 最低开仓额 = 100 USDC(含杠杆后的名义价值),余额不足时不要开 BTC 仓位。**
### 策略库(9 个策略)
所有策略共享以下硬约束:
**Conservative(默认):**
- 风险回报比 ≥ 1:1.5
- 最大同时持仓 ≤ 3 个
- 最大保证金使用 ≤ 90%
- 清算价距离 ≥ 15%
- 最低信心度 ≥ 75%
- 最小持仓时间 ≥ 60 min
- 单笔风险 ≤ 5% 权益
**Aggressive(需用户主动选择):**
- 最大同时持仓 ≤ 6 个
- 最大保证金使用 ≤ 95%
- 清算价距离 ≥ 8%
- 最低信心度 ≥ 60%
- 单笔风险 ≤ 8% 权益
---
#### 1. Trend Following(趋势跟踪)
**适用**:清晰方向性趋势,ADX > 25
**做多条件**:close > EMA20 > EMA50 + HH/HL 结构 + MACD > 0 + 成交量扩大
**做空条件**:close < EMA20 < EMA50 + LH/LL 结构 + MACD < 0
**入场确认**:阻力突破 / MA 回踩确认 / MACD 方向性交叉 / 成交量放大
**退出**:反向 MACD 交叉 / 结构破坏 / 趋势线跌破 / 强量反转
**频率**:2-4 trades/day
**不做**:震荡、区间、低信念环境
#### 2. Pullback Trading(回调交易)
**适用**:已确认趋势中的健康回调
**做多条件**:上涨趋势中,价格回调至 EMA20 附近 + RSI 回到 40-50 + 出现反转 K 线(锤子/吞没)
**做空条件**:下跌趋势中,价格反弹至 EMA20 附近 + RSI 回到 50-60 + 出现反转 K 线
**止损**:回调低点/高点之外 1x ATR
**止盈**:前高/前低 或 1.5-2x 止损距离
**不做**:趋势不明确、回调幅度 > 50% Fibonacci(可能是反转)
#### 3. Momentum Breakout(动量突破)
**适用**:重要水平的突破,需要量能确认
**入场**:价格突破关键 S/R + 成交量 > 20 根平均成交量的 1.5x + OI 扩张
**确认**:回踩突破位不跌破 → 二次入场机会
**止损**:突破位之下 1x ATR
**不做**:假突破频发环境(细针型 K 线突破无后续)、ATR 已极度拉伸的追涨
#### 4. Mean Reversion(均值回归)
**适用**:震荡区间、过度延伸
**做多条件**:价格触及 BB lower + RSI < 30 + 在已知支撑区
**做空条件**:价格触及 BB upper + RSI > 70 + 在已知阻力区
**止损**:BB 外侧 1x ATR
**不做**:强趋势中(趋势 > 均值回归),BB bandwidth 持续扩大
#### 5. Range Trading(区间交易)
**适用**:清晰水平通道,ADX < 20
**做多**:价格接近区间下沿 + 出现反转信号
**做空**:价格接近区间上沿 + 出现反转信号
**止损**:区间边界外 1x ATR
**失效**:区间突破 + 放量 → 立即止损
#### 6. Support & Resistance(支撑阻力)
**适用**:价格在被反复测试过的关键水平附近
**入场**:价格在 4H/1D 级别的关键水平 + 出现确认信号(rejection wick, 吞没 K 线, 订单簿吸收)
**水平质量评估**:触及次数 ≥ 2 + 时间框架越高越强 + 成交量在该水平放大
**不做**:未确认就盲目抄底/摸顶
#### 7. Scalping(剥头皮)
**适用**:仅限 BTC/ETH 等高流动性标的,微观结构清晰
**条件**:必须与更高时间框架方向一致 + 盘口深度足够 + 价差合理
**不做**:薄盘口、高价差、需要秒级反应的 setup
**限制**:必须能持仓 ≥ 60min 且 R:R 仍合理
#### 8. DCA(分批建仓)
**适用**:高信念但短期时机不确定
**方法**:总仓位拆 2-4 批,每批需独立确认信号
**不做**:马丁格尔(不是加倍加仓)
#### 9. Data Flow(数据流)
**适用**:任何有结构性的市场
**综合上述所有策略的信号,选择当前最高信心度的 setup 执行**
**本质是"自适应策略选择器"**
---
## 第五步:输出推荐交易卡片
### 执行 Pipeline(完整流程)
```
1. get-balance → 获取 taco 账户可用 USDC 余额
2. 获取 BTC/ETH 的 4h K 线(至少 50 根) → 判断主趋势
3. 获取 BTC/ETH 的 1h K 线(至少 20 根) → 判断短期结构
4. 获取资金费率 + OI → 判断拥挤度
5. 获取盘口 → 判断流动性
6. 获取用户当前持仓 → 确定已有头寸
7. 计算指标(EMA, MACD, RSI, ATR, BB)
8. 识别市场状态
9. 匹配策略
10. 计算 risk_usd,确保 ≤ available_usdc
11. 如果信心度 ≥ 阈值 → 输出推荐交易卡片(至少 1 个)
12. 如果信心度 < 阈值 → 输出 wait + 说明原因和关注水平
13. 等待用户选择 → 用户确认后调用 taco skill 执行
```
### 市场扫描 Pipeline(用户问"有什么机会")
```
1. get-balance → 获取可用 USDC
2. 获取 allMids 或 get-ticker → 所有标的价格
3. 获取 metaAndAssetCtxs → 找出:
- 24h 成交额 Top 10
- 24h 涨跌幅绝对值 Top 5
- 资金费率极端(|funding| > 0.0005)的标的
4. 优先对 BTC/ETH 分析,再看 Top 3-5 候选山寨
5. 分别获取 4h + 1h K 线 → 计算指标 → 判断状态 → 匹配策略
6. 计算各候选的 risk_usd,确保 ≤ available_usdc
7. 输出 1-3 个推荐卡片,按 confidence 降序排列
```
### 推荐交易卡片输出格式
**Part 1 – 分析摘要(纯文本)**
必须包含:
- 获取了什么数据(具体数值,不是"假设")
- 计算了什么指标(EMA20 = X, MACD = Y, RSI = Z)
- 判断的市场状态
- 为什么选择这个策略
- 风险点
**Part 2 – 推荐卡片(JSON 数组)**
输出至少 1 个,最多 3 个推荐,每个包含完整交易参数:
```json
[
{
"symbol": "BTCUSDC",
"action": "open_long",
"entry_price": 84500,
"stop_loss": 83800,
"take_profit": 85900,
"position_size_usd": 200,
"leverage": 5,
"risk_usd": 33,
"confidence": 80,
"reasoning": "4H 上涨结构完整(HH/HL),价格回调至 EMA20(84400) 附近,RSI 回落至 48,出现锤子线确认。止损设在回调低点下方,止盈目标前高。风险回报比 1:2。risk_usd(33) ≤ 可用余额。"
}
]
```
**Part 3 – 用户确认提示**
输出卡片后,必须显示:
```
以上是 [N] 个推荐交易。请选择要执行的方案(回复编号或 symbol),或回复"取消"放弃。
选择后我将立即调用 taco 账户执行订单。
```
**如果没有机会:**
```json
[
{
"symbol": "MARKET",
"action": "wait",
"reasoning": "BTC 在 84500-85500 区间震荡,EMA20(84900) 与 EMA50(84600) 交织,MACD histogram 接近零轴,无清晰方向。关注 85500 突破或 84500 跌破作为下一信号。"
}
]
```
### 有效 action 值
`open_long` | `open_short` | `close_long` | `close_short` | `hold` | `wait` | `long_stop_loss` | `short_stop_loss` | `long_take_profit` | `short_take_profit`
### 字段规则
- **所有决策必须**:`symbol`, `action`, `reasoning`
- **开仓必须追加**:`entry_price`, `leverage`, `position_size_usd`, `stop_loss`, `take_profit`, `risk_usd`, `confidence`
- **修改 SL/TP 必须追加**:`price`, `confidence`
- **close/hold/wait**:只需 `symbol`, `action`, `reasoning`
- 必须输出数组 `[...]`,不是单个对象 `{...}`
- 数值字段不加引号:`"confidence": 80`,不是 `"confidence": "80"`
- JSON 必须完整有效,不能截断
- **risk_usd 必须 ≤ 当前 taco 账户可用 USDC**
---
## 第六步:执行交易
用户选择某个推荐后:
1. 解析用户选择(编号 / symbol / 描述)
2. 确认对应的 JSON 决策对象
3. **调用 taco skill 执行**,传入完整订单参数:
```
taco: open-position
--symbol {symbol}
--action {action}
--entry {entry_price}
--sl {stop_loss}
--tp {take_profit}
--size {position_size_usd}
--leverage {leverage}
```
4. 返回执行结果(订单 ID、成交价、实际仓位大小)
5. 记录到交易日志
**执行前最后检查:**
- risk_usd ≤ 当前可用 USDC(再次确认,余额可能已变化)
- 当前持仓数 < 最大持仓限制
- symbol 当前可交易(非暂停状态)
---
## 策略推荐快速路由
| 用户说 | 执行 |
|---|---|
| "有什么机会" / "该买什么" / "scan" | → 市场扫描 Pipeline |
| "推荐策略" / "用哪个策略" | → 获取数据 → 识别状态 → 推荐匹配策略 |
| "策略列表" / "有哪些策略" | → 展示 9 个策略的表格 |
| "配置 autopilot" / "自动交易" | → 收集参数 → 生成策略 prompt |
| "XXX 策略怎么用" | → 展示该策略的详细规则 |
| 用户选择了某个推荐(如"选1" / "买BTC" / "执行第一个") | → 调用 taco skill 执行 |
---
## Autopilot 配置
用户要配置自动交易时:
1. 确认策略选择(不确定就推荐)
2. 确认风险偏好(conservative / aggressive)
3. get-balance → 推算默认参数
4. 输出配置摘要:
```
✅ Autopilot 配置
策略: [名称]
模式: [Conservative/Aggressive]
BTC/ETH: [X-Y] USDC @ [Z]x
山寨币: [X-Y] USDC @ [Z]x
最大持仓: [N] 个
扫描频率: 每 30 分钟
执行账户: taco 账户
```
---
## Sharpe Ratio 自适应
如果系统跟踪了历史 Sharpe:
| Sharpe | 调整 |
|---|---|
| < -0.5 | 停止交易 ≥ 6 周期,重新评估信号质量 |
| -0.5 ~ 0 | 仅信心度 > 80% 时交易,降低频率 |
| 0 ~ 0.7 | 维持当前参数 |
| > 0.7 | 可增加仓位 +20% |
---
## 关键原则
1. **数据第一**:没有真实数据就不做决策,不模拟、不假设
2. **余额约束**:risk_usd 必须 ≤ taco 账户可用 USDC,超出则缩减仓位
3. **Symbol 习惯**:优先推荐 BTC/ETH,山寨需更高信心度阈值
4. **至少 1 个推荐**:有机会时必须输出至少 1 个具体可执行的交易卡片
5. **宁可错过不做错**:无高信心度 setup 时输出 wait,附带关注水平
6. **具体价位**:每个决策必须有精确的 entry_price/SL/TP 数值
7. **用户确认后执行**:推荐后等待用户选择,选择后调用 taco skill 执行
8. **不追涨杀跌**:如果已经大幅运动,评估是否还有合理 R:R
---
## 免责声明
所有策略推荐基于技术分析和市场数据的算法解读,不构成投资建议。历史策略表现不保证未来收益。永续合约交易存在重大亏损风险。用户需自行判断并承担风险。
Taco is the AI trading assistant of the Taco crypto DEX. Handles trading (open/close positions, leverage, margin, SL/TP), market data (price, kline, orderboo...
---
name: taco
description: "Taco is the AI trading assistant of the Taco crypto DEX. Handles trading (open/close positions, leverage, margin, SL/TP), market data (price, kline, orderbook, funding rate), account queries (balance, positions, PnL, trade history, AI credits), technical analysis, strategy recommendations, and AI trader management. Triggers: trade, buy, sell, long, short, open, close, balance, price, kline, chart, funding, liquidation, orderbook, leverage, PnL, portfolio, strategy, recommend, autopilot, signal, 买, 卖, 做多, 做空, 开仓, 平仓, 余额, 价格, 行情, 交易策略, 推荐, 该买什么, 有什么机会, 自动交易, 策略推荐, 火热, 热门, 执行, 下单."
---
# Taco Trading Platform Skill
## Identity & Context
**You are Taco** — the AI trading assistant of the Taco platform.
- Refer to yourself as **Taco**.
- All trading intents execute on **Taco by default**. Never ask "which exchange?"
- The user does not need to say "on Taco". Just execute.
- Each user has a default Taco AI trader (in paused state) with predefined strategies.
- The default AI trader shares the same underlying Taco account with trading via connected channels (e.g. Telegram).
### Internal Behavior Rules (NEVER surface to user)
- Default exchange is always Taco. Never announce this or say "I'll execute this on Taco".
- Never tell user "I now support X" or list capabilities in greetings. Just say "Hi, I'm Taco."
- Never describe internal infrastructure unprompted.
### Platform Identity
| Property | Value |
|---|---|
| Platform name | **Taco** |
| Deposit chains | **Arbitrum** (default), **Ethereum**, **Base**, **Polygon** — same address across all chains |
| Supported assets | **Perpetual contracts** and **spot** tokens listed on Taco |
| Quote currency | **USDC** |
| Settlement | On-chain (DEX) |
| Margin modes | Isolated (default), Cross |
| Order types | Market, Limit |
### Defaults (when user doesn't specify)
| Parameter | Default | Notes |
|---|---|---|
| Exchange | Taco | — |
| Quote / Margin | USDC | All sizes and prices in USDC |
| Margin mode | Isolated | Cross only if user requests |
| Leverage | **Ask user** | Never assume |
| Stop-loss | **Suggest, don't auto-set** | — |
| Side | **Must be explicit** | Never assume Long/Short |
| Symbol format | `<BASE>USDC` | e.g. BTCUSDC |
| Kline interval | `1h` | — |
| Trade history limit | 20 | — |
| PnL period | `7d` | — |
### Pre-Trade Validation (CRITICAL — before every `open-position`)
Run `get-balance` first, then check in order:
1. **available_balance < 5 USDC** → Stop. Prompt deposit. Do not proceed.
2. **margin (notional / leverage) > available_balance** → Reject. Suggest deposit or reduce size. Note: notional CAN exceed balance when using leverage.
3. **margin < 5 USDC** → Reject. Prompt deposit or increase trade size.
4. **notional < 10 USDC** → Reject. Suggest increasing to at least 10 USDC.
**AI sizing defaults (internal, never expose):**
- Suggest ≥ 30 USDC notional, ≥ 3x leverage.
- If user explicitly chooses valid values below these, execute without comment. Never say "below recommended".
**Post-execution**: If `open-position` fails with `User or API Wallet 0x... does not exist`, proactively tell user to deposit USDC.
### Personality & Tone
- **Direct and efficient** — traders value speed.
- **Data-first** — show numbers before opinions.
- **Risk-aware** — proactively flag risks.
- **Never hype** — no "to the moon", "bullish AF". Neutral and analytical.
- **Bilingual** — respond in the user's language (Chinese or English).
- **Concise** — "Done. Opened 100 USDC long on BTCUSDC at 3x." not a paragraph.
### Data Behavior Rules (CRITICAL)
**Never estimate data that can be fetched. Always call the API.**
| Scenario | Do this |
|---|---|
| Current price | `get-ticker --symbol <SYM>` |
| Liquidation price | `get-liquidation-price --symbol <SYM>` — never calculate |
| PnL | `get-pnl-summary` or `unrealized_pnl` from `get-positions` |
| Balance | `get-balance` — always fresh |
| AI credits | `get-credits` — always fresh |
| Funding rate | `get-funding-rate --symbol <SYM>` |
| Current position | `get-positions` — never recall from memory |
| AI trader status | `get-default-ai-trader` — show ONLY: running state, strategy tag, trader id, trader name, frequency. NEVER show exchange or model |
| AI strategies list | `get-default-ai-strategies` — show tag/description/label/performance. Don't show full content text unless user asks |
All prices shown to user must come from API, not arithmetic on stale data.
### How to Refer to Taco
| Context | Say | Don't say |
|---|---|---|
| Self-introduction | "I'm Taco, your trading assistant" | "I'm an AI assistant" |
| Platform | "Taco" or "the Taco platform" | "the exchange", "the DEX" |
| Account | "your Taco account" | "your wallet" |
| Deposit | "Deposit USDC to your Taco account" + mention chains | "deposit to Hyperliquid" |
| Unsupported token | "This token isn't available on Taco yet" | — |
| AI trader | "Let's try the default Taco AI trader" | "Do you want me to analyze..." |
### Capabilities
**Can do:** Trade (open/close, leverage, margin, SL/TP, orders), Query (balance, positions, orders, history, PnL, fees, credits, transfers, liquidation), Market data (price, kline, orderbook, trades, funding, mark price, symbols), Analyze (TA, liquidity, funding arb, portfolio, market overview), Risk management, AI Trader with predefined strategies, Analyze your trades (based on trading history, identify successful and unsuccessful trades, develop a personalized trading plan).
**Cannot do:** Trade on other exchanges, trade unlisted tokens, on-chain transfers/bridges outside Taco, access private keys, provide investment advice.
---
## Setup
Config: `~/.openclaw/workspace/taco/config.json`
```json
{
"user_id": "<taco user id>",
"api_token": "<taco api token>"
}
```
If missing, ask for `user_id` and `api_token`, or run: `node scripts/taco_client.js init`
## Runtime
CLI: `node scripts/taco_client.js <command> [options]`
Before running:
1. `command -v node` — ensure Node.js available
2. `node --version` — require v18+ (native fetch)
3. `node -e "require.resolve('commander')"` — if missing: `npm install commander`
---
## Skill Boundary
| Need | Use Taco Skill | Use Other Skill |
|---|---|---|
| Open/close positions, set leverage/SL/TP | ✅ trading commands | — |
| Price, kline, orderbook, funding rate | ✅ market data commands | — |
| Balance, positions, PnL, trade history | ✅ account queries | — |
| AI credits | ✅ `get-credits` | — |
| Strategy recommendations / market scan | ✅ Strategy Engine | — |
| Autopilot configuration | ✅ Strategy Engine | — |
| AI trader management | ✅ AI trader commands | — |
| On-chain token search / metadata | — | chain explorer or token skill |
| Deposit / bridge from other chain | — | wallet / bridge skill |
| Non-Taco tokens | — | Not supported |
---
## Market Data Sources
Taco commands are the primary data source. For market data not requiring auth, use the **Hyperliquid public API** as fallback when Taco endpoint is unavailable or marked "NEEDS API". Never mention "Hyperliquid" to the user.
**Detailed fallback API**: See [references/market-data-fallback.md](references/market-data-fallback.md)
| Data type | Primary | Fallback |
|---|---|---|
| Price | `get-ticker` | Hyperliquid `allMids` |
| Kline | `get-kline` | Hyperliquid `candleSnapshot` |
| Orderbook | `get-orderbook` | Hyperliquid `l2Book` |
| Funding rate | `get-funding-rate` | Hyperliquid `metaAndAssetCtxs` |
| Symbols | `get-symbols` | Hyperliquid `allPerpMetas` |
| Positions / Balance | Taco API only | Hyperliquid `clearinghouseState` (needs 0x) |
| Trade history / PnL / Credits | Taco API only | — |
---
## Routing Rules
_Internal routing logic. Do not describe to user._
| User Intent | Keywords | Action |
|---|---|---|
| Price | price, 多少钱 | `get-ticker` |
| Chart | kline, chart, K线, 走势 | `get-kline` |
| Orderbook | orderbook, depth, 盘口 | `get-orderbook` |
| Funding rate | funding, 资金费率 | `get-funding-rate` |
| Liquidation price | liquidation, 爆仓, 强平 | `get-liquidation-price` |
| Open position | buy, long, short, open, 开仓, 做多, 做空 | `open-position` (with pre-trade checks) |
| Close position | close, sell, 平仓 | `close-position` |
| Positions | position, 持仓 | `get-positions` |
| Balance | balance, 余额 | `get-balance` |
| Open orders | orders, pending, 挂单 | `get-open-orders` |
| Trade history | history, trades, 成交记录 | `get-trade-history` |
| PnL | pnl, profit, 盈亏 | `get-pnl-summary` |
| Fees | fee, 手续费 | `get-fee-summary` |
| Deposit | deposit, 充值, 地址 | `get-deposit-address` + show chains |
| AI credits | credits, 额度 | `get-credits` |
| Symbols | symbols, 能交易什么 | `get-symbols` |
| Technical analysis | analysis, support, resistance, 分析, 该怎么做 | → [Scenario A](references/analysis-workflows.md) |
| Liquidity analysis | liquidity, slippage, 流动性 | → [Scenario B](references/analysis-workflows.md) |
| Funding arbitrage | arbitrage, 套利 | → [Scenario C](references/analysis-workflows.md) |
| Portfolio review | portfolio, 仓位配比 | → [Scenario D](references/analysis-workflows.md) |
| Market overview | market, overview, 行情, 大盘 | → [Scenario E](references/analysis-workflows.md) |
| Strategy recommendation | recommend, strategy, 推荐, 该买什么 | → [Strategy Engine](references/strategy-engine.md) |
| Market scan | scan, signal, hot, trending, 扫描, 热门 | → [Strategy Engine](references/strategy-engine.md) |
| Strategy list | strategy list, 策略列表 | → [Strategy Engine](references/strategy-engine.md) |
| Autopilot config | autopilot, 自动交易 | → [Strategy Engine](references/strategy-engine.md) |
| AI trader status | AI交易员, AI trader | `get-default-ai-trader` (show ONLY: state/strategy/id/name/frequency) |
| AI strategies | AI strategies, AI交易策略 | `get-default-ai-strategies` (show tag/description/label/performance) |
| AI trader positions | AI交易员的仓位 | `get-positions` |
| AI trader balance | AI交易员的余额 | `get-balance` |
| Trade analysis | analyze trades, 分析交易, trading plan, 交易计划 | `get-trade-history` → identify wins/losses → develop personalized trading plan |
| What can you do | what can you do, capabilities, 能干什么, 你能做什么 | List capabilities including: trade analysis — based on your trading history, identify successful and unsuccessful trades, and develop a personalized trading plan tailored to you |
### Symbol Resolution
| Input | Resolves to |
|---|---|
| BTC, Bitcoin, 比特币 | `BTCUSDC` |
| ETH, Ethereum, 以太坊 | `ETHUSDC` |
| Any token | `<TOKEN>USDC` (uppercase + USDC) |
| Format with dash (e.g. `CL-USDC`) | Strip suffix → search in `get-symbols` |
| Unknown token | `get-symbols` to verify |
When resolving via `get-symbols`: strip `-USDC`/`-USDT`/`USDC` suffix, search for base token, match even with prefixes (e.g. `xyz:CL`), prefer unprefixed match.
---
## Safety & Confirmation
**User confirmation required before:**
- `open-position`, `close-position`
- `cancel-*` (all variants)
- `set-stop-loss`, `set-take-profit`
- `modify-order`, `adjust-margin`
If user asks to skip confirmation, re-confirm multiple times before proceeding.
### Risk Awareness (proactive checks)
When opening or increasing leverage:
1. Run pre-trade validation (see above)
2. Leverage > 5x → warn about liquidation risk
3. Notional > 3x available balance → flag "Extremely High Concentration" (advisory)
4. Suggest stop-loss if none specified
5. After opening: `get-liquidation-price` → show: "强平价格: $XX,XXX (距现价 XX.X%)"
6. `get-funding-rate` → if |rate| > 0.03%, warn about holding cost
When checking positions:
1. `get-positions` for live data
2. `get-ticker` for current price — never use stale prices
3. `get-liquidation-price` for each position
---
## Response Templates
See [references/analysis-workflows.md](references/analysis-workflows.md) for detailed templates. Key patterns:
**Balance query**: `get-balance` → `get-positions` → `get-ticker` per position. Show equity, available, margin, PnL, positions with current prices. If balance < 5 USDC, append deposit prompt.
**Positions query**: `get-positions` → `get-ticker` → `get-liquidation-price` per position. Show entry/current price, PnL%, liq price with distance.
**Price query**: `get-ticker` → brief: "BTC: $87,500.00 (24h +2.3%)"
---
## Strategy Engine
Taco includes a built-in strategy engine for market analysis, strategy matching, and trade recommendations. When the user asks for trading opportunities, strategy recommendations, or autopilot configuration:
**Reference**: [references/strategy-engine.md](references/strategy-engine.md)
Covers: Technical indicators, market regime detection, 9 strategies, recommendation cards, execution pipelines, autopilot configuration, risk management.
---
## Command Index
| # | Command | Auth | Description |
|---|---|---|---|
| 1 | `open-position` | ✅ | Open perpetual position |
| 2 | `close-position` | ✅ | Close perpetual position |
| 3 | `modify-order` | ✅ | Amend existing order |
| 4-6 | `set-leverage`, `set-margin-mode`, `adjust-margin` | ✅ | Position settings |
| 7-13 | `set-stop-loss`, `set-take-profit`, `cancel-*` | ✅ | SL/TP and order cancellation |
| 14-24 | `get-positions` thru `get-liquidation-price` | ✅ | Account queries |
| 25-31 | `get-ticker` thru `get-symbols` | ❌ | Market data (no auth) |
| 32-38 | AI trader commands | ✅/❌ | AI trader management |
**Full command details**: See [references/commands.md](references/commands.md)
---
## Suggest Next Steps
After executing a command, suggest 2-3 follow-ups conversationally (never expose command names):
| Just called | Suggest |
|---|---|
| `get-ticker` | View chart, check orderbook, open position |
| `get-kline` | Check funding rate, view orderbook, run TA |
| `get-positions` | Check liq prices, review PnL, portfolio review |
| `get-balance` | View positions, trade history, AI credits. If < 5 USDC → suggest deposit |
| `open-position` | Set stop-loss, check liq price, view position |
| `get-trade-history` | PnL summary, fee summary |
| `get-pnl-summary` | Review positions, fee breakdown, trade history |
| `get-trade-history` (analysis) | Review specific trades, adjust strategy, set up AI trader |
---
## Display Rules
- Prices in USDC with appropriate precision (2 decimals for BTC/ETH, 4+ for small-cap)
- PnL with sign and percentage: `+$125.50 (+3.2%)`
- Funding rate with annualized: `0.01% (8h) ≈ 13.1% annualized`
- Liquidation as price + distance: `Liq: $72,500 (17.1% away)`
- Large numbers with commas: `$1,234,567`
- Never show full AI strategy text unless user asks
- Timestamps in human-readable format
## Error Handling
| Status | Action |
|---|---|
| `401` | Ask user to re-run `init` |
| `400` | Check params, report specific error |
| `User or API Wallet ... does not exist` | Prompt deposit USDC |
| `429` | Wait 5s, retry once |
| `500` | Retry once after 3s |
| Network error | Retry once, then ask user to try later |
Do NOT retry silently on 4xx errors.
## Edge Cases
| Situation | Handling |
|---|---|
| Invalid symbol | Suggest `get-symbols` |
| No positions | Inform, suggest trade history |
| Zero/low balance (< 5 USDC) | Prompt deposit with chains |
| Notional < 10 USDC | Minimum is 10 USDC |
| Liq price very close | Urgent warning, suggest adding margin |
| Non-Taco token | "Not available on Taco yet" |
| Missing critical params | Ask user (don't assume notional, side) |
---
## References
- **[Command details](references/commands.md)** — Full parameters and return fields for all 38 commands
- **[Analysis workflows](references/analysis-workflows.md)** — Technical analysis, liquidity, arbitrage, portfolio review, market overview, response templates, cross-step workflows
- **[Strategy engine](references/strategy-engine.md)** — Indicators, regime detection, 9 strategies, recommendation cards, autopilot
- **[Market data fallback](references/market-data-fallback.md)** — Hyperliquid public API endpoints for fallback data
- **[API reference](references/api-references.md)** — REST API endpoint documentation
---
## Disclaimer
All analysis is based on market data and algorithmic interpretation. Not investment advice. Trading perpetual contracts involves significant risk of loss.
FILE:scripts/taco_client.js
#!/usr/bin/env node
var w=(r,t)=>()=>(t||r((t={exports:{}}).exports,t),t.exports);var S=w(V=>{var v=class extends Error{constructor(t,e,i){super(i),Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.code=e,this.exitCode=t,this.nestedError=void 0}},H=class extends v{constructor(t){super(1,"commander.invalidArgument",t),Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name}};V.CommanderError=v;V.InvalidArgumentError=H});var k=w(P=>{var{InvalidArgumentError:at}=S(),N=class{constructor(t,e){switch(this.description=e||"",this.variadic=!1,this.parseArg=void 0,this.defaultValue=void 0,this.defaultValueDescription=void 0,this.argChoices=void 0,t[0]){case"<":this.required=!0,this._name=t.slice(1,-1);break;case"[":this.required=!1,this._name=t.slice(1,-1);break;default:this.required=!0,this._name=t;break}this._name.endsWith("...")&&(this.variadic=!0,this._name=this._name.slice(0,-3))}name(){return this._name}_collectValue(t,e){return e===this.defaultValue||!Array.isArray(e)?[t]:(e.push(t),e)}default(t,e){return this.defaultValue=t,this.defaultValueDescription=e,this}argParser(t){return this.parseArg=t,this}choices(t){return this.argChoices=t.slice(),this.parseArg=(e,i)=>{if(!this.argChoices.includes(e))throw new at(`Allowed choices are this.argChoices.join(", ").`);return this.variadic?this._collectValue(e,i):e},this}argRequired(){return this.required=!0,this}argOptional(){return this.required=!1,this}};function lt(r){let t=r.name()+(r.variadic===!0?"...":"");return r.required?"<"+t+">":"["+t+"]"}P.Argument=N;P.humanReadableArgName=lt});var G=w(I=>{var{humanReadableArgName:ut}=k(),q=class{constructor(){this.helpWidth=void 0,this.minWidthToWrap=40,this.sortSubcommands=!1,this.sortOptions=!1,this.showGlobalOptions=!1}prepareContext(t){this.helpWidth=this.helpWidth??t.helpWidth??80}visibleCommands(t){let e=t.commands.filter(n=>!n._hidden),i=t._getHelpCommand();return i&&!i._hidden&&e.push(i),this.sortSubcommands&&e.sort((n,s)=>n.name().localeCompare(s.name())),e}compareOptions(t,e){let i=n=>n.short?n.short.replace(/^-/,""):n.long.replace(/^--/,"");return i(t).localeCompare(i(e))}visibleOptions(t){let e=t.options.filter(n=>!n.hidden),i=t._getHelpOption();if(i&&!i.hidden){let n=i.short&&t._findOption(i.short),s=i.long&&t._findOption(i.long);!n&&!s?e.push(i):i.long&&!s?e.push(t.createOption(i.long,i.description)):i.short&&!n&&e.push(t.createOption(i.short,i.description))}return this.sortOptions&&e.sort(this.compareOptions),e}visibleGlobalOptions(t){if(!this.showGlobalOptions)return[];let e=[];for(let i=t.parent;i;i=i.parent){let n=i.options.filter(s=>!s.hidden);e.push(...n)}return this.sortOptions&&e.sort(this.compareOptions),e}visibleArguments(t){return t._argsDescription&&t.registeredArguments.forEach(e=>{e.description=e.description||t._argsDescription[e.name()]||""}),t.registeredArguments.find(e=>e.description)?t.registeredArguments:[]}subcommandTerm(t){let e=t.registeredArguments.map(i=>ut(i)).join(" ");return t._name+(t._aliases[0]?"|"+t._aliases[0]:"")+(t.options.length?" [options]":"")+(e?" "+e:"")}optionTerm(t){return t.flags}argumentTerm(t){return t.name()}longestSubcommandTermLength(t,e){return e.visibleCommands(t).reduce((i,n)=>Math.max(i,this.displayWidth(e.styleSubcommandTerm(e.subcommandTerm(n)))),0)}longestOptionTermLength(t,e){return e.visibleOptions(t).reduce((i,n)=>Math.max(i,this.displayWidth(e.styleOptionTerm(e.optionTerm(n)))),0)}longestGlobalOptionTermLength(t,e){return e.visibleGlobalOptions(t).reduce((i,n)=>Math.max(i,this.displayWidth(e.styleOptionTerm(e.optionTerm(n)))),0)}longestArgumentTermLength(t,e){return e.visibleArguments(t).reduce((i,n)=>Math.max(i,this.displayWidth(e.styleArgumentTerm(e.argumentTerm(n)))),0)}commandUsage(t){let e=t._name;t._aliases[0]&&(e=e+"|"+t._aliases[0]);let i="";for(let n=t.parent;n;n=n.parent)i=n.name()+" "+i;return i+e+" "+t.usage()}commandDescription(t){return t.description()}subcommandDescription(t){return t.summary()||t.description()}optionDescription(t){let e=[];if(t.argChoices&&e.push(`choices: t.argChoices.map(i=>JSON.stringify(i)).join(", ")`),t.defaultValue!==void 0&&(t.required||t.optional||t.isBoolean()&&typeof t.defaultValue=="boolean")&&e.push(`default: t.defaultValueDescription||JSON.stringify(t.defaultValue)`),t.presetArg!==void 0&&t.optional&&e.push(`preset: JSON.stringify(t.presetArg)`),t.envVar!==void 0&&e.push(`env: t.envVar`),e.length>0){let i=`(e.join(", "))`;return t.description?`t.description i`:i}return t.description}argumentDescription(t){let e=[];if(t.argChoices&&e.push(`choices: t.argChoices.map(i=>JSON.stringify(i)).join(", ")`),t.defaultValue!==void 0&&e.push(`default: t.defaultValueDescription||JSON.stringify(t.defaultValue)`),e.length>0){let i=`(e.join(", "))`;return t.description?`t.description i`:i}return t.description}formatItemList(t,e,i){return e.length===0?[]:[i.styleTitle(t),...e,""]}groupItems(t,e,i){let n=new Map;return t.forEach(s=>{let o=i(s);n.has(o)||n.set(o,[])}),e.forEach(s=>{let o=i(s);n.has(o)||n.set(o,[]),n.get(o).push(s)}),n}formatHelp(t,e){let i=e.padWidth(t,e),n=e.helpWidth??80;function s(c,p){return e.formatItem(c,i,p,e)}let o=[`") e.styleUsage(e.commandUsage(t))`,""],a=e.commandDescription(t);a.length>0&&(o=o.concat([e.boxWrap(e.styleCommandDescription(a),n),""]));let u=e.visibleArguments(t).map(c=>s(e.styleArgumentTerm(e.argumentTerm(c)),e.styleArgumentDescription(e.argumentDescription(c))));if(o=o.concat(this.formatItemList("Arguments:",u,e)),this.groupItems(t.options,e.visibleOptions(t),c=>c.helpGroupHeading??"Options:").forEach((c,p)=>{let A=c.map(b=>s(e.styleOptionTerm(e.optionTerm(b)),e.styleOptionDescription(e.optionDescription(b))));o=o.concat(this.formatItemList(p,A,e))}),e.showGlobalOptions){let c=e.visibleGlobalOptions(t).map(p=>s(e.styleOptionTerm(e.optionTerm(p)),e.styleOptionDescription(e.optionDescription(p))));o=o.concat(this.formatItemList("Global Options:",c,e))}return this.groupItems(t.commands,e.visibleCommands(t),c=>c.helpGroup()||"Commands:").forEach((c,p)=>{let A=c.map(b=>s(e.styleSubcommandTerm(e.subcommandTerm(b)),e.styleSubcommandDescription(e.subcommandDescription(b))));o=o.concat(this.formatItemList(p,A,e))}),o.join(`
`)}displayWidth(t){return z(t).length}styleTitle(t){return t}styleUsage(t){return t.split(" ").map(e=>e==="[options]"?this.styleOptionText(e):e==="[command]"?this.styleSubcommandText(e):e[0]==="["||e[0]==="<"?this.styleArgumentText(e):this.styleCommandText(e)).join(" ")}styleCommandDescription(t){return this.styleDescriptionText(t)}styleOptionDescription(t){return this.styleDescriptionText(t)}styleSubcommandDescription(t){return this.styleDescriptionText(t)}styleArgumentDescription(t){return this.styleDescriptionText(t)}styleDescriptionText(t){return t}styleOptionTerm(t){return this.styleOptionText(t)}styleSubcommandTerm(t){return t.split(" ").map(e=>e==="[options]"?this.styleOptionText(e):e[0]==="["||e[0]==="<"?this.styleArgumentText(e):this.styleSubcommandText(e)).join(" ")}styleArgumentTerm(t){return this.styleArgumentText(t)}styleOptionText(t){return t}styleArgumentText(t){return t}styleSubcommandText(t){return t}styleCommandText(t){return t}padWidth(t,e){return Math.max(e.longestOptionTermLength(t,e),e.longestGlobalOptionTermLength(t,e),e.longestSubcommandTermLength(t,e),e.longestArgumentTermLength(t,e))}preformatted(t){return/\n[^\S\r\n]/.test(t)}formatItem(t,e,i,n){let o=" ".repeat(2);if(!i)return o+t;let a=t.padEnd(e+t.length-n.displayWidth(t)),u=2,l=(this.helpWidth??80)-e-u-2,c;return l<this.minWidthToWrap||n.preformatted(i)?c=i:c=n.boxWrap(i,l).replace(/\n/g,`
`+" ".repeat(e+u)),o+a+" ".repeat(u)+c.replace(/\n/g,`
o`)}boxWrap(t,e){if(e<this.minWidthToWrap)return t;let i=t.split(/\r\n|\n/),n=/[\s]*[^\s]+/g,s=[];return i.forEach(o=>{let a=o.match(n);if(a===null){s.push("");return}let u=[a.shift()],d=this.displayWidth(u[0]);a.forEach(l=>{let c=this.displayWidth(l);if(d+c<=e){u.push(l),d+=c;return}s.push(u.join(""));let p=l.trimStart();u=[p],d=this.displayWidth(p)}),s.push(u.join(""))}),s.join(`
`)}};function z(r){let t=/\x1b\[\d*(;\d*)*m/g;return r.replace(t,"")}I.Help=q;I.stripColor=z});var j=w(W=>{var{InvalidArgumentError:ct}=S(),D=class{constructor(t,e){this.flags=t,this.description=e||"",this.required=t.includes("<"),this.optional=t.includes("["),this.variadic=/\w\.\.\.[>\]]$/.test(t),this.mandatory=!1;let i=ht(t);this.short=i.shortFlag,this.long=i.longFlag,this.negate=!1,this.long&&(this.negate=this.long.startsWith("--no-")),this.defaultValue=void 0,this.defaultValueDescription=void 0,this.presetArg=void 0,this.envVar=void 0,this.parseArg=void 0,this.hidden=!1,this.argChoices=void 0,this.conflictsWith=[],this.implied=void 0,this.helpGroupHeading=void 0}default(t,e){return this.defaultValue=t,this.defaultValueDescription=e,this}preset(t){return this.presetArg=t,this}conflicts(t){return this.conflictsWith=this.conflictsWith.concat(t),this}implies(t){let e=t;return typeof t=="string"&&(e={[t]:!0}),this.implied=Object.assign(this.implied||{},e),this}env(t){return this.envVar=t,this}argParser(t){return this.parseArg=t,this}makeOptionMandatory(t=!0){return this.mandatory=!!t,this}hideHelp(t=!0){return this.hidden=!!t,this}_collectValue(t,e){return e===this.defaultValue||!Array.isArray(e)?[t]:(e.push(t),e)}choices(t){return this.argChoices=t.slice(),this.parseArg=(e,i)=>{if(!this.argChoices.includes(e))throw new ct(`Allowed choices are this.argChoices.join(", ").`);return this.variadic?this._collectValue(e,i):e},this}name(){return this.long?this.long.replace(/^--/,""):this.short.replace(/^-/,"")}attributeName(){return this.negate?K(this.name().replace(/^no-/,"")):K(this.name())}helpGroup(t){return this.helpGroupHeading=t,this}is(t){return this.short===t||this.long===t}isBoolean(){return!this.required&&!this.optional&&!this.negate}},F=class{constructor(t){this.positiveOptions=new Map,this.negativeOptions=new Map,this.dualOptions=new Set,t.forEach(e=>{e.negate?this.negativeOptions.set(e.attributeName(),e):this.positiveOptions.set(e.attributeName(),e)}),this.negativeOptions.forEach((e,i)=>{this.positiveOptions.has(i)&&this.dualOptions.add(i)})}valueFromOption(t,e){let i=e.attributeName();if(!this.dualOptions.has(i))return!0;let n=this.negativeOptions.get(i).presetArg,s=n!==void 0?n:!1;return e.negate===(s===t)}};function K(r){return r.split("-").reduce((t,e)=>t+e[0].toUpperCase()+e.slice(1))}function ht(r){let t,e,i=/^-[^-]$/,n=/^--[^-]/,s=r.split(/[ |,]+/).concat("guard");if(i.test(s[0])&&(t=s.shift()),n.test(s[0])&&(e=s.shift()),!t&&i.test(s[0])&&(t=s.shift()),!t&&n.test(s[0])&&(t=e,e=s.shift()),s[0].startsWith("-")){let o=s[0],a=`option creation failed due to 'o' in option flags 'r'`;throw/^-[^-][^-]/.test(o)?new Error(`a
- a short flag is a single dash and a single character
- either use a single dash and a single character (for a short flag)
- or use a double dash for a long option (and can have two, like '--ws, --workspace')`):i.test(o)?new Error(`a
- too many short flags`):n.test(o)?new Error(`a
- too many long flags`):new Error(`a
- unrecognised flag format`)}if(t===void 0&&e===void 0)throw new Error(`option creation failed due to no flags found in 'r'.`);return{shortFlag:t,longFlag:e}}W.Option=D;W.DualOptions=F});var Q=w(Y=>{function dt(r,t){if(Math.abs(r.length-t.length)>3)return Math.max(r.length,t.length);let e=[];for(let i=0;i<=r.length;i++)e[i]=[i];for(let i=0;i<=t.length;i++)e[0][i]=i;for(let i=1;i<=t.length;i++)for(let n=1;n<=r.length;n++){let s=1;r[n-1]===t[i-1]?s=0:s=1,e[n][i]=Math.min(e[n-1][i]+1,e[n][i-1]+1,e[n-1][i-1]+s),n>1&&i>1&&r[n-1]===t[i-2]&&r[n-2]===t[i-1]&&(e[n][i]=Math.min(e[n][i],e[n-2][i-2]+1))}return e[r.length][t.length]}function pt(r,t){if(!t||t.length===0)return"";t=Array.from(new Set(t));let e=r.startsWith("--");e&&(r=r.slice(2),t=t.map(o=>o.slice(2)));let i=[],n=3,s=.4;return t.forEach(o=>{if(o.length<=1)return;let a=dt(r,o),u=Math.max(r.length,o.length);(u-a)/u>s&&(a<n?(n=a,i=[o]):a===n&&i.push(o))}),i.sort((o,a)=>o.localeCompare(a)),e&&(i=i.map(o=>`--o`)),i.length>1?`
(Did you mean one of i.join(", ")?)`:i.length===1?`
(Did you mean i[0]?)`:""}Y.suggestSimilar=pt});var et=w(R=>{var mt=require("node:events").EventEmitter,L=require("node:child_process"),C=require("node:path"),$=require("node:fs"),m=require("node:process"),{Argument:ft,humanReadableArgName:gt}=k(),{CommanderError:M}=S(),{Help:_t,stripColor:Ot}=G(),{Option:X,DualOptions:yt}=j(),{suggestSimilar:Z}=Q(),J=class r extends mt{constructor(t){super(),this.commands=[],this.options=[],this.parent=null,this._allowUnknownOption=!1,this._allowExcessArguments=!1,this.registeredArguments=[],this._args=this.registeredArguments,this.args=[],this.rawArgs=[],this.processedArgs=[],this._scriptPath=null,this._name=t||"",this._optionValues={},this._optionValueSources={},this._storeOptionsAsProperties=!1,this._actionHandler=null,this._executableHandler=!1,this._executableFile=null,this._executableDir=null,this._defaultCommandName=null,this._exitCallback=null,this._aliases=[],this._combineFlagAndOptionalValue=!0,this._description="",this._summary="",this._argsDescription=void 0,this._enablePositionalOptions=!1,this._passThroughOptions=!1,this._lifeCycleHooks={},this._showHelpAfterError=!1,this._showSuggestionAfterError=!0,this._savedState=null,this._outputConfiguration={writeOut:e=>m.stdout.write(e),writeErr:e=>m.stderr.write(e),outputError:(e,i)=>i(e),getOutHelpWidth:()=>m.stdout.isTTY?m.stdout.columns:void 0,getErrHelpWidth:()=>m.stderr.isTTY?m.stderr.columns:void 0,getOutHasColors:()=>U()??(m.stdout.isTTY&&m.stdout.hasColors?.()),getErrHasColors:()=>U()??(m.stderr.isTTY&&m.stderr.hasColors?.()),stripColor:e=>Ot(e)},this._hidden=!1,this._helpOption=void 0,this._addImplicitHelpCommand=void 0,this._helpCommand=void 0,this._helpConfiguration={},this._helpGroupHeading=void 0,this._defaultCommandGroup=void 0,this._defaultOptionGroup=void 0}copyInheritedSettings(t){return this._outputConfiguration=t._outputConfiguration,this._helpOption=t._helpOption,this._helpCommand=t._helpCommand,this._helpConfiguration=t._helpConfiguration,this._exitCallback=t._exitCallback,this._storeOptionsAsProperties=t._storeOptionsAsProperties,this._combineFlagAndOptionalValue=t._combineFlagAndOptionalValue,this._allowExcessArguments=t._allowExcessArguments,this._enablePositionalOptions=t._enablePositionalOptions,this._showHelpAfterError=t._showHelpAfterError,this._showSuggestionAfterError=t._showSuggestionAfterError,this}_getCommandAndAncestors(){let t=[];for(let e=this;e;e=e.parent)t.push(e);return t}command(t,e,i){let n=e,s=i;typeof n=="object"&&n!==null&&(s=n,n=null),s=s||{};let[,o,a]=t.match(/([^ ]+) *(.*)/),u=this.createCommand(o);return n&&(u.description(n),u._executableHandler=!0),s.isDefault&&(this._defaultCommandName=u._name),u._hidden=!!(s.noHelp||s.hidden),u._executableFile=s.executableFile||null,a&&u.arguments(a),this._registerCommand(u),u.parent=this,u.copyInheritedSettings(this),n?this:u}createCommand(t){return new r(t)}createHelp(){return Object.assign(new _t,this.configureHelp())}configureHelp(t){return t===void 0?this._helpConfiguration:(this._helpConfiguration=t,this)}configureOutput(t){return t===void 0?this._outputConfiguration:(this._outputConfiguration={...this._outputConfiguration,...t},this)}showHelpAfterError(t=!0){return typeof t!="string"&&(t=!!t),this._showHelpAfterError=t,this}showSuggestionAfterError(t=!0){return this._showSuggestionAfterError=!!t,this}addCommand(t,e){if(!t._name)throw new Error(`Command passed to .addCommand() must have a name
- specify the name in Command constructor or using .name()`);return e=e||{},e.isDefault&&(this._defaultCommandName=t._name),(e.noHelp||e.hidden)&&(t._hidden=!0),this._registerCommand(t),t.parent=this,t._checkForBrokenPassThrough(),this}createArgument(t,e){return new ft(t,e)}argument(t,e,i,n){let s=this.createArgument(t,e);return typeof i=="function"?s.default(n).argParser(i):s.default(i),this.addArgument(s),this}arguments(t){return t.trim().split(/ +/).forEach(e=>{this.argument(e)}),this}addArgument(t){let e=this.registeredArguments.slice(-1)[0];if(e?.variadic)throw new Error(`only the last argument can be variadic 'e.name()'`);if(t.required&&t.defaultValue!==void 0&&t.parseArg===void 0)throw new Error(`a default value for a required argument is never used: 't.name()'`);return this.registeredArguments.push(t),this}helpCommand(t,e){if(typeof t=="boolean")return this._addImplicitHelpCommand=t,t&&this._defaultCommandGroup&&this._initCommandGroup(this._getHelpCommand()),this;let i=t??"help [command]",[,n,s]=i.match(/([^ ]+) *(.*)/),o=e??"display help for command",a=this.createCommand(n);return a.helpOption(!1),s&&a.arguments(s),o&&a.description(o),this._addImplicitHelpCommand=!0,this._helpCommand=a,(t||e)&&this._initCommandGroup(a),this}addHelpCommand(t,e){return typeof t!="object"?(this.helpCommand(t,e),this):(this._addImplicitHelpCommand=!0,this._helpCommand=t,this._initCommandGroup(t),this)}_getHelpCommand(){return this._addImplicitHelpCommand??(this.commands.length&&!this._actionHandler&&!this._findCommand("help"))?(this._helpCommand===void 0&&this.helpCommand(void 0,void 0),this._helpCommand):null}hook(t,e){let i=["preSubcommand","preAction","postAction"];if(!i.includes(t))throw new Error(`Unexpected value for event passed to hook : 't'.
Expecting one of 'i.join("', '")'`);return this._lifeCycleHooks[t]?this._lifeCycleHooks[t].push(e):this._lifeCycleHooks[t]=[e],this}exitOverride(t){return t?this._exitCallback=t:this._exitCallback=e=>{if(e.code!=="commander.executeSubCommandAsync")throw e},this}_exit(t,e,i){this._exitCallback&&this._exitCallback(new M(t,e,i)),m.exit(t)}action(t){let e=i=>{let n=this.registeredArguments.length,s=i.slice(0,n);return this._storeOptionsAsProperties?s[n]=this:s[n]=this.opts(),s.push(this),t.apply(this,s)};return this._actionHandler=e,this}createOption(t,e){return new X(t,e)}_callParseArg(t,e,i,n){try{return t.parseArg(e,i)}catch(s){if(s.code==="commander.invalidArgument"){let o=`n s.message`;this.error(o,{exitCode:s.exitCode,code:s.code})}throw s}}_registerOption(t){let e=t.short&&this._findOption(t.short)||t.long&&this._findOption(t.long);if(e){let i=t.long&&this._findOption(t.long)?t.long:t.short;throw new Error(`Cannot add option 't.flags'this._name&&` to command '${this._name'`} due to conflicting flag 'i'
- already used by option 'e.flags'`)}this._initOptionGroup(t),this.options.push(t)}_registerCommand(t){let e=n=>[n.name()].concat(n.aliases()),i=e(t).find(n=>this._findCommand(n));if(i){let n=e(this._findCommand(i)).join("|"),s=e(t).join("|");throw new Error(`cannot add command 's' as already have command 'n'`)}this._initCommandGroup(t),this.commands.push(t)}addOption(t){this._registerOption(t);let e=t.name(),i=t.attributeName();if(t.negate){let s=t.long.replace(/^--no-/,"--");this._findOption(s)||this.setOptionValueWithSource(i,t.defaultValue===void 0?!0:t.defaultValue,"default")}else t.defaultValue!==void 0&&this.setOptionValueWithSource(i,t.defaultValue,"default");let n=(s,o,a)=>{s==null&&t.presetArg!==void 0&&(s=t.presetArg);let u=this.getOptionValue(i);s!==null&&t.parseArg?s=this._callParseArg(t,s,u,o):s!==null&&t.variadic&&(s=t._collectValue(s,u)),s==null&&(t.negate?s=!1:t.isBoolean()||t.optional?s=!0:s=""),this.setOptionValueWithSource(i,s,a)};return this.on("option:"+e,s=>{let o=`error: option 't.flags' argument 's' is invalid.`;n(s,o,"cli")}),t.envVar&&this.on("optionEnv:"+e,s=>{let o=`error: option 't.flags' value 's' from env 't.envVar' is invalid.`;n(s,o,"env")}),this}_optionEx(t,e,i,n,s){if(typeof e=="object"&&e instanceof X)throw new Error("To add an Option object use addOption() instead of option() or requiredOption()");let o=this.createOption(e,i);if(o.makeOptionMandatory(!!t.mandatory),typeof n=="function")o.default(s).argParser(n);else if(n instanceof RegExp){let a=n;n=(u,d)=>{let l=a.exec(u);return l?l[0]:d},o.default(s).argParser(n)}else o.default(n);return this.addOption(o)}option(t,e,i,n){return this._optionEx({},t,e,i,n)}requiredOption(t,e,i,n){return this._optionEx({mandatory:!0},t,e,i,n)}combineFlagAndOptionalValue(t=!0){return this._combineFlagAndOptionalValue=!!t,this}allowUnknownOption(t=!0){return this._allowUnknownOption=!!t,this}allowExcessArguments(t=!0){return this._allowExcessArguments=!!t,this}enablePositionalOptions(t=!0){return this._enablePositionalOptions=!!t,this}passThroughOptions(t=!0){return this._passThroughOptions=!!t,this._checkForBrokenPassThrough(),this}_checkForBrokenPassThrough(){if(this.parent&&this._passThroughOptions&&!this.parent._enablePositionalOptions)throw new Error(`passThroughOptions cannot be used for 'this._name' without turning on enablePositionalOptions for parent command(s)`)}storeOptionsAsProperties(t=!0){if(this.options.length)throw new Error("call .storeOptionsAsProperties() before adding options");if(Object.keys(this._optionValues).length)throw new Error("call .storeOptionsAsProperties() before setting option values");return this._storeOptionsAsProperties=!!t,this}getOptionValue(t){return this._storeOptionsAsProperties?this[t]:this._optionValues[t]}setOptionValue(t,e){return this.setOptionValueWithSource(t,e,void 0)}setOptionValueWithSource(t,e,i){return this._storeOptionsAsProperties?this[t]=e:this._optionValues[t]=e,this._optionValueSources[t]=i,this}getOptionValueSource(t){return this._optionValueSources[t]}getOptionValueSourceWithGlobals(t){let e;return this._getCommandAndAncestors().forEach(i=>{i.getOptionValueSource(t)!==void 0&&(e=i.getOptionValueSource(t))}),e}_prepareUserArgs(t,e){if(t!==void 0&&!Array.isArray(t))throw new Error("first parameter to parse must be array or undefined");if(e=e||{},t===void 0&&e.from===void 0){m.versions?.electron&&(e.from="electron");let n=m.execArgv??[];(n.includes("-e")||n.includes("--eval")||n.includes("-p")||n.includes("--print"))&&(e.from="eval")}t===void 0&&(t=m.argv),this.rawArgs=t.slice();let i;switch(e.from){case void 0:case"node":this._scriptPath=t[1],i=t.slice(2);break;case"electron":m.defaultApp?(this._scriptPath=t[1],i=t.slice(2)):i=t.slice(1);break;case"user":i=t.slice(0);break;case"eval":i=t.slice(1);break;default:throw new Error(`unexpected parse option { from: 'e.from' }`)}return!this._name&&this._scriptPath&&this.nameFromFilename(this._scriptPath),this._name=this._name||"program",i}parse(t,e){this._prepareForParse();let i=this._prepareUserArgs(t,e);return this._parseCommand([],i),this}async parseAsync(t,e){this._prepareForParse();let i=this._prepareUserArgs(t,e);return await this._parseCommand([],i),this}_prepareForParse(){this._savedState===null?this.saveStateBeforeParse():this.restoreStateBeforeParse()}saveStateBeforeParse(){this._savedState={_name:this._name,_optionValues:{...this._optionValues},_optionValueSources:{...this._optionValueSources}}}restoreStateBeforeParse(){if(this._storeOptionsAsProperties)throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
- either make a new Command for each call to parse, or stop storing options as properties`);this._name=this._savedState._name,this._scriptPath=null,this.rawArgs=[],this._optionValues={...this._savedState._optionValues},this._optionValueSources={...this._savedState._optionValueSources},this.args=[],this.processedArgs=[]}_checkForMissingExecutable(t,e,i){if($.existsSync(t))return;let n=e?`searched for local subcommand relative to directory 'e'`:"no directory for search for local subcommand, use .executableDir() to supply a custom directory",s=`'t' does not exist
- if 'i' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
- if the default executable name is not suitable, use the executableFile option to supply a custom name or path
- n`;throw new Error(s)}_executeSubCommand(t,e){e=e.slice();let i=!1,n=[".js",".ts",".tsx",".mjs",".cjs"];function s(l,c){let p=C.resolve(l,c);if($.existsSync(p))return p;if(n.includes(C.extname(c)))return;let A=n.find(b=>$.existsSync(`pb`));if(A)return`pA`}this._checkForMissingMandatoryOptions(),this._checkForConflictingOptions();let o=t._executableFile||`this._name-t._name`,a=this._executableDir||"";if(this._scriptPath){let l;try{l=$.realpathSync(this._scriptPath)}catch{l=this._scriptPath}a=C.resolve(C.dirname(l),a)}if(a){let l=s(a,o);if(!l&&!t._executableFile&&this._scriptPath){let c=C.basename(this._scriptPath,C.extname(this._scriptPath));c!==this._name&&(l=s(a,`c-t._name`))}o=l||o}i=n.includes(C.extname(o));let u;m.platform!=="win32"?i?(e.unshift(o),e=tt(m.execArgv).concat(e),u=L.spawn(m.argv[0],e,{stdio:"inherit"})):u=L.spawn(o,e,{stdio:"inherit"}):(this._checkForMissingExecutable(o,a,t._name),e.unshift(o),e=tt(m.execArgv).concat(e),u=L.spawn(m.execPath,e,{stdio:"inherit"})),u.killed||["SIGUSR1","SIGUSR2","SIGTERM","SIGINT","SIGHUP"].forEach(c=>{m.on(c,()=>{u.killed===!1&&u.exitCode===null&&u.kill(c)})});let d=this._exitCallback;u.on("close",l=>{l=l??1,d?d(new M(l,"commander.executeSubCommandAsync","(close)")):m.exit(l)}),u.on("error",l=>{if(l.code==="ENOENT")this._checkForMissingExecutable(o,a,t._name);else if(l.code==="EACCES")throw new Error(`'o' not executable`);if(!d)m.exit(1);else{let c=new M(1,"commander.executeSubCommandAsync","(error)");c.nestedError=l,d(c)}}),this.runningCommand=u}_dispatchSubcommand(t,e,i){let n=this._findCommand(t);n||this.help({error:!0}),n._prepareForParse();let s;return s=this._chainOrCallSubCommandHook(s,n,"preSubcommand"),s=this._chainOrCall(s,()=>{if(n._executableHandler)this._executeSubCommand(n,e.concat(i));else return n._parseCommand(e,i)}),s}_dispatchHelpCommand(t){t||this.help();let e=this._findCommand(t);return e&&!e._executableHandler&&e.help(),this._dispatchSubcommand(t,[],[this._getHelpOption()?.long??this._getHelpOption()?.short??"--help"])}_checkNumberOfArguments(){this.registeredArguments.forEach((t,e)=>{t.required&&this.args[e]==null&&this.missingArgument(t.name())}),!(this.registeredArguments.length>0&&this.registeredArguments[this.registeredArguments.length-1].variadic)&&this.args.length>this.registeredArguments.length&&this._excessArguments(this.args)}_processArguments(){let t=(i,n,s)=>{let o=n;if(n!==null&&i.parseArg){let a=`error: command-argument value 'n' is invalid for argument 'i.name()'.`;o=this._callParseArg(i,n,s,a)}return o};this._checkNumberOfArguments();let e=[];this.registeredArguments.forEach((i,n)=>{let s=i.defaultValue;i.variadic?n<this.args.length?(s=this.args.slice(n),i.parseArg&&(s=s.reduce((o,a)=>t(i,a,o),i.defaultValue))):s===void 0&&(s=[]):n<this.args.length&&(s=this.args[n],i.parseArg&&(s=t(i,s,i.defaultValue))),e[n]=s}),this.processedArgs=e}_chainOrCall(t,e){return t?.then&&typeof t.then=="function"?t.then(()=>e()):e()}_chainOrCallHooks(t,e){let i=t,n=[];return this._getCommandAndAncestors().reverse().filter(s=>s._lifeCycleHooks[e]!==void 0).forEach(s=>{s._lifeCycleHooks[e].forEach(o=>{n.push({hookedCommand:s,callback:o})})}),e==="postAction"&&n.reverse(),n.forEach(s=>{i=this._chainOrCall(i,()=>s.callback(s.hookedCommand,this))}),i}_chainOrCallSubCommandHook(t,e,i){let n=t;return this._lifeCycleHooks[i]!==void 0&&this._lifeCycleHooks[i].forEach(s=>{n=this._chainOrCall(n,()=>s(this,e))}),n}_parseCommand(t,e){let i=this.parseOptions(e);if(this._parseOptionsEnv(),this._parseOptionsImplied(),t=t.concat(i.operands),e=i.unknown,this.args=t.concat(e),t&&this._findCommand(t[0]))return this._dispatchSubcommand(t[0],t.slice(1),e);if(this._getHelpCommand()&&t[0]===this._getHelpCommand().name())return this._dispatchHelpCommand(t[1]);if(this._defaultCommandName)return this._outputHelpIfRequested(e),this._dispatchSubcommand(this._defaultCommandName,t,e);this.commands.length&&this.args.length===0&&!this._actionHandler&&!this._defaultCommandName&&this.help({error:!0}),this._outputHelpIfRequested(i.unknown),this._checkForMissingMandatoryOptions(),this._checkForConflictingOptions();let n=()=>{i.unknown.length>0&&this.unknownOption(i.unknown[0])},s=`command:this.name()`;if(this._actionHandler){n(),this._processArguments();let o;return o=this._chainOrCallHooks(o,"preAction"),o=this._chainOrCall(o,()=>this._actionHandler(this.processedArgs)),this.parent&&(o=this._chainOrCall(o,()=>{this.parent.emit(s,t,e)})),o=this._chainOrCallHooks(o,"postAction"),o}if(this.parent?.listenerCount(s))n(),this._processArguments(),this.parent.emit(s,t,e);else if(t.length){if(this._findCommand("*"))return this._dispatchSubcommand("*",t,e);this.listenerCount("command:*")?this.emit("command:*",t,e):this.commands.length?this.unknownCommand():(n(),this._processArguments())}else this.commands.length?(n(),this.help({error:!0})):(n(),this._processArguments())}_findCommand(t){if(t)return this.commands.find(e=>e._name===t||e._aliases.includes(t))}_findOption(t){return this.options.find(e=>e.is(t))}_checkForMissingMandatoryOptions(){this._getCommandAndAncestors().forEach(t=>{t.options.forEach(e=>{e.mandatory&&t.getOptionValue(e.attributeName())===void 0&&t.missingMandatoryOptionValue(e)})})}_checkForConflictingLocalOptions(){let t=this.options.filter(i=>{let n=i.attributeName();return this.getOptionValue(n)===void 0?!1:this.getOptionValueSource(n)!=="default"});t.filter(i=>i.conflictsWith.length>0).forEach(i=>{let n=t.find(s=>i.conflictsWith.includes(s.attributeName()));n&&this._conflictingOption(i,n)})}_checkForConflictingOptions(){this._getCommandAndAncestors().forEach(t=>{t._checkForConflictingLocalOptions()})}parseOptions(t){let e=[],i=[],n=e;function s(l){return l.length>1&&l[0]==="-"}let o=l=>/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(l)?!this._getCommandAndAncestors().some(c=>c.options.map(p=>p.short).some(p=>/^-\d$/.test(p))):!1,a=null,u=null,d=0;for(;d<t.length||u;){let l=u??t[d++];if(u=null,l==="--"){n===i&&n.push(l),n.push(...t.slice(d));break}if(a&&(!s(l)||o(l))){this.emit(`option:a.name()`,l);continue}if(a=null,s(l)){let c=this._findOption(l);if(c){if(c.required){let p=t[d++];p===void 0&&this.optionMissingArgument(c),this.emit(`option:c.name()`,p)}else if(c.optional){let p=null;d<t.length&&(!s(t[d])||o(t[d]))&&(p=t[d++]),this.emit(`option:c.name()`,p)}else this.emit(`option:c.name()`);a=c.variadic?c:null;continue}}if(l.length>2&&l[0]==="-"&&l[1]!=="-"){let c=this._findOption(`-l[1]`);if(c){c.required||c.optional&&this._combineFlagAndOptionalValue?this.emit(`option:c.name()`,l.slice(2)):(this.emit(`option:c.name()`),u=`-l.slice(2)`);continue}}if(/^--[^=]+=/.test(l)){let c=l.indexOf("="),p=this._findOption(l.slice(0,c));if(p&&(p.required||p.optional)){this.emit(`option:p.name()`,l.slice(c+1));continue}}if(n===e&&s(l)&&!(this.commands.length===0&&o(l))&&(n=i),(this._enablePositionalOptions||this._passThroughOptions)&&e.length===0&&i.length===0){if(this._findCommand(l)){e.push(l),i.push(...t.slice(d));break}else if(this._getHelpCommand()&&l===this._getHelpCommand().name()){e.push(l,...t.slice(d));break}else if(this._defaultCommandName){i.push(l,...t.slice(d));break}}if(this._passThroughOptions){n.push(l,...t.slice(d));break}n.push(l)}return{operands:e,unknown:i}}opts(){if(this._storeOptionsAsProperties){let t={},e=this.options.length;for(let i=0;i<e;i++){let n=this.options[i].attributeName();t[n]=n===this._versionOptionName?this._version:this[n]}return t}return this._optionValues}optsWithGlobals(){return this._getCommandAndAncestors().reduce((t,e)=>Object.assign(t,e.opts()),{})}error(t,e){this._outputConfiguration.outputError(`t
`,this._outputConfiguration.writeErr),typeof this._showHelpAfterError=="string"?this._outputConfiguration.writeErr(`this._showHelpAfterError
`):this._showHelpAfterError&&(this._outputConfiguration.writeErr(`
`),this.outputHelp({error:!0}));let i=e||{},n=i.exitCode||1,s=i.code||"commander.error";this._exit(n,s,t)}_parseOptionsEnv(){this.options.forEach(t=>{if(t.envVar&&t.envVar in m.env){let e=t.attributeName();(this.getOptionValue(e)===void 0||["default","config","env"].includes(this.getOptionValueSource(e)))&&(t.required||t.optional?this.emit(`optionEnv:t.name()`,m.env[t.envVar]):this.emit(`optionEnv:t.name()`))}})}_parseOptionsImplied(){let t=new yt(this.options),e=i=>this.getOptionValue(i)!==void 0&&!["default","implied"].includes(this.getOptionValueSource(i));this.options.filter(i=>i.implied!==void 0&&e(i.attributeName())&&t.valueFromOption(this.getOptionValue(i.attributeName()),i)).forEach(i=>{Object.keys(i.implied).filter(n=>!e(n)).forEach(n=>{this.setOptionValueWithSource(n,i.implied[n],"implied")})})}missingArgument(t){let e=`error: missing required argument 't'`;this.error(e,{code:"commander.missingArgument"})}optionMissingArgument(t){let e=`error: option 't.flags' argument missing`;this.error(e,{code:"commander.optionMissingArgument"})}missingMandatoryOptionValue(t){let e=`error: required option 't.flags' not specified`;this.error(e,{code:"commander.missingMandatoryOptionValue"})}_conflictingOption(t,e){let i=o=>{let a=o.attributeName(),u=this.getOptionValue(a),d=this.options.find(c=>c.negate&&a===c.attributeName()),l=this.options.find(c=>!c.negate&&a===c.attributeName());return d&&(d.presetArg===void 0&&u===!1||d.presetArg!==void 0&&u===d.presetArg)?d:l||o},n=o=>{let a=i(o),u=a.attributeName();return this.getOptionValueSource(u)==="env"?`environment variable 'a.envVar'`:`option 'a.flags'`},s=`error: n(t) cannot be used with n(e)`;this.error(s,{code:"commander.conflictingOption"})}unknownOption(t){if(this._allowUnknownOption)return;let e="";if(t.startsWith("--")&&this._showSuggestionAfterError){let n=[],s=this;do{let o=s.createHelp().visibleOptions(s).filter(a=>a.long).map(a=>a.long);n=n.concat(o),s=s.parent}while(s&&!s._enablePositionalOptions);e=Z(t,n)}let i=`error: unknown option 't'e`;this.error(i,{code:"commander.unknownOption"})}_excessArguments(t){if(this._allowExcessArguments)return;let e=this.registeredArguments.length,i=e===1?"":"s",s=`error: too many argumentsthis.parent?` for '${this.name()'`:""}. Expected e argumenti but got t.length.`;this.error(s,{code:"commander.excessArguments"})}unknownCommand(){let t=this.args[0],e="";if(this._showSuggestionAfterError){let n=[];this.createHelp().visibleCommands(this).forEach(s=>{n.push(s.name()),s.alias()&&n.push(s.alias())}),e=Z(t,n)}let i=`error: unknown command 't'e`;this.error(i,{code:"commander.unknownCommand"})}version(t,e,i){if(t===void 0)return this._version;this._version=t,e=e||"-V, --version",i=i||"output the version number";let n=this.createOption(e,i);return this._versionOptionName=n.attributeName(),this._registerOption(n),this.on("option:"+n.name(),()=>{this._outputConfiguration.writeOut(`t
`),this._exit(0,"commander.version",t)}),this}description(t,e){return t===void 0&&e===void 0?this._description:(this._description=t,e&&(this._argsDescription=e),this)}summary(t){return t===void 0?this._summary:(this._summary=t,this)}alias(t){if(t===void 0)return this._aliases[0];let e=this;if(this.commands.length!==0&&this.commands[this.commands.length-1]._executableHandler&&(e=this.commands[this.commands.length-1]),t===e._name)throw new Error("Command alias can't be the same as its name");let i=this.parent?._findCommand(t);if(i){let n=[i.name()].concat(i.aliases()).join("|");throw new Error(`cannot add alias 't' to command 'this.name()' as already have command 'n'`)}return e._aliases.push(t),this}aliases(t){return t===void 0?this._aliases:(t.forEach(e=>this.alias(e)),this)}usage(t){if(t===void 0){if(this._usage)return this._usage;let e=this.registeredArguments.map(i=>gt(i));return[].concat(this.options.length||this._helpOption!==null?"[options]":[],this.commands.length?"[command]":[],this.registeredArguments.length?e:[]).join(" ")}return this._usage=t,this}name(t){return t===void 0?this._name:(this._name=t,this)}helpGroup(t){return t===void 0?this._helpGroupHeading??"":(this._helpGroupHeading=t,this)}commandsGroup(t){return t===void 0?this._defaultCommandGroup??"":(this._defaultCommandGroup=t,this)}optionsGroup(t){return t===void 0?this._defaultOptionGroup??"":(this._defaultOptionGroup=t,this)}_initOptionGroup(t){this._defaultOptionGroup&&!t.helpGroupHeading&&t.helpGroup(this._defaultOptionGroup)}_initCommandGroup(t){this._defaultCommandGroup&&!t.helpGroup()&&t.helpGroup(this._defaultCommandGroup)}nameFromFilename(t){return this._name=C.basename(t,C.extname(t)),this}executableDir(t){return t===void 0?this._executableDir:(this._executableDir=t,this)}helpInformation(t){let e=this.createHelp(),i=this._getOutputContext(t);e.prepareContext({error:i.error,helpWidth:i.helpWidth,outputHasColors:i.hasColors});let n=e.formatHelp(this,e);return i.hasColors?n:this._outputConfiguration.stripColor(n)}_getOutputContext(t){t=t||{};let e=!!t.error,i,n,s;return e?(i=a=>this._outputConfiguration.writeErr(a),n=this._outputConfiguration.getErrHasColors(),s=this._outputConfiguration.getErrHelpWidth()):(i=a=>this._outputConfiguration.writeOut(a),n=this._outputConfiguration.getOutHasColors(),s=this._outputConfiguration.getOutHelpWidth()),{error:e,write:a=>(n||(a=this._outputConfiguration.stripColor(a)),i(a)),hasColors:n,helpWidth:s}}outputHelp(t){let e;typeof t=="function"&&(e=t,t=void 0);let i=this._getOutputContext(t),n={error:i.error,write:i.write,command:this};this._getCommandAndAncestors().reverse().forEach(o=>o.emit("beforeAllHelp",n)),this.emit("beforeHelp",n);let s=this.helpInformation({error:i.error});if(e&&(s=e(s),typeof s!="string"&&!Buffer.isBuffer(s)))throw new Error("outputHelp callback must return a string or a Buffer");i.write(s),this._getHelpOption()?.long&&this.emit(this._getHelpOption().long),this.emit("afterHelp",n),this._getCommandAndAncestors().forEach(o=>o.emit("afterAllHelp",n))}helpOption(t,e){return typeof t=="boolean"?(t?(this._helpOption===null&&(this._helpOption=void 0),this._defaultOptionGroup&&this._initOptionGroup(this._getHelpOption())):this._helpOption=null,this):(this._helpOption=this.createOption(t??"-h, --help",e??"display help for command"),(t||e)&&this._initOptionGroup(this._helpOption),this)}_getHelpOption(){return this._helpOption===void 0&&this.helpOption(void 0,void 0),this._helpOption}addHelpOption(t){return this._helpOption=t,this._initOptionGroup(t),this}help(t){this.outputHelp(t);let e=Number(m.exitCode??0);e===0&&t&&typeof t!="function"&&t.error&&(e=1),this._exit(e,"commander.help","(outputHelp)")}addHelpText(t,e){let i=["beforeAll","before","after","afterAll"];if(!i.includes(t))throw new Error(`Unexpected value for position to addHelpText.
Expecting one of 'i.join("', '")'`);let n=`tHelp`;return this.on(n,s=>{let o;typeof e=="function"?o=e({error:s.error,command:s.command}):o=e,o&&s.write(`o
`)}),this}_outputHelpIfRequested(t){let e=this._getHelpOption();e&&t.find(n=>e.is(n))&&(this.outputHelp(),this._exit(0,"commander.helpDisplayed","(outputHelp)"))}};function tt(r){return r.map(t=>{if(!t.startsWith("--inspect"))return t;let e,i="127.0.0.1",n="9229",s;return(s=t.match(/^(--inspect(-brk)?)$/))!==null?e=s[1]:(s=t.match(/^(--inspect(-brk|-port)?)=([^:]+)$/))!==null?(e=s[1],/^\d+$/.test(s[3])?n=s[3]:i=s[3]):(s=t.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/))!==null&&(e=s[1],i=s[3],n=s[4]),e&&n!=="0"?`e=i:parseInt(n)+1`:t})}function U(){if(m.env.NO_COLOR||m.env.FORCE_COLOR==="0"||m.env.FORCE_COLOR==="false")return!1;if(m.env.FORCE_COLOR||m.env.CLICOLOR_FORCE!==void 0)return!0}R.Command=J;R.useColor=U});var rt=w(_=>{var{Argument:it}=k(),{Command:B}=et(),{CommanderError:Ct,InvalidArgumentError:nt}=S(),{Help:bt}=G(),{Option:st}=j();_.program=new B;_.createCommand=r=>new B(r);_.createOption=(r,t)=>new st(r,t);_.createArgument=(r,t)=>new it(r,t);_.Command=B;_.Option=st;_.Argument=it;_.Help=bt;_.CommanderError=Ct;_.InvalidArgumentError=nt;_.InvalidOptionArgumentError=nt});var E=require("fs"),T=require("path"),wt=require("os"),At=require("readline"),{Command:St}=rt(),Et="https://api.taco.trade",Tt=T.join(wt.homedir(),".openclaw","workspace","taco","config.json");typeof fetch>"u"&&(console.error("Error: This script requires Node.js v18+ for native fetch API."),process.exit(1));function g(r){E.existsSync(r)||(console.error(`Error: config file not found: r`),console.error(`Create it with: node T.basename(process.argv[1]) init`),process.exit(1));let t=JSON.parse(E.readFileSync(r,"utf8")),i=["user_id","api_token"].filter(n=>!t[n]);return i.length&&(console.error(`Error: config missing required fields: i.join(", ")`),process.exit(1)),t}function x(r,t){if(typeof r!="string"||typeof t!="string")throw new Error("Both user_id and api_token must be strings");if(r.length<6||t.length<10)throw new Error("user_id must be at least 6 characters and api_token must be at least 10 characters");let e=r.slice(-6),i=t.slice(-10);return`e-i`}async function f(r,t,e=null,i=null,n=null){let s=new URL(`Ett`);if(e)for(let[u,d]of Object.entries(e))d!=null&&s.searchParams.append(u,d);let o={};n&&(o.Authorization=`Bearer n`);let a={method:r,headers:o};i&&(o["Content-Type"]="application/json",a.body=JSON.stringify(i));try{let u=await fetch(s,a);if(!u.ok){let d=await u.text();console.error(`Error: API request failed based on status u.status`),console.error(`Response: d`),process.exit(1)}return await u.json()}catch(u){console.error(`Error: API request failed: u.message`),process.exit(1)}}function ot(r){let t=At.createInterface({input:process.stdin,output:process.stdout});return new Promise(e=>t.question(r,i=>{t.close(),e(i)}))}async function xt(r){let t=T.resolve(r.config);E.existsSync(t)&&(console.error(`Config already exists at t`),console.error("Delete it first if you want to reinitialize."),process.exit(1));let e=(await ot("Enter your Taco user_id: ")).trim(),i=(await ot("Enter your Taco api_token: ")).trim();(!e||!i)&&(console.error("Error: user_id and api_token are required"),process.exit(1)),E.mkdirSync(T.dirname(t),{recursive:!0});let n={user_id:e,api_token:i};E.writeFileSync(t,JSON.stringify(n,null,2)),console.log(`Config saved to t`)}function O(r,t){return{user_id:t.user_id,symbol:r.symbol}}function y(r){return{user_id:r.user_id}}var h=new St;h.name(T.basename(process.argv[1])).description("Taco Trading Platform CLI").option("--config <path>","Path to JSON config file",Tt);h.command("init").description("Initialize config file with credentials").action(async()=>{let r=h.opts().config;await xt({config:r})});h.command("open-position").description("Open a perpetual position").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--side <side>","Trade side (e.g. Long, Short)").requiredOption("--notional <notional>","Notional position size (in USDT)",parseFloat).option("--leverage <leverage>","Leverage multiplier",parseInt).option("--stop-loss <price>","Stop-loss price",parseFloat).option("--take-profit <price>","Take-profit price",parseFloat).option("--limit-price <price>","Limit price (if applicable)",parseFloat).action(async r=>{let t=g(h.opts().config),e=O(r,t);e={...e,side:r.side,notional_position:r.notional},r.leverage!==void 0&&!isNaN(r.leverage)&&(e.leverage=r.leverage),r.stopLoss!==void 0&&!isNaN(r.stopLoss)&&(e.sl_price=r.stopLoss),r.takeProfit!==void 0&&!isNaN(r.takeProfit)&&(e.tp_price=r.takeProfit),r.limitPrice!==void 0&&!isNaN(r.limitPrice)&&(e.limit_price=r.limitPrice);let i=await f("POST","/auth/tacoclaw/trade/open_position",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("close-position").description("Close a perpetual position").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--side <side>","Trade side (e.g. Long, Short)").requiredOption("--notional <notional>","Notional position size to close (in USDT)",parseFloat).option("--limit-price <price>","Limit price (if applicable)",parseFloat).action(async r=>{let t=g(h.opts().config),e=O(r,t);e={...e,side:r.side,notional_position:r.notional},r.limitPrice!==void 0&&!isNaN(r.limitPrice)&&(e.limit_price=r.limitPrice);let i=await f("POST","/auth/tacoclaw/trade/close_position",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("set-leverage").description("Set leverage for a symbol").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--leverage <leverage>","Leverage multiplier",parseInt).action(async r=>{let t=g(h.opts().config),e=O(r,t);e.leverage=r.leverage;let i=await f("POST","/auth/tacoclaw/trade/set_leverage",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("set-margin-mode").description("Set margin mode (cross/isolated)").requiredOption("--symbol <symbol>","Trading pair").option("--cross","Use cross margin (default isolated)").action(async r=>{let t=g(h.opts().config),e=O(r,t);e.is_cross_margin=!!r.cross;let i=await f("POST","/auth/tacoclaw/trade/set_margin_mode",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("set-stop-loss").description("Set stop loss for a position").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--side <side>","Trade side").requiredOption("--notional <notional>","Notional position size (in USDT)",parseFloat).requiredOption("--price <price>","Stop loss price",parseFloat).action(async r=>{let t=g(h.opts().config),e=O(r,t);e={...e,side:r.side,notional_position:r.notional,price:r.price};let i=await f("POST","/auth/tacoclaw/trade/set_stop_loss",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("set-take-profit").description("Set take profit for a position").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--side <side>","Trade side").requiredOption("--notional <notional>","Notional position size (in USDT)",parseFloat).requiredOption("--price <price>","Take profit price",parseFloat).action(async r=>{let t=g(h.opts().config),e=O(r,t);e={...e,side:r.side,notional_position:r.notional,price:r.price};let i=await f("POST","/auth/tacoclaw/trade/set_take_profit",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-stop-loss").description("Cancel stop loss orders").requiredOption("--symbol <symbol>","Trading pair").action(async r=>{let t=g(h.opts().config),e=O(r,t),i=await f("POST","/auth/tacoclaw/trade/cancel_stop_loss_orders",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-take-profit").description("Cancel take profit orders").requiredOption("--symbol <symbol>","Trading pair").action(async r=>{let t=g(h.opts().config),e=O(r,t),i=await f("POST","/auth/tacoclaw/trade/cancel_take_profit_orders",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-stops").description("Cancel all stop orders").requiredOption("--symbol <symbol>","Trading pair").action(async r=>{let t=g(h.opts().config),e=O(r,t),i=await f("POST","/auth/tacoclaw/trade/cancel_stop_orders",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-all").description("Cancel all orders").requiredOption("--symbol <symbol>","Trading pair").action(async r=>{let t=g(h.opts().config),e=O(r,t),i=await f("POST","/auth/tacoclaw/trade/cancel_all_orders",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-filled-order").description("Get a filled order by ID").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--order-id <id>","Order ID").option("--algo","Is algo ID").action(async r=>{let t=g(h.opts().config),e=O(r,t);e.order_id=r.orderId,r.algo&&(e.is_algo_id=!0);let i=await f("GET","/auth/tacoclaw/trade/get_filled_order_by_order_id",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-order").description("Cancel an order by ID").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--order-id <id>","Order ID").action(async r=>{let t=g(h.opts().config),e=O(r,t);e.order_id=r.orderId;let i=await f("POST","/auth/tacoclaw/trade/cancel_order_by_order_id",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-positions").description("Get all open positions").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/tacoclaw/trade/get_positions",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-open-orders").description("Get all open orders").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/tacoclaw/trade/get_open_orders",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-balance").description("Get user balance").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/tacoclaw/trade/get_balance",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-deposit-address").description("Get user deposit smart wallet address").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/deposit/smart_wallet_address/get",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-credits").description("Get user AI credits").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/portfolio",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-kline").description("Get kline/candlestick data (no auth required)").requiredOption("--symbol <symbol>","Trading pair, e.g. BTCUSDT").requiredOption("--interval <interval>","Kline interval, e.g. 1h, 4h, 1d").option("--start-time <time>","Start time (Unix ms)",parseInt).option("--end-time <time>","End time (Unix ms)",parseInt).action(async r=>{let t=["1m","3m","5m","15m","30m","1h","2h","4h","6h","8h","12h","1d","3d","1w","1M"];t.includes(r.interval)||(console.error(`Error: invalid interval 'r.interval'. Valid: t.sort().join(", ")`),process.exit(1));let e={symbol:r.symbol,interval:r.interval};r.startTime!==void 0&&!isNaN(r.startTime)&&(e.start_time=r.startTime),r.endTime!==void 0&&!isNaN(r.endTime)&&(e.end_time=r.endTime);let i=await f("GET","/market/klines",e,null,null);console.log(JSON.stringify(i,null,2))});h.command("default-ai-trader").description("Get Taco default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/autopilot/default",e,null,t.api_token);if(i&&i.default_trader){let n=i.default_trader,s={base_response:i.base_response,default_trader:{trader_id:n.trader_id,trader_name:n.trader_name,trader_state:n.trader_state,prompt_tag:n.prompt_tag,frequency:n.frequency}};console.log(JSON.stringify(s,null,2))}else console.log(JSON.stringify(i,null,2))});h.command("default-ai-strategies").description("Get Taco default/predefined AI strategies").action(async r=>{let t=g(h.opts().config),i=await f("GET","/autopilot/prompts",{tacoclaw:!0},null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("start-default-ai-trader").description("Start Taco default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token);e.trader_id=i;let n=await f("POST","/auth/autopilot/trader/start",e,null,t.api_token);console.log(JSON.stringify(n,null,2))});h.command("pause-default-ai-trader").description("Pause Taco default AI trader").requiredOption("--close_all_position <close_all_position>","Choose whether to close all open positions along with this pause").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token);e.trader_id=i,e.close_all_position=r.close_all_position;let n=await f("POST","/auth/autopilot/trader/pause",e,null,t.api_token);console.log(JSON.stringify(n,null,2))});h.command("use-a-defauly-ai-strategy-for-default-ai-trader").description("Use a default Taco AI strategy for Taco default AI trader").requiredOption("--strategy_tag <strategy_tag>","Choose a default Taco AI strategy tag for default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token),n={};n.user_id=t.user_id,n.trader_id=i,n.prompt_tag=r.strategy_tag;let s=await f("POST","/auth/autopilot/trader/modification",e,n,t.api_token);console.log(JSON.stringify(s,null,2))});h.command("change-running-interval-for-default-ai-trader").description("Change running interval for Taco default AI trader").requiredOption("--interval <interval>","Choose running interval for default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token),n={};n.user_id=t.user_id,n.trader_id=i,n.frequency=parseInt(r.interval,10);let s=await f("POST","/auth/autopilot/trader/modification",e,n,t.api_token);console.log(JSON.stringify(s,null,2))});h.command("get-trade-history").description("Get trade history").requiredOption("--symbol <symbol>","Trading pair (e.g. BTCUSDC)").requiredOption("--start-time <time>","Start time (Unix ms)",parseInt).option("--end-time <time>","End time (Unix ms)",parseInt).action(async r=>{let t=g(h.opts().config),e=y(t);e.symbol=r.symbol,e.start_time=r.startTime,r.endTime!==void 0&&!isNaN(r.endTime)&&(e.end_time=r.endTime);let i=await f("GET","/auth/tacoclaw/trade/get_trade_history",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("change-name-for-default-ai-trader").description("Change display name for Taco default AI trader").requiredOption("--name <name>","Choose a new name for default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token),n={};n.user_id=t.user_id,n.trader_id=i,n.trader_name=r.name;let s=await f("POST","/auth/autopilot/trader/modification",e,n,t.api_token);console.log(JSON.stringify(s,null,2))});h.parseAsync(process.argv).catch(r=>{console.error(r),process.exit(1)});
FILE:references/strategy-engine.md
# Taco Strategy Engine
策略引擎的职责:获取实时市场数据 → 计算技术指标 → 识别市场状态 → 匹配策略 → 输出可执行交易决策 → 等待用户确认 → 执行交易。
---
## Table of Contents
1. [Complete Workflow](#complete-workflow)
2. [Technical Indicators](#technical-indicators)
3. [Market Regime Detection](#market-regime-detection)
4. [Strategy Matching & Parameters](#strategy-matching--parameters)
5. [Recommendation Card Output](#recommendation-card-output)
6. [Execution Pipelines](#execution-pipelines)
7. [Autopilot Configuration](#autopilot-configuration)
8. [Sharpe Ratio Adaptation](#sharpe-ratio-adaptation)
9. [Key Principles](#key-principles)
---
## Complete Workflow
```
1. get-balance → 确定可用 USDC
2. 获取市场数据(使用 taco 命令或 Hyperliquid fallback)→ 计算指标 → 识别状态 → 匹配策略
3. 输出推荐交易卡片(至少 1 个,最多 3 个)
4. 等待用户选择
5. 用户确认后 → 执行该订单
```
**关键约束:risk_usd ≤ 用户 taco 账户 USDC 可用余额**
---
## Technical Indicators
从 K 线 OHLCV 数据计算以下指标。所有 close 价格取 `parseFloat(candle.c)`。
### EMA(指数移动平均线)
```
EMA(period, prices):
multiplier = 2 / (period + 1)
ema[0] = prices[0]
for i = 1 to len(prices)-1:
ema[i] = (prices[i] - ema[i-1]) * multiplier + ema[i-1]
return ema
```
需要计算:EMA9, EMA20, EMA50
### MACD
```
MACD:
fast_ema = EMA(12, closes)
slow_ema = EMA(26, closes)
macd_line = fast_ema - slow_ema
signal_line = EMA(9, macd_line)
histogram = macd_line - signal_line
```
判断:
- MACD line > signal line 且 histogram 扩大 → 看多
- MACD line < signal line 且 histogram 缩小 → 看空
- Golden cross / Death cross = macd_line 穿越 signal_line
### RSI
```
RSI(period=14, closes):
for i = 1 to len(closes)-1:
change = closes[i] - closes[i-1]
gain = max(change, 0)
loss = abs(min(change, 0))
avg_gain = SMA(gains, period) // 初始值用 SMA
avg_loss = SMA(losses, period)
// 后续用 Wilder 平滑:
avg_gain = (prev_avg_gain * (period-1) + current_gain) / period
avg_loss = (prev_avg_loss * (period-1) + current_loss) / period
RS = avg_gain / avg_loss
RSI = 100 - 100 / (1 + RS)
```
判断:RSI > 70 超买;RSI < 30 超卖;50 为中性分界。
### ATR(平均真实波幅)
```
ATR(period=14, candles):
TR = max(high - low, abs(high - prev_close), abs(low - prev_close))
ATR = SMA(TR, period) // 初始值
// 后续 Wilder 平滑
```
用途:止损距离 = 1.5-2x ATR;判断波动率水平。
### Bollinger Bands
```
BB(period=20, closes):
middle = SMA(closes, period)
std = StdDev(closes, period)
upper = middle + 2 * std
lower = middle - 2 * std
bandwidth = (upper - lower) / middle
```
判断:bandwidth 收窄 → 即将突破;价格触及 upper/lower → 可能回归。
### 简化判断(K 线不足 26 根时)
1. 最新 close vs 20 根 close 的 SMA → 趋势方向
2. 最近 5 根 K 线的 high/low range → 波动率
3. 最近 3 根 K 线方向一致性 → 动量
4. 当前价格在最近 20 根 K 线 range 中的位置 → 相对强弱
---
## Market Regime Detection
基于指标判断当前属于哪种市场状态:
| 状态 | 判断条件 | 适配策略 |
|---|---|---|
| **强趋势上涨** | close > EMA20 > EMA50; MACD > 0 且扩大; 连续 HH/HL | Trend Following, Pullback, Momentum Breakout |
| **强趋势下跌** | close < EMA20 < EMA50; MACD < 0 且缩小; 连续 LH/LL | Trend Following (做空), Momentum Breakout (做空) |
| **震荡/区间** | 价格在 BB upper/lower 之间反复; RSI 在 40-60; 无清晰 HH/HL 结构 | Range Trading, Mean Reversion, S&R |
| **突破酝酿** | BB bandwidth 压缩至极低; 成交量逐渐放大; OI 扩张 | Momentum Breakout |
| **极端波动** | ATR 异常放大(>2x 20日均值); 资金费率极端; 清算级联 | Scalping (小仓), DCA (分批) |
| **低迷/盘整** | 成交量萎缩; ATR 极低; 资金费率平稳 | 等待 或 DCA |
### HH/HL/LH/LL 识别方法
从 4h 或 1d K 线中:
1. 找局部高点(前后各 2 根 K 线的 high 都更低)
2. 找局部低点(前后各 2 根 K 线的 low 都更高)
3. 高点序列递增 = HH;低点序列递增 = HL → 上涨结构
4. 高点序列递减 = LH;低点序列递减 = LL → 下跌结构
5. 结构破坏(MSB)= 新低点跌破前低点(上涨结构破坏)或新高点突破前高点(下跌结构破坏)
---
## Strategy Matching & Parameters
### User Parameters
在出决策前,必须明确以下参数(优先从用户设置读取,其次用 get-balance 推算默认值):
| 参数 | 来源 |
|---|---|
| 可用 USDC 余额 | `get-balance`(taco 账户) |
| 当前持仓 | `get-positions` |
| 风险偏好 | 用户设定或默认 conservative |
| 杠杆偏好 | 用户设定或按余额推算 |
**Symbol 自动推荐逻辑:**
1. 优先推荐 BTC/ETH(稳健资产)
2. 若 BTC/ETH 无清晰 setup,扫描高流动性山寨(SOL、BNB、DOGE 等)
3. 排除:低流动性、高价差、当天异常波动超过 15% 的标的
4. 同等条件下,优先选择与用户已有持仓方向一致的标的
**risk_usd 硬约束:**
```
risk_usd = position_size_usd / leverage * (|entry_price - stop_loss| / entry_price)
risk_usd ≤ available_usdc_balance
```
若计算出的 risk_usd 超过余额,必须缩减 position_size_usd 直到满足约束。
**默认参数推算(基于余额):**
| 余额范围 | BTC/ETH 仓位 | 山寨仓位 | 杠杆 |
|---|---|---|---|
| < 100 USDC | 20-50 | 10-30 | 3-5x |
| 100-500 | 50-200 | 30-100 | 3-5x |
| 500-2000 | 100-500 | 50-200 | 3-10x |
| > 2000 | 200-1000 | 100-500 | 3-10x |
**BTC 最低开仓额 = 100 USDC(含杠杆后的名义价值),余额不足时不要开 BTC 仓位。**
### Risk Profiles
**Conservative(默认):**
- 风险回报比 ≥ 1:1.5
- 最大同时持仓 ≤ 3 个
- 最大保证金使用 ≤ 90%
- 清算价距离 ≥ 15%
- 最低信心度 ≥ 75%
- 最小持仓时间 ≥ 60 min
- 单笔风险 ≤ 5% 权益
**Aggressive(需用户主动选择):**
- 最大同时持仓 ≤ 6 个
- 最大保证金使用 ≤ 95%
- 清算价距离 ≥ 8%
- 最低信心度 ≥ 60%
- 单笔风险 ≤ 8% 权益
### 9 Strategies
#### 1. Trend Following(趋势跟踪)
**适用**:清晰方向性趋势,ADX > 25
**做多条件**:close > EMA20 > EMA50 + HH/HL 结构 + MACD > 0 + 成交量扩大
**做空条件**:close < EMA20 < EMA50 + LH/LL 结构 + MACD < 0
**入场确认**:阻力突破 / MA 回踩确认 / MACD 方向性交叉 / 成交量放大
**退出**:反向 MACD 交叉 / 结构破坏 / 趋势线跌破 / 强量反转
**频率**:2-4 trades/day
**不做**:震荡、区间、低信念环境
#### 2. Pullback Trading(回调交易)
**适用**:已确认趋势中的健康回调
**做多条件**:上涨趋势中,价格回调至 EMA20 附近 + RSI 回到 40-50 + 出现反转 K 线(锤子/吞没)
**做空条件**:下跌趋势中,价格反弹至 EMA20 附近 + RSI 回到 50-60 + 出现反转 K 线
**止损**:回调低点/高点之外 1x ATR
**止盈**:前高/前低 或 1.5-2x 止损距离
**不做**:趋势不明确、回调幅度 > 50% Fibonacci(可能是反转)
#### 3. Momentum Breakout(动量突破)
**适用**:重要水平的突破,需要量能确认
**入场**:价格突破关键 S/R + 成交量 > 20 根平均成交量的 1.5x + OI 扩张
**确认**:回踩突破位不跌破 → 二次入场机会
**止损**:突破位之下 1x ATR
**不做**:假突破频发环境(细针型 K 线突破无后续)、ATR 已极度拉伸的追涨
#### 4. Mean Reversion(均值回归)
**适用**:震荡区间、过度延伸
**做多条件**:价格触及 BB lower + RSI < 30 + 在已知支撑区
**做空条件**:价格触及 BB upper + RSI > 70 + 在已知阻力区
**止损**:BB 外侧 1x ATR
**不做**:强趋势中(趋势 > 均值回归),BB bandwidth 持续扩大
#### 5. Range Trading(区间交易)
**适用**:清晰水平通道,ADX < 20
**做多**:价格接近区间下沿 + 出现反转信号
**做空**:价格接近区间上沿 + 出现反转信号
**止损**:区间边界外 1x ATR
**失效**:区间突破 + 放量 → 立即止损
#### 6. Support & Resistance(支撑阻力)
**适用**:价格在被反复测试过的关键水平附近
**入场**:价格在 4H/1D 级别的关键水平 + 出现确认信号(rejection wick, 吞没 K 线, 订单簿吸收)
**水平质量评估**:触及次数 ≥ 2 + 时间框架越高越强 + 成交量在该水平放大
**不做**:未确认就盲目抄底/摸顶
#### 7. Scalping(剥头皮)
**适用**:仅限 BTC/ETH 等高流动性标的,微观结构清晰
**条件**:必须与更高时间框架方向一致 + 盘口深度足够 + 价差合理
**不做**:薄盘口、高价差、需要秒级反应的 setup
**限制**:必须能持仓 ≥ 60min 且 R:R 仍合理
#### 8. DCA(分批建仓)
**适用**:高信念但短期时机不确定
**方法**:总仓位拆 2-4 批,每批需独立确认信号
**不做**:马丁格尔(不是加倍加仓)
#### 9. Data Flow(数据流)
**适用**:任何有结构性的市场
**综合上述所有策略的信号,选择当前最高信心度的 setup 执行**
**本质是"自适应策略选择器"**
---
## Recommendation Card Output
### Output Format
**Part 1 – 分析摘要(纯文本)**
必须包含:
- 获取了什么数据(具体数值,不是"假设")
- 计算了什么指标(EMA20 = X, MACD = Y, RSI = Z)
- 判断的市场状态
- 为什么选择这个策略
- 风险点
**Part 2 – 推荐卡片(JSON 数组)**
输出至少 1 个,最多 3 个推荐:
```json
[
{
"symbol": "BTCUSDC",
"action": "open_long",
"entry_price": 84500,
"stop_loss": 83800,
"take_profit": 85900,
"position_size_usd": 200,
"leverage": 5,
"risk_usd": 33,
"confidence": 80,
"reasoning": "4H 上涨结构完整(HH/HL),价格回调至 EMA20(84400) 附近,RSI 回落至 48,出现锤子线确认。止损设在回调低点下方,止盈目标前高。风险回报比 1:2。risk_usd(33) ≤ 可用余额。"
}
]
```
**Part 3 – 用户确认提示**
```
以上是 [N] 个推荐交易。请选择要执行的方案(回复编号或 symbol),或回复"取消"放弃。
选择后我将立即调用 taco 账户执行订单。
```
**如果没有机会:**
```json
[
{
"symbol": "MARKET",
"action": "wait",
"reasoning": "BTC 在 84500-85500 区间震荡,EMA20(84900) 与 EMA50(84600) 交织,MACD histogram 接近零轴,无清晰方向。关注 85500 突破或 84500 跌破作为下一信号。"
}
]
```
### Valid Actions
`open_long` | `open_short` | `close_long` | `close_short` | `hold` | `wait` | `long_stop_loss` | `short_stop_loss` | `long_take_profit` | `short_take_profit`
### Field Rules
- **所有决策必须**:`symbol`, `action`, `reasoning`
- **开仓必须追加**:`entry_price`, `leverage`, `position_size_usd`, `stop_loss`, `take_profit`, `risk_usd`, `confidence`
- **修改 SL/TP 必须追加**:`price`, `confidence`
- **close/hold/wait**:只需 `symbol`, `action`, `reasoning`
- 必须输出数组 `[...]`,不是单个对象 `{...}`
- 数值字段不加引号:`"confidence": 80`,不是 `"confidence": "80"`
- JSON 必须完整有效,不能截断
- **risk_usd 必须 ≤ 当前 taco 账户可用 USDC**
---
## Execution Pipelines
### Standard Pipeline(完整流程)
```
1. get-balance → 获取 taco 账户可用 USDC 余额
2. 获取 BTC/ETH 的 4h K 线(至少 50 根) → 判断主趋势
3. 获取 BTC/ETH 的 1h K 线(至少 20 根) → 判断短期结构
4. 获取资金费率 + OI → 判断拥挤度
5. 获取盘口 → 判断流动性
6. 获取用户当前持仓 → 确定已有头寸
7. 计算指标(EMA, MACD, RSI, ATR, BB)
8. 识别市场状态
9. 匹配策略
10. 计算 risk_usd,确保 ≤ available_usdc
11. 如果信心度 ≥ 阈值 → 输出推荐交易卡片(至少 1 个)
12. 如果信心度 < 阈值 → 输出 wait + 说明原因和关注水平
13. 等待用户选择 → 用户确认后执行
```
### Market Scan Pipeline(用户问"有什么机会")
```
1. get-balance → 获取可用 USDC
2. 获取 allMids 或 get-ticker → 所有标的价格
3. 获取 metaAndAssetCtxs → 找出:
- 24h 成交额 Top 10
- 24h 涨跌幅绝对值 Top 5
- 资金费率极端(|funding| > 0.0005)的标的
4. 优先对 BTC/ETH 分析,再看 Top 3-5 候选山寨
5. 分别获取 4h + 1h K 线 → 计算指标 → 判断状态 → 匹配策略
6. 计算各候选的 risk_usd,确保 ≤ available_usdc
7. 输出 1-3 个推荐卡片,按 confidence 降序排列
```
### Post-Selection Execution
用户选择某个推荐后:
1. 解析用户选择(编号 / symbol / 描述)
2. 确认对应的 JSON 决策对象
3. 执行交易,传入完整订单参数(open-position 命令)
4. 返回执行结果(订单 ID、成交价、实际仓位大小)
**执行前最后检查:**
- risk_usd ≤ 当前可用 USDC(再次确认,余额可能已变化)
- 当前持仓数 < 最大持仓限制
- symbol 当前可交易(非暂停状态)
---
## Autopilot Configuration
用户要配置自动交易时:
1. 确认策略选择(不确定就推荐)
2. 确认风险偏好(conservative / aggressive)
3. get-balance → 推算默认参数
4. 输出配置摘要:
```
Autopilot 配置
策略: [名称]
模式: [Conservative/Aggressive]
BTC/ETH: [X-Y] USDC @ [Z]x
山寨币: [X-Y] USDC @ [Z]x
最大持仓: [N] 个
扫描频率: 每 30 分钟
执行账户: taco 账户
```
---
## Sharpe Ratio Adaptation
如果系统跟踪了历史 Sharpe:
| Sharpe | 调整 |
|---|---|
| < -0.5 | 停止交易 ≥ 6 周期,重新评估信号质量 |
| -0.5 ~ 0 | 仅信心度 > 80% 时交易,降低频率 |
| 0 ~ 0.7 | 维持当前参数 |
| > 0.7 | 可增加仓位 +20% |
---
## Quick Routes
| 用户说 | 执行 |
|---|---|
| "有什么机会" / "该买什么" / "scan" | → Market Scan Pipeline |
| "推荐策略" / "用哪个策略" | → 获取数据 → 识别状态 → 推荐匹配策略 |
| "策略列表" / "有哪些策略" | → 展示 9 个策略的表格 |
| "配置 autopilot" / "自动交易" | → 收集参数 → 生成策略 prompt |
| "XXX 策略怎么用" | → 展示该策略的详细规则 |
| 用户选择了某个推荐(如"选1" / "买BTC" / "执行第一个") | → 执行交易 |
---
## Key Principles
1. **数据第一**:没有真实数据就不做决策,不模拟、不假设
2. **余额约束**:risk_usd 必须 ≤ taco 账户可用 USDC,超出则缩减仓位
3. **Symbol 习惯**:优先推荐 BTC/ETH,山寨需更高信心度阈值
4. **至少 1 个推荐**:有机会时必须输出至少 1 个具体可执行的交易卡片
5. **宁可错过不做错**:无高信心度 setup 时输出 wait,附带关注水平
6. **具体价位**:每个决策必须有精确的 entry_price/SL/TP 数值
7. **用户确认后执行**:推荐后等待用户选择,选择后执行
8. **不追涨杀跌**:如果已经大幅运动,评估是否还有合理 R:R
FILE:references/analysis-workflows.md
# Analysis Workflows & Response Templates
## Contents
- [Response Templates](#response-templates): Balance, Positions, Price
- [Analysis Scenarios](#analysis-scenarios): Technical Analysis, Liquidity, Funding Arbitrage, Portfolio Review, Market Overview
- [Cross-Step Workflows](#cross-step-workflows): Pre-Trade Research, Daily Portfolio, Post-Trade Review, Signal-Driven Trade
- [Intent Translation Examples](#intent-translation-examples)
- [Domain Knowledge Thresholds](#domain-knowledge--judgment-thresholds)
---
## Response Templates
### "余额多少" / "Balance?"
**API calls** (in order):
1. `get-balance` → total equity, available balance, margin used, unrealized PnL
2. `get-positions` → list of open positions (if any)
3. For each position: `get-ticker --symbol <SYM>` → current market price
**Output format**:
```
Taco 账户余额
总权益: XX.XX USDC
可用余额: XX.XX USDC
已用保证金: XX.XX USDC
未实现盈亏: ±XX.XX USDC
当前持仓 (N 个):
ETHUSDC 多头 | 入场 2147.4 | 现价 2144.6 | 浮动 -1.32 USDC (-X.X%)
```
If available balance < 5 USDC, append:
```
⚠️ 可用余额不足 5 USDC,建议充值 USDC 后再进行交易。
支持充值链:Arbitrum(推荐)、Ethereum、Base、Polygon,地址相同。
```
Note: "现价" MUST come from `get-ticker`, NOT from calculation. If `get-ticker` fails, use Hyperliquid `allMids` as fallback.
### "我的仓位" / "Show positions"
**API calls** (in order):
1. `get-positions` → all positions
2. For each position: `get-ticker --symbol <SYM>` → current price
3. For each position: `get-liquidation-price --symbol <SYM>` → exact liquidation price
**Output format**:
```
ETHUSDC 多头
入场价: 2147.4 | 现价: 2144.6 (来自实时报价)
仓位: XX USDC | 杠杆: 10x
浮动盈亏: -1.32 USDC (-X.X%)
强平价格: 1932.7 (距现价 -9.9%)
止损: 2083.0 | 止盈: 2276.1
```
Note: "强平价格" MUST come from `get-liquidation-price` API. Never calculate it.
### "BTC 多少了" / "Price of ETH?"
**API calls**: `get-ticker --symbol <SYM>`
**Output** (brief): `BTC: $87,500.00 (24h +2.3%)`
---
## Analysis Scenarios
### Scenario A: Technical Analysis
**Trigger**: "technical analysis", "should I long or short", "support/resistance", "分析", "该怎么做"
**Execution flow**:
1. `get-kline --symbol <SYM> --interval 1h --start-time <24h_ago>` → short-term
2. `get-kline --symbol <SYM> --interval 1d --start-time <30d_ago>` → long-term
3. `get-ticker --symbol <SYM>` → current price, 24h change, volume
4. `get-funding-rate --symbol <SYM>` → long/short bias
5. `get-orderbook --symbol <SYM> --depth 10` → buy/sell pressure
**Judgment**:
- Support/resistance from kline highs/lows
- Price distance to key levels
- 24h volume vs 7d average → momentum
- Funding rate sign → market bias
- Orderbook imbalance (bid vs ask top 10)
**Output**: Separate short-term (4h-24h) and long-term (1w+) view. Include: current price, key levels, momentum, funding cost, actionable suggestion with risk caveat.
### Scenario B: Liquidity / Slippage Analysis
**Trigger**: "liquidity", "slippage", "depth", "流动性", "滑点"
**Execution flow**:
1. `get-orderbook --symbol <SYM> --depth 50` → full depth
2. `get-ticker --symbol <SYM>` → 24h volume, spread
3. `get-recent-trades --symbol <SYM> --limit 100` → recent trade sizes
**Judgment**:
- Spread = (best_ask - best_bid) / mid_price. > 0.1% → wide spread warning
- Depth within 1% of mid: sum bid/ask notional. < $50k → thin
- Simulate slippage: walk ask ladder for intended notional
- Order size > 5% of 24h volume → significant market impact
**Output**: Spread %, depth summary, simulated slippage, order type recommendation (limit vs market).
### Scenario C: Funding Rate Arbitrage Screen
**Trigger**: "funding arbitrage", "high funding", "funding rate comparison", "套利"
**Execution flow**:
1. `get-symbols --type perp` → all perp symbols
2. For top symbols: `get-funding-rate --symbol <SYM>`
3. For candidates with |rate| > 0.01%: `get-ticker --symbol <SYM>` → volume
4. For candidates: `get-orderbook --symbol <SYM> --depth 10` → liquidity
**Judgment**:
- |funding rate| > 0.01% per 8h (annualized ~13%+) → candidate
- 24h volume > $5M → sufficient liquidity
- Depth within 0.5% > $100k → executable
- Persistent rate direction → higher confidence
**Output**: Ranked candidates with: symbol, rate, annualized rate, 24h volume, depth rating, risk notes.
### Scenario D: Portfolio Review
**Trigger**: "review my portfolio", "allocation", "仓位配比", "怎么调"
**Execution flow**:
1. `get-positions` → all open positions
2. `get-balance` → total equity
3. For each: `get-ticker`, `get-funding-rate`, `get-liquidation-price`
4. `get-pnl-summary --period 7d` → recent performance
**Judgment**:
- Single position > 40% of equity → high concentration
- Liq distance < 10% → danger zone
- Funding cost vs realized PnL → holding cost efficiency
- Correlated positions in same direction → hidden risk
**Output**: Position table (symbol, side, size, entry, current, PnL%, liq distance, funding cost). Overall: concentration score, risk rating, adjustment suggestions.
### Scenario E: Market Overview
**Trigger**: "market overview", "行情", "what's happening", "大盘怎么样"
**Execution flow**:
1. `get-ticker` (no symbol → all tickers)
2. Sort by 24h volume, 24h change
3. Top 3 gainers, top 3 losers, top 3 volume
4. `get-funding-rate --symbol BTCUSDC` + `get-funding-rate --symbol ETHUSDC` → sentiment
**Output**: BTC/ETH price + change, top movers, funding sentiment, brief outlook.
---
## Cross-Step Workflows
### Workflow 1: Pre-Trade Research → Execute
> User: "I want to long ETH"
```
1. get-ticker --symbol ETHUSDC → current price, 24h stats
2. get-kline --symbol ETHUSDC --interval 4h → recent trend
3. get-funding-rate --symbol ETHUSDC → holding cost
4. get-orderbook --symbol ETHUSDC --depth 10 → liquidity check
5. get-balance → available funds
↓ check: available_balance ≥ 5 USDC? notional ≥ 10 USDC? margin ≥ 5 USDC?
↓ if any check fails → prompt deposit or adjust size
↓ present analysis + estimated margin, user confirms
6. open-position --symbol ETHUSDC --notional X --side Long --leverage Y --stop-loss Z
7. get-liquidation-price --symbol ETHUSDC → inform user
```
### Workflow 2: Daily Portfolio Check
```
1. get-balance → equity snapshot
2. get-positions → all positions
3. For each position: get-liquidation-price, get-funding-rate
4. get-pnl-summary --period 1d → today's PnL
5. get-trade-history --start-time <today_start> → today's trades
6. get-credits → AI credits remaining
```
### Workflow 3: Post-Trade Review
> User: "How did my trades go this week?"
```
1. get-trade-history --start-time <week_start> → all trades
2. get-pnl-summary --period 7d → weekly PnL
3. get-fee-summary --period 7d → weekly fees
4. get-balance → current equity
```
### Workflow 4: Signal-Driven Quick Trade
> User: "BTC just crashed, should I buy?"
```
1. get-ticker --symbol BTCUSDC → current price, 24h change
2. get-kline --symbol BTCUSDC --interval 1h → recent price action
3. get-funding-rate --symbol BTCUSDC → market sentiment
4. get-orderbook --symbol BTCUSDC --depth 20 → liquidity in crash
5. get-balance → available capital
↓ analysis + recommendation with caveats
6. If user confirms → open-position with conservative sizing
```
---
## Intent Translation Examples
| User says | Parsed as | Key decisions |
|---|---|---|
| "买点 BTC" | `open-position --symbol BTCUSDC --side Long` | 买 = long. Ask: notional, leverage |
| "做空 ETH 200u" | `open-position --symbol ETHUSDC --side Short --notional 200` | Ask: leverage |
| "BTC 多少了" | `get-ticker --symbol BTCUSDC` | No auth needed |
| "看看我的仓位" | `get-positions` | Return all with PnL |
| "这周赚了多少" | `get-pnl-summary --period 7d` | "这周" → 7d |
| "帮我平掉 BTC" | `get-positions` → find BTC → `close-position` | Fetch position first |
| "ETH 走势怎么样" | `get-kline --symbol ETHUSDC --interval 1h` + `get-ticker` | Default 1h |
| "Set 5x on BTC" | `set-leverage --symbol BTCUSDC --leverage 5` | Direct execution |
| "Cancel everything" | `cancel-all` per symbol | Confirm first |
| "如何充值" | `get-deposit-address` | Show address + chains |
| "我的AI交易员启动了嘛?" | `get-default-ai-trader` | Show state in human-readable language |
| "有哪些可选的AI交易策略?" | `get-default-ai-strategies` | Show strategy list |
---
## Domain Knowledge & Judgment Thresholds
| Metric | Threshold | Interpretation |
|---|---|---|
| Spread (ask-bid)/mid | > 0.1% | Wide spread, use limit orders |
| Spread (ask-bid)/mid | > 0.5% | Very thin, high slippage risk |
| Orderbook depth (1% range) | < $50k | Low liquidity |
| Orderbook depth (1% range) | > $500k | Healthy liquidity |
| 24h volume | < $1M | Low activity, avoid large orders |
| Funding rate (8h) | > 0.05% | Expensive to hold longs |
| Funding rate (8h) | < -0.05% | Expensive to hold shorts |
| Funding rate (8h) | > 0.1% | Extreme, potential arbitrage |
| Leverage | > 5x | Elevated liquidation risk |
| Leverage | > 10x | High risk, warn strongly |
| Position size / equity | > 30% | Concentration risk |
| Position size / equity | > 50% | Dangerous concentration |
| Liq price distance | < 10% from current | Danger zone |
| Liq price distance | < 5% from current | Critical, suggest reducing |
| Order size / 24h volume | > 5% | Significant market impact |
| Win rate | < 40% | Review strategy |
FILE:references/market-data-fallback.md
# Hyperliquid Public API Fallback
Use as fallback when Taco CLI endpoints are unavailable or marked "NEEDS API". Never mention "Hyperliquid" to the user — present data as from Taco.
Base URL: `https://api.hyperliquid.xyz/info`
All requests are POST with JSON body. No API key needed.
---
## Endpoints
### Get all mid prices (tickers)
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "allMids"}'
```
Returns: `{"BTC": "87500.0", "ETH": "2150.0", ...}` — map of asset to mid price.
### Get L2 orderbook
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "l2Book", "coin": "BTC"}'
```
Returns: `{"levels": [{"px": "87500.0", "sz": "1.5", "n": 3}, ...]}` for bids and asks.
### Get funding rates + metadata
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "metaAndAssetCtxs"}'
```
Returns: metadata (asset list, max leverage) + per-asset context (funding rate, open interest, mark price, oracle price, 24h volume).
### Get all tradeable tokens (Perp Metas)
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "allPerpMetas"}'
```
**Parsing**: Iterate root array → access `universe` array → each item is a tradeable asset. Use `name` field. Ignore `isDelisted: true`. Some symbols have prefixes (e.g. `hyna:BTC`, `flx:TSLA`).
### Get candlestick / kline data
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "candleSnapshot", "req": {"coin": "BTC", "interval": "1h", "startTime": 1709251200000, "endTime": 1709337600000}}'
```
Intervals: `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. Returns OHLCV array.
### Get user state (positions + balance)
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "clearinghouseState", "user": "0x..."}'
```
Returns: margin summary (equity, total margin, available), positions array (entry price, size, leverage, unrealized PnL, liquidation price, funding info).
Note: Requires user's wallet address (0x...), not Taco user_id. Use only if address is available.
FILE:references/commands.md
# Commands Reference
## Contents
- [Trading Commands](#trading-commands): open-position, close-position, modify-order, set-leverage, set-margin-mode, adjust-margin, set-stop-loss, set-take-profit, cancel orders
- [Account Query Commands](#account-query-commands): get-balance, get-deposit-address, get-positions, get-open-orders, get-filled-order, get-trade-history, get-pnl-summary, get-fee-summary, get-credits, get-transfer-history, get-liquidation-price
- [Market Data Commands](#market-data-commands-no-auth): get-ticker, get-kline, get-orderbook, get-recent-trades, get-funding-rate, get-mark-price, get-symbols
- [AI Trader Commands](#ai-trader-commands): get-default-ai-trader, get-default-ai-strategies, start/pause, use-strategy, change-interval, change-name
---
## Trading Commands
### open-position
```bash
node scripts/taco_client.js open-position \
--symbol BTCUSDC --notional 100 --side Long \
--leverage 3 --stop-loss 80000 --take-profit 100000
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | Yes | Trading pair (e.g. BTCUSDC) |
| `--side` | Yes | `Long` or `Short` |
| `--notional` | Yes | Position size in USDC |
| `--leverage` | No | Leverage multiplier |
| `--stop-loss` | No | Stop-loss price |
| `--take-profit` | No | Take-profit price |
| `--limit-price` | No | Limit price for limit orders |
**Pre-trade validation** (check before executing):
1. `get-balance` → if available_balance < 5 USDC → reject, prompt deposit
2. If notional < 10 USDC → reject, suggest increasing
3. margin = notional / leverage. If margin < 5 USDC → reject
4. If margin > available_balance → reject. (notional CAN exceed balance with leverage)
5. Show estimated margin in confirmation: "预计占用保证金: XX.XX USDC (名义价值: XX.XX USDC)"
**Post-execution**: If fails with `User or API Wallet 0x... does not exist`, tell user to deposit USDC.
**Return fields**:
| Field | Type | Description |
|---|---|---|
| `order_id` | String | Order ID |
| `symbol` | String | Trading pair |
| `side` | String | Long/Short |
| `status` | String | Order status |
| `notional` | String | Position size |
| `price` | String | Execution/limit price |
### close-position
```bash
node scripts/taco_client.js close-position \
--symbol BTCUSDC --notional 100 --side Short
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | Yes | Trading pair |
| `--side` | Yes | `Long` or `Short` |
| `--notional` | Yes | Size to close in USDC |
| `--limit-price` | No | Limit price |
### modify-order (NEEDS API)
```bash
node scripts/taco_client.js modify-order \
--symbol BTCUSDC --order-id "12345" \
--new-price 86000 --new-notional 150
```
Amends price and/or size without cancel-and-replace. At least one of `--new-price` or `--new-notional` required.
### set-leverage
```bash
node scripts/taco_client.js set-leverage --symbol BTCUSDC --leverage 5
```
### set-margin-mode
```bash
# Cross margin
node scripts/taco_client.js set-margin-mode --symbol BTCUSDC --cross
# Isolated margin (default)
node scripts/taco_client.js set-margin-mode --symbol BTCUSDC
```
### adjust-margin (NEEDS API)
```bash
node scripts/taco_client.js adjust-margin \
--symbol BTCUSDC --amount 50 --action add
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | Yes | Trading pair |
| `--amount` | Yes | Margin amount in USDC |
| `--action` | Yes | `add` or `remove` |
### set-stop-loss / set-take-profit
```bash
node scripts/taco_client.js set-stop-loss \
--symbol BTCUSDC --side Long --notional 100 --price 85000
node scripts/taco_client.js set-take-profit \
--symbol BTCUSDC --side Long --notional 100 --price 95000
```
### Cancel orders
```bash
node scripts/taco_client.js cancel-stop-loss --symbol BTCUSDC
node scripts/taco_client.js cancel-take-profit --symbol BTCUSDC
node scripts/taco_client.js cancel-stops --symbol BTCUSDC
node scripts/taco_client.js cancel-all --symbol BTCUSDC
node scripts/taco_client.js cancel-order --symbol BTCUSDC --order-id "12345"
```
---
## Account Query Commands
### get-balance
```bash
node scripts/taco_client.js get-balance
```
| Field | Type | Description |
|---|---|---|
| `total_equity` | String | Total account equity in USDC |
| `available_balance` | String | Available for new orders |
| `used_margin` | String | Margin in use |
| `unrealized_pnl` | String | Unrealized PnL |
### get-deposit-address
```bash
node scripts/taco_client.js get-deposit-address
```
Returns `address` (String) — same address for all supported chains.
Supported chains: **Arbitrum** (default, lowest fees), **Ethereum**, **Base**, **Polygon**. Always mention chains when showing address.
### get-positions
```bash
node scripts/taco_client.js get-positions
```
| Field | Type | Description |
|---|---|---|
| `symbol` | String | Trading pair |
| `side` | String | Long/Short |
| `size` | String | Position size |
| `notional` | String | Notional value in USDC |
| `entry_price` | String | Average entry price |
| `mark_price` | String | Current mark price |
| `unrealized_pnl` | String | Unrealized PnL |
| `leverage` | String | Current leverage |
| `margin_mode` | String | Cross/Isolated |
| `liquidation_price` | String | Estimated liquidation price |
### get-open-orders
```bash
node scripts/taco_client.js get-open-orders
```
### get-filled-order
```bash
node scripts/taco_client.js get-filled-order \
--symbol BTCUSDC --order-id "12345"
```
Add `--algo` if the order ID is an algorithmic order ID.
### get-trade-history
```bash
node scripts/taco_client.js get-trade-history \
--symbol BTCUSDC --start-time 1709251200000
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | **Yes** | Trading pair (e.g. BTCUSDC) |
| `--start-time` | **Yes** | Unix ms |
| `--end-time` | No | Unix ms |
| Field | Type | Description |
|---|---|---|
| `exchange` | String | Exchange name (e.g. "Taco") |
| `order_id` | String | Order ID |
| `price` | String | Execution price |
| `quantity` | String | Size in base asset |
| `realized_pnl` | String | Realized PnL (if closing) |
| `timestamp` | Number | Unix ms |
| `trade_fee` | String | Fee paid |
### get-pnl-summary (NEEDS API)
```bash
node scripts/taco_client.js get-pnl-summary --period 7d --symbol BTCUSDC
```
| Param | Required | Description |
|---|---|---|
| `--period` | No | `1d`, `7d`, `30d`, `all`. Default `7d` |
| `--symbol` | No | Filter by symbol |
| Field | Type | Description |
|---|---|---|
| `realized_pnl` | String | Total realized PnL |
| `unrealized_pnl` | String | Current unrealized PnL |
| `funding_received` | String | Funding income |
| `funding_paid` | String | Funding expense |
| `fees_paid` | String | Total fees |
| `net_pnl` | String | Net PnL |
| `trade_count` | Number | Number of trades |
| `win_rate` | String | Win rate (0-1) |
### get-fee-summary (NEEDS API)
```bash
node scripts/taco_client.js get-fee-summary --period 30d
```
### get-credits (NEEDS API)
```bash
node scripts/taco_client.js get-credits
```
| Field | Type | Description |
|---|---|---|
| `free_credits` | Number | Remaining credits |
### get-transfer-history (NEEDS API)
```bash
node scripts/taco_client.js get-transfer-history --limit 20 --type deposit
```
### get-liquidation-price (NEEDS API)
```bash
node scripts/taco_client.js get-liquidation-price --symbol BTCUSDC
```
| Field | Type | Description |
|---|---|---|
| `liquidation_price` | String | Estimated liq price |
| `margin_ratio` | String | Current margin ratio |
| `maintenance_margin` | String | Maintenance margin required |
| `position_margin` | String | Position margin |
---
## Market Data Commands (No Auth)
### get-ticker (NEEDS API)
```bash
# Single symbol
node scripts/taco_client.js get-ticker --symbol BTCUSDC
# All tickers
node scripts/taco_client.js get-ticker
```
| Field | Type | Description |
|---|---|---|
| `symbol` | String | Trading pair |
| `last_price` | String | Last traded price |
| `bid_price` | String | Best bid |
| `ask_price` | String | Best ask |
| `high_24h` | String | 24h high |
| `low_24h` | String | 24h low |
| `volume_24h` | String | 24h volume (base) |
| `quote_volume_24h` | String | 24h volume (USDC) |
| `change_24h` | String | 24h change % |
| `open_interest` | String | Open interest |
### get-kline
```bash
node scripts/taco_client.js get-kline \
--symbol BTCUSDC --interval 1h --start-time 1709251200000
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | Yes | Trading pair |
| `--interval` | Yes | `1m`,`3m`,`5m`,`15m`,`30m`,`1h`,`2h`,`4h`,`6h`,`8h`,`12h`,`1d`,`3d`,`1w`,`1M` |
| `--start-time` | No | Unix ms |
| `--end-time` | No | Unix ms |
| Field | Type | Description |
|---|---|---|
| `open_time` | Number | Candle open time (Unix ms) |
| `close_time` | Number | Candle close time (Unix ms) |
| `open` | String | Open price |
| `high` | String | High price |
| `low` | String | Low price |
| `close` | String | Close price |
| `volume` | String | Volume (base asset) |
| `quote_volume` | String | Volume (USDC) |
| `trades_count` | Number | Number of trades |
Max 100 klines per request.
### get-orderbook (NEEDS API)
```bash
node scripts/taco_client.js get-orderbook --symbol BTCUSDC --depth 20
```
| Field | Type | Description |
|---|---|---|
| `bids` | Array | [[price, size], ...] descending |
| `asks` | Array | [[price, size], ...] ascending |
| `timestamp` | Number | Unix ms |
### get-recent-trades (NEEDS API)
```bash
node scripts/taco_client.js get-recent-trades --symbol BTCUSDC --limit 50
```
### get-funding-rate (NEEDS API)
```bash
# Current
node scripts/taco_client.js get-funding-rate --symbol BTCUSDC
# Historical
node scripts/taco_client.js get-funding-rate --symbol BTCUSDC --history --limit 24
```
| Field | Type | Description |
|---|---|---|
| `current_rate` | String | Current funding rate |
| `predicted_next_rate` | String | Predicted next rate |
| `next_funding_time` | Number | Countdown (Unix ms) |
| `annualized_rate` | String | Annualized % |
### get-mark-price (NEEDS API)
```bash
node scripts/taco_client.js get-mark-price --symbol BTCUSDC
```
### get-symbols (NEEDS API)
```bash
node scripts/taco_client.js get-symbols --type perp
```
| Field | Type | Description |
|---|---|---|
| `symbol` | String | e.g. BTCUSDC |
| `base_asset` | String | e.g. BTC |
| `quote_asset` | String | e.g. USDC |
| `type` | String | `perp` or `spot` |
| `min_order_size` | String | Minimum order size |
| `tick_size` | String | Price tick size |
| `max_leverage` | Number | Maximum leverage |
| `status` | String | `active` / `inactive` |
---
## AI Trader Commands
### get-default-ai-trader
```bash
node scripts/taco_client.js get-default-ai-trader
```
Display to user ONLY: running state(returned value of `1` means paused and returned value of `2` means running), currently used AI strategy tag, trader id, trader name, running frequency. **NEVER** display exchange (like hyperliquid) or model (like deepseek).
### get-default-ai-strategies
```bash
node scripts/taco_client.js get-default-ai-strategies
```
Show tag/description/label/performance data. Do not show complete long content text unless user explicitly asks.
### start-default-ai-trader
```bash
node scripts/taco_client.js start-default-ai-trader
```
### pause-default-ai-trader
```bash
node scripts/taco_client.js pause-default-ai-trader
```
### use-a-default-ai-strategy-for-default-ai-trader
```bash
node scripts/taco_client.js use-a-default-ai-strategy-for-default-ai-trader --strategy-tag <tag>
```
### change-running-interval-for-default-ai-trader
```bash
node scripts/taco_client.js change-running-interval-for-default-ai-trader --interval <interval>
```
### change-name-for-default-ai-trader
```bash
node scripts/taco_client.js change-name-for-default-ai-trader --name <name>
```
FILE:references/api-references.md
# Taco API Reference
## Base URL
`https://api.taco.trade`
## Authentication
Authenticated endpoints require:
- Query parameter: `user_id`
- Header: `Authorization: Bearer <api_token>`
- Request body (POST): include `api_token` and `user_id`
---
## MARKET DATA (No Auth Required)
#### get_kline
- endpoint: /market/klines
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair symbol, e.g. `BTCUSDC`, `ETHUSDC`
- interval (required): kline interval. Supported values: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `6h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M`
- start_time (optional): start time in Unix milliseconds
- end_time (optional): end time in Unix milliseconds
- response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"klines": [
{
"symbol": "BTCUSDC",
"interval": "1h",
"open_time": 1709251200000,
"close_time": 1709254800000,
"open": "62345.50",
"high": "62890.00",
"low": "62100.00",
"close": "62780.30",
"volume": "1234.567",
"quote_volume": "77012345.89",
"trades_count": 45678
}
]
}
```
- notes: Maximum 100 klines per response. Prices as strings for decimal precision.
#### get_ticker
- endpoint: /market/ticker
- method: GET
- parameters:
- query parameters:
- symbol (optional): trading pair. Omit for all tickers.
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"tickers": [
{
"symbol": "BTCUSDC",
"last_price": "87500.00",
"bid_price": "87499.50",
"ask_price": "87500.50",
"high_24h": "88200.00",
"low_24h": "85100.00",
"volume_24h": "12345.67",
"quote_volume_24h": "1080000000.00",
"change_24h": "2.35",
"open_interest": "55000.00",
"timestamp": 1709337600000
}
]
}
```
#### get_orderbook
- endpoint: /market/orderbook
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair
- depth (optional): number of levels per side, default 20, max 100
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbol": "BTCUSDC",
"bids": [["87499.50", "12.5"], ["87499.00", "8.3"]],
"asks": [["87500.50", "10.2"], ["87501.00", "15.7"]],
"timestamp": 1709337600000
}
```
#### get_recent_trades
- endpoint: /market/recent_trades
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair
- limit (optional): max trades, default 50, max 200
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trades": [
{
"symbol": "BTCUSDC",
"price": "87500.00",
"size": "0.5",
"side": "buy",
"timestamp": 1709337600000
}
]
}
```
#### get_funding_rate
- endpoint: /market/funding_rate
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair
- history (optional): `true` for historical rates
- limit (optional): number of periods for historical, default 8
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbol": "BTCUSDC",
"current_rate": "0.0001",
"predicted_next_rate": "0.00012",
"next_funding_time": 1709352000000,
"annualized_rate": "3.65",
"history": [
{ "rate": "0.0001", "timestamp": 1709337600000 }
]
}
```
#### get_mark_price
- endpoint: /market/mark_price
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbol": "BTCUSDC",
"mark_price": "87500.00",
"index_price": "87495.00",
"last_price": "87500.50",
"estimated_funding_rate": "0.0001"
}
```
#### get_symbols
- endpoint: /market/symbols
- method: GET
- parameters:
- query parameters:
- type (optional): `perp`, `spot`, or omit for all
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbols": [
{
"symbol": "BTCUSDC",
"base_asset": "BTC",
"quote_asset": "USDC",
"type": "perp",
"min_order_size": "0.001",
"tick_size": "0.10",
"max_leverage": 50,
"status": "active"
}
]
}
```
---
## TRADING (Auth Required)
#### open_position
- endpoint: /auth/tacoclaw/trade/open_position
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"side": "Long",
"symbol": "BTCUSDC",
"notional_position": 100.0,
"leverage": 3,
"sl_price": 80000.0,
"tp_price": 100000.0,
"limit_price": 87000.0
}
```
- `leverage`, `sl_price`, `tp_price`, `limit_price` are optional.
#### close_position
- endpoint: /auth/tacoclaw/trade/close_position
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"notional_position": 100.0,
"side": "Short",
"limit_price": 88000.0
}
```
- `limit_price` is optional.
#### modify_order
- endpoint: /auth/tacoclaw/trade/modify_order
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"order_id": "12345",
"new_price": 86000.0,
"new_notional_position": 150.0
}
```
- `new_price` and `new_notional_position` are both optional; at least one must be provided.
#### set_leverage
- endpoint: /auth/tacoclaw/trade/set_leverage
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"leverage": 5
}
```
#### set_margin_mode
- endpoint: /auth/tacoclaw/trade/set_margin_mode
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"is_cross_margin": true
}
```
#### adjust_margin
- endpoint: /auth/tacoclaw/trade/adjust_margin
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"amount": 50.0,
"action": "add"
}
```
- `action`: `add` or `remove`
#### set_stop_loss
- endpoint: /auth/tacoclaw/trade/set_stop_loss
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"side": "Long",
"notional_position": 100.0,
"price": 85000.0
}
```
#### set_take_profit
- endpoint: /auth/tacoclaw/trade/set_take_profit
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"side": "Long",
"notional_position": 100.0,
"price": 95000.0
}
```
#### cancel_stop_loss_orders
- endpoint: /auth/tacoclaw/trade/cancel_stop_loss_orders
- method: POST
- body: `{ "api_token", "user_id", "symbol" }`
#### cancel_take_profit_orders
- endpoint: /auth/tacoclaw/trade/cancel_take_profit_orders
- method: POST
- body: `{ "api_token", "user_id", "symbol" }`
#### cancel_stop_orders
- endpoint: /auth/tacoclaw/trade/cancel_stop_orders
- method: POST
- body: `{ "api_token", "user_id", "symbol" }`
#### cancel_all_orders
- endpoint: /auth/tacoclaw/trade/cancel_all_orders
- method: POST
- body: `{ "api_token", "user_id", "symbol" }`
#### cancel_order_by_order_id
- endpoint: /auth/tacoclaw/trade/cancel_order_by_order_id
- method: POST
- body: `{ "api_token", "user_id", "symbol", "order_id" }`
---
## ACCOUNT QUERIES (Auth Required)
#### get_positions
- endpoint: /auth/tacoclaw/trade/get_positions
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
#### get_open_orders
- endpoint: /auth/tacoclaw/trade/get_open_orders
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
#### get_balance
- endpoint: /auth/tacoclaw/trade/get_balance
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
#### get_filled_order_by_order_id
- endpoint: /auth/tacoclaw/trade/get_filled_order_by_order_id
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
- symbol: trading pair
- order_id: the order ID
- is_algo_id (optional): true if order_id is an algo order ID
#### get_trade_history
- endpoint: /auth/tacoclaw/trade/get_trade_history
- method: GET
- parameters:
- query parameters:
- user_id (required): taco user id
- symbol (required): trading pair (e.g. BTCUSDC)
- start_time (required): Unix ms
- end_time (optional): Unix ms
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"records": [
{
"exchange": "Taco",
"order_id": "50473587382",
"price": "2127.1",
"quantity": "0.047",
"realized_pnl": "0",
"timestamp": 1774515516630,
"trade_fee": "0.045987"
}
]
}
```
#### get_pnl_summary
- endpoint: /auth/tacoclaw/account/get_pnl_summary
- method: GET
- parameters:
- query parameters:
- user_id (required)
- period (optional): `1d`, `7d`, `30d`, `all`. Default `7d`
- symbol (optional): filter by symbol
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"period": "7d",
"realized_pnl": "1250.00",
"unrealized_pnl": "-320.00",
"funding_received": "15.00",
"funding_paid": "-45.00",
"fees_paid": "-180.00",
"net_pnl": "720.00",
"trade_count": 34,
"win_rate": "0.62"
}
```
#### get_fee_summary
- endpoint: /auth/tacoclaw/account/get_fee_summary
- method: GET
- parameters:
- query parameters:
- user_id (required)
- period (optional): `1d`, `7d`, `30d`, `all`. Default `30d`
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"period": "30d",
"maker_fees": "-120.00",
"taker_fees": "-380.00",
"funding_fees_net": "-30.00",
"total_fees": "-530.00",
"fee_tier": "VIP1",
"maker_rate": "0.0002",
"taker_rate": "0.0005"
}
```
#### get_credits
- endpoint: /auth/portfolio
- method: GET
- parameters:
- query parameters:
- user_id (required)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"email": "[email protected]",
"free_credits": 30,
"user_id": "did:privy:cmmn6zf1602ub0cl5016mwz2m"
}
```
#### get_deposit_address
- endpoint: /auth/deposit/smart_wallet_address/get
- method: GET
- parameters:
- query parameters:
- user_id (required)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"address": "0x123"
}
```
#### get_transfer_history
- endpoint: /auth/tacoclaw/account/get_transfer_history
- method: GET
- parameters:
- query parameters:
- user_id (required)
- type (optional): `deposit`, `withdrawal`, or omit for all
- limit (optional): max records, default 20
- start_time (optional): Unix ms
- end_time (optional): Unix ms
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"transfers": [
{
"transfer_id": "tf_001",
"type": "deposit",
"amount": "1000.00",
"asset": "USDC",
"status": "completed",
"tx_hash": "0xabc...",
"timestamp": 1709337600000
}
]
}
```
#### get_liquidation_price
- endpoint: /auth/tacoclaw/trade/get_liquidation_price
- method: GET
- parameters:
- query parameters:
- user_id (required)
- symbol (required): trading pair
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbol": "BTCUSDC",
"side": "Long",
"liquidation_price": "72500.00",
"margin_ratio": "0.15",
"maintenance_margin": "125.00",
"position_margin": "875.00"
}
```
#### get_default_ai_trader
- endpoint: /auth/autopilot/default
- method: GET
- parameters:
- query parameters:
- user_id (required)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"default_trader": {
"exchange": "Hyper",
"frequency": 1800,
"prompt_tag": "taco-Data Dana",
"provider": "deepseek",
"provider_model": "deepseek-chat",
"trader_id": "gua6gh-deepseek-1765187795374342",
"trader_name": "gua6ghTacoDefault",
"trader_state": 1,
"use_taco": true,
"user_id": "did:privy:cmionawye03s0lb0cgwgua6gh",
"openclaw_connected": true,
"counter": false,
"exchange_api": "privy"
}
}
```
- meaning of `trader_state` field: 1 means paused (could be started via `start_default_ai_trader`), 2 means running (could be paused via `pause_default_ai_trader`), 0 means deleted (can not be used any more)
#### get_default_ai_strategies
- endpoint: /auth/autopilot/prompts
- method: GET
- parameters:
- query parameters:
- tacoclaw (boolean, optional)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"taco_prompts": [
{
"content": "You are a professional cryptocurrency trading AI......",
"detail": {
"annual_apr": 12.59,
"description": "Build or exit positions in planned installments, reducing the timing risk compared with putting all capital to work at once.",
"executed_trades": 3206,
"labels": [
"Systematic Accumulation",
"Conservative",
"Long Term"
],
"max_drawdown": 10.86,
"pl_ratio": 1.02,
"simu_end": 1773936000,
"simu_start": 1742400000,
"title": "Dollar Cost Average",
"win_rate": 55.26
},
"owner": "taco-autopilot",
"tag": "taco-dollar-cost-average",
"target": "*"
}
}
```
#### start_default_ai_trader
- endpoint: /auth/autopilot/trader/start
- method: POST
- parameters:
- query parameters:
- user_id (required)
- trader_id (required, same as `trader_id` field of get_default_ai_trader result)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 2
}
```
#### pause_default_ai_trader
- endpoint: /auth/autopilot/trader/pause
- method: POST
- parameters:
- query parameters:
- user_id (required)
- trader_id (required, same as `trader_id` field of `get_default_ai_trader` result)
- close_all_position (required)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 1
}
```
#### use_a_default_ai_strategy_for_default_ai_trader
- endpoint: /auth/autopilot/trader/modification
- method: POST
- parameters:
- query parameters:
- user_id (required)
- request body:
```json
{
"user_id": "<user_id>",
"trader_id": "gua6gh-deepseek-1765187859937789",
"prompt_tag": "taco-Momentum Max"
}
```
- valid value for `prompt_tag` should be from `tag` field of `get_default_ai_strategies` result
- `trader_id` should same as from `trader_id` field of `get_default_ai_trader` result
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 2
}
```
#### change_running_interval_for_default_ai_trader
- endpoint: /auth/autopilot/trader/modification
- method: POST
- parameters:
- query parameters:
- user_id (required)
- request body:
```json
{
"user_id": "<user_id>",
"trader_id": "gua6gh-deepseek-1765187859937789",
"frequency": 30
}
```
- valid values for `frequency` are: 15/30/60/120/180/360
- `trader_id` should same as from `trader_id` field of `get_default_ai_trader` result
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 2
}
```
#### change_name_for_default_ai_trader
- endpoint: /auth/autopilot/trader/modification
- method: POST
- parameters:
- query parameters:
- user_id (required)
- request body:
```json
{
"user_id": "<user_id>",
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_name": "newname"
}
```
- `trader_id` should be same as the `trader_id` field of `get_default_ai_trader` result
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 2
}
```
Taco is the AI trading assistant of the Taco crypto DEX. Handles trading (open/close positions, leverage, margin, SL/TP), market data (price, kline, orderboo...
---
name: taco
description: "Taco is the AI trading assistant of the Taco crypto DEX. Handles trading (open/close positions, leverage, margin, SL/TP), market data (price, kline, orderbook, funding rate), account queries (balance, positions, PnL, trade history, AI credits), technical analysis, strategy recommendations, and AI trader management. Triggers: trade, buy, sell, long, short, open, close, balance, price, kline, chart, funding, liquidation, orderbook, leverage, PnL, portfolio, strategy, recommend, autopilot, signal, 买, 卖, 做多, 做空, 开仓, 平仓, 余额, 价格, 行情, 交易策略, 推荐, 该买什么, 有什么机会, 自动交易, 策略推荐, 火热, 热门, 执行, 下单."
---
# Taco Trading Platform Skill
## Identity & Context
**You are Taco** — the AI trading assistant of the Taco platform.
- Refer to yourself as **Taco**.
- All trading intents execute on **Taco by default**. Never ask "which exchange?"
- The user does not need to say "on Taco". Just execute.
- Each user has a default Taco AI trader (in paused state) with predefined strategies.
- The default AI trader shares the same underlying Taco account with trading via connected channels (e.g. Telegram).
### Internal Behavior Rules (NEVER surface to user)
- Default exchange is always Taco. Never announce this or say "I'll execute this on Taco".
- Never tell user "I now support X" or list capabilities in greetings. Just say "Hi, I'm Taco."
- Never describe internal infrastructure unprompted.
### Platform Identity
| Property | Value |
|---|---|
| Platform name | **Taco** |
| Deposit chains | **Arbitrum** (default), **Ethereum**, **Base**, **Polygon** — same address across all chains |
| Supported assets | **Perpetual contracts** and **spot** tokens listed on Taco |
| Quote currency | **USDC** |
| Settlement | On-chain (DEX) |
| Margin modes | Isolated (default), Cross |
| Order types | Market, Limit |
### Defaults (when user doesn't specify)
| Parameter | Default | Notes |
|---|---|---|
| Exchange | Taco | — |
| Quote / Margin | USDC | All sizes and prices in USDC |
| Margin mode | Isolated | Cross only if user requests |
| Leverage | **Ask user** | Never assume |
| Stop-loss | **Suggest, don't auto-set** | — |
| Side | **Must be explicit** | Never assume Long/Short |
| Symbol format | `<BASE>USDC` | e.g. BTCUSDC |
| Kline interval | `1h` | — |
| Trade history limit | 20 | — |
| PnL period | `7d` | — |
### Pre-Trade Validation (CRITICAL — before every `open-position`)
Run `get-balance` first, then check in order:
1. **available_balance < 5 USDC** → Stop. Prompt deposit. Do not proceed.
2. **margin (notional / leverage) > available_balance** → Reject. Suggest deposit or reduce size. Note: notional CAN exceed balance when using leverage.
3. **margin < 5 USDC** → Reject. Prompt deposit or increase trade size.
4. **notional < 10 USDC** → Reject. Suggest increasing to at least 10 USDC.
**AI sizing defaults (internal, never expose):**
- Suggest ≥ 30 USDC notional, ≥ 3x leverage.
- If user explicitly chooses valid values below these, execute without comment. Never say "below recommended".
**Post-execution**: If `open-position` fails with `User or API Wallet 0x... does not exist`, proactively tell user to deposit USDC.
### Personality & Tone
- **Direct and efficient** — traders value speed.
- **Data-first** — show numbers before opinions.
- **Risk-aware** — proactively flag risks.
- **Never hype** — no "to the moon", "bullish AF". Neutral and analytical.
- **Bilingual** — respond in the user's language (Chinese or English).
- **Concise** — "Done. Opened 100 USDC long on BTCUSDC at 3x." not a paragraph.
### Data Behavior Rules (CRITICAL)
**Never estimate data that can be fetched. Always call the API.**
| Scenario | Do this |
|---|---|
| Current price | `get-ticker --symbol <SYM>` |
| Liquidation price | `get-liquidation-price --symbol <SYM>` — never calculate |
| PnL | `get-pnl-summary` or `unrealized_pnl` from `get-positions` |
| Balance | `get-balance` — always fresh |
| AI credits | `get-credits` — always fresh |
| Funding rate | `get-funding-rate --symbol <SYM>` |
| Current position | `get-positions` — never recall from memory |
| AI trader status | `get-default-ai-trader` — show ONLY: running state, strategy tag, trader id, trader name, frequency. NEVER show exchange or model |
| AI strategies list | `get-default-ai-strategies` — show tag/description/label/performance. Don't show full content text unless user asks |
All prices shown to user must come from API, not arithmetic on stale data.
### How to Refer to Taco
| Context | Say | Don't say |
|---|---|---|
| Self-introduction | "I'm Taco, your trading assistant" | "I'm an AI assistant" |
| Platform | "Taco" or "the Taco platform" | "the exchange", "the DEX" |
| Account | "your Taco account" | "your wallet" |
| Deposit | "Deposit USDC to your Taco account" + mention chains | "deposit to Hyperliquid" |
| Unsupported token | "This token isn't available on Taco yet" | — |
| AI trader | "Let's try the default Taco AI trader" | "Do you want me to analyze..." |
### Capabilities
**Can do:** Trade (open/close, leverage, margin, SL/TP, orders), Query (balance, positions, orders, history, PnL, fees, credits, transfers, liquidation), Market data (price, kline, orderbook, trades, funding, mark price, symbols), Analyze (TA, liquidity, funding arb, portfolio, market overview), Risk management, AI Trader with predefined strategies, Analyze your trades (based on trading history, identify successful and unsuccessful trades, develop a personalized trading plan).
**Cannot do:** Trade on other exchanges, trade unlisted tokens, on-chain transfers/bridges outside Taco, access private keys, provide investment advice.
---
## Setup
Config: `~/.openclaw/workspace/taco/config.json`
```json
{
"user_id": "<taco user id>",
"api_token": "<taco api token>"
}
```
If missing, ask for `user_id` and `api_token`, or run: `node scripts/taco_client.js init`
## Runtime
CLI: `node scripts/taco_client.js <command> [options]`
Before running:
1. `command -v node` — ensure Node.js available
2. `node --version` — require v18+ (native fetch)
3. `node -e "require.resolve('commander')"` — if missing: `npm install commander`
---
## Skill Boundary
| Need | Use Taco Skill | Use Other Skill |
|---|---|---|
| Open/close positions, set leverage/SL/TP | ✅ trading commands | — |
| Price, kline, orderbook, funding rate | ✅ market data commands | — |
| Balance, positions, PnL, trade history | ✅ account queries | — |
| AI credits | ✅ `get-credits` | — |
| Strategy recommendations / market scan | ✅ Strategy Engine | — |
| Autopilot configuration | ✅ Strategy Engine | — |
| AI trader management | ✅ AI trader commands | — |
| On-chain token search / metadata | — | chain explorer or token skill |
| Deposit / bridge from other chain | — | wallet / bridge skill |
| Non-Taco tokens | — | Not supported |
---
## Market Data Sources
Taco commands are the primary data source. For market data not requiring auth, use the **Hyperliquid public API** as fallback when Taco endpoint is unavailable or marked "NEEDS API". Never mention "Hyperliquid" to the user.
**Detailed fallback API**: See [references/market-data-fallback.md](references/market-data-fallback.md)
| Data type | Primary | Fallback |
|---|---|---|
| Price | `get-ticker` | Hyperliquid `allMids` |
| Kline | `get-kline` | Hyperliquid `candleSnapshot` |
| Orderbook | `get-orderbook` | Hyperliquid `l2Book` |
| Funding rate | `get-funding-rate` | Hyperliquid `metaAndAssetCtxs` |
| Symbols | `get-symbols` | Hyperliquid `allPerpMetas` |
| Positions / Balance | Taco API only | Hyperliquid `clearinghouseState` (needs 0x) |
| Trade history / PnL / Credits | Taco API only | — |
---
## Routing Rules
_Internal routing logic. Do not describe to user._
| User Intent | Keywords | Action |
|---|---|---|
| Price | price, 多少钱 | `get-ticker` |
| Chart | kline, chart, K线, 走势 | `get-kline` |
| Orderbook | orderbook, depth, 盘口 | `get-orderbook` |
| Funding rate | funding, 资金费率 | `get-funding-rate` |
| Liquidation price | liquidation, 爆仓, 强平 | `get-liquidation-price` |
| Open position | buy, long, short, open, 开仓, 做多, 做空 | `open-position` (with pre-trade checks) |
| Close position | close, sell, 平仓 | `close-position` |
| Positions | position, 持仓 | `get-positions` |
| Balance | balance, 余额 | `get-balance` |
| Open orders | orders, pending, 挂单 | `get-open-orders` |
| Trade history | history, trades, 成交记录 | `get-trade-history` |
| PnL | pnl, profit, 盈亏 | `get-pnl-summary` |
| Fees | fee, 手续费 | `get-fee-summary` |
| Deposit | deposit, 充值, 地址 | `get-deposit-address` + show chains |
| AI credits | credits, 额度 | `get-credits` |
| Symbols | symbols, 能交易什么 | `get-symbols` |
| Technical analysis | analysis, support, resistance, 分析, 该怎么做 | → [Scenario A](references/analysis-workflows.md) |
| Liquidity analysis | liquidity, slippage, 流动性 | → [Scenario B](references/analysis-workflows.md) |
| Funding arbitrage | arbitrage, 套利 | → [Scenario C](references/analysis-workflows.md) |
| Portfolio review | portfolio, 仓位配比 | → [Scenario D](references/analysis-workflows.md) |
| Market overview | market, overview, 行情, 大盘 | → [Scenario E](references/analysis-workflows.md) |
| Strategy recommendation | recommend, strategy, 推荐, 该买什么 | → [Strategy Engine](references/strategy-engine.md) |
| Market scan | scan, signal, hot, trending, 扫描, 热门 | → [Strategy Engine](references/strategy-engine.md) |
| Strategy list | strategy list, 策略列表 | → [Strategy Engine](references/strategy-engine.md) |
| Autopilot config | autopilot, 自动交易 | → [Strategy Engine](references/strategy-engine.md) |
| AI trader status | AI交易员, AI trader | `get-default-ai-trader` (show ONLY: state/strategy/id/name/frequency) |
| AI strategies | AI strategies, AI交易策略 | `get-default-ai-strategies` (show tag/description/label/performance) |
| AI trader positions | AI交易员的仓位 | `get-positions` |
| AI trader balance | AI交易员的余额 | `get-balance` |
| Trade analysis | analyze trades, 分析交易, trading plan, 交易计划 | `get-trade-history` → identify wins/losses → develop personalized trading plan |
| What can you do | what can you do, capabilities, 能干什么, 你能做什么 | List capabilities including: trade analysis — based on your trading history, identify successful and unsuccessful trades, and develop a personalized trading plan tailored to you |
### Symbol Resolution
| Input | Resolves to |
|---|---|
| BTC, Bitcoin, 比特币 | `BTCUSDC` |
| ETH, Ethereum, 以太坊 | `ETHUSDC` |
| Any token | `<TOKEN>USDC` (uppercase + USDC) |
| Format with dash (e.g. `CL-USDC`) | Strip suffix → search in `get-symbols` |
| Unknown token | `get-symbols` to verify |
When resolving via `get-symbols`: strip `-USDC`/`-USDT`/`USDC` suffix, search for base token, match even with prefixes (e.g. `xyz:CL`), prefer unprefixed match.
---
## Safety & Confirmation
**User confirmation required before:**
- `open-position`, `close-position`
- `cancel-*` (all variants)
- `set-stop-loss`, `set-take-profit`
- `modify-order`, `adjust-margin`
If user asks to skip confirmation, re-confirm multiple times before proceeding.
### Risk Awareness (proactive checks)
When opening or increasing leverage:
1. Run pre-trade validation (see above)
2. Leverage > 5x → warn about liquidation risk
3. Notional > 3x available balance → flag "Extremely High Concentration" (advisory)
4. Suggest stop-loss if none specified
5. After opening: `get-liquidation-price` → show: "强平价格: $XX,XXX (距现价 XX.X%)"
6. `get-funding-rate` → if |rate| > 0.03%, warn about holding cost
When checking positions:
1. `get-positions` for live data
2. `get-ticker` for current price — never use stale prices
3. `get-liquidation-price` for each position
---
## Response Templates
See [references/analysis-workflows.md](references/analysis-workflows.md) for detailed templates. Key patterns:
**Balance query**: `get-balance` → `get-positions` → `get-ticker` per position. Show equity, available, margin, PnL, positions with current prices. If balance < 5 USDC, append deposit prompt.
**Positions query**: `get-positions` → `get-ticker` → `get-liquidation-price` per position. Show entry/current price, PnL%, liq price with distance.
**Price query**: `get-ticker` → brief: "BTC: $87,500.00 (24h +2.3%)"
---
## Strategy Engine
Taco includes a built-in strategy engine for market analysis, strategy matching, and trade recommendations. When the user asks for trading opportunities, strategy recommendations, or autopilot configuration:
**Reference**: [references/strategy-engine.md](references/strategy-engine.md)
Covers: Technical indicators, market regime detection, 9 strategies, recommendation cards, execution pipelines, autopilot configuration, risk management.
---
## Command Index
| # | Command | Auth | Description |
|---|---|---|---|
| 1 | `open-position` | ✅ | Open perpetual position |
| 2 | `close-position` | ✅ | Close perpetual position |
| 3 | `modify-order` | ✅ | Amend existing order |
| 4-6 | `set-leverage`, `set-margin-mode`, `adjust-margin` | ✅ | Position settings |
| 7-13 | `set-stop-loss`, `set-take-profit`, `cancel-*` | ✅ | SL/TP and order cancellation |
| 14-24 | `get-positions` thru `get-liquidation-price` | ✅ | Account queries |
| 25-31 | `get-ticker` thru `get-symbols` | ❌ | Market data (no auth) |
| 32-38 | AI trader commands | ✅/❌ | AI trader management |
**Full command details**: See [references/commands.md](references/commands.md)
---
## Suggest Next Steps
After executing a command, suggest 2-3 follow-ups conversationally (never expose command names):
| Just called | Suggest |
|---|---|
| `get-ticker` | View chart, check orderbook, open position |
| `get-kline` | Check funding rate, view orderbook, run TA |
| `get-positions` | Check liq prices, review PnL, portfolio review |
| `get-balance` | View positions, trade history, AI credits. If < 5 USDC → suggest deposit |
| `open-position` | Set stop-loss, check liq price, view position |
| `get-trade-history` | PnL summary, fee summary |
| `get-pnl-summary` | Review positions, fee breakdown, trade history |
| `get-trade-history` (analysis) | Review specific trades, adjust strategy, set up AI trader |
---
## Display Rules
- Prices in USDC with appropriate precision (2 decimals for BTC/ETH, 4+ for small-cap)
- PnL with sign and percentage: `+$125.50 (+3.2%)`
- Funding rate with annualized: `0.01% (8h) ≈ 13.1% annualized`
- Liquidation as price + distance: `Liq: $72,500 (17.1% away)`
- Large numbers with commas: `$1,234,567`
- Never show full AI strategy text unless user asks
- Timestamps in human-readable format
## Error Handling
| Status | Action |
|---|---|
| `401` | Ask user to re-run `init` |
| `400` | Check params, report specific error |
| `User or API Wallet ... does not exist` | Prompt deposit USDC |
| `429` | Wait 5s, retry once |
| `500` | Retry once after 3s |
| Network error | Retry once, then ask user to try later |
Do NOT retry silently on 4xx errors.
## Edge Cases
| Situation | Handling |
|---|---|
| Invalid symbol | Suggest `get-symbols` |
| No positions | Inform, suggest trade history |
| Zero/low balance (< 5 USDC) | Prompt deposit with chains |
| Notional < 10 USDC | Minimum is 10 USDC |
| Liq price very close | Urgent warning, suggest adding margin |
| Non-Taco token | "Not available on Taco yet" |
| Missing critical params | Ask user (don't assume notional, side) |
---
## References
- **[Command details](references/commands.md)** — Full parameters and return fields for all 38 commands
- **[Analysis workflows](references/analysis-workflows.md)** — Technical analysis, liquidity, arbitrage, portfolio review, market overview, response templates, cross-step workflows
- **[Strategy engine](references/strategy-engine.md)** — Indicators, regime detection, 9 strategies, recommendation cards, autopilot
- **[Market data fallback](references/market-data-fallback.md)** — Hyperliquid public API endpoints for fallback data
- **[API reference](references/api-references.md)** — REST API endpoint documentation
---
## Disclaimer
All analysis is based on market data and algorithmic interpretation. Not investment advice. Trading perpetual contracts involves significant risk of loss.
FILE:scripts/taco_client.js
#!/usr/bin/env node
var w=(r,t)=>()=>(t||r((t={exports:{}}).exports,t),t.exports);var S=w(V=>{var v=class extends Error{constructor(t,e,i){super(i),Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name,this.code=e,this.exitCode=t,this.nestedError=void 0}},H=class extends v{constructor(t){super(1,"commander.invalidArgument",t),Error.captureStackTrace(this,this.constructor),this.name=this.constructor.name}};V.CommanderError=v;V.InvalidArgumentError=H});var k=w(P=>{var{InvalidArgumentError:at}=S(),N=class{constructor(t,e){switch(this.description=e||"",this.variadic=!1,this.parseArg=void 0,this.defaultValue=void 0,this.defaultValueDescription=void 0,this.argChoices=void 0,t[0]){case"<":this.required=!0,this._name=t.slice(1,-1);break;case"[":this.required=!1,this._name=t.slice(1,-1);break;default:this.required=!0,this._name=t;break}this._name.endsWith("...")&&(this.variadic=!0,this._name=this._name.slice(0,-3))}name(){return this._name}_collectValue(t,e){return e===this.defaultValue||!Array.isArray(e)?[t]:(e.push(t),e)}default(t,e){return this.defaultValue=t,this.defaultValueDescription=e,this}argParser(t){return this.parseArg=t,this}choices(t){return this.argChoices=t.slice(),this.parseArg=(e,i)=>{if(!this.argChoices.includes(e))throw new at(`Allowed choices are this.argChoices.join(", ").`);return this.variadic?this._collectValue(e,i):e},this}argRequired(){return this.required=!0,this}argOptional(){return this.required=!1,this}};function lt(r){let t=r.name()+(r.variadic===!0?"...":"");return r.required?"<"+t+">":"["+t+"]"}P.Argument=N;P.humanReadableArgName=lt});var G=w(I=>{var{humanReadableArgName:ut}=k(),q=class{constructor(){this.helpWidth=void 0,this.minWidthToWrap=40,this.sortSubcommands=!1,this.sortOptions=!1,this.showGlobalOptions=!1}prepareContext(t){this.helpWidth=this.helpWidth??t.helpWidth??80}visibleCommands(t){let e=t.commands.filter(n=>!n._hidden),i=t._getHelpCommand();return i&&!i._hidden&&e.push(i),this.sortSubcommands&&e.sort((n,s)=>n.name().localeCompare(s.name())),e}compareOptions(t,e){let i=n=>n.short?n.short.replace(/^-/,""):n.long.replace(/^--/,"");return i(t).localeCompare(i(e))}visibleOptions(t){let e=t.options.filter(n=>!n.hidden),i=t._getHelpOption();if(i&&!i.hidden){let n=i.short&&t._findOption(i.short),s=i.long&&t._findOption(i.long);!n&&!s?e.push(i):i.long&&!s?e.push(t.createOption(i.long,i.description)):i.short&&!n&&e.push(t.createOption(i.short,i.description))}return this.sortOptions&&e.sort(this.compareOptions),e}visibleGlobalOptions(t){if(!this.showGlobalOptions)return[];let e=[];for(let i=t.parent;i;i=i.parent){let n=i.options.filter(s=>!s.hidden);e.push(...n)}return this.sortOptions&&e.sort(this.compareOptions),e}visibleArguments(t){return t._argsDescription&&t.registeredArguments.forEach(e=>{e.description=e.description||t._argsDescription[e.name()]||""}),t.registeredArguments.find(e=>e.description)?t.registeredArguments:[]}subcommandTerm(t){let e=t.registeredArguments.map(i=>ut(i)).join(" ");return t._name+(t._aliases[0]?"|"+t._aliases[0]:"")+(t.options.length?" [options]":"")+(e?" "+e:"")}optionTerm(t){return t.flags}argumentTerm(t){return t.name()}longestSubcommandTermLength(t,e){return e.visibleCommands(t).reduce((i,n)=>Math.max(i,this.displayWidth(e.styleSubcommandTerm(e.subcommandTerm(n)))),0)}longestOptionTermLength(t,e){return e.visibleOptions(t).reduce((i,n)=>Math.max(i,this.displayWidth(e.styleOptionTerm(e.optionTerm(n)))),0)}longestGlobalOptionTermLength(t,e){return e.visibleGlobalOptions(t).reduce((i,n)=>Math.max(i,this.displayWidth(e.styleOptionTerm(e.optionTerm(n)))),0)}longestArgumentTermLength(t,e){return e.visibleArguments(t).reduce((i,n)=>Math.max(i,this.displayWidth(e.styleArgumentTerm(e.argumentTerm(n)))),0)}commandUsage(t){let e=t._name;t._aliases[0]&&(e=e+"|"+t._aliases[0]);let i="";for(let n=t.parent;n;n=n.parent)i=n.name()+" "+i;return i+e+" "+t.usage()}commandDescription(t){return t.description()}subcommandDescription(t){return t.summary()||t.description()}optionDescription(t){let e=[];if(t.argChoices&&e.push(`choices: t.argChoices.map(i=>JSON.stringify(i)).join(", ")`),t.defaultValue!==void 0&&(t.required||t.optional||t.isBoolean()&&typeof t.defaultValue=="boolean")&&e.push(`default: t.defaultValueDescription||JSON.stringify(t.defaultValue)`),t.presetArg!==void 0&&t.optional&&e.push(`preset: JSON.stringify(t.presetArg)`),t.envVar!==void 0&&e.push(`env: t.envVar`),e.length>0){let i=`(e.join(", "))`;return t.description?`t.description i`:i}return t.description}argumentDescription(t){let e=[];if(t.argChoices&&e.push(`choices: t.argChoices.map(i=>JSON.stringify(i)).join(", ")`),t.defaultValue!==void 0&&e.push(`default: t.defaultValueDescription||JSON.stringify(t.defaultValue)`),e.length>0){let i=`(e.join(", "))`;return t.description?`t.description i`:i}return t.description}formatItemList(t,e,i){return e.length===0?[]:[i.styleTitle(t),...e,""]}groupItems(t,e,i){let n=new Map;return t.forEach(s=>{let o=i(s);n.has(o)||n.set(o,[])}),e.forEach(s=>{let o=i(s);n.has(o)||n.set(o,[]),n.get(o).push(s)}),n}formatHelp(t,e){let i=e.padWidth(t,e),n=e.helpWidth??80;function s(c,p){return e.formatItem(c,i,p,e)}let o=[`") e.styleUsage(e.commandUsage(t))`,""],a=e.commandDescription(t);a.length>0&&(o=o.concat([e.boxWrap(e.styleCommandDescription(a),n),""]));let u=e.visibleArguments(t).map(c=>s(e.styleArgumentTerm(e.argumentTerm(c)),e.styleArgumentDescription(e.argumentDescription(c))));if(o=o.concat(this.formatItemList("Arguments:",u,e)),this.groupItems(t.options,e.visibleOptions(t),c=>c.helpGroupHeading??"Options:").forEach((c,p)=>{let A=c.map(b=>s(e.styleOptionTerm(e.optionTerm(b)),e.styleOptionDescription(e.optionDescription(b))));o=o.concat(this.formatItemList(p,A,e))}),e.showGlobalOptions){let c=e.visibleGlobalOptions(t).map(p=>s(e.styleOptionTerm(e.optionTerm(p)),e.styleOptionDescription(e.optionDescription(p))));o=o.concat(this.formatItemList("Global Options:",c,e))}return this.groupItems(t.commands,e.visibleCommands(t),c=>c.helpGroup()||"Commands:").forEach((c,p)=>{let A=c.map(b=>s(e.styleSubcommandTerm(e.subcommandTerm(b)),e.styleSubcommandDescription(e.subcommandDescription(b))));o=o.concat(this.formatItemList(p,A,e))}),o.join(`
`)}displayWidth(t){return z(t).length}styleTitle(t){return t}styleUsage(t){return t.split(" ").map(e=>e==="[options]"?this.styleOptionText(e):e==="[command]"?this.styleSubcommandText(e):e[0]==="["||e[0]==="<"?this.styleArgumentText(e):this.styleCommandText(e)).join(" ")}styleCommandDescription(t){return this.styleDescriptionText(t)}styleOptionDescription(t){return this.styleDescriptionText(t)}styleSubcommandDescription(t){return this.styleDescriptionText(t)}styleArgumentDescription(t){return this.styleDescriptionText(t)}styleDescriptionText(t){return t}styleOptionTerm(t){return this.styleOptionText(t)}styleSubcommandTerm(t){return t.split(" ").map(e=>e==="[options]"?this.styleOptionText(e):e[0]==="["||e[0]==="<"?this.styleArgumentText(e):this.styleSubcommandText(e)).join(" ")}styleArgumentTerm(t){return this.styleArgumentText(t)}styleOptionText(t){return t}styleArgumentText(t){return t}styleSubcommandText(t){return t}styleCommandText(t){return t}padWidth(t,e){return Math.max(e.longestOptionTermLength(t,e),e.longestGlobalOptionTermLength(t,e),e.longestSubcommandTermLength(t,e),e.longestArgumentTermLength(t,e))}preformatted(t){return/\n[^\S\r\n]/.test(t)}formatItem(t,e,i,n){let o=" ".repeat(2);if(!i)return o+t;let a=t.padEnd(e+t.length-n.displayWidth(t)),u=2,l=(this.helpWidth??80)-e-u-2,c;return l<this.minWidthToWrap||n.preformatted(i)?c=i:c=n.boxWrap(i,l).replace(/\n/g,`
`+" ".repeat(e+u)),o+a+" ".repeat(u)+c.replace(/\n/g,`
o`)}boxWrap(t,e){if(e<this.minWidthToWrap)return t;let i=t.split(/\r\n|\n/),n=/[\s]*[^\s]+/g,s=[];return i.forEach(o=>{let a=o.match(n);if(a===null){s.push("");return}let u=[a.shift()],d=this.displayWidth(u[0]);a.forEach(l=>{let c=this.displayWidth(l);if(d+c<=e){u.push(l),d+=c;return}s.push(u.join(""));let p=l.trimStart();u=[p],d=this.displayWidth(p)}),s.push(u.join(""))}),s.join(`
`)}};function z(r){let t=/\x1b\[\d*(;\d*)*m/g;return r.replace(t,"")}I.Help=q;I.stripColor=z});var j=w(W=>{var{InvalidArgumentError:ct}=S(),D=class{constructor(t,e){this.flags=t,this.description=e||"",this.required=t.includes("<"),this.optional=t.includes("["),this.variadic=/\w\.\.\.[>\]]$/.test(t),this.mandatory=!1;let i=ht(t);this.short=i.shortFlag,this.long=i.longFlag,this.negate=!1,this.long&&(this.negate=this.long.startsWith("--no-")),this.defaultValue=void 0,this.defaultValueDescription=void 0,this.presetArg=void 0,this.envVar=void 0,this.parseArg=void 0,this.hidden=!1,this.argChoices=void 0,this.conflictsWith=[],this.implied=void 0,this.helpGroupHeading=void 0}default(t,e){return this.defaultValue=t,this.defaultValueDescription=e,this}preset(t){return this.presetArg=t,this}conflicts(t){return this.conflictsWith=this.conflictsWith.concat(t),this}implies(t){let e=t;return typeof t=="string"&&(e={[t]:!0}),this.implied=Object.assign(this.implied||{},e),this}env(t){return this.envVar=t,this}argParser(t){return this.parseArg=t,this}makeOptionMandatory(t=!0){return this.mandatory=!!t,this}hideHelp(t=!0){return this.hidden=!!t,this}_collectValue(t,e){return e===this.defaultValue||!Array.isArray(e)?[t]:(e.push(t),e)}choices(t){return this.argChoices=t.slice(),this.parseArg=(e,i)=>{if(!this.argChoices.includes(e))throw new ct(`Allowed choices are this.argChoices.join(", ").`);return this.variadic?this._collectValue(e,i):e},this}name(){return this.long?this.long.replace(/^--/,""):this.short.replace(/^-/,"")}attributeName(){return this.negate?K(this.name().replace(/^no-/,"")):K(this.name())}helpGroup(t){return this.helpGroupHeading=t,this}is(t){return this.short===t||this.long===t}isBoolean(){return!this.required&&!this.optional&&!this.negate}},F=class{constructor(t){this.positiveOptions=new Map,this.negativeOptions=new Map,this.dualOptions=new Set,t.forEach(e=>{e.negate?this.negativeOptions.set(e.attributeName(),e):this.positiveOptions.set(e.attributeName(),e)}),this.negativeOptions.forEach((e,i)=>{this.positiveOptions.has(i)&&this.dualOptions.add(i)})}valueFromOption(t,e){let i=e.attributeName();if(!this.dualOptions.has(i))return!0;let n=this.negativeOptions.get(i).presetArg,s=n!==void 0?n:!1;return e.negate===(s===t)}};function K(r){return r.split("-").reduce((t,e)=>t+e[0].toUpperCase()+e.slice(1))}function ht(r){let t,e,i=/^-[^-]$/,n=/^--[^-]/,s=r.split(/[ |,]+/).concat("guard");if(i.test(s[0])&&(t=s.shift()),n.test(s[0])&&(e=s.shift()),!t&&i.test(s[0])&&(t=s.shift()),!t&&n.test(s[0])&&(t=e,e=s.shift()),s[0].startsWith("-")){let o=s[0],a=`option creation failed due to 'o' in option flags 'r'`;throw/^-[^-][^-]/.test(o)?new Error(`a
- a short flag is a single dash and a single character
- either use a single dash and a single character (for a short flag)
- or use a double dash for a long option (and can have two, like '--ws, --workspace')`):i.test(o)?new Error(`a
- too many short flags`):n.test(o)?new Error(`a
- too many long flags`):new Error(`a
- unrecognised flag format`)}if(t===void 0&&e===void 0)throw new Error(`option creation failed due to no flags found in 'r'.`);return{shortFlag:t,longFlag:e}}W.Option=D;W.DualOptions=F});var Q=w(Y=>{function dt(r,t){if(Math.abs(r.length-t.length)>3)return Math.max(r.length,t.length);let e=[];for(let i=0;i<=r.length;i++)e[i]=[i];for(let i=0;i<=t.length;i++)e[0][i]=i;for(let i=1;i<=t.length;i++)for(let n=1;n<=r.length;n++){let s=1;r[n-1]===t[i-1]?s=0:s=1,e[n][i]=Math.min(e[n-1][i]+1,e[n][i-1]+1,e[n-1][i-1]+s),n>1&&i>1&&r[n-1]===t[i-2]&&r[n-2]===t[i-1]&&(e[n][i]=Math.min(e[n][i],e[n-2][i-2]+1))}return e[r.length][t.length]}function pt(r,t){if(!t||t.length===0)return"";t=Array.from(new Set(t));let e=r.startsWith("--");e&&(r=r.slice(2),t=t.map(o=>o.slice(2)));let i=[],n=3,s=.4;return t.forEach(o=>{if(o.length<=1)return;let a=dt(r,o),u=Math.max(r.length,o.length);(u-a)/u>s&&(a<n?(n=a,i=[o]):a===n&&i.push(o))}),i.sort((o,a)=>o.localeCompare(a)),e&&(i=i.map(o=>`--o`)),i.length>1?`
(Did you mean one of i.join(", ")?)`:i.length===1?`
(Did you mean i[0]?)`:""}Y.suggestSimilar=pt});var et=w(R=>{var mt=require("node:events").EventEmitter,L=require("node:child_process"),C=require("node:path"),$=require("node:fs"),m=require("node:process"),{Argument:ft,humanReadableArgName:gt}=k(),{CommanderError:M}=S(),{Help:_t,stripColor:Ot}=G(),{Option:X,DualOptions:yt}=j(),{suggestSimilar:Z}=Q(),J=class r extends mt{constructor(t){super(),this.commands=[],this.options=[],this.parent=null,this._allowUnknownOption=!1,this._allowExcessArguments=!1,this.registeredArguments=[],this._args=this.registeredArguments,this.args=[],this.rawArgs=[],this.processedArgs=[],this._scriptPath=null,this._name=t||"",this._optionValues={},this._optionValueSources={},this._storeOptionsAsProperties=!1,this._actionHandler=null,this._executableHandler=!1,this._executableFile=null,this._executableDir=null,this._defaultCommandName=null,this._exitCallback=null,this._aliases=[],this._combineFlagAndOptionalValue=!0,this._description="",this._summary="",this._argsDescription=void 0,this._enablePositionalOptions=!1,this._passThroughOptions=!1,this._lifeCycleHooks={},this._showHelpAfterError=!1,this._showSuggestionAfterError=!0,this._savedState=null,this._outputConfiguration={writeOut:e=>m.stdout.write(e),writeErr:e=>m.stderr.write(e),outputError:(e,i)=>i(e),getOutHelpWidth:()=>m.stdout.isTTY?m.stdout.columns:void 0,getErrHelpWidth:()=>m.stderr.isTTY?m.stderr.columns:void 0,getOutHasColors:()=>U()??(m.stdout.isTTY&&m.stdout.hasColors?.()),getErrHasColors:()=>U()??(m.stderr.isTTY&&m.stderr.hasColors?.()),stripColor:e=>Ot(e)},this._hidden=!1,this._helpOption=void 0,this._addImplicitHelpCommand=void 0,this._helpCommand=void 0,this._helpConfiguration={},this._helpGroupHeading=void 0,this._defaultCommandGroup=void 0,this._defaultOptionGroup=void 0}copyInheritedSettings(t){return this._outputConfiguration=t._outputConfiguration,this._helpOption=t._helpOption,this._helpCommand=t._helpCommand,this._helpConfiguration=t._helpConfiguration,this._exitCallback=t._exitCallback,this._storeOptionsAsProperties=t._storeOptionsAsProperties,this._combineFlagAndOptionalValue=t._combineFlagAndOptionalValue,this._allowExcessArguments=t._allowExcessArguments,this._enablePositionalOptions=t._enablePositionalOptions,this._showHelpAfterError=t._showHelpAfterError,this._showSuggestionAfterError=t._showSuggestionAfterError,this}_getCommandAndAncestors(){let t=[];for(let e=this;e;e=e.parent)t.push(e);return t}command(t,e,i){let n=e,s=i;typeof n=="object"&&n!==null&&(s=n,n=null),s=s||{};let[,o,a]=t.match(/([^ ]+) *(.*)/),u=this.createCommand(o);return n&&(u.description(n),u._executableHandler=!0),s.isDefault&&(this._defaultCommandName=u._name),u._hidden=!!(s.noHelp||s.hidden),u._executableFile=s.executableFile||null,a&&u.arguments(a),this._registerCommand(u),u.parent=this,u.copyInheritedSettings(this),n?this:u}createCommand(t){return new r(t)}createHelp(){return Object.assign(new _t,this.configureHelp())}configureHelp(t){return t===void 0?this._helpConfiguration:(this._helpConfiguration=t,this)}configureOutput(t){return t===void 0?this._outputConfiguration:(this._outputConfiguration={...this._outputConfiguration,...t},this)}showHelpAfterError(t=!0){return typeof t!="string"&&(t=!!t),this._showHelpAfterError=t,this}showSuggestionAfterError(t=!0){return this._showSuggestionAfterError=!!t,this}addCommand(t,e){if(!t._name)throw new Error(`Command passed to .addCommand() must have a name
- specify the name in Command constructor or using .name()`);return e=e||{},e.isDefault&&(this._defaultCommandName=t._name),(e.noHelp||e.hidden)&&(t._hidden=!0),this._registerCommand(t),t.parent=this,t._checkForBrokenPassThrough(),this}createArgument(t,e){return new ft(t,e)}argument(t,e,i,n){let s=this.createArgument(t,e);return typeof i=="function"?s.default(n).argParser(i):s.default(i),this.addArgument(s),this}arguments(t){return t.trim().split(/ +/).forEach(e=>{this.argument(e)}),this}addArgument(t){let e=this.registeredArguments.slice(-1)[0];if(e?.variadic)throw new Error(`only the last argument can be variadic 'e.name()'`);if(t.required&&t.defaultValue!==void 0&&t.parseArg===void 0)throw new Error(`a default value for a required argument is never used: 't.name()'`);return this.registeredArguments.push(t),this}helpCommand(t,e){if(typeof t=="boolean")return this._addImplicitHelpCommand=t,t&&this._defaultCommandGroup&&this._initCommandGroup(this._getHelpCommand()),this;let i=t??"help [command]",[,n,s]=i.match(/([^ ]+) *(.*)/),o=e??"display help for command",a=this.createCommand(n);return a.helpOption(!1),s&&a.arguments(s),o&&a.description(o),this._addImplicitHelpCommand=!0,this._helpCommand=a,(t||e)&&this._initCommandGroup(a),this}addHelpCommand(t,e){return typeof t!="object"?(this.helpCommand(t,e),this):(this._addImplicitHelpCommand=!0,this._helpCommand=t,this._initCommandGroup(t),this)}_getHelpCommand(){return this._addImplicitHelpCommand??(this.commands.length&&!this._actionHandler&&!this._findCommand("help"))?(this._helpCommand===void 0&&this.helpCommand(void 0,void 0),this._helpCommand):null}hook(t,e){let i=["preSubcommand","preAction","postAction"];if(!i.includes(t))throw new Error(`Unexpected value for event passed to hook : 't'.
Expecting one of 'i.join("', '")'`);return this._lifeCycleHooks[t]?this._lifeCycleHooks[t].push(e):this._lifeCycleHooks[t]=[e],this}exitOverride(t){return t?this._exitCallback=t:this._exitCallback=e=>{if(e.code!=="commander.executeSubCommandAsync")throw e},this}_exit(t,e,i){this._exitCallback&&this._exitCallback(new M(t,e,i)),m.exit(t)}action(t){let e=i=>{let n=this.registeredArguments.length,s=i.slice(0,n);return this._storeOptionsAsProperties?s[n]=this:s[n]=this.opts(),s.push(this),t.apply(this,s)};return this._actionHandler=e,this}createOption(t,e){return new X(t,e)}_callParseArg(t,e,i,n){try{return t.parseArg(e,i)}catch(s){if(s.code==="commander.invalidArgument"){let o=`n s.message`;this.error(o,{exitCode:s.exitCode,code:s.code})}throw s}}_registerOption(t){let e=t.short&&this._findOption(t.short)||t.long&&this._findOption(t.long);if(e){let i=t.long&&this._findOption(t.long)?t.long:t.short;throw new Error(`Cannot add option 't.flags'this._name&&` to command '${this._name'`} due to conflicting flag 'i'
- already used by option 'e.flags'`)}this._initOptionGroup(t),this.options.push(t)}_registerCommand(t){let e=n=>[n.name()].concat(n.aliases()),i=e(t).find(n=>this._findCommand(n));if(i){let n=e(this._findCommand(i)).join("|"),s=e(t).join("|");throw new Error(`cannot add command 's' as already have command 'n'`)}this._initCommandGroup(t),this.commands.push(t)}addOption(t){this._registerOption(t);let e=t.name(),i=t.attributeName();if(t.negate){let s=t.long.replace(/^--no-/,"--");this._findOption(s)||this.setOptionValueWithSource(i,t.defaultValue===void 0?!0:t.defaultValue,"default")}else t.defaultValue!==void 0&&this.setOptionValueWithSource(i,t.defaultValue,"default");let n=(s,o,a)=>{s==null&&t.presetArg!==void 0&&(s=t.presetArg);let u=this.getOptionValue(i);s!==null&&t.parseArg?s=this._callParseArg(t,s,u,o):s!==null&&t.variadic&&(s=t._collectValue(s,u)),s==null&&(t.negate?s=!1:t.isBoolean()||t.optional?s=!0:s=""),this.setOptionValueWithSource(i,s,a)};return this.on("option:"+e,s=>{let o=`error: option 't.flags' argument 's' is invalid.`;n(s,o,"cli")}),t.envVar&&this.on("optionEnv:"+e,s=>{let o=`error: option 't.flags' value 's' from env 't.envVar' is invalid.`;n(s,o,"env")}),this}_optionEx(t,e,i,n,s){if(typeof e=="object"&&e instanceof X)throw new Error("To add an Option object use addOption() instead of option() or requiredOption()");let o=this.createOption(e,i);if(o.makeOptionMandatory(!!t.mandatory),typeof n=="function")o.default(s).argParser(n);else if(n instanceof RegExp){let a=n;n=(u,d)=>{let l=a.exec(u);return l?l[0]:d},o.default(s).argParser(n)}else o.default(n);return this.addOption(o)}option(t,e,i,n){return this._optionEx({},t,e,i,n)}requiredOption(t,e,i,n){return this._optionEx({mandatory:!0},t,e,i,n)}combineFlagAndOptionalValue(t=!0){return this._combineFlagAndOptionalValue=!!t,this}allowUnknownOption(t=!0){return this._allowUnknownOption=!!t,this}allowExcessArguments(t=!0){return this._allowExcessArguments=!!t,this}enablePositionalOptions(t=!0){return this._enablePositionalOptions=!!t,this}passThroughOptions(t=!0){return this._passThroughOptions=!!t,this._checkForBrokenPassThrough(),this}_checkForBrokenPassThrough(){if(this.parent&&this._passThroughOptions&&!this.parent._enablePositionalOptions)throw new Error(`passThroughOptions cannot be used for 'this._name' without turning on enablePositionalOptions for parent command(s)`)}storeOptionsAsProperties(t=!0){if(this.options.length)throw new Error("call .storeOptionsAsProperties() before adding options");if(Object.keys(this._optionValues).length)throw new Error("call .storeOptionsAsProperties() before setting option values");return this._storeOptionsAsProperties=!!t,this}getOptionValue(t){return this._storeOptionsAsProperties?this[t]:this._optionValues[t]}setOptionValue(t,e){return this.setOptionValueWithSource(t,e,void 0)}setOptionValueWithSource(t,e,i){return this._storeOptionsAsProperties?this[t]=e:this._optionValues[t]=e,this._optionValueSources[t]=i,this}getOptionValueSource(t){return this._optionValueSources[t]}getOptionValueSourceWithGlobals(t){let e;return this._getCommandAndAncestors().forEach(i=>{i.getOptionValueSource(t)!==void 0&&(e=i.getOptionValueSource(t))}),e}_prepareUserArgs(t,e){if(t!==void 0&&!Array.isArray(t))throw new Error("first parameter to parse must be array or undefined");if(e=e||{},t===void 0&&e.from===void 0){m.versions?.electron&&(e.from="electron");let n=m.execArgv??[];(n.includes("-e")||n.includes("--eval")||n.includes("-p")||n.includes("--print"))&&(e.from="eval")}t===void 0&&(t=m.argv),this.rawArgs=t.slice();let i;switch(e.from){case void 0:case"node":this._scriptPath=t[1],i=t.slice(2);break;case"electron":m.defaultApp?(this._scriptPath=t[1],i=t.slice(2)):i=t.slice(1);break;case"user":i=t.slice(0);break;case"eval":i=t.slice(1);break;default:throw new Error(`unexpected parse option { from: 'e.from' }`)}return!this._name&&this._scriptPath&&this.nameFromFilename(this._scriptPath),this._name=this._name||"program",i}parse(t,e){this._prepareForParse();let i=this._prepareUserArgs(t,e);return this._parseCommand([],i),this}async parseAsync(t,e){this._prepareForParse();let i=this._prepareUserArgs(t,e);return await this._parseCommand([],i),this}_prepareForParse(){this._savedState===null?this.saveStateBeforeParse():this.restoreStateBeforeParse()}saveStateBeforeParse(){this._savedState={_name:this._name,_optionValues:{...this._optionValues},_optionValueSources:{...this._optionValueSources}}}restoreStateBeforeParse(){if(this._storeOptionsAsProperties)throw new Error(`Can not call parse again when storeOptionsAsProperties is true.
- either make a new Command for each call to parse, or stop storing options as properties`);this._name=this._savedState._name,this._scriptPath=null,this.rawArgs=[],this._optionValues={...this._savedState._optionValues},this._optionValueSources={...this._savedState._optionValueSources},this.args=[],this.processedArgs=[]}_checkForMissingExecutable(t,e,i){if($.existsSync(t))return;let n=e?`searched for local subcommand relative to directory 'e'`:"no directory for search for local subcommand, use .executableDir() to supply a custom directory",s=`'t' does not exist
- if 'i' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
- if the default executable name is not suitable, use the executableFile option to supply a custom name or path
- n`;throw new Error(s)}_executeSubCommand(t,e){e=e.slice();let i=!1,n=[".js",".ts",".tsx",".mjs",".cjs"];function s(l,c){let p=C.resolve(l,c);if($.existsSync(p))return p;if(n.includes(C.extname(c)))return;let A=n.find(b=>$.existsSync(`pb`));if(A)return`pA`}this._checkForMissingMandatoryOptions(),this._checkForConflictingOptions();let o=t._executableFile||`this._name-t._name`,a=this._executableDir||"";if(this._scriptPath){let l;try{l=$.realpathSync(this._scriptPath)}catch{l=this._scriptPath}a=C.resolve(C.dirname(l),a)}if(a){let l=s(a,o);if(!l&&!t._executableFile&&this._scriptPath){let c=C.basename(this._scriptPath,C.extname(this._scriptPath));c!==this._name&&(l=s(a,`c-t._name`))}o=l||o}i=n.includes(C.extname(o));let u;m.platform!=="win32"?i?(e.unshift(o),e=tt(m.execArgv).concat(e),u=L.spawn(m.argv[0],e,{stdio:"inherit"})):u=L.spawn(o,e,{stdio:"inherit"}):(this._checkForMissingExecutable(o,a,t._name),e.unshift(o),e=tt(m.execArgv).concat(e),u=L.spawn(m.execPath,e,{stdio:"inherit"})),u.killed||["SIGUSR1","SIGUSR2","SIGTERM","SIGINT","SIGHUP"].forEach(c=>{m.on(c,()=>{u.killed===!1&&u.exitCode===null&&u.kill(c)})});let d=this._exitCallback;u.on("close",l=>{l=l??1,d?d(new M(l,"commander.executeSubCommandAsync","(close)")):m.exit(l)}),u.on("error",l=>{if(l.code==="ENOENT")this._checkForMissingExecutable(o,a,t._name);else if(l.code==="EACCES")throw new Error(`'o' not executable`);if(!d)m.exit(1);else{let c=new M(1,"commander.executeSubCommandAsync","(error)");c.nestedError=l,d(c)}}),this.runningCommand=u}_dispatchSubcommand(t,e,i){let n=this._findCommand(t);n||this.help({error:!0}),n._prepareForParse();let s;return s=this._chainOrCallSubCommandHook(s,n,"preSubcommand"),s=this._chainOrCall(s,()=>{if(n._executableHandler)this._executeSubCommand(n,e.concat(i));else return n._parseCommand(e,i)}),s}_dispatchHelpCommand(t){t||this.help();let e=this._findCommand(t);return e&&!e._executableHandler&&e.help(),this._dispatchSubcommand(t,[],[this._getHelpOption()?.long??this._getHelpOption()?.short??"--help"])}_checkNumberOfArguments(){this.registeredArguments.forEach((t,e)=>{t.required&&this.args[e]==null&&this.missingArgument(t.name())}),!(this.registeredArguments.length>0&&this.registeredArguments[this.registeredArguments.length-1].variadic)&&this.args.length>this.registeredArguments.length&&this._excessArguments(this.args)}_processArguments(){let t=(i,n,s)=>{let o=n;if(n!==null&&i.parseArg){let a=`error: command-argument value 'n' is invalid for argument 'i.name()'.`;o=this._callParseArg(i,n,s,a)}return o};this._checkNumberOfArguments();let e=[];this.registeredArguments.forEach((i,n)=>{let s=i.defaultValue;i.variadic?n<this.args.length?(s=this.args.slice(n),i.parseArg&&(s=s.reduce((o,a)=>t(i,a,o),i.defaultValue))):s===void 0&&(s=[]):n<this.args.length&&(s=this.args[n],i.parseArg&&(s=t(i,s,i.defaultValue))),e[n]=s}),this.processedArgs=e}_chainOrCall(t,e){return t?.then&&typeof t.then=="function"?t.then(()=>e()):e()}_chainOrCallHooks(t,e){let i=t,n=[];return this._getCommandAndAncestors().reverse().filter(s=>s._lifeCycleHooks[e]!==void 0).forEach(s=>{s._lifeCycleHooks[e].forEach(o=>{n.push({hookedCommand:s,callback:o})})}),e==="postAction"&&n.reverse(),n.forEach(s=>{i=this._chainOrCall(i,()=>s.callback(s.hookedCommand,this))}),i}_chainOrCallSubCommandHook(t,e,i){let n=t;return this._lifeCycleHooks[i]!==void 0&&this._lifeCycleHooks[i].forEach(s=>{n=this._chainOrCall(n,()=>s(this,e))}),n}_parseCommand(t,e){let i=this.parseOptions(e);if(this._parseOptionsEnv(),this._parseOptionsImplied(),t=t.concat(i.operands),e=i.unknown,this.args=t.concat(e),t&&this._findCommand(t[0]))return this._dispatchSubcommand(t[0],t.slice(1),e);if(this._getHelpCommand()&&t[0]===this._getHelpCommand().name())return this._dispatchHelpCommand(t[1]);if(this._defaultCommandName)return this._outputHelpIfRequested(e),this._dispatchSubcommand(this._defaultCommandName,t,e);this.commands.length&&this.args.length===0&&!this._actionHandler&&!this._defaultCommandName&&this.help({error:!0}),this._outputHelpIfRequested(i.unknown),this._checkForMissingMandatoryOptions(),this._checkForConflictingOptions();let n=()=>{i.unknown.length>0&&this.unknownOption(i.unknown[0])},s=`command:this.name()`;if(this._actionHandler){n(),this._processArguments();let o;return o=this._chainOrCallHooks(o,"preAction"),o=this._chainOrCall(o,()=>this._actionHandler(this.processedArgs)),this.parent&&(o=this._chainOrCall(o,()=>{this.parent.emit(s,t,e)})),o=this._chainOrCallHooks(o,"postAction"),o}if(this.parent?.listenerCount(s))n(),this._processArguments(),this.parent.emit(s,t,e);else if(t.length){if(this._findCommand("*"))return this._dispatchSubcommand("*",t,e);this.listenerCount("command:*")?this.emit("command:*",t,e):this.commands.length?this.unknownCommand():(n(),this._processArguments())}else this.commands.length?(n(),this.help({error:!0})):(n(),this._processArguments())}_findCommand(t){if(t)return this.commands.find(e=>e._name===t||e._aliases.includes(t))}_findOption(t){return this.options.find(e=>e.is(t))}_checkForMissingMandatoryOptions(){this._getCommandAndAncestors().forEach(t=>{t.options.forEach(e=>{e.mandatory&&t.getOptionValue(e.attributeName())===void 0&&t.missingMandatoryOptionValue(e)})})}_checkForConflictingLocalOptions(){let t=this.options.filter(i=>{let n=i.attributeName();return this.getOptionValue(n)===void 0?!1:this.getOptionValueSource(n)!=="default"});t.filter(i=>i.conflictsWith.length>0).forEach(i=>{let n=t.find(s=>i.conflictsWith.includes(s.attributeName()));n&&this._conflictingOption(i,n)})}_checkForConflictingOptions(){this._getCommandAndAncestors().forEach(t=>{t._checkForConflictingLocalOptions()})}parseOptions(t){let e=[],i=[],n=e;function s(l){return l.length>1&&l[0]==="-"}let o=l=>/^-(\d+|\d*\.\d+)(e[+-]?\d+)?$/.test(l)?!this._getCommandAndAncestors().some(c=>c.options.map(p=>p.short).some(p=>/^-\d$/.test(p))):!1,a=null,u=null,d=0;for(;d<t.length||u;){let l=u??t[d++];if(u=null,l==="--"){n===i&&n.push(l),n.push(...t.slice(d));break}if(a&&(!s(l)||o(l))){this.emit(`option:a.name()`,l);continue}if(a=null,s(l)){let c=this._findOption(l);if(c){if(c.required){let p=t[d++];p===void 0&&this.optionMissingArgument(c),this.emit(`option:c.name()`,p)}else if(c.optional){let p=null;d<t.length&&(!s(t[d])||o(t[d]))&&(p=t[d++]),this.emit(`option:c.name()`,p)}else this.emit(`option:c.name()`);a=c.variadic?c:null;continue}}if(l.length>2&&l[0]==="-"&&l[1]!=="-"){let c=this._findOption(`-l[1]`);if(c){c.required||c.optional&&this._combineFlagAndOptionalValue?this.emit(`option:c.name()`,l.slice(2)):(this.emit(`option:c.name()`),u=`-l.slice(2)`);continue}}if(/^--[^=]+=/.test(l)){let c=l.indexOf("="),p=this._findOption(l.slice(0,c));if(p&&(p.required||p.optional)){this.emit(`option:p.name()`,l.slice(c+1));continue}}if(n===e&&s(l)&&!(this.commands.length===0&&o(l))&&(n=i),(this._enablePositionalOptions||this._passThroughOptions)&&e.length===0&&i.length===0){if(this._findCommand(l)){e.push(l),i.push(...t.slice(d));break}else if(this._getHelpCommand()&&l===this._getHelpCommand().name()){e.push(l,...t.slice(d));break}else if(this._defaultCommandName){i.push(l,...t.slice(d));break}}if(this._passThroughOptions){n.push(l,...t.slice(d));break}n.push(l)}return{operands:e,unknown:i}}opts(){if(this._storeOptionsAsProperties){let t={},e=this.options.length;for(let i=0;i<e;i++){let n=this.options[i].attributeName();t[n]=n===this._versionOptionName?this._version:this[n]}return t}return this._optionValues}optsWithGlobals(){return this._getCommandAndAncestors().reduce((t,e)=>Object.assign(t,e.opts()),{})}error(t,e){this._outputConfiguration.outputError(`t
`,this._outputConfiguration.writeErr),typeof this._showHelpAfterError=="string"?this._outputConfiguration.writeErr(`this._showHelpAfterError
`):this._showHelpAfterError&&(this._outputConfiguration.writeErr(`
`),this.outputHelp({error:!0}));let i=e||{},n=i.exitCode||1,s=i.code||"commander.error";this._exit(n,s,t)}_parseOptionsEnv(){this.options.forEach(t=>{if(t.envVar&&t.envVar in m.env){let e=t.attributeName();(this.getOptionValue(e)===void 0||["default","config","env"].includes(this.getOptionValueSource(e)))&&(t.required||t.optional?this.emit(`optionEnv:t.name()`,m.env[t.envVar]):this.emit(`optionEnv:t.name()`))}})}_parseOptionsImplied(){let t=new yt(this.options),e=i=>this.getOptionValue(i)!==void 0&&!["default","implied"].includes(this.getOptionValueSource(i));this.options.filter(i=>i.implied!==void 0&&e(i.attributeName())&&t.valueFromOption(this.getOptionValue(i.attributeName()),i)).forEach(i=>{Object.keys(i.implied).filter(n=>!e(n)).forEach(n=>{this.setOptionValueWithSource(n,i.implied[n],"implied")})})}missingArgument(t){let e=`error: missing required argument 't'`;this.error(e,{code:"commander.missingArgument"})}optionMissingArgument(t){let e=`error: option 't.flags' argument missing`;this.error(e,{code:"commander.optionMissingArgument"})}missingMandatoryOptionValue(t){let e=`error: required option 't.flags' not specified`;this.error(e,{code:"commander.missingMandatoryOptionValue"})}_conflictingOption(t,e){let i=o=>{let a=o.attributeName(),u=this.getOptionValue(a),d=this.options.find(c=>c.negate&&a===c.attributeName()),l=this.options.find(c=>!c.negate&&a===c.attributeName());return d&&(d.presetArg===void 0&&u===!1||d.presetArg!==void 0&&u===d.presetArg)?d:l||o},n=o=>{let a=i(o),u=a.attributeName();return this.getOptionValueSource(u)==="env"?`environment variable 'a.envVar'`:`option 'a.flags'`},s=`error: n(t) cannot be used with n(e)`;this.error(s,{code:"commander.conflictingOption"})}unknownOption(t){if(this._allowUnknownOption)return;let e="";if(t.startsWith("--")&&this._showSuggestionAfterError){let n=[],s=this;do{let o=s.createHelp().visibleOptions(s).filter(a=>a.long).map(a=>a.long);n=n.concat(o),s=s.parent}while(s&&!s._enablePositionalOptions);e=Z(t,n)}let i=`error: unknown option 't'e`;this.error(i,{code:"commander.unknownOption"})}_excessArguments(t){if(this._allowExcessArguments)return;let e=this.registeredArguments.length,i=e===1?"":"s",s=`error: too many argumentsthis.parent?` for '${this.name()'`:""}. Expected e argumenti but got t.length.`;this.error(s,{code:"commander.excessArguments"})}unknownCommand(){let t=this.args[0],e="";if(this._showSuggestionAfterError){let n=[];this.createHelp().visibleCommands(this).forEach(s=>{n.push(s.name()),s.alias()&&n.push(s.alias())}),e=Z(t,n)}let i=`error: unknown command 't'e`;this.error(i,{code:"commander.unknownCommand"})}version(t,e,i){if(t===void 0)return this._version;this._version=t,e=e||"-V, --version",i=i||"output the version number";let n=this.createOption(e,i);return this._versionOptionName=n.attributeName(),this._registerOption(n),this.on("option:"+n.name(),()=>{this._outputConfiguration.writeOut(`t
`),this._exit(0,"commander.version",t)}),this}description(t,e){return t===void 0&&e===void 0?this._description:(this._description=t,e&&(this._argsDescription=e),this)}summary(t){return t===void 0?this._summary:(this._summary=t,this)}alias(t){if(t===void 0)return this._aliases[0];let e=this;if(this.commands.length!==0&&this.commands[this.commands.length-1]._executableHandler&&(e=this.commands[this.commands.length-1]),t===e._name)throw new Error("Command alias can't be the same as its name");let i=this.parent?._findCommand(t);if(i){let n=[i.name()].concat(i.aliases()).join("|");throw new Error(`cannot add alias 't' to command 'this.name()' as already have command 'n'`)}return e._aliases.push(t),this}aliases(t){return t===void 0?this._aliases:(t.forEach(e=>this.alias(e)),this)}usage(t){if(t===void 0){if(this._usage)return this._usage;let e=this.registeredArguments.map(i=>gt(i));return[].concat(this.options.length||this._helpOption!==null?"[options]":[],this.commands.length?"[command]":[],this.registeredArguments.length?e:[]).join(" ")}return this._usage=t,this}name(t){return t===void 0?this._name:(this._name=t,this)}helpGroup(t){return t===void 0?this._helpGroupHeading??"":(this._helpGroupHeading=t,this)}commandsGroup(t){return t===void 0?this._defaultCommandGroup??"":(this._defaultCommandGroup=t,this)}optionsGroup(t){return t===void 0?this._defaultOptionGroup??"":(this._defaultOptionGroup=t,this)}_initOptionGroup(t){this._defaultOptionGroup&&!t.helpGroupHeading&&t.helpGroup(this._defaultOptionGroup)}_initCommandGroup(t){this._defaultCommandGroup&&!t.helpGroup()&&t.helpGroup(this._defaultCommandGroup)}nameFromFilename(t){return this._name=C.basename(t,C.extname(t)),this}executableDir(t){return t===void 0?this._executableDir:(this._executableDir=t,this)}helpInformation(t){let e=this.createHelp(),i=this._getOutputContext(t);e.prepareContext({error:i.error,helpWidth:i.helpWidth,outputHasColors:i.hasColors});let n=e.formatHelp(this,e);return i.hasColors?n:this._outputConfiguration.stripColor(n)}_getOutputContext(t){t=t||{};let e=!!t.error,i,n,s;return e?(i=a=>this._outputConfiguration.writeErr(a),n=this._outputConfiguration.getErrHasColors(),s=this._outputConfiguration.getErrHelpWidth()):(i=a=>this._outputConfiguration.writeOut(a),n=this._outputConfiguration.getOutHasColors(),s=this._outputConfiguration.getOutHelpWidth()),{error:e,write:a=>(n||(a=this._outputConfiguration.stripColor(a)),i(a)),hasColors:n,helpWidth:s}}outputHelp(t){let e;typeof t=="function"&&(e=t,t=void 0);let i=this._getOutputContext(t),n={error:i.error,write:i.write,command:this};this._getCommandAndAncestors().reverse().forEach(o=>o.emit("beforeAllHelp",n)),this.emit("beforeHelp",n);let s=this.helpInformation({error:i.error});if(e&&(s=e(s),typeof s!="string"&&!Buffer.isBuffer(s)))throw new Error("outputHelp callback must return a string or a Buffer");i.write(s),this._getHelpOption()?.long&&this.emit(this._getHelpOption().long),this.emit("afterHelp",n),this._getCommandAndAncestors().forEach(o=>o.emit("afterAllHelp",n))}helpOption(t,e){return typeof t=="boolean"?(t?(this._helpOption===null&&(this._helpOption=void 0),this._defaultOptionGroup&&this._initOptionGroup(this._getHelpOption())):this._helpOption=null,this):(this._helpOption=this.createOption(t??"-h, --help",e??"display help for command"),(t||e)&&this._initOptionGroup(this._helpOption),this)}_getHelpOption(){return this._helpOption===void 0&&this.helpOption(void 0,void 0),this._helpOption}addHelpOption(t){return this._helpOption=t,this._initOptionGroup(t),this}help(t){this.outputHelp(t);let e=Number(m.exitCode??0);e===0&&t&&typeof t!="function"&&t.error&&(e=1),this._exit(e,"commander.help","(outputHelp)")}addHelpText(t,e){let i=["beforeAll","before","after","afterAll"];if(!i.includes(t))throw new Error(`Unexpected value for position to addHelpText.
Expecting one of 'i.join("', '")'`);let n=`tHelp`;return this.on(n,s=>{let o;typeof e=="function"?o=e({error:s.error,command:s.command}):o=e,o&&s.write(`o
`)}),this}_outputHelpIfRequested(t){let e=this._getHelpOption();e&&t.find(n=>e.is(n))&&(this.outputHelp(),this._exit(0,"commander.helpDisplayed","(outputHelp)"))}};function tt(r){return r.map(t=>{if(!t.startsWith("--inspect"))return t;let e,i="127.0.0.1",n="9229",s;return(s=t.match(/^(--inspect(-brk)?)$/))!==null?e=s[1]:(s=t.match(/^(--inspect(-brk|-port)?)=([^:]+)$/))!==null?(e=s[1],/^\d+$/.test(s[3])?n=s[3]:i=s[3]):(s=t.match(/^(--inspect(-brk|-port)?)=([^:]+):(\d+)$/))!==null&&(e=s[1],i=s[3],n=s[4]),e&&n!=="0"?`e=i:parseInt(n)+1`:t})}function U(){if(m.env.NO_COLOR||m.env.FORCE_COLOR==="0"||m.env.FORCE_COLOR==="false")return!1;if(m.env.FORCE_COLOR||m.env.CLICOLOR_FORCE!==void 0)return!0}R.Command=J;R.useColor=U});var rt=w(_=>{var{Argument:it}=k(),{Command:B}=et(),{CommanderError:Ct,InvalidArgumentError:nt}=S(),{Help:bt}=G(),{Option:st}=j();_.program=new B;_.createCommand=r=>new B(r);_.createOption=(r,t)=>new st(r,t);_.createArgument=(r,t)=>new it(r,t);_.Command=B;_.Option=st;_.Argument=it;_.Help=bt;_.CommanderError=Ct;_.InvalidArgumentError=nt;_.InvalidOptionArgumentError=nt});var E=require("fs"),T=require("path"),wt=require("os"),At=require("readline"),{Command:St}=rt(),Et="https://api.dev.taco.trading",Tt=T.join(wt.homedir(),".openclaw","workspace","taco","config.json");typeof fetch>"u"&&(console.error("Error: This script requires Node.js v18+ for native fetch API."),process.exit(1));function g(r){E.existsSync(r)||(console.error(`Error: config file not found: r`),console.error(`Create it with: node T.basename(process.argv[1]) init`),process.exit(1));let t=JSON.parse(E.readFileSync(r,"utf8")),i=["user_id","api_token"].filter(n=>!t[n]);return i.length&&(console.error(`Error: config missing required fields: i.join(", ")`),process.exit(1)),t}function x(r,t){if(typeof r!="string"||typeof t!="string")throw new Error("Both user_id and api_token must be strings");if(r.length<6||t.length<10)throw new Error("user_id must be at least 6 characters and api_token must be at least 10 characters");let e=r.slice(-6),i=t.slice(-10);return`e-i`}async function f(r,t,e=null,i=null,n=null){let s=new URL(`Ett`);if(e)for(let[u,d]of Object.entries(e))d!=null&&s.searchParams.append(u,d);let o={};n&&(o.Authorization=`Bearer n`);let a={method:r,headers:o};i&&(o["Content-Type"]="application/json",a.body=JSON.stringify(i));try{let u=await fetch(s,a);if(!u.ok){let d=await u.text();console.error(`Error: API request failed based on status u.status`),console.error(`Response: d`),process.exit(1)}return await u.json()}catch(u){console.error(`Error: API request failed: u.message`),process.exit(1)}}function ot(r){let t=At.createInterface({input:process.stdin,output:process.stdout});return new Promise(e=>t.question(r,i=>{t.close(),e(i)}))}async function xt(r){let t=T.resolve(r.config);E.existsSync(t)&&(console.error(`Config already exists at t`),console.error("Delete it first if you want to reinitialize."),process.exit(1));let e=(await ot("Enter your Taco user_id: ")).trim(),i=(await ot("Enter your Taco api_token: ")).trim();(!e||!i)&&(console.error("Error: user_id and api_token are required"),process.exit(1)),E.mkdirSync(T.dirname(t),{recursive:!0});let n={user_id:e,api_token:i};E.writeFileSync(t,JSON.stringify(n,null,2)),console.log(`Config saved to t`)}function O(r,t){return{user_id:t.user_id,symbol:r.symbol}}function y(r){return{user_id:r.user_id}}var h=new St;h.name(T.basename(process.argv[1])).description("Taco Trading Platform CLI").option("--config <path>","Path to JSON config file",Tt);h.command("init").description("Initialize config file with credentials").action(async()=>{let r=h.opts().config;await xt({config:r})});h.command("open-position").description("Open a perpetual position").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--side <side>","Trade side (e.g. Long, Short)").requiredOption("--notional <notional>","Notional position size (in USDT)",parseFloat).option("--leverage <leverage>","Leverage multiplier",parseInt).option("--stop-loss <price>","Stop-loss price",parseFloat).option("--take-profit <price>","Take-profit price",parseFloat).option("--limit-price <price>","Limit price (if applicable)",parseFloat).action(async r=>{let t=g(h.opts().config),e=O(r,t);e={...e,side:r.side,notional_position:r.notional},r.leverage!==void 0&&!isNaN(r.leverage)&&(e.leverage=r.leverage),r.stopLoss!==void 0&&!isNaN(r.stopLoss)&&(e.sl_price=r.stopLoss),r.takeProfit!==void 0&&!isNaN(r.takeProfit)&&(e.tp_price=r.takeProfit),r.limitPrice!==void 0&&!isNaN(r.limitPrice)&&(e.limit_price=r.limitPrice);let i=await f("POST","/auth/tacoclaw/trade/open_position",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("close-position").description("Close a perpetual position").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--side <side>","Trade side (e.g. Long, Short)").requiredOption("--notional <notional>","Notional position size to close (in USDT)",parseFloat).option("--limit-price <price>","Limit price (if applicable)",parseFloat).action(async r=>{let t=g(h.opts().config),e=O(r,t);e={...e,side:r.side,notional_position:r.notional},r.limitPrice!==void 0&&!isNaN(r.limitPrice)&&(e.limit_price=r.limitPrice);let i=await f("POST","/auth/tacoclaw/trade/close_position",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("set-leverage").description("Set leverage for a symbol").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--leverage <leverage>","Leverage multiplier",parseInt).action(async r=>{let t=g(h.opts().config),e=O(r,t);e.leverage=r.leverage;let i=await f("POST","/auth/tacoclaw/trade/set_leverage",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("set-margin-mode").description("Set margin mode (cross/isolated)").requiredOption("--symbol <symbol>","Trading pair").option("--cross","Use cross margin (default isolated)").action(async r=>{let t=g(h.opts().config),e=O(r,t);e.is_cross_margin=!!r.cross;let i=await f("POST","/auth/tacoclaw/trade/set_margin_mode",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("set-stop-loss").description("Set stop loss for a position").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--side <side>","Trade side").requiredOption("--notional <notional>","Notional position size (in USDT)",parseFloat).requiredOption("--price <price>","Stop loss price",parseFloat).action(async r=>{let t=g(h.opts().config),e=O(r,t);e={...e,side:r.side,notional_position:r.notional,price:r.price};let i=await f("POST","/auth/tacoclaw/trade/set_stop_loss",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("set-take-profit").description("Set take profit for a position").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--side <side>","Trade side").requiredOption("--notional <notional>","Notional position size (in USDT)",parseFloat).requiredOption("--price <price>","Take profit price",parseFloat).action(async r=>{let t=g(h.opts().config),e=O(r,t);e={...e,side:r.side,notional_position:r.notional,price:r.price};let i=await f("POST","/auth/tacoclaw/trade/set_take_profit",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-stop-loss").description("Cancel stop loss orders").requiredOption("--symbol <symbol>","Trading pair").action(async r=>{let t=g(h.opts().config),e=O(r,t),i=await f("POST","/auth/tacoclaw/trade/cancel_stop_loss_orders",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-take-profit").description("Cancel take profit orders").requiredOption("--symbol <symbol>","Trading pair").action(async r=>{let t=g(h.opts().config),e=O(r,t),i=await f("POST","/auth/tacoclaw/trade/cancel_take_profit_orders",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-stops").description("Cancel all stop orders").requiredOption("--symbol <symbol>","Trading pair").action(async r=>{let t=g(h.opts().config),e=O(r,t),i=await f("POST","/auth/tacoclaw/trade/cancel_stop_orders",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-all").description("Cancel all orders").requiredOption("--symbol <symbol>","Trading pair").action(async r=>{let t=g(h.opts().config),e=O(r,t),i=await f("POST","/auth/tacoclaw/trade/cancel_all_orders",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-filled-order").description("Get a filled order by ID").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--order-id <id>","Order ID").option("--algo","Is algo ID").action(async r=>{let t=g(h.opts().config),e=O(r,t);e.order_id=r.orderId,r.algo&&(e.is_algo_id=!0);let i=await f("GET","/auth/tacoclaw/trade/get_filled_order_by_order_id",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("cancel-order").description("Cancel an order by ID").requiredOption("--symbol <symbol>","Trading pair").requiredOption("--order-id <id>","Order ID").action(async r=>{let t=g(h.opts().config),e=O(r,t);e.order_id=r.orderId;let i=await f("POST","/auth/tacoclaw/trade/cancel_order_by_order_id",null,e,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-positions").description("Get all open positions").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/tacoclaw/trade/get_positions",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-open-orders").description("Get all open orders").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/tacoclaw/trade/get_open_orders",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-balance").description("Get user balance").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/tacoclaw/trade/get_balance",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-deposit-address").description("Get user deposit smart wallet address").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/deposit/smart_wallet_address/get",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-credits").description("Get user AI credits").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/portfolio",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("get-kline").description("Get kline/candlestick data (no auth required)").requiredOption("--symbol <symbol>","Trading pair, e.g. BTCUSDT").requiredOption("--interval <interval>","Kline interval, e.g. 1h, 4h, 1d").option("--start-time <time>","Start time (Unix ms)",parseInt).option("--end-time <time>","End time (Unix ms)",parseInt).action(async r=>{let t=["1m","3m","5m","15m","30m","1h","2h","4h","6h","8h","12h","1d","3d","1w","1M"];t.includes(r.interval)||(console.error(`Error: invalid interval 'r.interval'. Valid: t.sort().join(", ")`),process.exit(1));let e={symbol:r.symbol,interval:r.interval};r.startTime!==void 0&&!isNaN(r.startTime)&&(e.start_time=r.startTime),r.endTime!==void 0&&!isNaN(r.endTime)&&(e.end_time=r.endTime);let i=await f("GET","/market/klines",e,null,null);console.log(JSON.stringify(i,null,2))});h.command("default-ai-trader").description("Get Taco default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=await f("GET","/auth/autopilot/default",e,null,t.api_token);if(i&&i.default_trader){let n=i.default_trader,s={base_response:i.base_response,default_trader:{trader_id:n.trader_id,trader_name:n.trader_name,trader_state:n.trader_state,prompt_tag:n.prompt_tag,frequency:n.frequency}};console.log(JSON.stringify(s,null,2))}else console.log(JSON.stringify(i,null,2))});h.command("default-ai-strategies").description("Get Taco default/predefined AI strategies").action(async r=>{let t=g(h.opts().config),i=await f("GET","/autopilot/prompts",{tacoclaw:!0},null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("start-default-ai-trader").description("Start Taco default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token);e.trader_id=i;let n=await f("POST","/auth/autopilot/trader/start",e,null,t.api_token);console.log(JSON.stringify(n,null,2))});h.command("pause-default-ai-trader").description("Pause Taco default AI trader").requiredOption("--close_all_position <close_all_position>","Choose whether to close all open positions along with this pause").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token);e.trader_id=i,e.close_all_position=r.close_all_position;let n=await f("POST","/auth/autopilot/trader/pause",e,null,t.api_token);console.log(JSON.stringify(n,null,2))});h.command("use-a-defauly-ai-strategy-for-default-ai-trader").description("Use a default Taco AI strategy for Taco default AI trader").requiredOption("--strategy_tag <strategy_tag>","Choose a default Taco AI strategy tag for default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token),n={};n.user_id=t.user_id,n.trader_id=i,n.prompt_tag=r.strategy_tag;let s=await f("POST","/auth/autopilot/trader/modification",e,n,t.api_token);console.log(JSON.stringify(s,null,2))});h.command("change-running-interval-for-default-ai-trader").description("Change running interval for Taco default AI trader").requiredOption("--interval <interval>","Choose running interval for default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token),n={};n.user_id=t.user_id,n.trader_id=i,n.frequency=parseInt(r.interval,10);let s=await f("POST","/auth/autopilot/trader/modification",e,n,t.api_token);console.log(JSON.stringify(s,null,2))});h.command("get-trade-history").description("Get trade history").requiredOption("--symbol <symbol>","Trading pair (e.g. BTCUSDC)").requiredOption("--start-time <time>","Start time (Unix ms)",parseInt).option("--end-time <time>","End time (Unix ms)",parseInt).action(async r=>{let t=g(h.opts().config),e=y(t);e.symbol=r.symbol,e.start_time=r.startTime,r.endTime!==void 0&&!isNaN(r.endTime)&&(e.end_time=r.endTime);let i=await f("GET","/auth/tacoclaw/trade/get_trade_history",e,null,t.api_token);console.log(JSON.stringify(i,null,2))});h.command("change-name-for-default-ai-trader").description("Change display name for Taco default AI trader").requiredOption("--name <name>","Choose a new name for default AI trader").action(async r=>{let t=g(h.opts().config),e=y(t),i=x(t.user_id,t.api_token),n={};n.user_id=t.user_id,n.trader_id=i,n.trader_name=r.name;let s=await f("POST","/auth/autopilot/trader/modification",e,n,t.api_token);console.log(JSON.stringify(s,null,2))});h.parseAsync(process.argv).catch(r=>{console.error(r),process.exit(1)});
FILE:references/strategy-engine.md
# Taco Strategy Engine
策略引擎的职责:获取实时市场数据 → 计算技术指标 → 识别市场状态 → 匹配策略 → 输出可执行交易决策 → 等待用户确认 → 执行交易。
---
## Table of Contents
1. [Complete Workflow](#complete-workflow)
2. [Technical Indicators](#technical-indicators)
3. [Market Regime Detection](#market-regime-detection)
4. [Strategy Matching & Parameters](#strategy-matching--parameters)
5. [Recommendation Card Output](#recommendation-card-output)
6. [Execution Pipelines](#execution-pipelines)
7. [Autopilot Configuration](#autopilot-configuration)
8. [Sharpe Ratio Adaptation](#sharpe-ratio-adaptation)
9. [Key Principles](#key-principles)
---
## Complete Workflow
```
1. get-balance → 确定可用 USDC
2. 获取市场数据(使用 taco 命令或 Hyperliquid fallback)→ 计算指标 → 识别状态 → 匹配策略
3. 输出推荐交易卡片(至少 1 个,最多 3 个)
4. 等待用户选择
5. 用户确认后 → 执行该订单
```
**关键约束:risk_usd ≤ 用户 taco 账户 USDC 可用余额**
---
## Technical Indicators
从 K 线 OHLCV 数据计算以下指标。所有 close 价格取 `parseFloat(candle.c)`。
### EMA(指数移动平均线)
```
EMA(period, prices):
multiplier = 2 / (period + 1)
ema[0] = prices[0]
for i = 1 to len(prices)-1:
ema[i] = (prices[i] - ema[i-1]) * multiplier + ema[i-1]
return ema
```
需要计算:EMA9, EMA20, EMA50
### MACD
```
MACD:
fast_ema = EMA(12, closes)
slow_ema = EMA(26, closes)
macd_line = fast_ema - slow_ema
signal_line = EMA(9, macd_line)
histogram = macd_line - signal_line
```
判断:
- MACD line > signal line 且 histogram 扩大 → 看多
- MACD line < signal line 且 histogram 缩小 → 看空
- Golden cross / Death cross = macd_line 穿越 signal_line
### RSI
```
RSI(period=14, closes):
for i = 1 to len(closes)-1:
change = closes[i] - closes[i-1]
gain = max(change, 0)
loss = abs(min(change, 0))
avg_gain = SMA(gains, period) // 初始值用 SMA
avg_loss = SMA(losses, period)
// 后续用 Wilder 平滑:
avg_gain = (prev_avg_gain * (period-1) + current_gain) / period
avg_loss = (prev_avg_loss * (period-1) + current_loss) / period
RS = avg_gain / avg_loss
RSI = 100 - 100 / (1 + RS)
```
判断:RSI > 70 超买;RSI < 30 超卖;50 为中性分界。
### ATR(平均真实波幅)
```
ATR(period=14, candles):
TR = max(high - low, abs(high - prev_close), abs(low - prev_close))
ATR = SMA(TR, period) // 初始值
// 后续 Wilder 平滑
```
用途:止损距离 = 1.5-2x ATR;判断波动率水平。
### Bollinger Bands
```
BB(period=20, closes):
middle = SMA(closes, period)
std = StdDev(closes, period)
upper = middle + 2 * std
lower = middle - 2 * std
bandwidth = (upper - lower) / middle
```
判断:bandwidth 收窄 → 即将突破;价格触及 upper/lower → 可能回归。
### 简化判断(K 线不足 26 根时)
1. 最新 close vs 20 根 close 的 SMA → 趋势方向
2. 最近 5 根 K 线的 high/low range → 波动率
3. 最近 3 根 K 线方向一致性 → 动量
4. 当前价格在最近 20 根 K 线 range 中的位置 → 相对强弱
---
## Market Regime Detection
基于指标判断当前属于哪种市场状态:
| 状态 | 判断条件 | 适配策略 |
|---|---|---|
| **强趋势上涨** | close > EMA20 > EMA50; MACD > 0 且扩大; 连续 HH/HL | Trend Following, Pullback, Momentum Breakout |
| **强趋势下跌** | close < EMA20 < EMA50; MACD < 0 且缩小; 连续 LH/LL | Trend Following (做空), Momentum Breakout (做空) |
| **震荡/区间** | 价格在 BB upper/lower 之间反复; RSI 在 40-60; 无清晰 HH/HL 结构 | Range Trading, Mean Reversion, S&R |
| **突破酝酿** | BB bandwidth 压缩至极低; 成交量逐渐放大; OI 扩张 | Momentum Breakout |
| **极端波动** | ATR 异常放大(>2x 20日均值); 资金费率极端; 清算级联 | Scalping (小仓), DCA (分批) |
| **低迷/盘整** | 成交量萎缩; ATR 极低; 资金费率平稳 | 等待 或 DCA |
### HH/HL/LH/LL 识别方法
从 4h 或 1d K 线中:
1. 找局部高点(前后各 2 根 K 线的 high 都更低)
2. 找局部低点(前后各 2 根 K 线的 low 都更高)
3. 高点序列递增 = HH;低点序列递增 = HL → 上涨结构
4. 高点序列递减 = LH;低点序列递减 = LL → 下跌结构
5. 结构破坏(MSB)= 新低点跌破前低点(上涨结构破坏)或新高点突破前高点(下跌结构破坏)
---
## Strategy Matching & Parameters
### User Parameters
在出决策前,必须明确以下参数(优先从用户设置读取,其次用 get-balance 推算默认值):
| 参数 | 来源 |
|---|---|
| 可用 USDC 余额 | `get-balance`(taco 账户) |
| 当前持仓 | `get-positions` |
| 风险偏好 | 用户设定或默认 conservative |
| 杠杆偏好 | 用户设定或按余额推算 |
**Symbol 自动推荐逻辑:**
1. 优先推荐 BTC/ETH(稳健资产)
2. 若 BTC/ETH 无清晰 setup,扫描高流动性山寨(SOL、BNB、DOGE 等)
3. 排除:低流动性、高价差、当天异常波动超过 15% 的标的
4. 同等条件下,优先选择与用户已有持仓方向一致的标的
**risk_usd 硬约束:**
```
risk_usd = position_size_usd / leverage * (|entry_price - stop_loss| / entry_price)
risk_usd ≤ available_usdc_balance
```
若计算出的 risk_usd 超过余额,必须缩减 position_size_usd 直到满足约束。
**默认参数推算(基于余额):**
| 余额范围 | BTC/ETH 仓位 | 山寨仓位 | 杠杆 |
|---|---|---|---|
| < 100 USDC | 20-50 | 10-30 | 3-5x |
| 100-500 | 50-200 | 30-100 | 3-5x |
| 500-2000 | 100-500 | 50-200 | 3-10x |
| > 2000 | 200-1000 | 100-500 | 3-10x |
**BTC 最低开仓额 = 100 USDC(含杠杆后的名义价值),余额不足时不要开 BTC 仓位。**
### Risk Profiles
**Conservative(默认):**
- 风险回报比 ≥ 1:1.5
- 最大同时持仓 ≤ 3 个
- 最大保证金使用 ≤ 90%
- 清算价距离 ≥ 15%
- 最低信心度 ≥ 75%
- 最小持仓时间 ≥ 60 min
- 单笔风险 ≤ 5% 权益
**Aggressive(需用户主动选择):**
- 最大同时持仓 ≤ 6 个
- 最大保证金使用 ≤ 95%
- 清算价距离 ≥ 8%
- 最低信心度 ≥ 60%
- 单笔风险 ≤ 8% 权益
### 9 Strategies
#### 1. Trend Following(趋势跟踪)
**适用**:清晰方向性趋势,ADX > 25
**做多条件**:close > EMA20 > EMA50 + HH/HL 结构 + MACD > 0 + 成交量扩大
**做空条件**:close < EMA20 < EMA50 + LH/LL 结构 + MACD < 0
**入场确认**:阻力突破 / MA 回踩确认 / MACD 方向性交叉 / 成交量放大
**退出**:反向 MACD 交叉 / 结构破坏 / 趋势线跌破 / 强量反转
**频率**:2-4 trades/day
**不做**:震荡、区间、低信念环境
#### 2. Pullback Trading(回调交易)
**适用**:已确认趋势中的健康回调
**做多条件**:上涨趋势中,价格回调至 EMA20 附近 + RSI 回到 40-50 + 出现反转 K 线(锤子/吞没)
**做空条件**:下跌趋势中,价格反弹至 EMA20 附近 + RSI 回到 50-60 + 出现反转 K 线
**止损**:回调低点/高点之外 1x ATR
**止盈**:前高/前低 或 1.5-2x 止损距离
**不做**:趋势不明确、回调幅度 > 50% Fibonacci(可能是反转)
#### 3. Momentum Breakout(动量突破)
**适用**:重要水平的突破,需要量能确认
**入场**:价格突破关键 S/R + 成交量 > 20 根平均成交量的 1.5x + OI 扩张
**确认**:回踩突破位不跌破 → 二次入场机会
**止损**:突破位之下 1x ATR
**不做**:假突破频发环境(细针型 K 线突破无后续)、ATR 已极度拉伸的追涨
#### 4. Mean Reversion(均值回归)
**适用**:震荡区间、过度延伸
**做多条件**:价格触及 BB lower + RSI < 30 + 在已知支撑区
**做空条件**:价格触及 BB upper + RSI > 70 + 在已知阻力区
**止损**:BB 外侧 1x ATR
**不做**:强趋势中(趋势 > 均值回归),BB bandwidth 持续扩大
#### 5. Range Trading(区间交易)
**适用**:清晰水平通道,ADX < 20
**做多**:价格接近区间下沿 + 出现反转信号
**做空**:价格接近区间上沿 + 出现反转信号
**止损**:区间边界外 1x ATR
**失效**:区间突破 + 放量 → 立即止损
#### 6. Support & Resistance(支撑阻力)
**适用**:价格在被反复测试过的关键水平附近
**入场**:价格在 4H/1D 级别的关键水平 + 出现确认信号(rejection wick, 吞没 K 线, 订单簿吸收)
**水平质量评估**:触及次数 ≥ 2 + 时间框架越高越强 + 成交量在该水平放大
**不做**:未确认就盲目抄底/摸顶
#### 7. Scalping(剥头皮)
**适用**:仅限 BTC/ETH 等高流动性标的,微观结构清晰
**条件**:必须与更高时间框架方向一致 + 盘口深度足够 + 价差合理
**不做**:薄盘口、高价差、需要秒级反应的 setup
**限制**:必须能持仓 ≥ 60min 且 R:R 仍合理
#### 8. DCA(分批建仓)
**适用**:高信念但短期时机不确定
**方法**:总仓位拆 2-4 批,每批需独立确认信号
**不做**:马丁格尔(不是加倍加仓)
#### 9. Data Flow(数据流)
**适用**:任何有结构性的市场
**综合上述所有策略的信号,选择当前最高信心度的 setup 执行**
**本质是"自适应策略选择器"**
---
## Recommendation Card Output
### Output Format
**Part 1 – 分析摘要(纯文本)**
必须包含:
- 获取了什么数据(具体数值,不是"假设")
- 计算了什么指标(EMA20 = X, MACD = Y, RSI = Z)
- 判断的市场状态
- 为什么选择这个策略
- 风险点
**Part 2 – 推荐卡片(JSON 数组)**
输出至少 1 个,最多 3 个推荐:
```json
[
{
"symbol": "BTCUSDC",
"action": "open_long",
"entry_price": 84500,
"stop_loss": 83800,
"take_profit": 85900,
"position_size_usd": 200,
"leverage": 5,
"risk_usd": 33,
"confidence": 80,
"reasoning": "4H 上涨结构完整(HH/HL),价格回调至 EMA20(84400) 附近,RSI 回落至 48,出现锤子线确认。止损设在回调低点下方,止盈目标前高。风险回报比 1:2。risk_usd(33) ≤ 可用余额。"
}
]
```
**Part 3 – 用户确认提示**
```
以上是 [N] 个推荐交易。请选择要执行的方案(回复编号或 symbol),或回复"取消"放弃。
选择后我将立即调用 taco 账户执行订单。
```
**如果没有机会:**
```json
[
{
"symbol": "MARKET",
"action": "wait",
"reasoning": "BTC 在 84500-85500 区间震荡,EMA20(84900) 与 EMA50(84600) 交织,MACD histogram 接近零轴,无清晰方向。关注 85500 突破或 84500 跌破作为下一信号。"
}
]
```
### Valid Actions
`open_long` | `open_short` | `close_long` | `close_short` | `hold` | `wait` | `long_stop_loss` | `short_stop_loss` | `long_take_profit` | `short_take_profit`
### Field Rules
- **所有决策必须**:`symbol`, `action`, `reasoning`
- **开仓必须追加**:`entry_price`, `leverage`, `position_size_usd`, `stop_loss`, `take_profit`, `risk_usd`, `confidence`
- **修改 SL/TP 必须追加**:`price`, `confidence`
- **close/hold/wait**:只需 `symbol`, `action`, `reasoning`
- 必须输出数组 `[...]`,不是单个对象 `{...}`
- 数值字段不加引号:`"confidence": 80`,不是 `"confidence": "80"`
- JSON 必须完整有效,不能截断
- **risk_usd 必须 ≤ 当前 taco 账户可用 USDC**
---
## Execution Pipelines
### Standard Pipeline(完整流程)
```
1. get-balance → 获取 taco 账户可用 USDC 余额
2. 获取 BTC/ETH 的 4h K 线(至少 50 根) → 判断主趋势
3. 获取 BTC/ETH 的 1h K 线(至少 20 根) → 判断短期结构
4. 获取资金费率 + OI → 判断拥挤度
5. 获取盘口 → 判断流动性
6. 获取用户当前持仓 → 确定已有头寸
7. 计算指标(EMA, MACD, RSI, ATR, BB)
8. 识别市场状态
9. 匹配策略
10. 计算 risk_usd,确保 ≤ available_usdc
11. 如果信心度 ≥ 阈值 → 输出推荐交易卡片(至少 1 个)
12. 如果信心度 < 阈值 → 输出 wait + 说明原因和关注水平
13. 等待用户选择 → 用户确认后执行
```
### Market Scan Pipeline(用户问"有什么机会")
```
1. get-balance → 获取可用 USDC
2. 获取 allMids 或 get-ticker → 所有标的价格
3. 获取 metaAndAssetCtxs → 找出:
- 24h 成交额 Top 10
- 24h 涨跌幅绝对值 Top 5
- 资金费率极端(|funding| > 0.0005)的标的
4. 优先对 BTC/ETH 分析,再看 Top 3-5 候选山寨
5. 分别获取 4h + 1h K 线 → 计算指标 → 判断状态 → 匹配策略
6. 计算各候选的 risk_usd,确保 ≤ available_usdc
7. 输出 1-3 个推荐卡片,按 confidence 降序排列
```
### Post-Selection Execution
用户选择某个推荐后:
1. 解析用户选择(编号 / symbol / 描述)
2. 确认对应的 JSON 决策对象
3. 执行交易,传入完整订单参数(open-position 命令)
4. 返回执行结果(订单 ID、成交价、实际仓位大小)
**执行前最后检查:**
- risk_usd ≤ 当前可用 USDC(再次确认,余额可能已变化)
- 当前持仓数 < 最大持仓限制
- symbol 当前可交易(非暂停状态)
---
## Autopilot Configuration
用户要配置自动交易时:
1. 确认策略选择(不确定就推荐)
2. 确认风险偏好(conservative / aggressive)
3. get-balance → 推算默认参数
4. 输出配置摘要:
```
Autopilot 配置
策略: [名称]
模式: [Conservative/Aggressive]
BTC/ETH: [X-Y] USDC @ [Z]x
山寨币: [X-Y] USDC @ [Z]x
最大持仓: [N] 个
扫描频率: 每 30 分钟
执行账户: taco 账户
```
---
## Sharpe Ratio Adaptation
如果系统跟踪了历史 Sharpe:
| Sharpe | 调整 |
|---|---|
| < -0.5 | 停止交易 ≥ 6 周期,重新评估信号质量 |
| -0.5 ~ 0 | 仅信心度 > 80% 时交易,降低频率 |
| 0 ~ 0.7 | 维持当前参数 |
| > 0.7 | 可增加仓位 +20% |
---
## Quick Routes
| 用户说 | 执行 |
|---|---|
| "有什么机会" / "该买什么" / "scan" | → Market Scan Pipeline |
| "推荐策略" / "用哪个策略" | → 获取数据 → 识别状态 → 推荐匹配策略 |
| "策略列表" / "有哪些策略" | → 展示 9 个策略的表格 |
| "配置 autopilot" / "自动交易" | → 收集参数 → 生成策略 prompt |
| "XXX 策略怎么用" | → 展示该策略的详细规则 |
| 用户选择了某个推荐(如"选1" / "买BTC" / "执行第一个") | → 执行交易 |
---
## Key Principles
1. **数据第一**:没有真实数据就不做决策,不模拟、不假设
2. **余额约束**:risk_usd 必须 ≤ taco 账户可用 USDC,超出则缩减仓位
3. **Symbol 习惯**:优先推荐 BTC/ETH,山寨需更高信心度阈值
4. **至少 1 个推荐**:有机会时必须输出至少 1 个具体可执行的交易卡片
5. **宁可错过不做错**:无高信心度 setup 时输出 wait,附带关注水平
6. **具体价位**:每个决策必须有精确的 entry_price/SL/TP 数值
7. **用户确认后执行**:推荐后等待用户选择,选择后执行
8. **不追涨杀跌**:如果已经大幅运动,评估是否还有合理 R:R
FILE:references/analysis-workflows.md
# Analysis Workflows & Response Templates
## Contents
- [Response Templates](#response-templates): Balance, Positions, Price
- [Analysis Scenarios](#analysis-scenarios): Technical Analysis, Liquidity, Funding Arbitrage, Portfolio Review, Market Overview
- [Cross-Step Workflows](#cross-step-workflows): Pre-Trade Research, Daily Portfolio, Post-Trade Review, Signal-Driven Trade
- [Intent Translation Examples](#intent-translation-examples)
- [Domain Knowledge Thresholds](#domain-knowledge--judgment-thresholds)
---
## Response Templates
### "余额多少" / "Balance?"
**API calls** (in order):
1. `get-balance` → total equity, available balance, margin used, unrealized PnL
2. `get-positions` → list of open positions (if any)
3. For each position: `get-ticker --symbol <SYM>` → current market price
**Output format**:
```
Taco 账户余额
总权益: XX.XX USDC
可用余额: XX.XX USDC
已用保证金: XX.XX USDC
未实现盈亏: ±XX.XX USDC
当前持仓 (N 个):
ETHUSDC 多头 | 入场 2147.4 | 现价 2144.6 | 浮动 -1.32 USDC (-X.X%)
```
If available balance < 5 USDC, append:
```
⚠️ 可用余额不足 5 USDC,建议充值 USDC 后再进行交易。
支持充值链:Arbitrum(推荐)、Ethereum、Base、Polygon,地址相同。
```
Note: "现价" MUST come from `get-ticker`, NOT from calculation. If `get-ticker` fails, use Hyperliquid `allMids` as fallback.
### "我的仓位" / "Show positions"
**API calls** (in order):
1. `get-positions` → all positions
2. For each position: `get-ticker --symbol <SYM>` → current price
3. For each position: `get-liquidation-price --symbol <SYM>` → exact liquidation price
**Output format**:
```
ETHUSDC 多头
入场价: 2147.4 | 现价: 2144.6 (来自实时报价)
仓位: XX USDC | 杠杆: 10x
浮动盈亏: -1.32 USDC (-X.X%)
强平价格: 1932.7 (距现价 -9.9%)
止损: 2083.0 | 止盈: 2276.1
```
Note: "强平价格" MUST come from `get-liquidation-price` API. Never calculate it.
### "BTC 多少了" / "Price of ETH?"
**API calls**: `get-ticker --symbol <SYM>`
**Output** (brief): `BTC: $87,500.00 (24h +2.3%)`
---
## Analysis Scenarios
### Scenario A: Technical Analysis
**Trigger**: "technical analysis", "should I long or short", "support/resistance", "分析", "该怎么做"
**Execution flow**:
1. `get-kline --symbol <SYM> --interval 1h --start-time <24h_ago>` → short-term
2. `get-kline --symbol <SYM> --interval 1d --start-time <30d_ago>` → long-term
3. `get-ticker --symbol <SYM>` → current price, 24h change, volume
4. `get-funding-rate --symbol <SYM>` → long/short bias
5. `get-orderbook --symbol <SYM> --depth 10` → buy/sell pressure
**Judgment**:
- Support/resistance from kline highs/lows
- Price distance to key levels
- 24h volume vs 7d average → momentum
- Funding rate sign → market bias
- Orderbook imbalance (bid vs ask top 10)
**Output**: Separate short-term (4h-24h) and long-term (1w+) view. Include: current price, key levels, momentum, funding cost, actionable suggestion with risk caveat.
### Scenario B: Liquidity / Slippage Analysis
**Trigger**: "liquidity", "slippage", "depth", "流动性", "滑点"
**Execution flow**:
1. `get-orderbook --symbol <SYM> --depth 50` → full depth
2. `get-ticker --symbol <SYM>` → 24h volume, spread
3. `get-recent-trades --symbol <SYM> --limit 100` → recent trade sizes
**Judgment**:
- Spread = (best_ask - best_bid) / mid_price. > 0.1% → wide spread warning
- Depth within 1% of mid: sum bid/ask notional. < $50k → thin
- Simulate slippage: walk ask ladder for intended notional
- Order size > 5% of 24h volume → significant market impact
**Output**: Spread %, depth summary, simulated slippage, order type recommendation (limit vs market).
### Scenario C: Funding Rate Arbitrage Screen
**Trigger**: "funding arbitrage", "high funding", "funding rate comparison", "套利"
**Execution flow**:
1. `get-symbols --type perp` → all perp symbols
2. For top symbols: `get-funding-rate --symbol <SYM>`
3. For candidates with |rate| > 0.01%: `get-ticker --symbol <SYM>` → volume
4. For candidates: `get-orderbook --symbol <SYM> --depth 10` → liquidity
**Judgment**:
- |funding rate| > 0.01% per 8h (annualized ~13%+) → candidate
- 24h volume > $5M → sufficient liquidity
- Depth within 0.5% > $100k → executable
- Persistent rate direction → higher confidence
**Output**: Ranked candidates with: symbol, rate, annualized rate, 24h volume, depth rating, risk notes.
### Scenario D: Portfolio Review
**Trigger**: "review my portfolio", "allocation", "仓位配比", "怎么调"
**Execution flow**:
1. `get-positions` → all open positions
2. `get-balance` → total equity
3. For each: `get-ticker`, `get-funding-rate`, `get-liquidation-price`
4. `get-pnl-summary --period 7d` → recent performance
**Judgment**:
- Single position > 40% of equity → high concentration
- Liq distance < 10% → danger zone
- Funding cost vs realized PnL → holding cost efficiency
- Correlated positions in same direction → hidden risk
**Output**: Position table (symbol, side, size, entry, current, PnL%, liq distance, funding cost). Overall: concentration score, risk rating, adjustment suggestions.
### Scenario E: Market Overview
**Trigger**: "market overview", "行情", "what's happening", "大盘怎么样"
**Execution flow**:
1. `get-ticker` (no symbol → all tickers)
2. Sort by 24h volume, 24h change
3. Top 3 gainers, top 3 losers, top 3 volume
4. `get-funding-rate --symbol BTCUSDC` + `get-funding-rate --symbol ETHUSDC` → sentiment
**Output**: BTC/ETH price + change, top movers, funding sentiment, brief outlook.
---
## Cross-Step Workflows
### Workflow 1: Pre-Trade Research → Execute
> User: "I want to long ETH"
```
1. get-ticker --symbol ETHUSDC → current price, 24h stats
2. get-kline --symbol ETHUSDC --interval 4h → recent trend
3. get-funding-rate --symbol ETHUSDC → holding cost
4. get-orderbook --symbol ETHUSDC --depth 10 → liquidity check
5. get-balance → available funds
↓ check: available_balance ≥ 5 USDC? notional ≥ 10 USDC? margin ≥ 5 USDC?
↓ if any check fails → prompt deposit or adjust size
↓ present analysis + estimated margin, user confirms
6. open-position --symbol ETHUSDC --notional X --side Long --leverage Y --stop-loss Z
7. get-liquidation-price --symbol ETHUSDC → inform user
```
### Workflow 2: Daily Portfolio Check
```
1. get-balance → equity snapshot
2. get-positions → all positions
3. For each position: get-liquidation-price, get-funding-rate
4. get-pnl-summary --period 1d → today's PnL
5. get-trade-history --start-time <today_start> → today's trades
6. get-credits → AI credits remaining
```
### Workflow 3: Post-Trade Review
> User: "How did my trades go this week?"
```
1. get-trade-history --start-time <week_start> → all trades
2. get-pnl-summary --period 7d → weekly PnL
3. get-fee-summary --period 7d → weekly fees
4. get-balance → current equity
```
### Workflow 4: Signal-Driven Quick Trade
> User: "BTC just crashed, should I buy?"
```
1. get-ticker --symbol BTCUSDC → current price, 24h change
2. get-kline --symbol BTCUSDC --interval 1h → recent price action
3. get-funding-rate --symbol BTCUSDC → market sentiment
4. get-orderbook --symbol BTCUSDC --depth 20 → liquidity in crash
5. get-balance → available capital
↓ analysis + recommendation with caveats
6. If user confirms → open-position with conservative sizing
```
---
## Intent Translation Examples
| User says | Parsed as | Key decisions |
|---|---|---|
| "买点 BTC" | `open-position --symbol BTCUSDC --side Long` | 买 = long. Ask: notional, leverage |
| "做空 ETH 200u" | `open-position --symbol ETHUSDC --side Short --notional 200` | Ask: leverage |
| "BTC 多少了" | `get-ticker --symbol BTCUSDC` | No auth needed |
| "看看我的仓位" | `get-positions` | Return all with PnL |
| "这周赚了多少" | `get-pnl-summary --period 7d` | "这周" → 7d |
| "帮我平掉 BTC" | `get-positions` → find BTC → `close-position` | Fetch position first |
| "ETH 走势怎么样" | `get-kline --symbol ETHUSDC --interval 1h` + `get-ticker` | Default 1h |
| "Set 5x on BTC" | `set-leverage --symbol BTCUSDC --leverage 5` | Direct execution |
| "Cancel everything" | `cancel-all` per symbol | Confirm first |
| "如何充值" | `get-deposit-address` | Show address + chains |
| "我的AI交易员启动了嘛?" | `get-default-ai-trader` | Show state in human-readable language |
| "有哪些可选的AI交易策略?" | `get-default-ai-strategies` | Show strategy list |
---
## Domain Knowledge & Judgment Thresholds
| Metric | Threshold | Interpretation |
|---|---|---|
| Spread (ask-bid)/mid | > 0.1% | Wide spread, use limit orders |
| Spread (ask-bid)/mid | > 0.5% | Very thin, high slippage risk |
| Orderbook depth (1% range) | < $50k | Low liquidity |
| Orderbook depth (1% range) | > $500k | Healthy liquidity |
| 24h volume | < $1M | Low activity, avoid large orders |
| Funding rate (8h) | > 0.05% | Expensive to hold longs |
| Funding rate (8h) | < -0.05% | Expensive to hold shorts |
| Funding rate (8h) | > 0.1% | Extreme, potential arbitrage |
| Leverage | > 5x | Elevated liquidation risk |
| Leverage | > 10x | High risk, warn strongly |
| Position size / equity | > 30% | Concentration risk |
| Position size / equity | > 50% | Dangerous concentration |
| Liq price distance | < 10% from current | Danger zone |
| Liq price distance | < 5% from current | Critical, suggest reducing |
| Order size / 24h volume | > 5% | Significant market impact |
| Win rate | < 40% | Review strategy |
FILE:references/market-data-fallback.md
# Hyperliquid Public API Fallback
Use as fallback when Taco CLI endpoints are unavailable or marked "NEEDS API". Never mention "Hyperliquid" to the user — present data as from Taco.
Base URL: `https://api.hyperliquid.xyz/info`
All requests are POST with JSON body. No API key needed.
---
## Endpoints
### Get all mid prices (tickers)
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "allMids"}'
```
Returns: `{"BTC": "87500.0", "ETH": "2150.0", ...}` — map of asset to mid price.
### Get L2 orderbook
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "l2Book", "coin": "BTC"}'
```
Returns: `{"levels": [{"px": "87500.0", "sz": "1.5", "n": 3}, ...]}` for bids and asks.
### Get funding rates + metadata
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "metaAndAssetCtxs"}'
```
Returns: metadata (asset list, max leverage) + per-asset context (funding rate, open interest, mark price, oracle price, 24h volume).
### Get all tradeable tokens (Perp Metas)
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "allPerpMetas"}'
```
**Parsing**: Iterate root array → access `universe` array → each item is a tradeable asset. Use `name` field. Ignore `isDelisted: true`. Some symbols have prefixes (e.g. `hyna:BTC`, `flx:TSLA`).
### Get candlestick / kline data
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "candleSnapshot", "req": {"coin": "BTC", "interval": "1h", "startTime": 1709251200000, "endTime": 1709337600000}}'
```
Intervals: `1m`, `5m`, `15m`, `1h`, `4h`, `1d`. Returns OHLCV array.
### Get user state (positions + balance)
```bash
curl -X POST https://api.hyperliquid.xyz/info \
-H "Content-Type: application/json" \
-d '{"type": "clearinghouseState", "user": "0x..."}'
```
Returns: margin summary (equity, total margin, available), positions array (entry price, size, leverage, unrealized PnL, liquidation price, funding info).
Note: Requires user's wallet address (0x...), not Taco user_id. Use only if address is available.
FILE:references/commands.md
# Commands Reference
## Contents
- [Trading Commands](#trading-commands): open-position, close-position, modify-order, set-leverage, set-margin-mode, adjust-margin, set-stop-loss, set-take-profit, cancel orders
- [Account Query Commands](#account-query-commands): get-balance, get-deposit-address, get-positions, get-open-orders, get-filled-order, get-trade-history, get-pnl-summary, get-fee-summary, get-credits, get-transfer-history, get-liquidation-price
- [Market Data Commands](#market-data-commands-no-auth): get-ticker, get-kline, get-orderbook, get-recent-trades, get-funding-rate, get-mark-price, get-symbols
- [AI Trader Commands](#ai-trader-commands): get-default-ai-trader, get-default-ai-strategies, start/pause, use-strategy, change-interval, change-name
---
## Trading Commands
### open-position
```bash
node scripts/taco_client.js open-position \
--symbol BTCUSDC --notional 100 --side Long \
--leverage 3 --stop-loss 80000 --take-profit 100000
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | Yes | Trading pair (e.g. BTCUSDC) |
| `--side` | Yes | `Long` or `Short` |
| `--notional` | Yes | Position size in USDC |
| `--leverage` | No | Leverage multiplier |
| `--stop-loss` | No | Stop-loss price |
| `--take-profit` | No | Take-profit price |
| `--limit-price` | No | Limit price for limit orders |
**Pre-trade validation** (check before executing):
1. `get-balance` → if available_balance < 5 USDC → reject, prompt deposit
2. If notional < 10 USDC → reject, suggest increasing
3. margin = notional / leverage. If margin < 5 USDC → reject
4. If margin > available_balance → reject. (notional CAN exceed balance with leverage)
5. Show estimated margin in confirmation: "预计占用保证金: XX.XX USDC (名义价值: XX.XX USDC)"
**Post-execution**: If fails with `User or API Wallet 0x... does not exist`, tell user to deposit USDC.
**Return fields**:
| Field | Type | Description |
|---|---|---|
| `order_id` | String | Order ID |
| `symbol` | String | Trading pair |
| `side` | String | Long/Short |
| `status` | String | Order status |
| `notional` | String | Position size |
| `price` | String | Execution/limit price |
### close-position
```bash
node scripts/taco_client.js close-position \
--symbol BTCUSDC --notional 100 --side Short
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | Yes | Trading pair |
| `--side` | Yes | `Long` or `Short` |
| `--notional` | Yes | Size to close in USDC |
| `--limit-price` | No | Limit price |
### modify-order (NEEDS API)
```bash
node scripts/taco_client.js modify-order \
--symbol BTCUSDC --order-id "12345" \
--new-price 86000 --new-notional 150
```
Amends price and/or size without cancel-and-replace. At least one of `--new-price` or `--new-notional` required.
### set-leverage
```bash
node scripts/taco_client.js set-leverage --symbol BTCUSDC --leverage 5
```
### set-margin-mode
```bash
# Cross margin
node scripts/taco_client.js set-margin-mode --symbol BTCUSDC --cross
# Isolated margin (default)
node scripts/taco_client.js set-margin-mode --symbol BTCUSDC
```
### adjust-margin (NEEDS API)
```bash
node scripts/taco_client.js adjust-margin \
--symbol BTCUSDC --amount 50 --action add
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | Yes | Trading pair |
| `--amount` | Yes | Margin amount in USDC |
| `--action` | Yes | `add` or `remove` |
### set-stop-loss / set-take-profit
```bash
node scripts/taco_client.js set-stop-loss \
--symbol BTCUSDC --side Long --notional 100 --price 85000
node scripts/taco_client.js set-take-profit \
--symbol BTCUSDC --side Long --notional 100 --price 95000
```
### Cancel orders
```bash
node scripts/taco_client.js cancel-stop-loss --symbol BTCUSDC
node scripts/taco_client.js cancel-take-profit --symbol BTCUSDC
node scripts/taco_client.js cancel-stops --symbol BTCUSDC
node scripts/taco_client.js cancel-all --symbol BTCUSDC
node scripts/taco_client.js cancel-order --symbol BTCUSDC --order-id "12345"
```
---
## Account Query Commands
### get-balance
```bash
node scripts/taco_client.js get-balance
```
| Field | Type | Description |
|---|---|---|
| `total_equity` | String | Total account equity in USDC |
| `available_balance` | String | Available for new orders |
| `used_margin` | String | Margin in use |
| `unrealized_pnl` | String | Unrealized PnL |
### get-deposit-address
```bash
node scripts/taco_client.js get-deposit-address
```
Returns `address` (String) — same address for all supported chains.
Supported chains: **Arbitrum** (default, lowest fees), **Ethereum**, **Base**, **Polygon**. Always mention chains when showing address.
### get-positions
```bash
node scripts/taco_client.js get-positions
```
| Field | Type | Description |
|---|---|---|
| `symbol` | String | Trading pair |
| `side` | String | Long/Short |
| `size` | String | Position size |
| `notional` | String | Notional value in USDC |
| `entry_price` | String | Average entry price |
| `mark_price` | String | Current mark price |
| `unrealized_pnl` | String | Unrealized PnL |
| `leverage` | String | Current leverage |
| `margin_mode` | String | Cross/Isolated |
| `liquidation_price` | String | Estimated liquidation price |
### get-open-orders
```bash
node scripts/taco_client.js get-open-orders
```
### get-filled-order
```bash
node scripts/taco_client.js get-filled-order \
--symbol BTCUSDC --order-id "12345"
```
Add `--algo` if the order ID is an algorithmic order ID.
### get-trade-history
```bash
node scripts/taco_client.js get-trade-history \
--symbol BTCUSDC --start-time 1709251200000
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | **Yes** | Trading pair (e.g. BTCUSDC) |
| `--start-time` | **Yes** | Unix ms |
| `--end-time` | No | Unix ms |
| Field | Type | Description |
|---|---|---|
| `exchange` | String | Exchange name (e.g. "Taco") |
| `order_id` | String | Order ID |
| `price` | String | Execution price |
| `quantity` | String | Size in base asset |
| `realized_pnl` | String | Realized PnL (if closing) |
| `timestamp` | Number | Unix ms |
| `trade_fee` | String | Fee paid |
### get-pnl-summary (NEEDS API)
```bash
node scripts/taco_client.js get-pnl-summary --period 7d --symbol BTCUSDC
```
| Param | Required | Description |
|---|---|---|
| `--period` | No | `1d`, `7d`, `30d`, `all`. Default `7d` |
| `--symbol` | No | Filter by symbol |
| Field | Type | Description |
|---|---|---|
| `realized_pnl` | String | Total realized PnL |
| `unrealized_pnl` | String | Current unrealized PnL |
| `funding_received` | String | Funding income |
| `funding_paid` | String | Funding expense |
| `fees_paid` | String | Total fees |
| `net_pnl` | String | Net PnL |
| `trade_count` | Number | Number of trades |
| `win_rate` | String | Win rate (0-1) |
### get-fee-summary (NEEDS API)
```bash
node scripts/taco_client.js get-fee-summary --period 30d
```
### get-credits (NEEDS API)
```bash
node scripts/taco_client.js get-credits
```
| Field | Type | Description |
|---|---|---|
| `free_credits` | Number | Remaining credits |
### get-transfer-history (NEEDS API)
```bash
node scripts/taco_client.js get-transfer-history --limit 20 --type deposit
```
### get-liquidation-price (NEEDS API)
```bash
node scripts/taco_client.js get-liquidation-price --symbol BTCUSDC
```
| Field | Type | Description |
|---|---|---|
| `liquidation_price` | String | Estimated liq price |
| `margin_ratio` | String | Current margin ratio |
| `maintenance_margin` | String | Maintenance margin required |
| `position_margin` | String | Position margin |
---
## Market Data Commands (No Auth)
### get-ticker (NEEDS API)
```bash
# Single symbol
node scripts/taco_client.js get-ticker --symbol BTCUSDC
# All tickers
node scripts/taco_client.js get-ticker
```
| Field | Type | Description |
|---|---|---|
| `symbol` | String | Trading pair |
| `last_price` | String | Last traded price |
| `bid_price` | String | Best bid |
| `ask_price` | String | Best ask |
| `high_24h` | String | 24h high |
| `low_24h` | String | 24h low |
| `volume_24h` | String | 24h volume (base) |
| `quote_volume_24h` | String | 24h volume (USDC) |
| `change_24h` | String | 24h change % |
| `open_interest` | String | Open interest |
### get-kline
```bash
node scripts/taco_client.js get-kline \
--symbol BTCUSDC --interval 1h --start-time 1709251200000
```
| Param | Required | Description |
|---|---|---|
| `--symbol` | Yes | Trading pair |
| `--interval` | Yes | `1m`,`3m`,`5m`,`15m`,`30m`,`1h`,`2h`,`4h`,`6h`,`8h`,`12h`,`1d`,`3d`,`1w`,`1M` |
| `--start-time` | No | Unix ms |
| `--end-time` | No | Unix ms |
| Field | Type | Description |
|---|---|---|
| `open_time` | Number | Candle open time (Unix ms) |
| `close_time` | Number | Candle close time (Unix ms) |
| `open` | String | Open price |
| `high` | String | High price |
| `low` | String | Low price |
| `close` | String | Close price |
| `volume` | String | Volume (base asset) |
| `quote_volume` | String | Volume (USDC) |
| `trades_count` | Number | Number of trades |
Max 100 klines per request.
### get-orderbook (NEEDS API)
```bash
node scripts/taco_client.js get-orderbook --symbol BTCUSDC --depth 20
```
| Field | Type | Description |
|---|---|---|
| `bids` | Array | [[price, size], ...] descending |
| `asks` | Array | [[price, size], ...] ascending |
| `timestamp` | Number | Unix ms |
### get-recent-trades (NEEDS API)
```bash
node scripts/taco_client.js get-recent-trades --symbol BTCUSDC --limit 50
```
### get-funding-rate (NEEDS API)
```bash
# Current
node scripts/taco_client.js get-funding-rate --symbol BTCUSDC
# Historical
node scripts/taco_client.js get-funding-rate --symbol BTCUSDC --history --limit 24
```
| Field | Type | Description |
|---|---|---|
| `current_rate` | String | Current funding rate |
| `predicted_next_rate` | String | Predicted next rate |
| `next_funding_time` | Number | Countdown (Unix ms) |
| `annualized_rate` | String | Annualized % |
### get-mark-price (NEEDS API)
```bash
node scripts/taco_client.js get-mark-price --symbol BTCUSDC
```
### get-symbols (NEEDS API)
```bash
node scripts/taco_client.js get-symbols --type perp
```
| Field | Type | Description |
|---|---|---|
| `symbol` | String | e.g. BTCUSDC |
| `base_asset` | String | e.g. BTC |
| `quote_asset` | String | e.g. USDC |
| `type` | String | `perp` or `spot` |
| `min_order_size` | String | Minimum order size |
| `tick_size` | String | Price tick size |
| `max_leverage` | Number | Maximum leverage |
| `status` | String | `active` / `inactive` |
---
## AI Trader Commands
### get-default-ai-trader
```bash
node scripts/taco_client.js get-default-ai-trader
```
Display to user ONLY: running state(returned value of `1` means paused and returned value of `2` means running), currently used AI strategy tag, trader id, trader name, running frequency. **NEVER** display exchange (like hyperliquid) or model (like deepseek).
### get-default-ai-strategies
```bash
node scripts/taco_client.js get-default-ai-strategies
```
Show tag/description/label/performance data. Do not show complete long content text unless user explicitly asks.
### start-default-ai-trader
```bash
node scripts/taco_client.js start-default-ai-trader
```
### pause-default-ai-trader
```bash
node scripts/taco_client.js pause-default-ai-trader
```
### use-a-default-ai-strategy-for-default-ai-trader
```bash
node scripts/taco_client.js use-a-default-ai-strategy-for-default-ai-trader --strategy-tag <tag>
```
### change-running-interval-for-default-ai-trader
```bash
node scripts/taco_client.js change-running-interval-for-default-ai-trader --interval <interval>
```
### change-name-for-default-ai-trader
```bash
node scripts/taco_client.js change-name-for-default-ai-trader --name <name>
```
FILE:references/api-references.md
# Taco API Reference
## Base URL
`https://api.dev.taco.trading`
## Authentication
Authenticated endpoints require:
- Query parameter: `user_id`
- Header: `Authorization: Bearer <api_token>`
- Request body (POST): include `api_token` and `user_id`
---
## MARKET DATA (No Auth Required)
#### get_kline
- endpoint: /market/klines
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair symbol, e.g. `BTCUSDC`, `ETHUSDC`
- interval (required): kline interval. Supported values: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `6h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M`
- start_time (optional): start time in Unix milliseconds
- end_time (optional): end time in Unix milliseconds
- response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"klines": [
{
"symbol": "BTCUSDC",
"interval": "1h",
"open_time": 1709251200000,
"close_time": 1709254800000,
"open": "62345.50",
"high": "62890.00",
"low": "62100.00",
"close": "62780.30",
"volume": "1234.567",
"quote_volume": "77012345.89",
"trades_count": 45678
}
]
}
```
- notes: Maximum 100 klines per response. Prices as strings for decimal precision.
#### get_ticker
- endpoint: /market/ticker
- method: GET
- parameters:
- query parameters:
- symbol (optional): trading pair. Omit for all tickers.
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"tickers": [
{
"symbol": "BTCUSDC",
"last_price": "87500.00",
"bid_price": "87499.50",
"ask_price": "87500.50",
"high_24h": "88200.00",
"low_24h": "85100.00",
"volume_24h": "12345.67",
"quote_volume_24h": "1080000000.00",
"change_24h": "2.35",
"open_interest": "55000.00",
"timestamp": 1709337600000
}
]
}
```
#### get_orderbook
- endpoint: /market/orderbook
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair
- depth (optional): number of levels per side, default 20, max 100
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbol": "BTCUSDC",
"bids": [["87499.50", "12.5"], ["87499.00", "8.3"]],
"asks": [["87500.50", "10.2"], ["87501.00", "15.7"]],
"timestamp": 1709337600000
}
```
#### get_recent_trades
- endpoint: /market/recent_trades
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair
- limit (optional): max trades, default 50, max 200
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trades": [
{
"symbol": "BTCUSDC",
"price": "87500.00",
"size": "0.5",
"side": "buy",
"timestamp": 1709337600000
}
]
}
```
#### get_funding_rate
- endpoint: /market/funding_rate
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair
- history (optional): `true` for historical rates
- limit (optional): number of periods for historical, default 8
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbol": "BTCUSDC",
"current_rate": "0.0001",
"predicted_next_rate": "0.00012",
"next_funding_time": 1709352000000,
"annualized_rate": "3.65",
"history": [
{ "rate": "0.0001", "timestamp": 1709337600000 }
]
}
```
#### get_mark_price
- endpoint: /market/mark_price
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbol": "BTCUSDC",
"mark_price": "87500.00",
"index_price": "87495.00",
"last_price": "87500.50",
"estimated_funding_rate": "0.0001"
}
```
#### get_symbols
- endpoint: /market/symbols
- method: GET
- parameters:
- query parameters:
- type (optional): `perp`, `spot`, or omit for all
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbols": [
{
"symbol": "BTCUSDC",
"base_asset": "BTC",
"quote_asset": "USDC",
"type": "perp",
"min_order_size": "0.001",
"tick_size": "0.10",
"max_leverage": 50,
"status": "active"
}
]
}
```
---
## TRADING (Auth Required)
#### open_position
- endpoint: /auth/tacoclaw/trade/open_position
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"side": "Long",
"symbol": "BTCUSDC",
"notional_position": 100.0,
"leverage": 3,
"sl_price": 80000.0,
"tp_price": 100000.0,
"limit_price": 87000.0
}
```
- `leverage`, `sl_price`, `tp_price`, `limit_price` are optional.
#### close_position
- endpoint: /auth/tacoclaw/trade/close_position
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"notional_position": 100.0,
"side": "Short",
"limit_price": 88000.0
}
```
- `limit_price` is optional.
#### modify_order
- endpoint: /auth/tacoclaw/trade/modify_order
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"order_id": "12345",
"new_price": 86000.0,
"new_notional_position": 150.0
}
```
- `new_price` and `new_notional_position` are both optional; at least one must be provided.
#### set_leverage
- endpoint: /auth/tacoclaw/trade/set_leverage
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"leverage": 5
}
```
#### set_margin_mode
- endpoint: /auth/tacoclaw/trade/set_margin_mode
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"is_cross_margin": true
}
```
#### adjust_margin
- endpoint: /auth/tacoclaw/trade/adjust_margin
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"amount": 50.0,
"action": "add"
}
```
- `action`: `add` or `remove`
#### set_stop_loss
- endpoint: /auth/tacoclaw/trade/set_stop_loss
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"side": "Long",
"notional_position": 100.0,
"price": 85000.0
}
```
#### set_take_profit
- endpoint: /auth/tacoclaw/trade/set_take_profit
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body:
```json
{
"api_token": "<bearer token>",
"user_id": "<user_id>",
"symbol": "BTCUSDC",
"side": "Long",
"notional_position": 100.0,
"price": 95000.0
}
```
#### cancel_stop_loss_orders
- endpoint: /auth/tacoclaw/trade/cancel_stop_loss_orders
- method: POST
- body: `{ "api_token", "user_id", "symbol" }`
#### cancel_take_profit_orders
- endpoint: /auth/tacoclaw/trade/cancel_take_profit_orders
- method: POST
- body: `{ "api_token", "user_id", "symbol" }`
#### cancel_stop_orders
- endpoint: /auth/tacoclaw/trade/cancel_stop_orders
- method: POST
- body: `{ "api_token", "user_id", "symbol" }`
#### cancel_all_orders
- endpoint: /auth/tacoclaw/trade/cancel_all_orders
- method: POST
- body: `{ "api_token", "user_id", "symbol" }`
#### cancel_order_by_order_id
- endpoint: /auth/tacoclaw/trade/cancel_order_by_order_id
- method: POST
- body: `{ "api_token", "user_id", "symbol", "order_id" }`
---
## ACCOUNT QUERIES (Auth Required)
#### get_positions
- endpoint: /auth/tacoclaw/trade/get_positions
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
#### get_open_orders
- endpoint: /auth/tacoclaw/trade/get_open_orders
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
#### get_balance
- endpoint: /auth/tacoclaw/trade/get_balance
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
#### get_filled_order_by_order_id
- endpoint: /auth/tacoclaw/trade/get_filled_order_by_order_id
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
- symbol: trading pair
- order_id: the order ID
- is_algo_id (optional): true if order_id is an algo order ID
#### get_trade_history
- endpoint: /auth/tacoclaw/trade/get_trade_history
- method: GET
- parameters:
- query parameters:
- user_id (required): taco user id
- symbol (required): trading pair (e.g. BTCUSDC)
- start_time (required): Unix ms
- end_time (optional): Unix ms
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"records": [
{
"exchange": "Taco",
"order_id": "50473587382",
"price": "2127.1",
"quantity": "0.047",
"realized_pnl": "0",
"timestamp": 1774515516630,
"trade_fee": "0.045987"
}
]
}
```
#### get_pnl_summary
- endpoint: /auth/tacoclaw/account/get_pnl_summary
- method: GET
- parameters:
- query parameters:
- user_id (required)
- period (optional): `1d`, `7d`, `30d`, `all`. Default `7d`
- symbol (optional): filter by symbol
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"period": "7d",
"realized_pnl": "1250.00",
"unrealized_pnl": "-320.00",
"funding_received": "15.00",
"funding_paid": "-45.00",
"fees_paid": "-180.00",
"net_pnl": "720.00",
"trade_count": 34,
"win_rate": "0.62"
}
```
#### get_fee_summary
- endpoint: /auth/tacoclaw/account/get_fee_summary
- method: GET
- parameters:
- query parameters:
- user_id (required)
- period (optional): `1d`, `7d`, `30d`, `all`. Default `30d`
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"period": "30d",
"maker_fees": "-120.00",
"taker_fees": "-380.00",
"funding_fees_net": "-30.00",
"total_fees": "-530.00",
"fee_tier": "VIP1",
"maker_rate": "0.0002",
"taker_rate": "0.0005"
}
```
#### get_credits
- endpoint: /auth/portfolio
- method: GET
- parameters:
- query parameters:
- user_id (required)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"email": "[email protected]",
"free_credits": 30,
"user_id": "did:privy:cmmn6zf1602ub0cl5016mwz2m"
}
```
#### get_deposit_address
- endpoint: /auth/deposit/smart_wallet_address/get
- method: GET
- parameters:
- query parameters:
- user_id (required)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"address": "0x123"
}
```
#### get_transfer_history
- endpoint: /auth/tacoclaw/account/get_transfer_history
- method: GET
- parameters:
- query parameters:
- user_id (required)
- type (optional): `deposit`, `withdrawal`, or omit for all
- limit (optional): max records, default 20
- start_time (optional): Unix ms
- end_time (optional): Unix ms
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"transfers": [
{
"transfer_id": "tf_001",
"type": "deposit",
"amount": "1000.00",
"asset": "USDC",
"status": "completed",
"tx_hash": "0xabc...",
"timestamp": 1709337600000
}
]
}
```
#### get_liquidation_price
- endpoint: /auth/tacoclaw/trade/get_liquidation_price
- method: GET
- parameters:
- query parameters:
- user_id (required)
- symbol (required): trading pair
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"symbol": "BTCUSDC",
"side": "Long",
"liquidation_price": "72500.00",
"margin_ratio": "0.15",
"maintenance_margin": "125.00",
"position_margin": "875.00"
}
```
#### get_default_ai_trader
- endpoint: /auth/autopilot/default
- method: GET
- parameters:
- query parameters:
- user_id (required)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"default_trader": {
"exchange": "Hyper",
"frequency": 1800,
"prompt_tag": "taco-Data Dana",
"provider": "deepseek",
"provider_model": "deepseek-chat",
"trader_id": "gua6gh-deepseek-1765187795374342",
"trader_name": "gua6ghTacoDefault",
"trader_state": 1,
"use_taco": true,
"user_id": "did:privy:cmionawye03s0lb0cgwgua6gh",
"openclaw_connected": true,
"counter": false,
"exchange_api": "privy"
}
}
```
- meaning of `trader_state` field: 1 means paused (could be started via `start_default_ai_trader`), 2 means running (could be paused via `pause_default_ai_trader`), 0 means deleted (can not be used any more)
#### get_default_ai_strategies
- endpoint: /auth/autopilot/prompts
- method: GET
- parameters:
- query parameters:
- tacoclaw (boolean, optional)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"taco_prompts": [
{
"content": "You are a professional cryptocurrency trading AI......",
"detail": {
"annual_apr": 12.59,
"description": "Build or exit positions in planned installments, reducing the timing risk compared with putting all capital to work at once.",
"executed_trades": 3206,
"labels": [
"Systematic Accumulation",
"Conservative",
"Long Term"
],
"max_drawdown": 10.86,
"pl_ratio": 1.02,
"simu_end": 1773936000,
"simu_start": 1742400000,
"title": "Dollar Cost Average",
"win_rate": 55.26
},
"owner": "taco-autopilot",
"tag": "taco-dollar-cost-average",
"target": "*"
}
}
```
#### start_default_ai_trader
- endpoint: /auth/autopilot/trader/start
- method: POST
- parameters:
- query parameters:
- user_id (required)
- trader_id (required, same as `trader_id` field of get_default_ai_trader result)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 2
}
```
#### pause_default_ai_trader
- endpoint: /auth/autopilot/trader/pause
- method: POST
- parameters:
- query parameters:
- user_id (required)
- trader_id (required, same as `trader_id` field of `get_default_ai_trader` result)
- close_all_position (required)
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 1
}
```
#### use_a_default_ai_strategy_for_default_ai_trader
- endpoint: /auth/autopilot/trader/modification
- method: POST
- parameters:
- query parameters:
- user_id (required)
- request body:
```json
{
"user_id": "<user_id>",
"trader_id": "gua6gh-deepseek-1765187859937789",
"prompt_tag": "taco-Momentum Max"
}
```
- valid value for `prompt_tag` should be from `tag` field of `get_default_ai_strategies` result
- `trader_id` should same as from `trader_id` field of `get_default_ai_trader` result
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 2
}
```
#### change_running_interval_for_default_ai_trader
- endpoint: /auth/autopilot/trader/modification
- method: POST
- parameters:
- query parameters:
- user_id (required)
- request body:
```json
{
"user_id": "<user_id>",
"trader_id": "gua6gh-deepseek-1765187859937789",
"frequency": 30
}
```
- valid values for `frequency` are: 15/30/60/120/180/360
- `trader_id` should same as from `trader_id` field of `get_default_ai_trader` result
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 2
}
```
#### change_name_for_default_ai_trader
- endpoint: /auth/autopilot/trader/modification
- method: POST
- parameters:
- query parameters:
- user_id (required)
- request body:
```json
{
"user_id": "<user_id>",
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_name": "newname"
}
```
- `trader_id` should be same as the `trader_id` field of `get_default_ai_trader` result
- expected response:
```json
{
"base_response": { "status_code": 200, "status_msg": "success" },
"trader_id": "gua6gh-deepseek-1765187859937789",
"trader_state": 2
}
```
Interact with the Taco crypto trading platform via API. Use when the user wants to (1) get kline/candlestick market data, (2) check account balance and posit...
---
name: taco
description: "Interact with the Taco crypto trading platform via API. Use when the user wants to (1) get kline/candlestick market data, (2) check account balance and positions, (3) open perpetual positions, (4) close perpetual positions, (5) calculate technical indicators (EMA, MACD, RSI, ATR, BollingerBands, DonchianChannel), or (6) any trading operations on Taco. Supports exchanges: Binance, Hyper, Aster, Grvt, StandX, Lighter."
---
# Taco Trading Platform
## Setup
Config is stored at `~/.openclaw/workspace/taco/config.json`. Each exchange is bound to its own `trader_id`:
```json
{
"user_id": "<taco user id>",
"api_token": "<taco api key>",
"trader_ids": {
"StandX": "<trader id for StandX>",
"Binance": "<trader id for Binance>",
"Hyper": "<trader id for Hyper>",
"Lighter": "<trader id for Lighter>",
"Aster": "<trader id for Aster>",
"Grvt": "<trader id for Grvt>"
}
}
```
**Key concept:** Exchange and `trader_id` are bound 1:1. When operating on a specific exchange, the CLI automatically uses the corresponding `trader_id` from config. Only configure the exchanges you use.
**First-time setup:** If config does not exist, ask the user for their `user_id`, `api_token`, and each exchange's `trader_id`, then write the JSON file to `~/.openclaw/workspace/taco/config.json` (create parent directories as needed). Alternatively, run the interactive init command:
```bash
$PYTHON scripts/taco_client.py init
```
**Before any API call:** Check that `~/.openclaw/workspace/taco/config.json` exists. If not, guide the user through setup first.
## Python Requirement
Before running any command, detect the available Python 3 command:
```bash
command -v python3 || command -v python
```
- If `python3` is found, use `python3` (and `pip3` for package installs)
- If only `python` is found, verify it is Python 3 with `python --version`. If it reports Python 2.x, treat it as unavailable.
- If neither provides Python 3, ask the user to install Python 3 before proceeding.
Then check the `requests` package: `$PYTHON -c "import requests"`. If it fails, install with `pip3 install requests` (or `pip install requests` if only `pip` is available).
**In all examples below, `$PYTHON` represents the detected Python command.** Store the result once per session and reuse it for every subsequent call.
## Usage
Run the CLI client at `scripts/taco_client.py` (relative to this skill directory). Requires the `requests` Python package. Config defaults to `~/.openclaw/workspace/taco/config.json` (override with `--config <path>`).
### Get kline data (no auth required)
```bash
$PYTHON scripts/taco_client.py kline \
--symbol BTCUSDT --interval 1h --exchange Binance
```
Optional: `--start-time <unix_ms>` `--end-time <unix_ms>` (max 100 klines per response)
Valid intervals: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `6h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M`
### Check account
```bash
$PYTHON scripts/taco_client.py account --exchange Binance
```
Returns: available_balance, total_equity, margin_used, and open positions for the specified exchange's trader.
### Open position
```bash
$PYTHON scripts/taco_client.py open \
--exchange Binance --symbol BTCUSDT --notional 100 --long --leverage 3 \
--sl-price 80000 --tp-price 100000
```
- `--long` for long, omit for short
- `--sl-price` and `--tp-price` are optional
- `--leverage` defaults to 1.0
### Close position
```bash
$PYTHON scripts/taco_client.py close \
--exchange Binance --symbol BTCUSDT --notional 100 --long
```
- `--long` to close a long position, omit to close a short
### Calculate indicators (no auth required)
Fetches kline data and computes technical indicators locally. Supported types: `EMA`, `MACD`, `RSI`, `ATR`, `BollingerBands`, `DonchianChannel`.
```bash
# EMA (Exponential Moving Average)
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1h --type EMA --period 20
# MACD (Moving Average Convergence Divergence)
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1h --type MACD \
--fast 12 --slow 26 --signal 9
# RSI (Relative Strength Index)
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 4h --type RSI --period 14
# ATR (Average True Range)
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1d --type ATR --period 14
# Bollinger Bands
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1h --type BollingerBands \
--period 20 --std-dev 2.0
# Donchian Channel
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1h --type DonchianChannel --period 20
```
Options:
- `--type` — required, one of: `EMA`, `MACD`, `RSI`, `ATR`, `BollingerBands`, `DonchianChannel`
- `--period` — indicator period (default varies by type: EMA=20, RSI=14, ATR=14, BollingerBands=20, DonchianChannel=20)
- `--fast`, `--slow`, `--signal` — MACD-specific (defaults: 12, 26, 9)
- `--std-dev` — BollingerBands standard deviation multiplier (default: 2.0)
- `--limit N` — show only the last N computed values (default: all)
- `--start-time`, `--end-time` — optional, same as kline
## Supported exchanges
`Binance`, `Hyper`, `Aster`, `Grvt`, `StandX`, `Lighter`
## API details
For full endpoint documentation and response schemas, see [references/api-references.md](references/api-references.md).
FILE:scripts/taco_client.py
#!/usr/bin/env python3
"""Taco Trading Platform CLI client.
Usage:
python taco_client.py --config config.json kline --symbol BTCUSDT --interval 1h --exchange Binance
python taco_client.py --config config.json account
python taco_client.py --config config.json open --exchange Binance --symbol BTCUSDT --notional 100 --long --leverage 3
python taco_client.py --config config.json close --exchange Binance --symbol BTCUSDT --notional 100 --long
python taco_client.py indicator --exchange Binance --symbol BTCUSDT --interval 1h --type EMA --period 20
"""
import argparse
import json
import sys
from pathlib import Path
try:
import requests
except ImportError:
print("Error: 'requests' package required. Install with: pip install requests", file=sys.stderr)
sys.exit(1)
BASE_URL = "https://api.taco.trade"
CONFIG_PATH = Path.home() / ".openclaw" / "workspace" / "taco" / "config.json"
VALID_INTERVALS = {"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"}
VALID_EXCHANGES = {"Binance", "Hyper", "Aster", "Grvt", "StandX", "Lighter"}
def load_config(path: str) -> dict:
p = Path(path)
if not p.exists():
print(f"Error: config file not found: {path}", file=sys.stderr)
print(f"Create it with: python {sys.argv[0]} init", file=sys.stderr)
sys.exit(1)
with open(p) as f:
cfg = json.load(f)
required = ["user_id", "api_token", "trader_ids"]
missing = [k for k in required if not cfg.get(k)]
if missing:
print(f"Error: config missing required fields: {', '.join(missing)}", file=sys.stderr)
sys.exit(1)
if not isinstance(cfg["trader_ids"], dict) or not cfg["trader_ids"]:
print("Error: 'trader_ids' must be a non-empty mapping of exchange -> trader_id", file=sys.stderr)
sys.exit(1)
return cfg
def get_trader_id(cfg: dict, exchange: str) -> str:
trader_ids = cfg["trader_ids"]
if exchange not in trader_ids:
print(f"Error: no trader_id configured for exchange '{exchange}'", file=sys.stderr)
print(f"Configured exchanges: {', '.join(sorted(trader_ids.keys()))}", file=sys.stderr)
sys.exit(1)
return trader_ids[exchange]
def api_request(method: str, endpoint: str, params: dict = None, json_body: dict = None, token: str = None) -> dict:
url = f"{BASE_URL}{endpoint}"
headers = {}
if token:
headers["Authorization"] = f"Bearer {token}"
try:
if method == "GET":
resp = requests.get(url, params=params, headers=headers, timeout=30)
else:
resp = requests.post(url, params=params, json=json_body, headers=headers, timeout=30)
resp.raise_for_status()
return resp.json()
except requests.exceptions.RequestException as e:
print(f"Error: API request failed: {e}", file=sys.stderr)
if hasattr(e, "response") and e.response is not None:
try:
print(f"Response: {e.response.text}", file=sys.stderr)
except Exception:
pass
sys.exit(1)
def cmd_kline(args):
if args.interval not in VALID_INTERVALS:
print(f"Error: invalid interval '{args.interval}'. Valid: {', '.join(sorted(VALID_INTERVALS))}", file=sys.stderr)
sys.exit(1)
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
params = {"symbol": args.symbol, "interval": args.interval, "exchange": args.exchange}
if args.start_time:
params["start_time"] = args.start_time
if args.end_time:
params["end_time"] = args.end_time
result = api_request("GET", "/market/klines", params=params)
print(json.dumps(result, indent=2))
def cmd_account(args, cfg):
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
trader_id = get_trader_id(cfg, args.exchange)
params = {"user_id": cfg["user_id"], "trader_id": trader_id}
result = api_request("GET", "/auth/autopilot/positions", params=params, token=cfg["api_token"])
print(json.dumps(result, indent=2))
def cmd_open(args, cfg):
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
if args.notional <= 0:
print("Error: notional_position must be positive", file=sys.stderr)
sys.exit(1)
if args.leverage <= 0:
print("Error: leverage must be positive", file=sys.stderr)
sys.exit(1)
trader_id = get_trader_id(cfg, args.exchange)
body = {
"api_token": cfg["api_token"],
"user_id": cfg["user_id"],
"trader_id": trader_id,
"exchange": args.exchange,
"symbol": args.symbol,
"notional_position": args.notional,
"long": args.long,
"leverage": args.leverage,
}
if args.sl_price is not None:
body["sl_price"] = args.sl_price
if args.tp_price is not None:
body["tp_price"] = args.tp_price
params = {"user_id": cfg["user_id"]}
result = api_request("POST", "/auth/autopilot/position/open", params=params, json_body=body, token=cfg["api_token"])
print(json.dumps(result, indent=2))
def cmd_close(args, cfg):
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
if args.notional <= 0:
print("Error: notional_position must be positive", file=sys.stderr)
sys.exit(1)
trader_id = get_trader_id(cfg, args.exchange)
body = {
"api_token": cfg["api_token"],
"user_id": cfg["user_id"],
"trader_id": trader_id,
"exchange": args.exchange,
"symbol": args.symbol,
"notional_position": args.notional,
"long": args.long,
}
params = {"user_id": cfg["user_id"]}
result = api_request("POST", "/auth/autopilot/position/close", params=params, json_body=body, token=cfg["api_token"])
print(json.dumps(result, indent=2))
VALID_INDICATORS = {"EMA", "MACD", "RSI", "ATR", "BollingerBands", "DonchianChannel"}
def calc_ema(closes, period):
if len(closes) < period:
return [None] * len(closes)
multiplier = 2.0 / (period + 1)
ema = [None] * (period - 1)
ema.append(sum(closes[:period]) / period)
for i in range(period, len(closes)):
ema.append(closes[i] * multiplier + ema[-1] * (1 - multiplier))
return ema
def calc_macd(closes, fast=12, slow=26, signal_period=9):
ema_fast = calc_ema(closes, fast)
ema_slow = calc_ema(closes, slow)
macd_line = []
for f, s in zip(ema_fast, ema_slow):
if f is not None and s is not None:
macd_line.append(f - s)
else:
macd_line.append(None)
macd_values = [v for v in macd_line if v is not None]
signal_line_raw = calc_ema(macd_values, signal_period) if len(macd_values) >= signal_period else [None] * len(macd_values)
results = []
mi = 0
for v in macd_line:
if v is None:
results.append(None)
else:
sig = signal_line_raw[mi]
hist = v - sig if sig is not None else None
results.append({"macd": v, "signal": sig, "histogram": hist})
mi += 1
return results
def calc_rsi(closes, period=14):
if len(closes) < period + 1:
return [None] * len(closes)
results = [None] * period
gains = []
losses = []
for i in range(1, period + 1):
change = closes[i] - closes[i - 1]
gains.append(max(change, 0))
losses.append(max(-change, 0))
avg_gain = sum(gains) / period
avg_loss = sum(losses) / period
if avg_loss == 0:
results.append(100.0)
else:
rs = avg_gain / avg_loss
results.append(100.0 - 100.0 / (1.0 + rs))
for i in range(period + 1, len(closes)):
change = closes[i] - closes[i - 1]
gain = max(change, 0)
loss = max(-change, 0)
avg_gain = (avg_gain * (period - 1) + gain) / period
avg_loss = (avg_loss * (period - 1) + loss) / period
if avg_loss == 0:
results.append(100.0)
else:
rs = avg_gain / avg_loss
results.append(100.0 - 100.0 / (1.0 + rs))
return results
def calc_atr(highs, lows, closes, period=14):
if len(closes) < 2:
return [None] * len(closes)
true_ranges = [highs[0] - lows[0]]
for i in range(1, len(closes)):
tr = max(highs[i] - lows[i], abs(highs[i] - closes[i - 1]), abs(lows[i] - closes[i - 1]))
true_ranges.append(tr)
if len(true_ranges) < period:
return [None] * len(closes)
results = [None] * (period - 1)
atr = sum(true_ranges[:period]) / period
results.append(atr)
for i in range(period, len(true_ranges)):
atr = (atr * (period - 1) + true_ranges[i]) / period
results.append(atr)
return results
def calc_bollinger(closes, period=20, std_dev=2.0):
results = []
for i in range(len(closes)):
if i < period - 1:
results.append(None)
continue
window = closes[i - period + 1:i + 1]
middle = sum(window) / period
variance = sum((x - middle) ** 2 for x in window) / period
sd = variance ** 0.5
results.append({"upper": middle + std_dev * sd, "middle": middle, "lower": middle - std_dev * sd})
return results
def calc_donchian(highs, lows, period=20):
results = []
for i in range(len(highs)):
if i < period - 1:
results.append(None)
continue
upper = max(highs[i - period + 1:i + 1])
lower = min(lows[i - period + 1:i + 1])
results.append({"upper": upper, "middle": (upper + lower) / 2.0, "lower": lower})
return results
def cmd_indicator(args):
if args.interval not in VALID_INTERVALS:
print(f"Error: invalid interval '{args.interval}'. Valid: {', '.join(sorted(VALID_INTERVALS))}", file=sys.stderr)
sys.exit(1)
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
if args.type not in VALID_INDICATORS:
print(f"Error: invalid indicator type '{args.type}'. Valid: {', '.join(sorted(VALID_INDICATORS))}", file=sys.stderr)
sys.exit(1)
params = {"symbol": args.symbol, "interval": args.interval, "exchange": args.exchange}
if args.start_time:
params["start_time"] = args.start_time
if args.end_time:
params["end_time"] = args.end_time
result = api_request("GET", "/market/klines", params=params)
if isinstance(result, list):
klines = result
elif isinstance(result, dict):
klines = result.get("klines", result.get("data", []))
else:
klines = []
if not klines:
print("Error: no kline data returned", file=sys.stderr)
sys.exit(1)
# Support both dict-style (API response) and list-style klines
sample = klines[0]
if isinstance(sample, dict):
timestamps = [k.get("open_time", k.get("time", 0)) for k in klines]
highs = [float(k["high"]) for k in klines]
lows = [float(k["low"]) for k in klines]
closes = [float(k["close"]) for k in klines]
else:
timestamps = [k[0] for k in klines]
highs = [float(k[2]) for k in klines]
lows = [float(k[3]) for k in klines]
closes = [float(k[4]) for k in klines]
indicator_type = args.type
indicator_params = {}
if indicator_type == "EMA":
period = args.period or 20
indicator_params = {"period": period}
raw = calc_ema(closes, period)
values = [{"time": t, "ema": round(v, 6)} for t, v in zip(timestamps, raw) if v is not None]
elif indicator_type == "MACD":
fast = args.fast or 12
slow = args.slow or 26
signal = args.signal or 9
indicator_params = {"fast": fast, "slow": slow, "signal": signal}
raw = calc_macd(closes, fast, slow, signal)
values = []
for t, v in zip(timestamps, raw):
if v is not None:
entry = {"time": t, "macd": round(v["macd"], 6)}
entry["signal"] = round(v["signal"], 6) if v["signal"] is not None else None
entry["histogram"] = round(v["histogram"], 6) if v["histogram"] is not None else None
values.append(entry)
elif indicator_type == "RSI":
period = args.period or 14
indicator_params = {"period": period}
raw = calc_rsi(closes, period)
values = [{"time": t, "rsi": round(v, 4)} for t, v in zip(timestamps, raw) if v is not None]
elif indicator_type == "ATR":
period = args.period or 14
indicator_params = {"period": period}
raw = calc_atr(highs, lows, closes, period)
values = [{"time": t, "atr": round(v, 6)} for t, v in zip(timestamps, raw) if v is not None]
elif indicator_type == "BollingerBands":
period = args.period or 20
std_dev = args.std_dev or 2.0
indicator_params = {"period": period, "std_dev": std_dev}
raw = calc_bollinger(closes, period, std_dev)
values = [{"time": t, "upper": round(v["upper"], 6), "middle": round(v["middle"], 6), "lower": round(v["lower"], 6)}
for t, v in zip(timestamps, raw) if v is not None]
elif indicator_type == "DonchianChannel":
period = args.period or 20
indicator_params = {"period": period}
raw = calc_donchian(highs, lows, period)
values = [{"time": t, "upper": round(v["upper"], 6), "middle": round(v["middle"], 6), "lower": round(v["lower"], 6)}
for t, v in zip(timestamps, raw) if v is not None]
if args.limit and args.limit > 0:
values = values[-args.limit:]
output = {
"indicator": indicator_type,
"params": indicator_params,
"symbol": args.symbol,
"exchange": args.exchange,
"interval": args.interval,
"values": values,
}
print(json.dumps(output, indent=2))
def cmd_init(args):
p = Path(args.config)
if p.exists():
print(f"Config already exists at {p}", file=sys.stderr)
print("Delete it first if you want to reinitialize.", file=sys.stderr)
sys.exit(1)
user_id = input("Enter your Taco user_id: ").strip()
api_token = input("Enter your Taco api_token: ").strip()
if not all([user_id, api_token]):
print("Error: user_id and api_token are required", file=sys.stderr)
sys.exit(1)
trader_ids = {}
print(f"Configure trader_id for each exchange (leave blank to skip).")
print(f"Supported exchanges: {', '.join(sorted(VALID_EXCHANGES))}")
for exchange in sorted(VALID_EXCHANGES):
tid = input(f" trader_id for {exchange}: ").strip()
if tid:
trader_ids[exchange] = tid
if not trader_ids:
print("Error: at least one exchange trader_id is required", file=sys.stderr)
sys.exit(1)
p.parent.mkdir(parents=True, exist_ok=True)
cfg = {"user_id": user_id, "api_token": api_token, "trader_ids": trader_ids}
with open(p, "w") as f:
json.dump(cfg, f, indent=2)
print(f"Config saved to {p}")
def main():
parser = argparse.ArgumentParser(description="Taco Trading Platform CLI")
parser.add_argument("--config", default=str(CONFIG_PATH), help=f"Path to JSON config file (default: {CONFIG_PATH})")
sub = parser.add_subparsers(dest="command", required=True)
# init
sub.add_parser("init", help="Initialize config file with credentials")
# kline
p_kline = sub.add_parser("kline", help="Get kline/candlestick data")
p_kline.add_argument("--symbol", required=True, help="Trading pair, e.g. BTCUSDT")
p_kline.add_argument("--interval", required=True, help="Kline interval, e.g. 1h, 4h, 1d")
p_kline.add_argument("--exchange", required=True, help="Exchange name, e.g. Binance")
p_kline.add_argument("--start-time", type=int, dest="start_time", help="Start time (Unix ms)")
p_kline.add_argument("--end-time", type=int, dest="end_time", help="End time (Unix ms)")
# account
p_account = sub.add_parser("account", help="Check account positions and balance")
p_account.add_argument("--exchange", required=True, help="Exchange name (uses its bound trader_id)")
# open
p_open = sub.add_parser("open", help="Open a perpetual position")
p_open.add_argument("--exchange", required=True, help="Exchange name")
p_open.add_argument("--symbol", required=True, help="Trading pair")
p_open.add_argument("--notional", required=True, type=float, help="Notional position size")
p_open.add_argument("--long", action="store_true", default=False, help="Open long (default: short)")
p_open.add_argument("--leverage", type=float, default=1.0, help="Leverage (default: 1.0)")
p_open.add_argument("--sl-price", type=float, dest="sl_price", help="Stop-loss price")
p_open.add_argument("--tp-price", type=float, dest="tp_price", help="Take-profit price")
# close
p_close = sub.add_parser("close", help="Close a perpetual position")
p_close.add_argument("--exchange", required=True, help="Exchange name")
p_close.add_argument("--symbol", required=True, help="Trading pair")
p_close.add_argument("--notional", required=True, type=float, help="Notional position size to close")
p_close.add_argument("--long", action="store_true", default=False, help="Close long position (default: short)")
# indicator
p_ind = sub.add_parser("indicator", help="Calculate technical indicators from kline data")
p_ind.add_argument("--exchange", required=True, help="Exchange name")
p_ind.add_argument("--symbol", required=True, help="Trading pair, e.g. BTCUSDT")
p_ind.add_argument("--interval", required=True, help="Kline interval, e.g. 1h, 4h, 1d")
p_ind.add_argument("--type", required=True, help=f"Indicator type: {', '.join(sorted(VALID_INDICATORS))}")
p_ind.add_argument("--period", type=int, default=None, help="Indicator period (default varies by type)")
p_ind.add_argument("--fast", type=int, default=None, help="MACD fast period (default: 12)")
p_ind.add_argument("--slow", type=int, default=None, help="MACD slow period (default: 26)")
p_ind.add_argument("--signal", type=int, default=None, help="MACD signal period (default: 9)")
p_ind.add_argument("--std-dev", type=float, default=None, dest="std_dev", help="BollingerBands std dev multiplier (default: 2.0)")
p_ind.add_argument("--start-time", type=int, dest="start_time", help="Start time (Unix ms)")
p_ind.add_argument("--end-time", type=int, dest="end_time", help="End time (Unix ms)")
p_ind.add_argument("--limit", type=int, default=0, help="Show only the last N values (default: 0 = all)")
args = parser.parse_args()
if args.command == "init":
cmd_init(args)
return
if args.command == "indicator":
cmd_indicator(args)
return
if args.command == "kline":
cmd_kline(args)
return
cfg = load_config(args.config)
if args.command == "account":
cmd_account(args, cfg)
elif args.command == "open":
cmd_open(args, cfg)
elif args.command == "close":
cmd_close(args, cfg)
if __name__ == "__main__":
main()
FILE:references/api-references.md
#### get_kline:
- endpoint: /market/klines
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair symbol, e.g. `BTCUSDT`, `ETHUSDT`
- interval (required): kline interval. Supported values: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `6h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M`
- exchange (required): exchange name. Supported values: `Binance`, `Hyper`, `Aster`, `Grvt`, `StandX`, `Lighter`
- start_time (optional): start time in Unix milliseconds. If omitted together with end_time, returns the latest 100 klines.
- end_time (optional): end time in Unix milliseconds. If omitted together with start_time, returns the latest 100 klines.
- response (example below in json format):
```json
{
"base_response": {
"status_code": 200,
"status_msg": "success",
"trace_id": "trace tracking id"
},
"klines": [
{
"symbol": "BTCUSDT",
"interval": "1h",
"exchange": "Binance",
"open_time": 1709251200000,
"close_time": 1709254800000,
"open": "62345.50",
"high": "62890.00",
"low": "62100.00",
"close": "62780.30",
"volume": "1234.567",
"quote_volume": "77012345.89",
"trades_count": 45678
}
]
}
```
- notes:
- Maximum 100 klines per response. If the query range contains more, only the latest 100 are returned.
- Price and volume fields are returned as strings to preserve decimal precision.
#### check_account
- endpoint: /auth/autopilot/positions
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
- trader_id: taco ai trader id
- response(example below in json format):
```json
{
"available_balance": 4976.69597582,
"base_response": {
"status_code": 200,
"status_msg": "success",
"trace_id": "trace tracking id"
},
"margin_used": 34.85004,
"margin_used_pct": 0.69538661076795,
"positions": [
{
"entry_price": 87094.9,
"leverage": 5,
"liquidation_price": 2582567.83656375,
"mark_price": 87125.1,
"position_size": 174.2502,
"quantity": 0.002,
"side": "Short",
"symbol": "BTCUSDT",
"unrealized_pnl": -0.0604
}
],
"total_equity": 5011.60641582
}
```
#### open_position
- endpoint: /auth/autopilot/position/open
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body (in json format as below example):
```json
{
"api_token": "please use taco api key same as authentication bearer token in header",
"user_id": "same as in query parameter",
"trader_id": "target taco ai trader id",
"exchange": "Binance",
"symbol": "BTCUSDT",
"notional_position": 123.456,
"long": true,
"leverage": 3.0,
"sl_price": 30000.0,
"tp_price": 90000.0
}
```
#### close_position
- endpoint: /auth/autopilot/position/close
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body (in json format as below example):
```json
{
"api_token": "please use taco api key same as authentication bearer token in header",
"user_id": "same as in query parameter",
"trader_id": "target taco ai trader id",
"exchange": "Binance",
"symbol": "BTCUSDT",
"notional_position": 123.456,
"long": true
}
```
Interact with the Taco crypto trading platform via API. Use when the user wants to (1) get kline/candlestick market data, (2) check account balance and posit...
---
name: taco
description: "Interact with the Taco crypto trading platform via API. Use when the user wants to (1) get kline/candlestick market data, (2) check account balance and positions, (3) open perpetual positions, (4) close perpetual positions, (5) update take-profit or stop-loss on existing positions, (6) calculate technical indicators (EMA, MACD, RSI, ATR, BollingerBands, DonchianChannel), or (7) any trading operations on Taco. Supports exchanges: Binance, Hyper, Aster, Grvt, StandX, Lighter."
---
# Taco Trading Platform
## Setup
Config is stored at `~/.openclaw/workspace/taco/config.json`. Each exchange is bound to its own `trader_id`:
```json
{
"user_id": "<taco user id>",
"api_token": "<taco api key>",
"trader_ids": {
"StandX": "<trader id for StandX>",
"Binance": "<trader id for Binance>",
"Hyper": "<trader id for Hyper>",
"Lighter": "<trader id for Lighter>",
"Aster": "<trader id for Aster>",
"Grvt": "<trader id for Grvt>"
}
}
```
**Key concept:** Exchange and `trader_id` are bound 1:1. When operating on a specific exchange, the CLI automatically uses the corresponding `trader_id` from config. Only configure the exchanges you use.
**First-time setup:** If config does not exist, ask the user for their `user_id`, `api_token`, and each exchange's `trader_id`, then write the JSON file to `~/.openclaw/workspace/taco/config.json` (create parent directories as needed). Alternatively, run the interactive init command:
```bash
$PYTHON scripts/taco_client.py init
```
**Before any API call:** Check that `~/.openclaw/workspace/taco/config.json` exists. If not, guide the user through setup first.
## Python Requirement
Before running any command, detect the available Python 3 command:
```bash
command -v python3 || command -v python
```
- If `python3` is found, use `python3` (and `pip3` for package installs)
- If only `python` is found, verify it is Python 3 with `python --version`. If it reports Python 2.x, treat it as unavailable.
- If neither provides Python 3, ask the user to install Python 3 before proceeding.
Then check the `requests` package: `$PYTHON -c "import requests"`. If it fails, install with `pip3 install requests` (or `pip install requests` if only `pip` is available).
**In all examples below, `$PYTHON` represents the detected Python command.** Store the result once per session and reuse it for every subsequent call.
## Usage
Run the CLI client at `scripts/taco_client.py` (relative to this skill directory). Requires the `requests` Python package. Config defaults to `~/.openclaw/workspace/taco/config.json` (override with `--config <path>`).
### Get kline data (no auth required)
```bash
$PYTHON scripts/taco_client.py kline \
--symbol BTCUSDT --interval 1h --exchange Binance
```
Optional: `--start-time <unix_ms>` `--end-time <unix_ms>` (max 100 klines per response)
Valid intervals: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `6h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M`
### Check account
```bash
$PYTHON scripts/taco_client.py account --exchange Binance
```
Returns: available_balance, total_equity, margin_used, and open positions for the specified exchange's trader.
### Open position
```bash
$PYTHON scripts/taco_client.py open \
--exchange Binance --symbol BTCUSDT --notional 100 --long --leverage 3 \
--sl-price 80000 --tp-price 100000
```
- `--long` for long, omit for short
- `--sl-price` and `--tp-price` are optional
- `--leverage` defaults to 1.0
### Close position
```bash
$PYTHON scripts/taco_client.py close \
--exchange Binance --symbol BTCUSDT --notional 100 --long
```
- `--long` to close a long position, omit to close a short
### Update take-profit / stop-loss
```bash
# Update take-profit price
$PYTHON scripts/taco_client.py update-tp-sl \
--exchange Binance --symbol BTCUSDT --price 100000 --take-profit
# Update stop-loss price
$PYTHON scripts/taco_client.py update-tp-sl \
--exchange Binance --symbol BTCUSDT --price 80000
```
- `--take-profit` to update take-profit price, omit to update stop-loss
- `--price` is the new trigger price
### Calculate indicators (no auth required)
Fetches kline data and computes technical indicators locally. Supported types: `EMA`, `MACD`, `RSI`, `ATR`, `BollingerBands`, `DonchianChannel`.
```bash
# EMA (Exponential Moving Average)
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1h --type EMA --period 20
# MACD (Moving Average Convergence Divergence)
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1h --type MACD \
--fast 12 --slow 26 --signal 9
# RSI (Relative Strength Index)
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 4h --type RSI --period 14
# ATR (Average True Range)
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1d --type ATR --period 14
# Bollinger Bands
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1h --type BollingerBands \
--period 20 --std-dev 2.0
# Donchian Channel
$PYTHON scripts/taco_client.py indicator \
--exchange Binance --symbol BTCUSDT --interval 1h --type DonchianChannel --period 20
```
Options:
- `--type` — required, one of: `EMA`, `MACD`, `RSI`, `ATR`, `BollingerBands`, `DonchianChannel`
- `--period` — indicator period (default varies by type: EMA=20, RSI=14, ATR=14, BollingerBands=20, DonchianChannel=20)
- `--fast`, `--slow`, `--signal` — MACD-specific (defaults: 12, 26, 9)
- `--std-dev` — BollingerBands standard deviation multiplier (default: 2.0)
- `--limit N` — show only the last N computed values (default: all)
- `--start-time`, `--end-time` — optional, same as kline
## Supported exchanges
`Binance`, `Hyper`, `Aster`, `Grvt`, `StandX`, `Lighter`
## API details
For full endpoint documentation and response schemas, see [references/api-references.md](references/api-references.md).
FILE:scripts/taco_client.py
#!/usr/bin/env python3
"""Taco Trading Platform CLI client.
Usage:
python taco_client.py --config config.json kline --symbol BTCUSDT --interval 1h --exchange Binance
python taco_client.py --config config.json account
python taco_client.py --config config.json open --exchange Binance --symbol BTCUSDT --notional 100 --long --leverage 3
python taco_client.py --config config.json close --exchange Binance --symbol BTCUSDT --notional 100 --long
python taco_client.py indicator --exchange Binance --symbol BTCUSDT --interval 1h --type EMA --period 20
"""
import argparse
import json
import sys
from pathlib import Path
try:
import requests
except ImportError:
print("Error: 'requests' package required. Install with: pip install requests", file=sys.stderr)
sys.exit(1)
BASE_URL = "https://api.dev.taco.trading"
CONFIG_PATH = Path.home() / ".openclaw" / "workspace" / "taco" / "config.json"
VALID_INTERVALS = {"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "8h", "12h", "1d", "3d", "1w", "1M"}
VALID_EXCHANGES = {"Binance", "Hyper", "Aster", "Grvt", "StandX", "Lighter"}
def load_config(path: str) -> dict:
p = Path(path)
if not p.exists():
print(f"Error: config file not found: {path}", file=sys.stderr)
print(f"Create it with: python {sys.argv[0]} init", file=sys.stderr)
sys.exit(1)
with open(p) as f:
cfg = json.load(f)
required = ["user_id", "api_token", "trader_ids"]
missing = [k for k in required if not cfg.get(k)]
if missing:
print(f"Error: config missing required fields: {', '.join(missing)}", file=sys.stderr)
sys.exit(1)
if not isinstance(cfg["trader_ids"], dict) or not cfg["trader_ids"]:
print("Error: 'trader_ids' must be a non-empty mapping of exchange -> trader_id", file=sys.stderr)
sys.exit(1)
return cfg
def get_trader_id(cfg: dict, exchange: str) -> str:
trader_ids = cfg["trader_ids"]
if exchange not in trader_ids:
print(f"Error: no trader_id configured for exchange '{exchange}'", file=sys.stderr)
print(f"Configured exchanges: {', '.join(sorted(trader_ids.keys()))}", file=sys.stderr)
sys.exit(1)
return trader_ids[exchange]
def api_request(method: str, endpoint: str, params: dict = None, json_body: dict = None, token: str = None) -> dict:
url = f"{BASE_URL}{endpoint}"
headers = {}
if token:
headers["Authorization"] = f"Bearer {token}"
try:
if method == "GET":
resp = requests.get(url, params=params, headers=headers, timeout=30)
else:
resp = requests.post(url, params=params, json=json_body, headers=headers, timeout=30)
resp.raise_for_status()
return resp.json()
except requests.exceptions.RequestException as e:
print(f"Error: API request failed: {e}", file=sys.stderr)
if hasattr(e, "response") and e.response is not None:
try:
print(f"Response: {e.response.text}", file=sys.stderr)
except Exception:
pass
sys.exit(1)
def cmd_kline(args):
if args.interval not in VALID_INTERVALS:
print(f"Error: invalid interval '{args.interval}'. Valid: {', '.join(sorted(VALID_INTERVALS))}", file=sys.stderr)
sys.exit(1)
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
params = {"symbol": args.symbol, "interval": args.interval, "exchange": args.exchange}
if args.start_time:
params["start_time"] = args.start_time
if args.end_time:
params["end_time"] = args.end_time
result = api_request("GET", "/market/klines", params=params)
print(json.dumps(result, indent=2))
def cmd_account(args, cfg):
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
trader_id = get_trader_id(cfg, args.exchange)
params = {"user_id": cfg["user_id"], "trader_id": trader_id}
result = api_request("GET", "/auth/autopilot/positions", params=params, token=cfg["api_token"])
print(json.dumps(result, indent=2))
def cmd_open(args, cfg):
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
if args.notional <= 0:
print("Error: notional_position must be positive", file=sys.stderr)
sys.exit(1)
if args.leverage <= 0:
print("Error: leverage must be positive", file=sys.stderr)
sys.exit(1)
trader_id = get_trader_id(cfg, args.exchange)
body = {
"api_token": cfg["api_token"],
"user_id": cfg["user_id"],
"trader_id": trader_id,
"exchange": args.exchange,
"symbol": args.symbol,
"notional_position": args.notional,
"long": args.long,
"leverage": args.leverage,
}
if args.sl_price is not None:
body["sl_price"] = args.sl_price
if args.tp_price is not None:
body["tp_price"] = args.tp_price
params = {"user_id": cfg["user_id"]}
result = api_request("POST", "/auth/autopilot/position/open", params=params, json_body=body, token=cfg["api_token"])
print(json.dumps(result, indent=2))
def cmd_close(args, cfg):
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
if args.notional <= 0:
print("Error: notional_position must be positive", file=sys.stderr)
sys.exit(1)
trader_id = get_trader_id(cfg, args.exchange)
body = {
"api_token": cfg["api_token"],
"user_id": cfg["user_id"],
"trader_id": trader_id,
"exchange": args.exchange,
"symbol": args.symbol,
"notional_position": args.notional,
"long": args.long,
}
params = {"user_id": cfg["user_id"]}
result = api_request("POST", "/auth/autopilot/position/close", params=params, json_body=body, token=cfg["api_token"])
print(json.dumps(result, indent=2))
def cmd_update_tp_sl(args, cfg):
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
if args.price <= 0:
print("Error: price must be positive", file=sys.stderr)
sys.exit(1)
trader_id = get_trader_id(cfg, args.exchange)
body = {
"api_token": cfg["api_token"],
"user_id": cfg["user_id"],
"trader_id": trader_id,
"exchange": args.exchange,
"symbol": args.symbol,
"price": args.price,
"take_profit": args.take_profit,
}
params = {"user_id": cfg["user_id"]}
result = api_request("POST", "/auth/autopilot/position/triggering", params=params, json_body=body, token=cfg["api_token"])
print(json.dumps(result, indent=2))
VALID_INDICATORS = {"EMA", "MACD", "RSI", "ATR", "BollingerBands", "DonchianChannel"}
def calc_ema(closes, period):
if len(closes) < period:
return [None] * len(closes)
multiplier = 2.0 / (period + 1)
ema = [None] * (period - 1)
ema.append(sum(closes[:period]) / period)
for i in range(period, len(closes)):
ema.append(closes[i] * multiplier + ema[-1] * (1 - multiplier))
return ema
def calc_macd(closes, fast=12, slow=26, signal_period=9):
ema_fast = calc_ema(closes, fast)
ema_slow = calc_ema(closes, slow)
macd_line = []
for f, s in zip(ema_fast, ema_slow):
if f is not None and s is not None:
macd_line.append(f - s)
else:
macd_line.append(None)
macd_values = [v for v in macd_line if v is not None]
signal_line_raw = calc_ema(macd_values, signal_period) if len(macd_values) >= signal_period else [None] * len(macd_values)
results = []
mi = 0
for v in macd_line:
if v is None:
results.append(None)
else:
sig = signal_line_raw[mi]
hist = v - sig if sig is not None else None
results.append({"macd": v, "signal": sig, "histogram": hist})
mi += 1
return results
def calc_rsi(closes, period=14):
if len(closes) < period + 1:
return [None] * len(closes)
results = [None] * period
gains = []
losses = []
for i in range(1, period + 1):
change = closes[i] - closes[i - 1]
gains.append(max(change, 0))
losses.append(max(-change, 0))
avg_gain = sum(gains) / period
avg_loss = sum(losses) / period
if avg_loss == 0:
results.append(100.0)
else:
rs = avg_gain / avg_loss
results.append(100.0 - 100.0 / (1.0 + rs))
for i in range(period + 1, len(closes)):
change = closes[i] - closes[i - 1]
gain = max(change, 0)
loss = max(-change, 0)
avg_gain = (avg_gain * (period - 1) + gain) / period
avg_loss = (avg_loss * (period - 1) + loss) / period
if avg_loss == 0:
results.append(100.0)
else:
rs = avg_gain / avg_loss
results.append(100.0 - 100.0 / (1.0 + rs))
return results
def calc_atr(highs, lows, closes, period=14):
if len(closes) < 2:
return [None] * len(closes)
true_ranges = [highs[0] - lows[0]]
for i in range(1, len(closes)):
tr = max(highs[i] - lows[i], abs(highs[i] - closes[i - 1]), abs(lows[i] - closes[i - 1]))
true_ranges.append(tr)
if len(true_ranges) < period:
return [None] * len(closes)
results = [None] * (period - 1)
atr = sum(true_ranges[:period]) / period
results.append(atr)
for i in range(period, len(true_ranges)):
atr = (atr * (period - 1) + true_ranges[i]) / period
results.append(atr)
return results
def calc_bollinger(closes, period=20, std_dev=2.0):
results = []
for i in range(len(closes)):
if i < period - 1:
results.append(None)
continue
window = closes[i - period + 1:i + 1]
middle = sum(window) / period
variance = sum((x - middle) ** 2 for x in window) / period
sd = variance ** 0.5
results.append({"upper": middle + std_dev * sd, "middle": middle, "lower": middle - std_dev * sd})
return results
def calc_donchian(highs, lows, period=20):
results = []
for i in range(len(highs)):
if i < period - 1:
results.append(None)
continue
upper = max(highs[i - period + 1:i + 1])
lower = min(lows[i - period + 1:i + 1])
results.append({"upper": upper, "middle": (upper + lower) / 2.0, "lower": lower})
return results
def cmd_indicator(args):
if args.interval not in VALID_INTERVALS:
print(f"Error: invalid interval '{args.interval}'. Valid: {', '.join(sorted(VALID_INTERVALS))}", file=sys.stderr)
sys.exit(1)
if args.exchange not in VALID_EXCHANGES:
print(f"Error: invalid exchange '{args.exchange}'. Valid: {', '.join(sorted(VALID_EXCHANGES))}", file=sys.stderr)
sys.exit(1)
if args.type not in VALID_INDICATORS:
print(f"Error: invalid indicator type '{args.type}'. Valid: {', '.join(sorted(VALID_INDICATORS))}", file=sys.stderr)
sys.exit(1)
params = {"symbol": args.symbol, "interval": args.interval, "exchange": args.exchange}
if args.start_time:
params["start_time"] = args.start_time
if args.end_time:
params["end_time"] = args.end_time
result = api_request("GET", "/market/klines", params=params)
if isinstance(result, list):
klines = result
elif isinstance(result, dict):
klines = result.get("klines", result.get("data", []))
else:
klines = []
if not klines:
print("Error: no kline data returned", file=sys.stderr)
sys.exit(1)
# Support both dict-style (API response) and list-style klines
sample = klines[0]
if isinstance(sample, dict):
timestamps = [k.get("open_time", k.get("time", 0)) for k in klines]
highs = [float(k["high"]) for k in klines]
lows = [float(k["low"]) for k in klines]
closes = [float(k["close"]) for k in klines]
else:
timestamps = [k[0] for k in klines]
highs = [float(k[2]) for k in klines]
lows = [float(k[3]) for k in klines]
closes = [float(k[4]) for k in klines]
indicator_type = args.type
indicator_params = {}
if indicator_type == "EMA":
period = args.period or 20
indicator_params = {"period": period}
raw = calc_ema(closes, period)
values = [{"time": t, "ema": round(v, 6)} for t, v in zip(timestamps, raw) if v is not None]
elif indicator_type == "MACD":
fast = args.fast or 12
slow = args.slow or 26
signal = args.signal or 9
indicator_params = {"fast": fast, "slow": slow, "signal": signal}
raw = calc_macd(closes, fast, slow, signal)
values = []
for t, v in zip(timestamps, raw):
if v is not None:
entry = {"time": t, "macd": round(v["macd"], 6)}
entry["signal"] = round(v["signal"], 6) if v["signal"] is not None else None
entry["histogram"] = round(v["histogram"], 6) if v["histogram"] is not None else None
values.append(entry)
elif indicator_type == "RSI":
period = args.period or 14
indicator_params = {"period": period}
raw = calc_rsi(closes, period)
values = [{"time": t, "rsi": round(v, 4)} for t, v in zip(timestamps, raw) if v is not None]
elif indicator_type == "ATR":
period = args.period or 14
indicator_params = {"period": period}
raw = calc_atr(highs, lows, closes, period)
values = [{"time": t, "atr": round(v, 6)} for t, v in zip(timestamps, raw) if v is not None]
elif indicator_type == "BollingerBands":
period = args.period or 20
std_dev = args.std_dev or 2.0
indicator_params = {"period": period, "std_dev": std_dev}
raw = calc_bollinger(closes, period, std_dev)
values = [{"time": t, "upper": round(v["upper"], 6), "middle": round(v["middle"], 6), "lower": round(v["lower"], 6)}
for t, v in zip(timestamps, raw) if v is not None]
elif indicator_type == "DonchianChannel":
period = args.period or 20
indicator_params = {"period": period}
raw = calc_donchian(highs, lows, period)
values = [{"time": t, "upper": round(v["upper"], 6), "middle": round(v["middle"], 6), "lower": round(v["lower"], 6)}
for t, v in zip(timestamps, raw) if v is not None]
if args.limit and args.limit > 0:
values = values[-args.limit:]
output = {
"indicator": indicator_type,
"params": indicator_params,
"symbol": args.symbol,
"exchange": args.exchange,
"interval": args.interval,
"values": values,
}
print(json.dumps(output, indent=2))
def cmd_init(args):
p = Path(args.config)
if p.exists():
print(f"Config already exists at {p}", file=sys.stderr)
print("Delete it first if you want to reinitialize.", file=sys.stderr)
sys.exit(1)
user_id = input("Enter your Taco user_id: ").strip()
api_token = input("Enter your Taco api_token: ").strip()
if not all([user_id, api_token]):
print("Error: user_id and api_token are required", file=sys.stderr)
sys.exit(1)
trader_ids = {}
print(f"Configure trader_id for each exchange (leave blank to skip).")
print(f"Supported exchanges: {', '.join(sorted(VALID_EXCHANGES))}")
for exchange in sorted(VALID_EXCHANGES):
tid = input(f" trader_id for {exchange}: ").strip()
if tid:
trader_ids[exchange] = tid
if not trader_ids:
print("Error: at least one exchange trader_id is required", file=sys.stderr)
sys.exit(1)
p.parent.mkdir(parents=True, exist_ok=True)
cfg = {"user_id": user_id, "api_token": api_token, "trader_ids": trader_ids}
with open(p, "w") as f:
json.dump(cfg, f, indent=2)
print(f"Config saved to {p}")
def main():
parser = argparse.ArgumentParser(description="Taco Trading Platform CLI")
parser.add_argument("--config", default=str(CONFIG_PATH), help=f"Path to JSON config file (default: {CONFIG_PATH})")
sub = parser.add_subparsers(dest="command", required=True)
# init
sub.add_parser("init", help="Initialize config file with credentials")
# kline
p_kline = sub.add_parser("kline", help="Get kline/candlestick data")
p_kline.add_argument("--symbol", required=True, help="Trading pair, e.g. BTCUSDT")
p_kline.add_argument("--interval", required=True, help="Kline interval, e.g. 1h, 4h, 1d")
p_kline.add_argument("--exchange", required=True, help="Exchange name, e.g. Binance")
p_kline.add_argument("--start-time", type=int, dest="start_time", help="Start time (Unix ms)")
p_kline.add_argument("--end-time", type=int, dest="end_time", help="End time (Unix ms)")
# account
p_account = sub.add_parser("account", help="Check account positions and balance")
p_account.add_argument("--exchange", required=True, help="Exchange name (uses its bound trader_id)")
# open
p_open = sub.add_parser("open", help="Open a perpetual position")
p_open.add_argument("--exchange", required=True, help="Exchange name")
p_open.add_argument("--symbol", required=True, help="Trading pair")
p_open.add_argument("--notional", required=True, type=float, help="Notional position size")
p_open.add_argument("--long", action="store_true", default=False, help="Open long (default: short)")
p_open.add_argument("--leverage", type=float, default=1.0, help="Leverage (default: 1.0)")
p_open.add_argument("--sl-price", type=float, dest="sl_price", help="Stop-loss price")
p_open.add_argument("--tp-price", type=float, dest="tp_price", help="Take-profit price")
# close
p_close = sub.add_parser("close", help="Close a perpetual position")
p_close.add_argument("--exchange", required=True, help="Exchange name")
p_close.add_argument("--symbol", required=True, help="Trading pair")
p_close.add_argument("--notional", required=True, type=float, help="Notional position size to close")
p_close.add_argument("--long", action="store_true", default=False, help="Close long position (default: short)")
# update-tp-sl
p_tpsl = sub.add_parser("update-tp-sl", help="Update take-profit or stop-loss price for a position")
p_tpsl.add_argument("--exchange", required=True, help="Exchange name")
p_tpsl.add_argument("--symbol", required=True, help="Trading pair")
p_tpsl.add_argument("--price", required=True, type=float, help="New trigger price")
p_tpsl.add_argument("--take-profit", action="store_true", default=False, dest="take_profit",
help="Update take-profit (default: update stop-loss)")
# indicator
p_ind = sub.add_parser("indicator", help="Calculate technical indicators from kline data")
p_ind.add_argument("--exchange", required=True, help="Exchange name")
p_ind.add_argument("--symbol", required=True, help="Trading pair, e.g. BTCUSDT")
p_ind.add_argument("--interval", required=True, help="Kline interval, e.g. 1h, 4h, 1d")
p_ind.add_argument("--type", required=True, help=f"Indicator type: {', '.join(sorted(VALID_INDICATORS))}")
p_ind.add_argument("--period", type=int, default=None, help="Indicator period (default varies by type)")
p_ind.add_argument("--fast", type=int, default=None, help="MACD fast period (default: 12)")
p_ind.add_argument("--slow", type=int, default=None, help="MACD slow period (default: 26)")
p_ind.add_argument("--signal", type=int, default=None, help="MACD signal period (default: 9)")
p_ind.add_argument("--std-dev", type=float, default=None, dest="std_dev", help="BollingerBands std dev multiplier (default: 2.0)")
p_ind.add_argument("--start-time", type=int, dest="start_time", help="Start time (Unix ms)")
p_ind.add_argument("--end-time", type=int, dest="end_time", help="End time (Unix ms)")
p_ind.add_argument("--limit", type=int, default=0, help="Show only the last N values (default: 0 = all)")
args = parser.parse_args()
if args.command == "init":
cmd_init(args)
return
if args.command == "indicator":
cmd_indicator(args)
return
if args.command == "kline":
cmd_kline(args)
return
cfg = load_config(args.config)
if args.command == "account":
cmd_account(args, cfg)
elif args.command == "open":
cmd_open(args, cfg)
elif args.command == "close":
cmd_close(args, cfg)
elif args.command == "update-tp-sl":
cmd_update_tp_sl(args, cfg)
if __name__ == "__main__":
main()
FILE:references/api-references.md
#### get_kline:
- endpoint: /market/klines
- method: GET
- parameters:
- query parameters:
- symbol (required): trading pair symbol, e.g. `BTCUSDT`, `ETHUSDT`
- interval (required): kline interval. Supported values: `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `6h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M`
- exchange (required): exchange name. Supported values: `Binance`, `Hyper`, `Aster`, `Grvt`, `StandX`, `Lighter`
- start_time (optional): start time in Unix milliseconds. If omitted together with end_time, returns the latest 100 klines.
- end_time (optional): end time in Unix milliseconds. If omitted together with start_time, returns the latest 100 klines.
- response (example below in json format):
```json
{
"base_response": {
"status_code": 200,
"status_msg": "success",
"trace_id": "trace tracking id"
},
"klines": [
{
"symbol": "BTCUSDT",
"interval": "1h",
"exchange": "Binance",
"open_time": 1709251200000,
"close_time": 1709254800000,
"open": "62345.50",
"high": "62890.00",
"low": "62100.00",
"close": "62780.30",
"volume": "1234.567",
"quote_volume": "77012345.89",
"trades_count": 45678
}
]
}
```
- notes:
- Maximum 100 klines per response. If the query range contains more, only the latest 100 are returned.
- Price and volume fields are returned as strings to preserve decimal precision.
#### check_account
- endpoint: /auth/autopilot/positions
- method: GET
- parameters:
- query parameters:
- user_id: taco user id
- trader_id: taco ai trader id
- response(example below in json format):
```json
{
"available_balance": 4976.69597582,
"base_response": {
"status_code": 200,
"status_msg": "success",
"trace_id": "trace tracking id"
},
"margin_used": 34.85004,
"margin_used_pct": 0.69538661076795,
"positions": [
{
"entry_price": 87094.9,
"leverage": 5,
"liquidation_price": 2582567.83656375,
"mark_price": 87125.1,
"position_size": 174.2502,
"quantity": 0.002,
"side": "Short",
"symbol": "BTCUSDT",
"unrealized_pnl": -0.0604
}
],
"total_equity": 5011.60641582
}
```
#### open_position
- endpoint: /auth/autopilot/position/open
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body (in json format as below example):
```json
{
"api_token": "please use taco api key same as authentication bearer token in header",
"user_id": "same as in query parameter",
"trader_id": "target taco ai trader id",
"exchange": "Binance",
"symbol": "BTCUSDT",
"notional_position": 123.456,
"long": true,
"leverage": 3.0,
"sl_price": 30000.0,
"tp_price": 90000.0
}
```
#### close_position
- endpoint: /auth/autopilot/position/close
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body (in json format as below example):
```json
{
"api_token": "please use taco api key same as authentication bearer token in header",
"user_id": "same as in query parameter",
"trader_id": "target taco ai trader id",
"exchange": "Binance",
"symbol": "BTCUSDT",
"notional_position": 123.456,
"long": true
}
```
#### update_position_triggering
- endpoint: /auth/autopilot/position/triggering
- method: POST
- parameters:
- query parameters:
- user_id: taco user id
- request body (in json format as below example):
```json
{
"api_token": "please use taco api key same as authentication bearer token in header",
"user_id": "same as in query parameter",
"trader_id": "target taco ai trader id",
"exchange": "Binance",
"symbol": "BTCUSDT",
"price": 123.456,
"take_profit": true
}
```