Skip to content

wezzcoetzee/moving-average-bot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Moving Average Trading Bot

An automated cryptocurrency trading bot that executes trades based on moving average crossovers on Hyperliquid perpetual futures.

Overview

A single-user application that automates a simple but effective trading strategy: buying when price is above the moving average (bullish trend) and selling when price is below (bearish trend). Connect your Hyperliquid wallet and configure your currencies, leverage, and position sizes.

The bot runs automatically at UTC midnight each day, checking all active currencies and executing trades based on the moving average signals.

Features

  • Automated Trading: Executes trades at UTC midnight based on moving average signals
  • Multi-Currency Support: Track and trade multiple cryptocurrencies
  • Configurable Strategy: Set custom moving average periods (e.g., 200 DMA) per currency
  • Flexible Leverage: Configure different leverage for long and short positions
  • Margin Allocation: Set maximum margin per currency to control position sizing
  • Cross Margin: All trades use cross margin for capital efficiency
  • ATR Trailing Stops: Optional ATR-based trailing stop loss with partial position closing
  • Partial Closes: Incrementally close positions as they move in your favor
  • Trade History: Full record of all trades with entry/exit prices and P/L
  • Performance Tracking: Daily position snapshots with cumulative P/L tracking
  • Wallet Tracking: Monitor wallet balance over time with historical charts
  • Real-time Dashboard: Dark-themed UI for monitoring your bot
  • Manual Override: Run the bot manually at any time from the dashboard
  • Discord Notifications: Receive trading reports via Discord webhooks after each bot run

How It Works

The bot follows a simple moving average strategy:

  1. At UTC Midnight: The bot checks each active currency
  2. Price vs MA: Compares current price against the configured moving average
  3. Signal Generation:
    • Price > MA → Open LONG position (or do nothing if already long)
    • Price < MA → Open SHORT position (or do nothing if already short)
  4. Position Management: If the signal changes, the bot closes the existing position and opens a new one in the opposite direction
  5. Snapshots: Records wallet balance and position snapshots for performance tracking

ATR Trailing Stops

The bot supports optional ATR (Average True Range) trailing stops for risk management. When enabled, the bot will:

  1. Track Favorable Movement: As the position moves in your favor, the bot records the most extreme price reached
  2. Calculate Dynamic Stop: The stop price trails behind the extreme price by a distance of ATR × multiplier
  3. Partial Position Closing: When the stop is hit, the bot closes a percentage of your position (not the entire position)
  4. Reset and Continue: After a partial close, the stop resets and begins trailing again with the remaining position

Configuration (per currency)

  • ATR Period: Number of periods to calculate ATR (e.g., 14)
  • ATR Multiplier: Distance multiplier for the stop (e.g., 2.0 means stop is 2×ATR away)
  • Close Percent: Percentage of position to close when stop is hit (e.g., 25% for quarter position)

Example

  • You're in a LONG position on BTC at $40,000
  • BTC rises to $45,000 (extreme price)
  • ATR is $500, multiplier is 2.0
  • Stop price is $45,000 - ($500 × 2.0) = $44,000
  • Price drops to $44,000 → 25% of position closes
  • Remaining 75% continues, stop resets to trail new extreme price

This allows you to lock in partial profits while staying in the trend.

Tech Stack

  • Framework: Next.js 16 with App Router
  • Database: PostgreSQL with Prisma ORM
  • Styling: Tailwind CSS + shadcn/ui
  • Charts: Recharts
  • Trading: @nktkas/hyperliquid SDK
  • Scheduling: node-cron
  • Runtime: Bun

Quick Start

Prerequisites

  • Node.js 18+ or Bun
  • PostgreSQL database
  • Hyperliquid account with API access

Installation

  1. Clone the repository
git clone https://github.com/wezzcoetzee/moving-average-bot.git
cd moving-average-bot

Local Development with Docker Compose

Use this path when you want the app, database, and optional cron runner isolated in containers while still hot-reloading local source changes.

  1. Create a local environment file
cp .env.example .env

Set a stable encryption key before creating bots. Keep this value unchanged for the life of your local database, because stored bot keys are encrypted with it.

openssl rand -hex 32

Paste the generated value into ENCRYPTION_KEY in .env. You can leave DATABASE_URL as-is for host-based development; Docker Compose overrides it inside containers to use the db service.

Compose uses .env for variable substitution when the file is present. Local Compose runs force Hyperliquid testnet mode on by default.

  1. Start the local stack
docker compose up app

The app service waits for Postgres, installs dependencies into a Docker volume, runs prisma generate, pushes the Prisma schema, and starts Next.js on http://localhost:3002.

  1. Develop locally
  • Edit files on your host machine; the app container has the repo mounted at /app and Next.js hot reloads changes.
  • Follow logs with docker compose logs -f app.
  • Run commands in the app container with docker compose exec app <command>, for example:
docker compose exec app bun run lint
docker compose exec app bun run test
docker compose exec app bunx prisma studio --hostname 0.0.0.0
  1. Attach a debugger when needed

Start the app with Node inspector enabled:

NODE_OPTIONS="--inspect=0.0.0.0:9229" docker compose up app

Then attach your debugger to localhost:9229.

  1. Run the cron worker locally

The cron service is behind a Compose profile so it does not start during normal UI development.

docker compose --profile cron up app cron

The cron worker calls the app at http://app:3002/api/cron using the same CRON_SECRET value as the app container.

  1. Connect to or reset the local database

From the host, Postgres is available at:

postgresql://moving_average_bot:moving_average_bot@localhost:5432/moving_average_bot

Stop containers without deleting data:

docker compose down

Reset all local Docker database data:

docker compose down -v

Local Development without Docker

  1. Install dependencies
bun install
  1. Set up environment variables
cp .env.example .env

Edit .env with your configuration. See .env.example for all available options.

Required Variables:

# Database connection
DATABASE_URL="postgresql://user:password@localhost:5432/moving_average_bot"

# Encryption key for private keys at rest (generate with: openssl rand -hex 32)
ENCRYPTION_KEY=""

# Hyperliquid private key (hex, starts with 0x)
HYPERLIQUID_PRIVATE_KEY="0x..."

# Cron endpoint authentication
CRON_SECRET="your_secret_here"

Optional Variables:

# Logger
LOG_LEVEL="INFO"                              # DEBUG | INFO | WARN | ERROR | SILENT
ENABLE_LOGS="true"                            # Master switch for logging

# Hyperliquid
HYPERLIQUID_TESTNET="false"                   # Server-side testnet flag
NEXT_PUBLIC_HYPERLIQUID_TESTNET="false"       # Client-side testnet flag

# Cron
API_URL="http://localhost:3002"               # Base URL for cron script
# CRON_EXPRESSION="0 0 * * *"                 # Override schedule (default: midnight UTC)
  1. Set up the database
bunx prisma generate
bunx prisma db push
  1. Run the development server
bun run dev

Open http://localhost:3002 to view the dashboard.

Configuration

Setup

  1. Set HYPERLIQUID_PRIVATE_KEY in your .env file
  2. Open the dashboard and add your currencies
  3. Enable the bot in Settings when ready

Adding Currencies

  1. Click "Add Currency"
  2. Configure:
    • Symbol: The trading pair (e.g., "BTC", "ETH")
    • Name: Display name (e.g., "Bitcoin")
    • Moving Average: Period in days (e.g., 200 for 200 DMA)
    • Long Leverage: Leverage for long positions
    • Short Leverage: Leverage for short positions
    • Margin Allocation: Max margin in USD for this currency

Bot Settings

  • Enable/Disable: Toggle the bot on/off from the header
  • Max Leverage: Maximum leverage applied on the exchange (default: 5)
  • Margin Allocation: Controls position sizing per currency

Discord Notifications

Receive trading reports via Discord webhooks after each bot run, with P/L for each position and overall wallet status.

Setup Steps

  1. Create a Discord Webhook

    • Open your Discord server and go to the channel where you want notifications
    • Click the gear icon (Edit Channel) → Integrations → Webhooks
    • Click "New Webhook", give it a name, and click "Copy Webhook URL"
  2. Configure in Settings

    • Go to Settings in the dashboard
    • Paste the webhook URL in the Discord Notifications section
    • Click "Save Settings"
    • Click "Test" to verify it works

Environment Variables Reference

Complete reference for all environment variables:

Required

Variable Description Example
DATABASE_URL PostgreSQL connection string postgresql://user:pass@localhost:5432/dbname
ENCRYPTION_KEY 64-char hex key (32 bytes) used to encrypt wallet private keys at rest. Generate with openssl rand -hex 32. a1b2c3... (64 hex chars)
HYPERLIQUID_PRIVATE_KEY Private key for Hyperliquid wallet (hex, 0x-prefixed). Only needed for the seed migration. 0xabc123...
CRON_SECRET Secret token for authenticating /api/cron endpoint your-secret-token-here

Logger

Variable Default Description
LOG_LEVEL INFO Minimum log level: DEBUG | INFO | WARN | ERROR | SILENT
ENABLE_LOGS true Master switch to enable/disable all logging

Examples:

LOG_LEVEL="DEBUG"         # Development: Show all debug messages
LOG_LEVEL="WARN"          # Production: Only warnings and errors
ENABLE_LOGS="false"       # Completely silent

Hyperliquid

Variable Default Description
HYPERLIQUID_TESTNET false Server-side testnet flag (API routes)
NEXT_PUBLIC_HYPERLIQUID_TESTNET false Client-side testnet flag (browser/hooks)

Cron

Variable Default Description
API_URL http://localhost:3002 Base URL for the cron script to call
CRON_EXPRESSION 0 0 * * * Cron schedule (default: midnight UTC daily)
CRON_ALERT_WEBHOOK_URL - Discord webhook URL for cron failure alerts (optional)

Deployment

Docker (Recommended)

The repo ships a Dockerfile (app), a Dockerfile.cron (scheduler), and a docker-compose.yml that wires both up alongside Postgres.

Build and run the full stack:

# ENCRYPTION_KEY is required - the stack refuses to start without it
echo "ENCRYPTION_KEY=$(openssl rand -hex 32)" >> .env

docker compose up -d --build          # app + database
docker compose --profile cron up -d   # add the scheduled trading worker

The client-side testnet flag is baked in at image build time, so pass it as a build arg if you are building the image yourself:

docker build --build-arg NEXT_PUBLIC_HYPERLIQUID_TESTNET=true -t moving-average-bot .

Docker Services

Container Description Port
moving-average-bot-app Next.js web dashboard 3002
moving-average-bot-db PostgreSQL database 5432
moving-average-bot-cron Runs the trading bot on a schedule (cron profile) -

Continuous Integration

.github/workflows/pr-checks.yml runs lint and tests on every pull request against main and posts the results as a PR comment. There is no deployment workflow in this repo — deploy however suits your infrastructure (the Docker images above are self-contained).

Self-Hosted (without Docker)

  1. Build the application:
bun run build
  1. Start the production server:
bun run start
  1. Run the cron script in a separate process:
bun run scripts/cron.ts

Or use PM2 for process management:

pm2 start "bun run scripts/cron.ts" --name "trading-bot-cron"

API Endpoints

The app is multi-bot: most resources are scoped under a bot ID (/api/bots/[botId]/...).

Bots

Endpoint Method Description
/api/bots GET List bots
/api/bots POST Create a bot
/api/bots/[botId] GET Get a bot
/api/bots/[botId] PATCH Update bot settings
/api/bots/[botId] DELETE Delete a bot
/api/bots/[botId]/run POST Run a single bot manually
/api/bots/run-all POST Run every enabled bot
/api/bots/[botId]/discord-test POST Send a test Discord notification

Currencies

Endpoint Method Description
/api/bots/[botId]/currencies GET List the bot's currencies
/api/bots/[botId]/currencies POST Add a currency
/api/bots/[botId]/currencies/[id] GET Get a currency
/api/bots/[botId]/currencies/[id] PATCH Update a currency
/api/bots/[botId]/currencies/[id] DELETE Delete a currency

Trades & Positions

Endpoint Method Description
/api/bots/[botId]/trades GET List trades
/api/bots/[botId]/trades/open GET Get open trades
/api/bots/[botId]/trades/[id] GET Get a specific trade
/api/bots/[botId]/trades/stats GET Get trading statistics
/api/bots/[botId]/positions/close POST Close a position manually
/api/bots/[botId]/positions/history GET Get position snapshot history
/api/bots/[botId]/positions/summary GET Get performance summary
/api/bots/[botId]/pnl GET Get profit and loss

Wallet & Prices

Endpoint Method Description
/api/bots/[botId]/wallet GET Get wallet balance and open positions
/api/bots/[botId]/wallet/history GET Get wallet balance history
/api/price/[symbol] GET Get price and moving average for a symbol

System

Endpoint Method Description
/api/health GET Health check
/api/metrics GET Prometheus metrics
/api/openapi GET OpenAPI specification
/api/settings GET, PATCH Global settings
/api/settings/env GET Report which env vars are configured
/api/cron GET, POST Cron trigger (protected by CRON_SECRET)

Database Schema

Currency

Field Type Description
id String Unique identifier
symbol String Trading symbol (e.g., "BTC")
name String Display name
movingAverage Int MA period in days
longLeverage Float Leverage for longs
shortLeverage Float Leverage for shorts
marginAllocation Float Max margin in USD
isActive Boolean Whether to trade this currency
atrTrailingEnabled Boolean Enable ATR trailing stop
atrPeriod Int ATR calculation period
atrMultiplier Float ATR multiplier for stop distance
atrClosePercent Float % of position to close at stop

Trade

Field Type Description
id String Unique identifier
currencyId String Reference to currency
type Enum LONG or SHORT
status Enum OPEN, CLOSED, or CANCELLED
entryPrice Float Entry price
exitPrice Float Exit price (when fully closed)
movingAverage Float MA value at trade time
leverage Float Leverage used
profitPercent Float Profit/loss as %
profitUsd Float Profit/loss in USD
quantity Float Initial position size
remainingQuantity Float Remaining after partial closes
extremePrice Float Most favorable price reached
atrStopPrice Float Current ATR stop price
atrStopOrderId String Hyperliquid stop order ID

PartialClose

Field Type Description
id String Unique identifier
tradeId String Reference to parent trade
quantity Float Amount closed
exitPrice Float Price at close
profitPercent Float Profit/loss as % for this close
profitUsd Float Profit/loss in USD for this close
isAtrStop Boolean Whether closed by ATR stop
closedAt DateTime When position was closed

Bot

Field Type Description
id String Unique identifier
name String Bot name
encryptedKey String Wallet private key, encrypted at rest (AES-256-GCM)
isEnabled Boolean Whether the bot is active
maxLeverage Float Max leverage on exchange (default: 5)
lastRunAt DateTime Last bot execution time
discordWebhookUrl String Discord webhook URL for notifications
discordNotifyOnRun Boolean Send Discord notifications after bot runs

PositionSnapshot

Field Type Description
id String Unique identifier
currencyId String Reference to currency
positionType Enum LONG, SHORT, or null
currentPrice Float Price at snapshot time
entryPrice Float Entry price of current position
movingAverage Float MA value at snapshot time
unrealizedPnlUsd Float Current unrealized P/L in USD
unrealizedPnlPct Float Current unrealized P/L as %
cumulativePnlUsd Float Total realized + unrealized P/L in USD
cumulativePnlPct Float Total realized + unrealized P/L as %
walletBalance Float Wallet balance at snapshot
positionSize Float Position size in coins
positionValue Float Position value in USD

WalletSnapshot

Field Type Description
id String Unique identifier
balance Float Wallet balance at snapshot time
timestamp DateTime When snapshot was taken

Security

  • Never commit your .env file — it is gitignored by default
  • Wallet private keys are stored in the database encrypted at rest with AES-256-GCM, using the 32-byte key from ENCRYPTION_KEY. They are decrypted only in memory at runtime. Losing ENCRYPTION_KEY means losing access to the stored keys, and leaking it is equivalent to leaking the wallets themselves — treat it like a password
  • Generate ENCRYPTION_KEY with openssl rand -hex 32. Never reuse an example value
  • Use a strong CRON_SECRET to protect the cron endpoint
  • The dashboard API routes are otherwise unauthenticated — do not expose the app directly to the public internet
  • Consider using a separate wallet for the bot with limited funds
  • Test on testnet first by setting HYPERLIQUID_TESTNET=true

Risk Disclaimer

USE AT YOUR OWN RISK

This software is for educational purposes only. Cryptocurrency trading involves significant risk of loss. Past performance does not guarantee future results.

  • This bot does not guarantee profits
  • Leverage amplifies both gains and losses
  • Market conditions can change rapidly
  • Technical issues can occur

Always:

  • Start with small amounts
  • Test on testnet first
  • Never invest more than you can afford to lose
  • Monitor your positions regularly

License

MIT © Wesley Coetzee

About

This bot uses the best moving averages to trade crypto currency on Hyperliquid

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages