
Trading Futures
- 89 installs
- 610 repo stars
- Updated June 26, 2026
- alsk1992/cloddsbot
trading-futures is a skill that trades leveraged perpetual futures across Binance, Bybit, MEXC, and Hyperliquid.
About
This skill trades leveraged perpetual futures across four exchanges: Binance, Bybit, MEXC, and Hyperliquid. It opens long and short positions with configurable leverage, sets take-profit and stop-loss, reads market data and funding rates, and tracks trades in a database. A developer uses it to run automated futures trading from the clodds bot.
- Trades perpetual futures on Binance, Bybit, MEXC, and Hyperliquid
- Opens long/short positions with leverage, take-profit, and stop-loss
- Database trade tracking, custom strategies, and A/B testing
Trading Futures by the numbers
- 89 all-time installs (skills.sh)
- Ranked #531 of 1,106 Finance & Trading skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
trading-futures capabilities & compatibility
Requires exchange API keys; DATABASE_URL optional. Trading fees and margin apply.
- Capabilities
- futures trading · position management · take profit stop loss · trade tracking
- Works with
- postgres
- Use cases
- trading
- Runs
- Runs locally
- Pricing
- Bring your own API key
What trading-futures says it does
Trade leveraged perpetual futures across 4 exchanges with database tracking, custom strategies, and A/B testing.
200+ methods across 4 exchanges. This is the complete reference.
npx skills add https://github.com/alsk1992/cloddsbot --skill trading-futuresAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 89 |
|---|---|
| repo stars | ★ 610 |
| Last updated | June 26, 2026 |
| Repository | alsk1992/cloddsbot ↗ |
What it does
Trade leveraged perpetual futures across Binance, Bybit, MEXC, and Hyperliquid.
Who is it for?
Automated leveraged futures trading and position management across major exchanges.
Skip if: Spot DEX swaps or prediction-market trading.
When should I use this skill?
You need to open, manage, or close leveraged perpetual futures positions programmatically.
What you get
Opened, managed, and closed futures positions with leverage, TP/SL, and trade logging.
- Opened/closed futures positions
- Trade statistics and P&L from the database
By the numbers
- 4 exchanges supported
- 200+ methods documented
- Up to 200x leverage on MEXC
Files
Perpetual Futures Trading - Complete API Reference
Trade leveraged perpetual futures across 4 exchanges with database tracking, custom strategies, and A/B testing.
200+ methods across 4 exchanges. This is the complete reference.
Supported Exchanges
| Exchange | Type | Max Leverage | KYC | API Methods |
|---|---|---|---|---|
| Binance Futures | CEX | 125x | Yes | 55+ |
| Bybit | CEX | 100x | Yes | 50+ |
| MEXC | CEX | 200x | No (small) | 35+ |
| Hyperliquid | DEX | 50x | No | 60+ |
Required Environment Variables
# Binance Futures
BINANCE_API_KEY=your_api_key
BINANCE_API_SECRET=your_api_secret
# Bybit
BYBIT_API_KEY=your_api_key
BYBIT_API_SECRET=your_api_secret
# MEXC (No KYC for small amounts)
MEXC_API_KEY=your_api_key
MEXC_API_SECRET=your_api_secret
# Hyperliquid (Fully decentralized, No KYC)
HYPERLIQUID_PRIVATE_KEY=your_private_key
HYPERLIQUID_WALLET_ADDRESS=0x...
# Optional: Database for trade tracking
DATABASE_URL=postgres://user:pass@localhost:5432/clodds---
Chat Commands
Account & Balance
/futures balance [exchange] # Check margin balance (all or specific)
/futures positions # View all open positions
/futures positions <exchange> # View positions on specific exchangeOpening Positions
/futures long <symbol> <size> [leverage]x # Open long position
/futures short <symbol> <size> [leverage]x # Open short position
# Examples:
/futures long BTCUSDT 0.1 10x # Open 0.1 BTC long at 10x
/futures short ETHUSDT 1 20x # Open 1 ETH short at 20x
/futures long BTCUSDT 0.01 # Use default leverageTake-Profit & Stop-Loss
/futures tp <symbol> <price> # Set take-profit
/futures sl <symbol> <price> # Set stop-loss
/futures tpsl <symbol> <tp> <sl> # Set both at once
# Examples:
/futures tp BTCUSDT 105000 # Take profit at $105k
/futures sl BTCUSDT 95000 # Stop loss at $95k
/futures tpsl BTCUSDT 105000 95000 # BothClosing Positions
/futures close <symbol> # Close specific position
/futures close-all # Close ALL positions (all exchanges)
/futures close-all <exchange> # Close all on specific exchangeMarket Data
/futures markets [exchange] # List available markets
/futures price <symbol> # Get current price
/futures funding <symbol> # Check funding rate
/futures orderbook <symbol> # View orderbook depthAccount Info
/futures stats # Trade statistics from database
/futures history [symbol] # Trade history
/futures pnl [period] # P&L summary (day/week/month)Leverage & Margin
/futures leverage <symbol> <value> # Set leverage
/futures margin <symbol> <mode> # Set margin mode (cross/isolated)---
TypeScript API Reference
Quick Setup
import { setupFromEnv } from 'clodds/trading/futures';
// Auto-configure from environment variables
const { clients, database, strategyEngine } = await setupFromEnv();
// Access individual clients
const binance = clients.binance;
const bybit = clients.bybit;
const mexc = clients.mexc;
const hyperliquid = clients.hyperliquid;Manual Client Setup
import {
BinanceFuturesClient,
BybitFuturesClient,
MexcFuturesClient,
HyperliquidClient,
FuturesDatabase,
StrategyEngine,
} from 'clodds/trading/futures';
// Binance
const binance = new BinanceFuturesClient({
apiKey: process.env.BINANCE_API_KEY!,
apiSecret: process.env.BINANCE_API_SECRET!,
testnet: false, // true for testnet
});
// Bybit
const bybit = new BybitFuturesClient({
apiKey: process.env.BYBIT_API_KEY!,
apiSecret: process.env.BYBIT_API_SECRET!,
testnet: false,
});
// MEXC (No KYC)
const mexc = new MexcFuturesClient({
apiKey: process.env.MEXC_API_KEY!,
apiSecret: process.env.MEXC_API_SECRET!,
});
// Hyperliquid (Decentralized, No KYC)
const hyperliquid = new HyperliquidClient({
privateKey: process.env.HYPERLIQUID_PRIVATE_KEY!,
walletAddress: process.env.HYPERLIQUID_WALLET_ADDRESS!,
testnet: false,
});---
Binance Futures API (55+ Methods)
Market Data
// Prices & Tickers
await binance.getMarkPrice('BTCUSDT');
await binance.getTicker24h('BTCUSDT');
await binance.getAllTickers();
await binance.getBookTicker('BTCUSDT');
// Orderbook & Trades
await binance.getOrderBook('BTCUSDT', 100);
await binance.getRecentTrades('BTCUSDT', 500);
await binance.getHistoricalTrades('BTCUSDT', 500);
await binance.getAggTrades('BTCUSDT');
// Klines (Candlesticks)
await binance.getKlines('BTCUSDT', '1h', 100);
await binance.getContinuousKlines('BTCUSDT', '1h', 'PERPETUAL');
await binance.getIndexPriceKlines('BTCUSDT', '1h');
await binance.getMarkPriceKlines('BTCUSDT', '1h');
await binance.getPremiumIndexKlines('BTCUSDT', '1h');
// Funding Rates
await binance.getFundingRate('BTCUSDT');
await binance.getFundingRateHistory('BTCUSDT', 100);
// Market Info
await binance.getExchangeInfo();
await binance.getOpenInterest('BTCUSDT');
await binance.getOpenInterestHistory('BTCUSDT', '1h');Trading
// Place Orders
await binance.placeOrder({
symbol: 'BTCUSDT',
side: 'BUY',
type: 'MARKET',
quantity: 0.01,
});
await binance.placeOrder({
symbol: 'BTCUSDT',
side: 'BUY',
type: 'LIMIT',
quantity: 0.01,
price: 95000,
timeInForce: 'GTC',
});
// With TP/SL
await binance.placeOrder({
symbol: 'BTCUSDT',
side: 'BUY',
type: 'MARKET',
quantity: 0.01,
takeProfit: 105000,
stopLoss: 95000,
});
// Batch Orders
await binance.placeBatchOrders([
{ symbol: 'BTCUSDT', side: 'BUY', type: 'LIMIT', quantity: 0.01, price: 94000 },
{ symbol: 'BTCUSDT', side: 'BUY', type: 'LIMIT', quantity: 0.01, price: 93000 },
]);
// Modify & Cancel
await binance.modifyOrder('BTCUSDT', orderId, { quantity: 0.02 });
await binance.cancelOrder('BTCUSDT', orderId);
await binance.cancelAllOrders('BTCUSDT');
await binance.cancelBatchOrders('BTCUSDT', [orderId1, orderId2]);
// Auto-cancel
await binance.setAutoCancel(60000); // Cancel all after 60s
await binance.cancelAutoCancel();Account & Positions
// Account Info
await binance.getAccountInfo();
await binance.getBalance();
await binance.getPositions();
await binance.getPositionRisk();
// Orders & History
await binance.getOpenOrders();
await binance.getOpenOrders('BTCUSDT');
await binance.getAllOrders('BTCUSDT');
await binance.getOrder('BTCUSDT', orderId);
await binance.getTradeHistory('BTCUSDT');
await binance.getIncomeHistory();
await binance.getIncomeHistory('BTCUSDT', 'REALIZED_PNL');
// Commission
await binance.getCommissionRate('BTCUSDT');Risk Management
// Leverage
await binance.setLeverage('BTCUSDT', 10);
await binance.getLeverageBrackets();
await binance.getLeverageBrackets('BTCUSDT');
// Margin Mode
await binance.setMarginType('BTCUSDT', 'ISOLATED');
await binance.modifyIsolatedMargin('BTCUSDT', 100, 'ADD');
await binance.modifyIsolatedMargin('BTCUSDT', 50, 'REDUCE');
// Position Mode
await binance.getPositionMode();
await binance.setPositionMode(true); // Hedge mode
await binance.setPositionMode(false); // One-way mode
// Multi-Asset Mode
await binance.getMultiAssetMode();
await binance.setMultiAssetMode(true);Analytics
// Market Analytics
await binance.getLongShortRatio('BTCUSDT', '1h');
await binance.getTopTraderLongShortRatio('BTCUSDT', '1h');
await binance.getTopTraderPositions('BTCUSDT', '1h');
await binance.getGlobalLongShortRatio('BTCUSDT', '1h');
await binance.getTakerBuySellVolume('BTCUSDT', '1h');Staking & Earn
// Staking
await binance.getStakingProducts();
await binance.stake('BNB', 10);
await binance.unstake('BNB', 5);
await binance.getStakingHistory();
await binance.getStakingPositions();Convert
// Convert between assets
await binance.getConvertPairs('USDT', 'BTC');
await binance.sendQuote('USDT', 'BTC', 100);
await binance.acceptQuote(quoteId);
await binance.getConvertHistory();Portfolio Margin
await binance.getPortfolioMarginAccount();
await binance.getPortfolioMarginBankruptcyLoan();
await binance.repayPortfolioMarginLoan();---
Bybit API (50+ Methods)
Market Data
await bybit.getTickers('linear');
await bybit.getTickers('linear', 'BTCUSDT');
await bybit.getOrderbook('BTCUSDT', 'linear');
await bybit.getKline('BTCUSDT', '1h', 'linear');
await bybit.getMarkPriceKline('BTCUSDT', '1h', 'linear');
await bybit.getIndexPriceKline('BTCUSDT', '1h', 'linear');
await bybit.getPremiumIndexPriceKline('BTCUSDT', '1h', 'linear');
await bybit.getInstrumentsInfo('linear');
await bybit.getFundingHistory('BTCUSDT', 'linear');
await bybit.getPublicTradingHistory('BTCUSDT', 'linear');
await bybit.getOpenInterest('BTCUSDT', 'linear', '1h');
await bybit.getHistoricalVolatility();
await bybit.getInsurance();
await bybit.getRiskLimit('linear');Trading
// Place Order
await bybit.placeOrder({
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Market',
qty: '0.01',
});
await bybit.placeOrder({
category: 'linear',
symbol: 'BTCUSDT',
side: 'Buy',
orderType: 'Limit',
qty: '0.01',
price: '95000',
timeInForce: 'GTC',
});
// Batch Orders
await bybit.placeBatchOrders('linear', [
{ symbol: 'BTCUSDT', side: 'Buy', orderType: 'Limit', qty: '0.01', price: '94000' },
{ symbol: 'BTCUSDT', side: 'Buy', orderType: 'Limit', qty: '0.01', price: '93000' },
]);
// Modify & Cancel
await bybit.amendOrder({ category: 'linear', symbol: 'BTCUSDT', orderId, qty: '0.02' });
await bybit.cancelOrder({ category: 'linear', symbol: 'BTCUSDT', orderId });
await bybit.cancelAllOrders({ category: 'linear', symbol: 'BTCUSDT' });
await bybit.cancelBatchOrders('linear', [{ symbol: 'BTCUSDT', orderId }]);Account & Positions
await bybit.getWalletBalance('UNIFIED');
await bybit.getPositionInfo('linear');
await bybit.getPositionInfo('linear', 'BTCUSDT');
await bybit.getOpenOrders('linear');
await bybit.getOrderHistory('linear');
await bybit.getExecutionList('linear');
await bybit.getClosedPnl('linear');
await bybit.getBorrowHistory();
await bybit.getCollateralInfo();
await bybit.getCoinGreeks();
await bybit.getFeeRate('linear', 'BTCUSDT');
await bybit.getAccountInfo();
await bybit.getTransactionLog();
await bybit.getMMPState('linear');
await bybit.setMMP({ baseCoin: 'BTC', window: '5000', frozenPeriod: '100', qtyLimit: '10', deltaLimit: '100' });
await bybit.resetMMP('BTC');Risk Management
await bybit.setLeverage({ category: 'linear', symbol: 'BTCUSDT', buyLeverage: '10', sellLeverage: '10' });
await bybit.setMarginMode('ISOLATED_MARGIN');
await bybit.setPositionMode({ category: 'linear', mode: 0 }); // 0=one-way, 3=hedge
await bybit.setRiskLimit({ category: 'linear', symbol: 'BTCUSDT', riskId: 1 });
await bybit.setTradingStop({ category: 'linear', symbol: 'BTCUSDT', takeProfit: '105000', stopLoss: '95000' });
await bybit.setTpSlMode({ category: 'linear', symbol: 'BTCUSDT', tpSlMode: 'Full' });
await bybit.addOrReduceMargin({ category: 'linear', symbol: 'BTCUSDT', margin: '100' });
await bybit.switchCrossIsolatedMargin({ category: 'linear', symbol: 'BTCUSDT', tradeMode: 1, buyLeverage: '10', sellLeverage: '10' });Copy Trading
await bybit.getCopyTradingLeaders();
await bybit.followLeader(leaderId);
await bybit.unfollowLeader(leaderId);
await bybit.getCopyPositions();
await bybit.closeCopyPosition(symbol);Lending & Earn
await bybit.getLendingProducts();
await bybit.depositToLending(productId, amount);
await bybit.redeemFromLending(productId, amount);
await bybit.getLendingOrders();
await bybit.getEarnProducts();
await bybit.getEarnOrders();---
Hyperliquid API (60+ Methods)
Market Data
await hyperliquid.getMeta();
await hyperliquid.getMetaAndAssetCtxs();
await hyperliquid.getAssetCtxs();
await hyperliquid.getAllMids();
await hyperliquid.getCandleSnapshot('BTC', '1h', startTime, endTime);
await hyperliquid.getL2Snapshot('BTC');
await hyperliquid.getFundingHistory('BTC', startTime, endTime);
await hyperliquid.getRecentTrades('BTC');
await hyperliquid.getPredictedFunding();Trading
// Place Order
await hyperliquid.placeOrder({
asset: 'BTC',
isBuy: true,
sz: 0.01,
limitPx: 95000,
orderType: { limit: { tif: 'Gtc' } },
reduceOnly: false,
});
// Market Order
await hyperliquid.placeOrder({
asset: 'BTC',
isBuy: true,
sz: 0.01,
limitPx: null,
orderType: { market: {} },
});
// TWAP Order
await hyperliquid.placeTwapOrder({
asset: 'BTC',
isBuy: true,
sz: 1.0,
duration: 3600, // 1 hour
randomize: true,
});
// Modify & Cancel
await hyperliquid.modifyOrder(orderId, { sz: 0.02 });
await hyperliquid.cancelOrder('BTC', orderId);
await hyperliquid.cancelAllOrders();
await hyperliquid.cancelOrdersByCloid(['cloid1', 'cloid2']);
// Batch Operations
await hyperliquid.batchModifyOrders([{ oid: orderId1, sz: 0.02 }, { oid: orderId2, sz: 0.03 }]);Account & Positions
await hyperliquid.getUserState(walletAddress);
await hyperliquid.getClearinghouseState(walletAddress);
await hyperliquid.getOpenOrders(walletAddress);
await hyperliquid.getFrontendOpenOrders(walletAddress);
await hyperliquid.getUserFills(walletAddress);
await hyperliquid.getUserFillsByTime(walletAddress, startTime, endTime);
await hyperliquid.getUserFunding(walletAddress);
await hyperliquid.getUserFundingHistory(walletAddress, startTime, endTime);
await hyperliquid.getHistoricalOrders(walletAddress);
await hyperliquid.getOrderStatus(walletAddress, orderId);
await hyperliquid.getTwapHistory(walletAddress);
await hyperliquid.getSubaccounts(walletAddress);Leverage & Margin
await hyperliquid.updateLeverage('BTC', 10, false); // false = cross
await hyperliquid.updateLeverage('BTC', 10, true); // true = isolated
await hyperliquid.updateIsolatedMargin('BTC', 100);Transfers
await hyperliquid.usdTransfer(toAddress, amount);
await hyperliquid.spotTransfer(toAddress, token, amount);
await hyperliquid.withdraw(amount);
await hyperliquid.classTransfer(amount, toPerp);Spot Trading
await hyperliquid.getSpotMeta();
await hyperliquid.getSpotMetaAndAssetCtxs();
await hyperliquid.getSpotClearinghouseState(walletAddress);
await hyperliquid.placeSpotOrder({
asset: 'HYPE',
isBuy: true,
sz: 10,
limitPx: 25,
});Vaults
await hyperliquid.getVaultDetails(vaultAddress);
await hyperliquid.getUserVaultEquities(walletAddress);
await hyperliquid.depositToVault(vaultAddress, amount);
await hyperliquid.withdrawFromVault(vaultAddress, amount);
await hyperliquid.getAllVaults();Staking
await hyperliquid.getValidatorSummaries();
await hyperliquid.getUserStakingSummary(walletAddress);
await hyperliquid.stakeHype(amount, validatorAddress);
await hyperliquid.unstakeHype(amount, validatorAddress);
await hyperliquid.claimStakingRewards();Delegations
await hyperliquid.getDelegatorSummary(walletAddress);
await hyperliquid.getDelegatorHistory(walletAddress);
await hyperliquid.delegate(amount, agentAddress);
await hyperliquid.undelegate(amount, agentAddress);Referrals & Analytics
await hyperliquid.getReferralState(walletAddress);
await hyperliquid.createReferralCode(code);
await hyperliquid.getReferredUsers(walletAddress);
await hyperliquid.getUserAnalytics(walletAddress);
await hyperliquid.getLeaderboard();
await hyperliquid.getMaxBuilderFee();---
MEXC API (35+ Methods)
Market Data
await mexc.getContractDetail('BTC_USDT');
await mexc.getAllContractDetails();
await mexc.getOrderbook('BTC_USDT');
await mexc.getKlines('BTC_USDT', '1h');
await mexc.getTicker('BTC_USDT');
await mexc.getAllTickers();
await mexc.getFundingRate('BTC_USDT');
await mexc.getFundingRateHistory('BTC_USDT');
await mexc.getOpenInterest('BTC_USDT');
await mexc.getRecentTrades('BTC_USDT');
await mexc.getIndexPrice('BTC_USDT');
await mexc.getFairPrice('BTC_USDT');Trading
// Place Order
await mexc.placeOrder({
symbol: 'BTC_USDT',
side: 1, // 1=Open Long, 2=Close Short, 3=Open Short, 4=Close Long
type: 5, // 1=Limit, 2=Post Only, 3=IOC, 4=FOK, 5=Market
vol: 1, // Contracts
leverage: 10,
});
// With TP/SL
await mexc.placeOrder({
symbol: 'BTC_USDT',
side: 1,
type: 5,
vol: 1,
leverage: 10,
takeProfit: 105000,
stopLoss: 95000,
});
// Batch Orders
await mexc.placeBatchOrders([
{ symbol: 'BTC_USDT', side: 1, type: 1, vol: 1, price: 94000, leverage: 10 },
{ symbol: 'BTC_USDT', side: 1, type: 1, vol: 1, price: 93000, leverage: 10 },
]);
// Trigger Order
await mexc.placeTriggerOrder({
symbol: 'BTC_USDT',
side: 1,
type: 1,
vol: 1,
triggerPrice: 96000,
triggerType: 1, // 1=Last Price, 2=Fair Price, 3=Index Price
executionPrice: 96100,
leverage: 10,
});
// Cancel
await mexc.cancelOrder('BTC_USDT', orderId);
await mexc.cancelAllOrders('BTC_USDT');
await mexc.cancelBatchOrders([orderId1, orderId2]);Account & Positions
await mexc.getAccountInfo();
await mexc.getPositions();
await mexc.getPositions('BTC_USDT');
await mexc.getOpenOrders();
await mexc.getOpenOrders('BTC_USDT');
await mexc.getOrderHistory('BTC_USDT');
await mexc.getTradeHistory('BTC_USDT');
await mexc.getTriggerOrders();
await mexc.getStopOrders();
await mexc.getRiskLimit('BTC_USDT');
await mexc.getAssets();
await mexc.getAssetRecords();Risk Management
await mexc.setLeverage('BTC_USDT', 10);
await mexc.changeMarginMode('BTC_USDT', 1); // 1=Isolated, 2=Cross
await mexc.changePositionMode(1); // 1=Hedge, 2=One-way
await mexc.autoAddMargin('BTC_USDT', true);---
Database Tracking
Initialize Database
import { FuturesDatabase } from 'clodds/trading/futures';
const db = new FuturesDatabase(process.env.DATABASE_URL!);
await db.initialize(); // Creates tables if not existLog Trades
await db.logTrade({
exchange: 'binance',
symbol: 'BTCUSDT',
orderId: '12345',
side: 'BUY',
price: 95000,
quantity: 0.01,
realizedPnl: 50.25,
commission: 0.95,
commissionAsset: 'USDT',
timestamp: Date.now(),
isMaker: false,
strategy: 'momentum',
variant: 'aggressive',
});Query Trades
// Get trades
const trades = await db.getTrades({ exchange: 'binance' });
const btcTrades = await db.getTrades({ exchange: 'binance', symbol: 'BTCUSDT' });
const recentTrades = await db.getTrades({ limit: 100 });
// Get statistics
const stats = await db.getTradeStats('binance');
// { totalTrades, winRate, totalPnl, avgPnl, bestTrade, worstTrade }
// Get variant performance
const results = await db.getVariantPerformance('momentum');
// { aggressive: { trades, pnl, winRate }, conservative: { ... } }---
Custom Strategies
Strategy Interface
import { FuturesStrategy, StrategySignal } from 'clodds/trading/futures';
interface FuturesStrategy {
name: string;
analyze(data: MarketData): Promise<StrategySignal | null>;
}
interface StrategySignal {
action: 'BUY' | 'SELL' | 'CLOSE';
symbol: string;
confidence: number; // 0-1
reason: string;
metadata?: Record<string, unknown>;
}Example Strategy
class RSIStrategy implements FuturesStrategy {
name = 'rsi-strategy';
constructor(private config: { period: number; oversold: number; overbought: number }) {}
async analyze(data: MarketData): Promise<StrategySignal | null> {
const rsi = calculateRSI(data.closes, this.config.period);
if (rsi < this.config.oversold) {
return {
action: 'BUY',
symbol: data.symbol,
confidence: (this.config.oversold - rsi) / this.config.oversold,
reason: `RSI oversold at ${rsi.toFixed(1)}`,
metadata: { rsi },
};
}
if (rsi > this.config.overbought) {
return {
action: 'SELL',
symbol: data.symbol,
confidence: (rsi - this.config.overbought) / (100 - this.config.overbought),
reason: `RSI overbought at ${rsi.toFixed(1)}`,
metadata: { rsi },
};
}
return null;
}
}Register & Run
const engine = new StrategyEngine(db);
engine.registerStrategy(new RSIStrategy({ period: 14, oversold: 30, overbought: 70 }));
// A/B Test Variants
engine.registerVariant('rsi-strategy', 'aggressive', { oversold: 25, overbought: 75 });
engine.registerVariant('rsi-strategy', 'conservative', { oversold: 35, overbought: 65 });---
Built-in Strategies
| Strategy | Logic | Config |
|---|---|---|
| MomentumStrategy | Follow price trends | lookbackPeriod, threshold |
| MeanReversionStrategy | Buy dips, sell rallies | maPeriod, deviationMultiplier |
| GridStrategy | Place orders at intervals | gridSize, levels, spacing |
---
Error Handling
All clients throw typed errors:
import { FuturesError, InsufficientBalanceError, InvalidOrderError } from 'clodds/trading/futures';
try {
await binance.placeOrder({ ... });
} catch (error) {
if (error instanceof InsufficientBalanceError) {
console.log('Not enough margin');
} else if (error instanceof InvalidOrderError) {
console.log('Invalid order params:', error.message);
} else if (error instanceof FuturesError) {
console.log('Exchange error:', error.code, error.message);
}
}---
Rate Limits
| Exchange | Limit | Notes |
|---|---|---|
| Binance | 2400/min | Per IP, weight-based |
| Bybit | 120/min | Per endpoint |
| Hyperliquid | 1200/min | Per wallet |
| MEXC | 20/sec | Per IP |
All clients automatically handle rate limiting with exponential backoff.
/**
* Trading Futures CLI Skill
*
* Full perpetual futures trading on Binance, Bybit, Hyperliquid, MEXC.
* Supports market/limit/stop orders, position management, and account info.
*/
const SUPPORTED_EXCHANGES = ['binance', 'bybit', 'hyperliquid', 'mexc'] as const;
type Exchange = typeof SUPPORTED_EXCHANGES[number];
type Side = 'BUY' | 'SELL';
type Margin = 'ISOLATED' | 'CROSS';
function helpText(): string {
return `**Futures Trading Commands**
**Orders:**
/futures open <symbol> <side> <size> [--leverage N] [--exchange X] - Open position
/futures long <symbol> <size> [--leverage N] [--exchange X] - Open long
/futures short <symbol> <size> [--leverage N] [--exchange X] - Open short
/futures close <symbol> [--exchange X] - Close position
/futures closeall [--exchange X] - Close ALL positions
/futures limit <symbol> <long|short> <size> <price> [--leverage N] - Limit order
/futures stop <symbol> <price> [--size N] [--side sell] [--exchange X] - Stop loss/entry
/futures tp <symbol> <price> [--size N] [--exchange X] - Take profit
/futures cancel <symbol> <orderId> [--exchange X] - Cancel order
/futures cancelall <symbol> [--exchange X] - Cancel all orders
**Info:**
/futures positions [--exchange X] - View positions
/futures orders [--symbol SYM] [--exchange X] - Open/pending orders
/futures balance [--exchange X] - Account balances
/futures account [--exchange X] - Detailed account info
/futures price <symbol> [--exchange X] - Current price
/futures book <symbol> [--exchange X] - Order book (top 5)
/futures markets [--exchange X] [--search BTC] - Available markets
/futures funding <symbol> [--exchange X] - Funding rates
/futures pnl [--exchange X] - P&L summary
/futures history [--symbol SYM] [--exchange X] [--limit 20] - Income history
/futures trades [symbol] [--exchange X] [--limit 20] - Trade history
/futures orderhistory [symbol] [--exchange X] [--limit 20] - Order history
**Config:**
/futures leverage <symbol> <multiplier> [--exchange X] - Set leverage
/futures margin <isolated|cross> --symbol <SYM> [--exchange X] - Set margin type
/futures exchanges - Configured exchanges
Exchanges: binance, bybit, hyperliquid, mexc`;
}
function parseFlag(parts: string[], flag: string, defaultVal: string): string {
const idx = parts.indexOf(flag);
if (idx === -1 || !parts[idx + 1]) return defaultVal;
// Don't treat another flag as a value
const val = parts[idx + 1];
if (val.startsWith('--')) return defaultVal;
return val;
}
function validateExchange(exchange: string, configured: string[]): string | null {
if (exchange === 'all') return null;
if (!(SUPPORTED_EXCHANGES as readonly string[]).includes(exchange)) {
return `Unknown exchange '${exchange}'. Supported: ${SUPPORTED_EXCHANGES.join(', ')}`;
}
if (!configured.includes(exchange)) {
return `Exchange '${exchange}' not configured. Configured: ${configured.join(', ')}`;
}
return null;
}
function validateLeverage(leverageStr: string): number | null {
const lev = parseInt(leverageStr, 10);
if (isNaN(lev) || lev < 1 || lev > 200) return null;
return lev;
}
/**
* Normalize symbol for exchange compatibility.
* Hyperliquid uses bare coin names (BTC, ETH, SOL) while others use BTCUSDT etc.
* This strips common quote suffixes when targeting Hyperliquid.
*/
function normalizeSymbol(symbol: string, exchange: string): string {
if (exchange === 'hyperliquid') {
// Strip common quote suffixes for Hyperliquid
return symbol
.replace(/USDT$/, '')
.replace(/USDC$/, '')
.replace(/USD$/, '')
.replace(/PERP$/, '')
.replace(/-PERP$/, '');
}
return symbol;
}
async function execute(args: string): Promise<string> {
const parts = args.trim().split(/\s+/);
const cmd = parts[0]?.toLowerCase() || 'help';
try {
const futuresMod = await import('../../../trading/futures/index');
// Try to set up from env vars
const { service } = await futuresMod.setupFromEnv();
const configuredExchanges = service.getExchanges();
if (configuredExchanges.length === 0 && cmd !== 'help' && cmd !== 'exchanges') {
return 'No exchanges configured. Set API keys in env vars:\n BINANCE_API_KEY + BINANCE_API_SECRET\n BYBIT_API_KEY + BYBIT_API_SECRET\n HYPERLIQUID_WALLET + HYPERLIQUID_PRIVATE_KEY\n MEXC_API_KEY + MEXC_API_SECRET';
}
const defaultExchange = configuredExchanges[0] || 'binance';
switch (cmd) {
case 'open': {
const symbol = parts[1]?.toUpperCase();
const side = parts[2]?.toUpperCase() as 'LONG' | 'SHORT';
const size = parseFloat(parts[3]);
if (!symbol || !side || isNaN(size)) return 'Usage: /futures open <symbol> <long|short> <size> [--leverage N] [--exchange X]';
if (side !== 'LONG' && side !== 'SHORT') return 'Side must be LONG or SHORT';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const leverage = validateLeverage(parseFlag(parts, '--leverage', '10'));
if (!leverage) return 'Leverage must be a number between 1 and 200.';
const sym = normalizeSymbol(symbol, exchange);
const order = side === 'LONG'
? await service.openLong(exchange, sym, size, leverage)
: await service.openShort(exchange, sym, size, leverage);
return `**Position Opened**
Exchange: ${exchange}
Symbol: ${order.symbol}
Side: ${side}
Size: ${order.size}
Leverage: ${order.leverage}x
Type: ${order.type}
Status: ${order.status}
Fill Price: ${order.avgFillPrice > 0 ? order.avgFillPrice : 'pending'}
Order ID: ${order.id}`;
}
case 'long': {
const symbol = parts[1]?.toUpperCase();
const size = parseFloat(parts[2]);
if (!symbol || isNaN(size)) return 'Usage: /futures long <symbol> <size> [--leverage N] [--exchange X]';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const leverage = validateLeverage(parseFlag(parts, '--leverage', '10'));
if (!leverage) return 'Leverage must be a number between 1 and 200.';
const sym = normalizeSymbol(symbol, exchange);
const order = await service.openLong(exchange, sym, size, leverage);
return `**Long Position Opened**
Exchange: ${exchange}
Symbol: ${order.symbol}
Size: ${order.size}
Leverage: ${order.leverage}x
Status: ${order.status}
Fill Price: ${order.avgFillPrice > 0 ? order.avgFillPrice : 'pending'}
Order ID: ${order.id}`;
}
case 'short': {
const symbol = parts[1]?.toUpperCase();
const size = parseFloat(parts[2]);
if (!symbol || isNaN(size)) return 'Usage: /futures short <symbol> <size> [--leverage N] [--exchange X]';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const leverage = validateLeverage(parseFlag(parts, '--leverage', '10'));
if (!leverage) return 'Leverage must be a number between 1 and 200.';
const sym = normalizeSymbol(symbol, exchange);
const order = await service.openShort(exchange, sym, size, leverage);
return `**Short Position Opened**
Exchange: ${exchange}
Symbol: ${order.symbol}
Size: ${order.size}
Leverage: ${order.leverage}x
Status: ${order.status}
Fill Price: ${order.avgFillPrice > 0 ? order.avgFillPrice : 'pending'}
Order ID: ${order.id}`;
}
case 'close': {
const symbol = parts[1]?.toUpperCase();
if (!symbol) return 'Usage: /futures close <symbol> [--exchange X]';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const sym = normalizeSymbol(symbol, exchange);
const result = await service.closePosition(exchange, sym);
if (!result) return `No open position found for ${symbol} on ${exchange}.`;
return `**Position Closed**
Exchange: ${exchange}
Symbol: ${result.symbol}
Size: ${result.size}
Fill Price: ${result.avgFillPrice > 0 ? result.avgFillPrice : 'N/A'}
Status: ${result.status}
Order ID: ${result.id}`;
}
case 'closeall':
case 'close-all': {
const exchangeInput = parseFlag(parts, '--exchange', 'all');
const exErr = validateExchange(exchangeInput, configuredExchanges);
if (exErr) return exErr;
let results: Array<{ symbol: string; exchange?: string }> = [];
if (exchangeInput === 'all') {
for (const ex of configuredExchanges) {
try {
const closed = await service.closeAllPositions(ex);
results.push(...closed.map(r => ({ symbol: r.symbol, exchange: ex })));
} catch {
results.push({ symbol: `[${ex}: error]` });
}
}
} else {
const closed = await service.closeAllPositions(exchangeInput as Exchange);
results = closed.map(r => ({ symbol: r.symbol, exchange: exchangeInput }));
}
if (results.length === 0) return 'No open positions to close.';
const lines = ['**Closed All Positions**', ''];
for (const r of results) {
lines.push(` ${r.symbol} (${r.exchange})`);
}
return lines.join('\n');
}
case 'positions':
case 'pos': {
const exchangeInput = parseFlag(parts, '--exchange', 'all');
const exErr = validateExchange(exchangeInput, configuredExchanges);
if (exErr) return exErr;
let positions: Awaited<ReturnType<typeof service.getPositions>> = [];
const errors: string[] = [];
if (exchangeInput === 'all') {
for (const ex of configuredExchanges) {
try {
const p = await service.getPositions(ex);
positions.push(...p);
} catch {
errors.push(ex);
}
}
} else {
positions = await service.getPositions(exchangeInput as Exchange);
}
if (positions.length === 0) {
return `No open positions${exchangeInput !== 'all' ? ` on ${exchangeInput}` : ''}.`;
}
const lines = ['**Open Futures Positions**', ''];
let totalPnl = 0;
for (const pos of positions) {
const pnlSign = pos.unrealizedPnl >= 0 ? '+' : '';
totalPnl += pos.unrealizedPnl;
lines.push(`**${pos.symbol}** (${pos.exchange})`);
lines.push(` Side: ${pos.side} | Size: ${pos.size} | Leverage: ${pos.leverage}x`);
lines.push(` Entry: ${pos.entryPrice} | Mark: ${pos.markPrice} | Liq: ${pos.liquidationPrice}`);
lines.push(` PnL: ${pnlSign}$${pos.unrealizedPnl.toFixed(2)} (${pnlSign}${pos.unrealizedPnlPct.toFixed(2)}%)`);
lines.push('');
}
const totalSign = totalPnl >= 0 ? '+' : '';
lines.push(`**Total Unrealized PnL: ${totalSign}$${totalPnl.toFixed(2)}**`);
if (errors.length > 0) {
lines.push('', `_Failed to fetch: ${errors.join(', ')}_`);
}
return lines.join('\n');
}
case 'funding': {
const symbol = parts[1]?.toUpperCase();
if (!symbol) return 'Usage: /futures funding <symbol> [--exchange binance]';
const exchangeInput = parseFlag(parts, '--exchange', 'all');
const exErr = validateExchange(exchangeInput, configuredExchanges);
if (exErr) return exErr;
if (exchangeInput === 'all') {
const lines = [`**Funding Rates for ${symbol}**`, ''];
for (const ex of configuredExchanges) {
try {
const funding = await service.getFundingRate(ex, normalizeSymbol(symbol, ex));
const ratePct = (funding.rate * 100).toFixed(4);
const nextTime = new Date(funding.nextFundingTime).toLocaleTimeString();
lines.push(` ${ex}: ${ratePct}% (next: ${nextTime})`);
} catch {
lines.push(` ${ex}: N/A`);
}
}
return lines.join('\n');
}
const funding = await service.getFundingRate(exchangeInput as Exchange, normalizeSymbol(symbol, exchangeInput));
const ratePct = (funding.rate * 100).toFixed(4);
const nextTime = new Date(funding.nextFundingTime).toLocaleTimeString();
return `**Funding Rate: ${symbol} (${exchangeInput})**
Rate: ${ratePct}%
Next Funding: ${nextTime}
Annualized: ${(funding.rate * 100 * 3 * 365).toFixed(2)}%`;
}
case 'leverage': {
const symbol = parts[1]?.toUpperCase();
const leverage = validateLeverage(parts[2] || '');
if (!symbol || !leverage) return 'Usage: /futures leverage <symbol> <multiplier> [--exchange X]\nLeverage must be between 1 and 200.';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const sym = normalizeSymbol(symbol, exchange);
await service.setLeverage(exchange, sym, leverage);
return `Leverage set to ${leverage}x for ${sym} on ${exchange}.`;
}
case 'pnl': {
const exchangeInput = parseFlag(parts, '--exchange', 'all');
const exErr = validateExchange(exchangeInput, configuredExchanges);
if (exErr) return exErr;
const balances: Awaited<ReturnType<typeof service.getBalance>>[] = [];
const errors: string[] = [];
if (exchangeInput === 'all') {
for (const ex of configuredExchanges) {
try {
balances.push(await service.getBalance(ex));
} catch {
errors.push(ex);
}
}
} else {
balances.push(await service.getBalance(exchangeInput as Exchange));
}
const lines = ['**Futures P&L Summary**', ''];
let totalBalance = 0;
let totalUnrealized = 0;
for (const bal of balances) {
totalBalance += bal.total;
totalUnrealized += bal.unrealizedPnl;
const pnlSign = bal.unrealizedPnl >= 0 ? '+' : '';
lines.push(`**${bal.exchange}** (${bal.asset})`);
lines.push(` Balance: $${bal.total.toFixed(2)} (available: $${bal.available.toFixed(2)})`);
lines.push(` Unrealized PnL: ${pnlSign}$${bal.unrealizedPnl.toFixed(2)}`);
lines.push(` Margin Balance: $${bal.marginBalance.toFixed(2)}`);
lines.push('');
}
const totalSign = totalUnrealized >= 0 ? '+' : '';
lines.push(`**Total Balance: $${totalBalance.toFixed(2)}**`);
lines.push(`**Total Unrealized: ${totalSign}$${totalUnrealized.toFixed(2)}**`);
if (errors.length > 0) {
lines.push('', `_Failed to fetch: ${errors.join(', ')}_`);
}
return lines.join('\n');
}
case 'exchanges': {
if (configuredExchanges.length === 0) {
return '**No exchanges configured.**\n\nSet API keys:\n BINANCE_API_KEY + BINANCE_API_SECRET\n BYBIT_API_KEY + BYBIT_API_SECRET\n HYPERLIQUID_WALLET + HYPERLIQUID_PRIVATE_KEY\n MEXC_API_KEY + MEXC_API_SECRET';
}
const lines = ['**Configured Exchanges**', ''];
for (const ex of configuredExchanges) {
lines.push(` - ${ex}`);
}
lines.push('', 'Supported: binance, bybit, hyperliquid, mexc');
return lines.join('\n');
}
case 'margin': {
const marginMode = parts[1]?.toUpperCase();
if (!marginMode || (marginMode !== 'ISOLATED' && marginMode !== 'CROSS')) {
return 'Usage: /futures margin <isolated|cross> --symbol <BTCUSDT> [--exchange X]';
}
const symbol = parseFlag(parts, '--symbol', '').toUpperCase();
if (!symbol) return 'Symbol required. Usage: /futures margin <isolated|cross> --symbol <BTCUSDT>';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
try {
const sym = normalizeSymbol(symbol, exchange);
await service.setMarginType(exchange, sym, marginMode as Margin);
return `Margin mode set to **${marginMode}** for ${sym} on ${exchange}.`;
} catch (err: unknown) {
return `Failed to set margin mode on ${exchange}: ${(err as Error)?.message || 'unknown error'}`;
}
}
case 'limit': {
const symbol = parts[1]?.toUpperCase();
const side = parts[2]?.toUpperCase() as 'LONG' | 'SHORT';
const size = parseFloat(parts[3]);
const price = parseFloat(parts[4]);
if (!symbol || !side || isNaN(size) || isNaN(price)) {
return 'Usage: /futures limit <symbol> <long|short> <size> <price> [--leverage N] [--exchange X]';
}
if (side !== 'LONG' && side !== 'SHORT') return 'Side must be LONG or SHORT';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const leverage = validateLeverage(parseFlag(parts, '--leverage', '10'));
if (!leverage) return 'Leverage must be a number between 1 and 200.';
const sym = normalizeSymbol(symbol, exchange);
const order = await service.placeOrder(exchange, {
symbol: sym,
side: side === 'LONG' ? 'BUY' : 'SELL',
type: 'LIMIT',
size,
price,
leverage,
});
return `**Limit Order Placed**
Exchange: ${exchange}
Symbol: ${order.symbol}
Side: ${side}
Size: ${order.size}
Price: ${price}
Leverage: ${order.leverage}x
Status: ${order.status}
Order ID: ${order.id}`;
}
case 'stop':
case 'sl': {
const symbol = parts[1]?.toUpperCase();
const triggerPrice = parseFloat(parts[2]);
if (!symbol || isNaN(triggerPrice)) {
return 'Usage: /futures stop <symbol> <trigger_price> [--size N] [--side sell|buy] [--exchange X]';
}
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const sizeStr = parseFlag(parts, '--size', '');
const sideOverride = parseFlag(parts, '--side', '').toUpperCase();
if (sizeStr && isNaN(parseFloat(sizeStr))) {
return 'Invalid --size value. Must be a number.';
}
// Get current position to determine side and size
const sym = normalizeSymbol(symbol, exchange);
const positions = await service.getPositions(exchange);
const position = positions.find(p => p.symbol === sym);
if (!position && !sizeStr) {
return `No open position for ${sym} on ${exchange}. Specify --size and --side to place stop without position.`;
}
const stopSize = sizeStr ? parseFloat(sizeStr) : (position?.size ?? 0);
let stopSide: Side;
if (sideOverride === 'BUY' || sideOverride === 'SELL') {
stopSide = sideOverride;
} else if (position) {
stopSide = position.side === 'LONG' ? 'SELL' : 'BUY';
} else {
return 'No position found. Specify --side buy or --side sell.';
}
const order = await service.placeOrder(exchange, {
symbol: sym,
side: stopSide,
type: 'STOP_MARKET',
size: stopSize,
stopPrice: triggerPrice,
reduceOnly: !sideOverride, // Only reduce-only if auto-detected from position
});
return `**Stop Order Placed**
Exchange: ${exchange}
Symbol: ${order.symbol}
Trigger: ${triggerPrice}
Size: ${stopSize}
Side: ${stopSide}
Status: ${order.status}
Order ID: ${order.id}`;
}
case 'history': {
const exchangeInput = parseFlag(parts, '--exchange', 'all');
const exErr = validateExchange(exchangeInput, configuredExchanges);
if (exErr) return exErr;
const symbol = parseFlag(parts, '--symbol', '').toUpperCase() || undefined;
const limitStr = parseFlag(parts, '--limit', '20');
const parsed = parseInt(limitStr, 10);
const limit = isNaN(parsed) ? 20 : parsed;
const lines: string[] = [];
let totalAmount = 0;
const exchanges = exchangeInput === 'all' ? configuredExchanges : [exchangeInput];
for (const ex of exchanges) {
try {
const sym = symbol ? normalizeSymbol(symbol, ex) : undefined;
const records = await service.getIncomeHistory(ex as Exchange, { symbol: sym, limit });
if (records.length === 0) continue;
lines.push(`**${ex}**`);
for (const rec of records) {
totalAmount += rec.income;
const sign = rec.income >= 0 ? '+' : '';
const time = new Date(rec.timestamp).toLocaleDateString();
lines.push(` ${time} | ${rec.symbol} | ${rec.incomeType} | ${sign}$${rec.income.toFixed(4)} ${rec.asset}`);
}
lines.push('');
} catch {
lines.push(`**${ex}**: _failed to fetch_`, '');
}
}
if (lines.length === 0) {
return `No income history found${symbol ? ` for ${symbol}` : ''}.`;
}
const totalSign = totalAmount >= 0 ? '+' : '';
lines.unshift(`**Income History**${symbol ? ` (${symbol})` : ''}`, '');
lines.push(`**Total: ${totalSign}$${totalAmount.toFixed(4)}**`);
return lines.join('\n');
}
case 'cancel': {
const symbol = parts[1]?.toUpperCase();
const orderId = parts[2];
if (!symbol || !orderId) return 'Usage: /futures cancel <symbol> <orderId> [--exchange X]';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const sym = normalizeSymbol(symbol, exchange);
await service.cancelOrder(exchange, sym, orderId);
return `Order ${orderId} canceled for ${sym} on ${exchange}.`;
}
case 'cancelall': {
const symbol = parts[1]?.toUpperCase();
if (!symbol) return 'Usage: /futures cancelall <symbol> [--exchange X]';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
// Cancel all open orders for the symbol
const sym = normalizeSymbol(symbol, exchange);
const openOrders = await service.getOpenOrders(exchange, sym);
if (openOrders.length === 0) return `No open orders for ${sym} on ${exchange}.`;
let canceled = 0;
for (const order of openOrders) {
try {
await service.cancelOrder(exchange, sym, order.id);
canceled++;
} catch { /* continue */ }
}
return `Canceled ${canceled}/${openOrders.length} orders for ${sym} on ${exchange}.`;
}
case 'orders': {
const exchangeInput = parseFlag(parts, '--exchange', 'all');
const exErr = validateExchange(exchangeInput, configuredExchanges);
if (exErr) return exErr;
const symbol = parseFlag(parts, '--symbol', '').toUpperCase() || undefined;
const allOrders: Awaited<ReturnType<typeof service.getOpenOrders>> = [];
const errors: string[] = [];
const exchanges = exchangeInput === 'all' ? configuredExchanges : [exchangeInput];
for (const ex of exchanges) {
try {
const sym = symbol ? normalizeSymbol(symbol, ex) : undefined;
const orders = await service.getOpenOrders(ex as Exchange, sym);
allOrders.push(...orders);
} catch {
errors.push(ex);
}
}
if (allOrders.length === 0) {
const msg = `No open orders${symbol ? ` for ${symbol}` : ''}.`;
return errors.length > 0 ? `${msg}\n_Failed to fetch: ${errors.join(', ')}_` : msg;
}
const lines = ['**Open Orders**', ''];
for (const o of allOrders) {
lines.push(`**${o.symbol}** (${o.exchange})`);
lines.push(` ${o.type} ${o.side} | Size: ${o.size} | Price: ${o.price || 'market'}${o.stopPrice ? ` | Trigger: ${o.stopPrice}` : ''}`);
lines.push(` Status: ${o.status} | Leverage: ${o.leverage}x | ID: ${o.id}`);
lines.push('');
}
if (errors.length > 0) {
lines.push(`_Failed to fetch: ${errors.join(', ')}_`);
}
return lines.join('\n');
}
case 'price': {
const symbol = parts[1]?.toUpperCase();
if (!symbol) return 'Usage: /futures price <symbol> [--exchange X]';
const exchangeInput = parseFlag(parts, '--exchange', 'all');
const exErr = validateExchange(exchangeInput, configuredExchanges);
if (exErr) return exErr;
if (exchangeInput === 'all') {
const lines = [`**Price: ${symbol}**`, ''];
for (const ex of configuredExchanges) {
try {
const sym = normalizeSymbol(symbol, ex);
const tickers = await service.getTickerPrice(ex, sym);
const ticker = tickers[0];
if (ticker) {
lines.push(` ${ex}: $${ticker.price}`);
} else {
lines.push(` ${ex}: N/A`);
}
} catch {
lines.push(` ${ex}: N/A`);
}
}
return lines.join('\n');
}
const sym = normalizeSymbol(symbol, exchangeInput);
const tickers = await service.getTickerPrice(exchangeInput as Exchange, sym);
const ticker = tickers[0];
if (!ticker) return `No price data for ${sym} on ${exchangeInput}.`;
return `**${symbol}** (${exchangeInput}): $${ticker.price}`;
}
case 'markets': {
const exchangeInput = parseFlag(parts, '--exchange', defaultExchange);
const exErr = validateExchange(exchangeInput, configuredExchanges);
if (exErr) return exErr;
const search = parseFlag(parts, '--search', '').toUpperCase();
let markets = await service.getMarkets(exchangeInput as Exchange);
if (search) {
markets = markets.filter(m =>
m.symbol.includes(search) || m.baseAsset.includes(search)
);
}
if (markets.length === 0) {
return `No markets found${search ? ` matching "${search}"` : ''} on ${exchangeInput}.`;
}
// Show first 30 to avoid flooding
const shown = markets.slice(0, 30);
const lines = [`**Markets on ${exchangeInput}** (${markets.length} total${search ? `, filtered by "${search}"` : ''})`, ''];
for (const m of shown) {
lines.push(` ${m.symbol} | ${m.baseAsset}/${m.quoteAsset} | Max ${m.maxLeverage}x`);
}
if (markets.length > 30) {
lines.push('', `_...and ${markets.length - 30} more. Use --search to filter._`);
}
return lines.join('\n');
}
case 'balance':
case 'bal': {
const exchangeInput = parseFlag(parts, '--exchange', 'all');
const exErr = validateExchange(exchangeInput, configuredExchanges);
if (exErr) return exErr;
const balances: Awaited<ReturnType<typeof service.getBalance>>[] = [];
const errors: string[] = [];
const exchanges = exchangeInput === 'all' ? configuredExchanges : [exchangeInput];
for (const ex of exchanges) {
try {
balances.push(await service.getBalance(ex as Exchange));
} catch {
errors.push(ex);
}
}
if (balances.length === 0) {
return errors.length > 0 ? `Failed to fetch balances: ${errors.join(', ')}` : 'No balances found.';
}
const lines = ['**Futures Balances**', ''];
for (const bal of balances) {
lines.push(`**${bal.exchange}** (${bal.asset})`);
lines.push(` Total: $${bal.total.toFixed(2)}`);
lines.push(` Available: $${bal.available.toFixed(2)}`);
lines.push(` Margin: $${bal.marginBalance.toFixed(2)}`);
lines.push(` Unrealized PnL: $${bal.unrealizedPnl.toFixed(2)}`);
lines.push('');
}
if (errors.length > 0) {
lines.push(`_Failed to fetch: ${errors.join(', ')}_`);
}
return lines.join('\n');
}
case 'account': {
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const info = await service.getAccountInfo(exchange);
const lines = [`**Account Info (${exchange})**`, ''];
lines.push(` Total Balance: $${info.totalWalletBalance.toFixed(2)}`);
lines.push(` Available: $${info.availableBalance.toFixed(2)}`);
lines.push(` Margin Used: $${info.totalPositionInitialMargin.toFixed(2)}`);
lines.push(` Unrealized PnL: $${info.totalUnrealizedProfit.toFixed(2)}`);
if (info.positions && info.positions.length > 0) {
lines.push('', ' **Positions:**');
for (const p of info.positions) {
lines.push(` ${p.symbol}: ${p.side} ${p.size} @ ${p.entryPrice} (PnL: $${p.unrealizedPnl.toFixed(2)})`);
}
}
return lines.join('\n');
}
case 'trades': {
const symbol = parts[1]?.toUpperCase();
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const limitStr = parseFlag(parts, '--limit', '20');
const parsedLimit = parseInt(limitStr, 10);
const limit = isNaN(parsedLimit) ? 20 : parsedLimit;
const sym = symbol ? normalizeSymbol(symbol, exchange) : undefined;
const trades = await service.getTradeHistory(exchange, sym, limit);
if (trades.length === 0) {
return `No trade history${sym ? ` for ${sym}` : ''} on ${exchange}.`;
}
const lines = [`**Trade History (${exchange})**${sym ? ` - ${sym}` : ''}`, ''];
for (const t of trades.slice(0, 30)) {
const time = new Date(t.timestamp).toLocaleString();
const pnl = t.realizedPnl ? ` | PnL: $${t.realizedPnl.toFixed(4)}` : '';
lines.push(` ${time} | ${t.symbol} | ${t.side} ${t.quantity} @ $${t.price}${pnl}`);
}
if (trades.length > 30) {
lines.push(` _...and ${trades.length - 30} more_`);
}
return lines.join('\n');
}
case 'orderhistory': {
const symbol = parts[1]?.toUpperCase();
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const limitStr = parseFlag(parts, '--limit', '20');
const parsedLimit = parseInt(limitStr, 10);
const limit = isNaN(parsedLimit) ? 20 : parsedLimit;
const sym = symbol ? normalizeSymbol(symbol, exchange) : undefined;
const orders = await service.getOrderHistory(exchange, sym, limit);
if (orders.length === 0) {
return `No order history${sym ? ` for ${sym}` : ''} on ${exchange}.`;
}
const lines = [`**Order History (${exchange})**${sym ? ` - ${sym}` : ''}`, ''];
for (const o of orders.slice(0, 30)) {
const time = new Date(o.timestamp).toLocaleString();
const fill = o.avgFillPrice > 0 ? ` filled @ $${o.avgFillPrice}` : '';
lines.push(` ${time} | ${o.symbol} | ${o.type} ${o.side} ${o.size}${fill} | ${o.status}`);
}
if (orders.length > 30) {
lines.push(` _...and ${orders.length - 30} more_`);
}
return lines.join('\n');
}
case 'book': {
const symbol = parts[1]?.toUpperCase();
if (!symbol) return 'Usage: /futures book <symbol> [--exchange X]';
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const sym = normalizeSymbol(symbol, exchange);
const book = await service.getOrderBook(exchange, sym);
const lines = [`**Order Book: ${sym} (${exchange})**`, ''];
lines.push(' **Asks (Sell)**');
const topAsks = (book.asks || []).slice(0, 5).reverse();
for (const [price, size] of topAsks) {
lines.push(` $${price} | ${size}`);
}
lines.push(' ---');
const topBids = (book.bids || []).slice(0, 5);
lines.push(' **Bids (Buy)**');
for (const [price, size] of topBids) {
lines.push(` $${price} | ${size}`);
}
const spread = topAsks.length > 0 && topBids.length > 0
? (topAsks[topAsks.length - 1][0] - topBids[0][0]).toFixed(4)
: 'N/A';
lines.push('', ` Spread: $${spread}`);
return lines.join('\n');
}
case 'tp': {
const symbol = parts[1]?.toUpperCase();
const triggerPrice = parseFloat(parts[2]);
if (!symbol || isNaN(triggerPrice)) {
return 'Usage: /futures tp <symbol> <trigger_price> [--size N] [--side sell|buy] [--exchange X]';
}
const exchange = parseFlag(parts, '--exchange', defaultExchange) as Exchange;
const exErr = validateExchange(exchange, configuredExchanges);
if (exErr) return exErr;
const sizeStr = parseFlag(parts, '--size', '');
const sideOverride = parseFlag(parts, '--side', '').toUpperCase();
const sym = normalizeSymbol(symbol, exchange);
const positions = await service.getPositions(exchange);
const position = positions.find(p => p.symbol === sym);
if (!position && !sizeStr) {
return `No open position for ${sym} on ${exchange}. Specify --size and --side.`;
}
const tpSize = sizeStr ? parseFloat(sizeStr) : position!.size;
let tpSide: Side;
if (sideOverride === 'BUY' || sideOverride === 'SELL') {
tpSide = sideOverride;
} else if (position) {
tpSide = position.side === 'LONG' ? 'SELL' : 'BUY';
} else {
return 'No position found. Specify --side buy or --side sell.';
}
const order = await service.placeOrder(exchange, {
symbol: sym,
side: tpSide,
type: 'TAKE_PROFIT_MARKET',
size: tpSize,
stopPrice: triggerPrice,
reduceOnly: true,
});
return `**Take Profit Order Placed**
Exchange: ${exchange}
Symbol: ${order.symbol}
Trigger: ${triggerPrice}
Size: ${tpSize}
Side: ${tpSide}
Status: ${order.status}
Order ID: ${order.id}`;
}
default:
return helpText();
}
} catch (err: unknown) {
if (cmd === 'help' || cmd === '') return helpText();
return `Error: ${(err as Error)?.message || 'Failed to load futures module'}\n\n${helpText()}`;
}
}
export default {
name: 'trading-futures',
description: 'Perpetual futures trading on Binance, Bybit, Hyperliquid, MEXC',
commands: ['/futures', '/trading-futures'],
handle: execute,
};
Related skills
FAQ
Which exchanges are supported?
Binance Futures (125x), Bybit (100x), MEXC (200x), and Hyperliquid (50x).
Can it track trades?
Yes, an optional DATABASE_URL enables trade tracking with statistics and P&L.