Skip to main content

aster-bot-trading

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

설치로 이동

소스 정보

저장소
reason-machines/trending-skills
최근 소스 활동
2026년 4월 28일 08:20
감지된 SKILL.md 언어
영어
스타
80
포크
15

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
aster-bot-trading
description
Automated perpetual futures trading bot for AsterDEX with dual strategies, risk management, and TypeScript/Node.js stack
triggers
["set up aster trading bot","configure asterdex bot","add trading strategy to aster bot","implement risk management for aster bot","deploy aster perp trading bot","configure peach hybrid strategy","set up watermellon strategy asterusdt","troubleshoot aster bot not trading"]
# 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 trendingOk = !config.requireTrending || adx >= config.adxThreshold; if (fast > mid && mid > slow && currentRsi >= rsiMinLong && trendingOk) { return 'long'; } if (fast < mid && mid < slow && currentRsi <= rsiMaxShort && trendingOk) { return 'short'; } return 'none'; } ``` ### Peach Hybrid V2 momentum check ```typescript // src/strategies/peachHybrid.ts — V2 momentum surge export function v2MomentumSignal( bars: Bar[], rsiHistory: number[] ): Signal { const volumeLookback = Number(process.env.PEACH_V2_VOLUME_LOOKBACK ?? 4); const volMultiplier = Number(process.env.PEACH_V2_VOLUME_MULTIPLIER ?? 1.5); const rsiThreshold = Number(process.env.PEACH_V2_RSI_MOMENTUM_THRESHOLD ?? 3.0); if (bars.length < volumeLookback + 1 || rsiHistory.length < 2) return 'none'; const recentBars = bars.slice(-volumeLookback - 1); const avgVolume = recentBars.slice(0, -1) .reduce((sum, b) => sum + b.volume, 0) / volumeLookback; const lastVolume = recentBars[recentBars.length - 1].volume; const volumeSurge = lastVolume > avgVolume * volMultiplier; const rsiChange = rsiHistory[rsiHistory.length - 1] - rsiHistory[rsiHistory.length - 2]; const rsiSurgeLong = rsiChange >= rsiThreshold; const rsiSurgeShort = rsiChange <= -rsiThreshold; if (volumeSurge && rsiSurgeLong) return 'long'; if (volumeSurge && rsiSurgeShort) return 'short'; return 'none'; } ``` ### AsterDEX REST API order placement ```typescript // src/execution/orderManager.ts import crypto from 'crypto'; import { config } from '../config'; interface OrderParams { symbol: string; side: 'BUY' | 'SELL'; type: 'MARKET' | 'LIMIT'; quantity: number; price?: number; reduceOnly?: boolean; } function signQuery(params: Record<string, string | number | boolean>): string { const query = new URLSearchParams( Object.entries(params).map(([k, v]) => [k, String(v)]) ).toString(); const sig = crypto .createHmac('sha256', config.apiSecret) .update(query) .digest('hex'); return `${query}&signature=${sig}`; } export async function placeOrder(params: OrderParams): Promise<unknown> { if (config.mode === 'dry-run') { console.log('[DRY-RUN] Would place order:', params); return { orderId: 'dry-run', status: 'SIMULATED' }; } const timestamp = Date.now(); const body = signQuery({ ...params, timestamp, recvWindow: 5000 }); const response = await fetch(`${config.rpcUrl}/fapi/v1/order`, { method: 'POST', headers: { 'X-MBX-APIKEY': config.apiKey, 'Content-Type': 'application/x-www-form-urlencoded',
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기