Claude
Skills
Sign in
Back

aster-bot-trading

Included with Lifetime
$97 forever

Automated perpetual futures trading bot for AsterDEX with dual strategies, risk management, and TypeScript/Node.js stack

Backend & APIs

What this skill does


# Aster Trading Bot

> Skill by [ara.so](https://ara.so) — Daily 2026 Skills collection.

Aster Bot is a TypeScript/Node.js automated trading system for **ASTERUSDT perpetual futures** on [AsterDEX](https://www.asterdex.com). It features dual strategy engines (Watermellon and Peach Hybrid), configurable risk controls, real-time WebSocket market data, and production-grade logging with CSV/JSON trade records.

---

## Installation

```bash
git clone https://github.com/SignalBot-Labs/aster-bot.git
cd aster-bot
npm install
cp env.example .env.local
```

Edit `.env.local` with your credentials (see Configuration below), then:

```bash
# Dry-run (no real orders)
npm run bot

# Live trading (real orders, real risk)
MODE=live npm run bot
```

---

## Configuration

All configuration is via environment variables in `.env.local`.

### Required

```env
ASTER_RPC_URL=https://fapi.asterdex.com
ASTER_WS_URL=wss://fstream.asterdex.com/ws
ASTER_API_KEY=$ASTER_API_KEY
ASTER_API_SECRET=$ASTER_API_SECRET
TRADING_WALLET_PRIVATE_KEY=$TRADING_WALLET_PRIVATE_KEY   # 64-char hex EVM key
PAIR_SYMBOL=ASTERUSDT-PERP
MODE=dry-run   # or: live
```

### Risk Management

```env
MAX_POSITION_USDT=10000
MAX_LEVERAGE=5           # Must be one of: 5, 10, 15, 50
MAX_FLIPS_PER_HOUR=12
STOP_LOSS_PCT=0
TAKE_PROFIT_PCT=0
USE_STOP_LOSS=false
EMERGENCY_STOP_LOSS_PCT=2.0
MAX_POSITIONS=1
REQUIRE_TRENDING_MARKET=true
ADX_THRESHOLD=25
```

### Strategy Selection

```env
STRATEGY_TYPE=peach-hybrid   # or: watermellon
```

### Timeframe

```env
VIRTUAL_TIMEFRAME_MS=30000   # Bar size in ms (e.g. 30000 = 30s bars)
```

### Startup Price Guard

The bot calls `web3.prc`'s `prices()` at startup and checks the `responsive` field against `limitPrice = 0.871` in `src/lib/spotPrice.ts`. If below, the bot exits.

```env
SKIP_MIN_SPOT_CHECK=true   # Skip price gate for local testing only
```

---

## Strategy Configuration

### Watermellon (EMA + RSI trend following)

```env
STRATEGY_TYPE=watermellon
EMA_FAST=8
EMA_MID=21
EMA_SLOW=48
RSI_LENGTH=14
RSI_MIN_LONG=42
RSI_MAX_SHORT=58
```

**Logic:**
- **Long:** bullish EMA stack (fast > mid > slow) + RSI ≥ `RSI_MIN_LONG` + ADX ≥ `ADX_THRESHOLD`
- **Short:** bearish EMA stack (fast < mid < slow) + RSI ≤ `RSI_MAX_SHORT` + ADX ≥ `ADX_THRESHOLD`

### Peach Hybrid (Dual V1 + V2 system)

```env
STRATEGY_TYPE=peach-hybrid

# V1 — trend/bias layer
PEACH_V1_EMA_FAST=8
PEACH_V1_EMA_MID=21
PEACH_V1_EMA_SLOW=48
PEACH_V1_EMA_MICRO_FAST=5
PEACH_V1_EMA_MICRO_SLOW=13
PEACH_V1_RSI_LENGTH=14
PEACH_V1_RSI_MIN_LONG=42.0
PEACH_V1_RSI_MAX_SHORT=58.0
PEACH_V1_MIN_BARS_BETWEEN=1
PEACH_V1_MIN_MOVE_PCT=0.10

# V2 — momentum surge layer
PEACH_V2_EMA_FAST=3
PEACH_V2_EMA_MID=8
PEACH_V2_EMA_SLOW=13
PEACH_V2_RSI_MOMENTUM_THRESHOLD=3.0
PEACH_V2_VOLUME_LOOKBACK=4
PEACH_V2_VOLUME_MULTIPLIER=1.5
PEACH_V2_EXIT_VOLUME_MULTIPLIER=1.2
```

---

## Key Commands

```bash
# Start the bot (dry-run by default)
npm run bot

# TypeScript compilation check
npx tsc --noEmit

# Build
npm run build

# Run compiled output
npm run start
```

---

## Project Structure

```
aster-bot/
├── src/
│   ├── bot.ts                  # Main entry point
│   ├── lib/
│   │   ├── spotPrice.ts        # Startup price guard (limitPrice = 0.871)
│   │   ├── logger.ts           # Console + file logging
│   │   └── state.ts            # Persistent state across restarts
│   ├── strategies/
│   │   ├── watermellon.ts      # EMA+RSI trend strategy
│   │   └── peachHybrid.ts      # V1+V2 dual strategy
│   ├── execution/
│   │   └── orderManager.ts     # Order placement, reconciliation
│   └── risk/
│       └── riskManager.ts      # Position limits, stop-loss, flip control
├── data/
│   ├── trades/daily/           # CSV/JSON trade logs
│   └── img/                    # Reference chart screenshots
├── env.example                 # Template for .env.local
└── package.json
```

---

## Real Code Examples

### Reading current configuration in TypeScript

```typescript
// src/config.ts
import * as dotenv from 'dotenv';
dotenv.config({ path: '.env.local' });

export const config = {
  rpcUrl: process.env.ASTER_RPC_URL ?? 'https://fapi.asterdex.com',
  wsUrl: process.env.ASTER_WS_URL ?? 'wss://fstream.asterdex.com/ws',
  apiKey: process.env.ASTER_API_KEY!,
  apiSecret: process.env.ASTER_API_SECRET!,
  privateKey: process.env.TRADING_WALLET_PRIVATE_KEY!,
  symbol: process.env.PAIR_SYMBOL ?? 'ASTERUSDT-PERP',
  mode: (process.env.MODE ?? 'dry-run') as 'dry-run' | 'live',
  maxPositionUsdt: Number(process.env.MAX_POSITION_USDT ?? 10000),
  maxLeverage: Number(process.env.MAX_LEVERAGE ?? 5),
  maxFlipsPerHour: Number(process.env.MAX_FLIPS_PER_HOUR ?? 12),
  emergencyStopLossPct: Number(process.env.EMERGENCY_STOP_LOSS_PCT ?? 2.0),
  adxThreshold: Number(process.env.ADX_THRESHOLD ?? 25),
  requireTrending: process.env.REQUIRE_TRENDING_MARKET === 'true',
  strategyType: (process.env.STRATEGY_TYPE ?? 'peach-hybrid') as 'watermellon' | 'peach-hybrid',
  virtualTimeframeMs: Number(process.env.VIRTUAL_TIMEFRAME_MS ?? 30000),
  skipMinSpotCheck: process.env.SKIP_MIN_SPOT_CHECK === 'true',
};

// Validate leverage
const VALID_LEVERAGES = [5, 10, 15, 50];
if (!VALID_LEVERAGES.includes(config.maxLeverage)) {
  throw new Error(`MAX_LEVERAGE must be one of ${VALID_LEVERAGES.join(', ')}, got ${config.maxLeverage}`);
}

// Validate private key
if (!config.privateKey || config.privateKey.length !== 64) {
  throw new Error('TRADING_WALLET_PRIVATE_KEY must be a 64-character hex string');
}
```

### Implementing a custom indicator (EMA calculation)

```typescript
// src/indicators/ema.ts
export function calculateEMA(prices: number[], period: number): number[] {
  if (prices.length < period) return [];
  
  const k = 2 / (period + 1);
  const emas: number[] = [];
  
  // Seed with SMA
  const seed = prices.slice(0, period).reduce((a, b) => a + b, 0) / period;
  emas.push(seed);
  
  for (let i = period; i < prices.length; i++) {
    emas.push(prices[i] * k + emas[emas.length - 1] * (1 - k));
  }
  
  return emas;
}

export function calculateRSI(prices: number[], period: number = 14): number[] {
  if (prices.length < period + 1) return [];
  
  const rsis: number[] = [];
  let avgGain = 0;
  let avgLoss = 0;

  for (let i = 1; i <= period; i++) {
    const change = prices[i] - prices[i - 1];
    if (change > 0) avgGain += change;
    else avgLoss += Math.abs(change);
  }
  avgGain /= period;
  avgLoss /= period;

  for (let i = period; i < prices.length - 1; i++) {
    const change = prices[i + 1] - prices[i];
    const gain = change > 0 ? change : 0;
    const loss = change < 0 ? Math.abs(change) : 0;
    avgGain = (avgGain * (period - 1) + gain) / period;
    avgLoss = (avgLoss * (period - 1) + loss) / period;
    const rs = avgLoss === 0 ? 100 : avgGain / avgLoss;
    rsis.push(100 - 100 / (1 + rs));
  }

  return rsis;
}
```

### Watermellon strategy signal generation

```typescript
// src/strategies/watermellon.ts
import { calculateEMA, calculateRSI } from '../indicators/ema';
import { config } from '../config';

export type Signal = 'long' | 'short' | 'none';

export interface Bar {
  close: number;
  volume: number;
  timestamp: number;
}

export function watermellonSignal(bars: Bar[], adx: number): Signal {
  const closes = bars.map(b => b.close);
  
  const emaFast = calculateEMA(closes, Number(process.env.EMA_FAST ?? 8));
  const emaMid  = calculateEMA(closes, Number(process.env.EMA_MID  ?? 21));
  const emaSlow = calculateEMA(closes, Number(process.env.EMA_SLOW ?? 48));
  const rsi     = calculateRSI(closes, Number(process.env.RSI_LENGTH ?? 14));

  if (!emaFast.length || !emaMid.length || !emaSlow.length || !rsi.length) {
    return 'none';
  }

  const fast = emaFast[emaFast.length - 1];
  const mid  = emaMid[emaMid.length - 1];
  const slow = emaSlow[emaSlow.length - 1];
  const currentRsi = rsi[rsi.length - 1];

  const rsiMinLong  = Number(process.env.RSI_MIN_LONG  ?? 42);
  const rsiMaxShort = Number(process.env.RSI_MAX_SHORT ?? 58);

  const trend

Related in Backend & APIs