Skip to main content
NikoFischer.com

Main navigation

  • Home
  • About
    • My Reading List
    • Recommended Youtube Channels
    • Life Rules
    • Podcast
  • 50-Day Challenge
  • Impressum
Sprachumschalter
  • German
  • English

Breadcrumb

  1. Home

Binance API Guide: Crypto Trading Bot with Supabase Backend - Live Trading Data Storage and Monitoring

🎸
🚀 Beta Running

PYNGUP: Rebellion against toxic productivity

Beta limited to 100 spots. Tasks become social commitments instead of lonely to-dos.

🚀 Join Beta 📖 Read Story "€487 wasted"

Want to build a professional crypto trading bot that not only trades but also stores and analyzes all data systematically? The combination of Binance API and Supabase as a backend is the perfect solution for scalable trading applications.

In this comprehensive guide, I'll show you how to connect the Binance API with Supabase to create a complete trading system - from authentication to live monitoring of your trading performance.

Why Binance API + Supabase?

Binance API provides access to the world's largest crypto exchange with:

  • Real-time Market Data
  • Order Management
  • Account Information
  • WebSocket Streams for Live Updates

Supabase as a backend delivers:

  • PostgreSQL Database for complex queries
  • Real-time Subscriptions
  • Edge Functions for API Processing
  • Row Level Security for secure data

Project Setup and Basic Configuration

Start with a new Supabase project at database.new and create Binance API keys in your Binance Account.

Supabase Database Schema

Run this SQL in your Supabase SQL Editor:

-- Trading Accounts Table
CREATE TABLE trading_accounts (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  exchange TEXT NOT NULL DEFAULT 'binance',
  api_key_encrypted TEXT NOT NULL,
  api_secret_encrypted TEXT NOT NULL,
  is_testnet BOOLEAN DEFAULT true,
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Trading Pairs Table
CREATE TABLE trading_pairs (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  symbol TEXT NOT NULL UNIQUE,
  base_asset TEXT NOT NULL,
  quote_asset TEXT NOT NULL,
  is_active BOOLEAN DEFAULT true,
  min_quantity DECIMAL,
  max_quantity DECIMAL,
  step_size DECIMAL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Price Data Table
CREATE TABLE price_data (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  symbol TEXT NOT NULL,
  price DECIMAL NOT NULL,
  volume DECIMAL,
  timestamp TIMESTAMPTZ NOT NULL,
  source TEXT DEFAULT 'binance_websocket',
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Trading Orders Table
CREATE TABLE trading_orders (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  account_id UUID REFERENCES trading_accounts(id) ON DELETE CASCADE,
  binance_order_id BIGINT,
  symbol TEXT NOT NULL,
  side TEXT CHECK (side IN ('BUY', 'SELL')),
  type TEXT CHECK (type IN ('MARKET', 'LIMIT', 'STOP_LOSS', 'STOP_LOSS_LIMIT')),
  quantity DECIMAL NOT NULL,
  price DECIMAL,
  stop_price DECIMAL,
  status TEXT DEFAULT 'NEW',
  filled_quantity DECIMAL DEFAULT 0,
  commission DECIMAL DEFAULT 0,
  commission_asset TEXT,
  executed_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Portfolio Holdings Table
CREATE TABLE portfolio_holdings (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  account_id UUID REFERENCES trading_accounts(id) ON DELETE CASCADE,
  asset TEXT NOT NULL,
  free_balance DECIMAL DEFAULT 0,
  locked_balance DECIMAL DEFAULT 0,
  total_balance DECIMAL GENERATED ALWAYS AS (free_balance + locked_balance) STORED,
  avg_buy_price DECIMAL DEFAULT 0,
  unrealized_pnl DECIMAL DEFAULT 0,
  updated_at TIMESTAMPTZ DEFAULT NOW(),
  UNIQUE(user_id, account_id, asset)
);

-- Trading Strategies Table
CREATE TABLE trading_strategies (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
  name TEXT NOT NULL,
  description TEXT,
  strategy_config JSONB NOT NULL,
  is_active BOOLEAN DEFAULT false,
  profit_loss DECIMAL DEFAULT 0,
  total_trades INTEGER DEFAULT 0,
  win_rate DECIMAL DEFAULT 0,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

-- Create indexes for better performance
CREATE INDEX idx_price_data_symbol_timestamp ON price_data(symbol, timestamp DESC);
CREATE INDEX idx_trading_orders_user_created ON trading_orders(user_id, created_at DESC);
CREATE INDEX idx_portfolio_holdings_user_account ON portfolio_holdings(user_id, account_id);

-- Row Level Security Policies
ALTER TABLE trading_accounts ENABLE ROW LEVEL SECURITY;
ALTER TABLE trading_orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE portfolio_holdings ENABLE ROW LEVEL SECURITY;
ALTER TABLE trading_strategies ENABLE ROW LEVEL SECURITY;

-- Users can only access their own data
CREATE POLICY "Users can manage their own trading accounts" ON trading_accounts
  FOR ALL USING (auth.uid() = user_id);

CREATE POLICY "Users can manage their own orders" ON trading_orders
  FOR ALL USING (auth.uid() = user_id);

CREATE POLICY "Users can view their own portfolio" ON portfolio_holdings
  FOR ALL USING (auth.uid() = user_id);

CREATE POLICY "Users can manage their own strategies" ON trading_strategies
  FOR ALL USING (auth.uid() = user_id);

-- Price data is public (read-only)
CREATE POLICY "Anyone can read price data" ON price_data
  FOR SELECT USING (true);

Binance API Integration with Supabase Edge Functions

Create a new Edge Function for Binance API integration:

# Terminal
supabase functions new binance-api-handler

Binance API Wrapper Function

// supabase/functions/binance-api-handler/index.ts
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import { crypto } from "https://deno.land/std/crypto/mod.ts"

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

interface BinanceConfig {
  apiKey: string
  apiSecret: string
  testnet?: boolean
}

class BinanceAPI {
  private apiKey: string
  private apiSecret: string
  private baseUrl: string

  constructor(config: BinanceConfig) {
    this.apiKey = config.apiKey
    this.apiSecret = config.apiSecret
    this.baseUrl = config.testnet 
      ? 'https://testnet.binance.vision/api'
      : 'https://api.binance.com/api'
  }

  private async signRequest(queryString: string): Promise {
    const encoder = new TextEncoder()
    const keyData = encoder.encode(this.apiSecret)
    const msgData = encoder.encode(queryString)
    
    const key = await crypto.subtle.importKey(
      'raw',
      keyData,
      { name: 'HMAC', hash: 'SHA-256' },
      false,
      ['sign']
    )
    
    const signature = await crypto.subtle.sign('HMAC', key, msgData)
    return Array.from(new Uint8Array(signature))
      .map(byte => byte.toString(16).padStart(2, '0'))
      .join('')
  }

  async getAccountInfo(): Promise {
    const timestamp = Date.now()
    const queryString = `timestamp=${timestamp}`
    const signature = await this.signRequest(queryString)
    
    const response = await fetch(
      `${this.baseUrl}/v3/account?${queryString}&signature=${signature}`,
      {
        headers: {
          'X-MBX-APIKEY': this.apiKey
        }
      }
    )
    
    return response.json()
  }

  async getAllOrders(symbol: string, limit = 500): Promise {
    const timestamp = Date.now()
    const queryString = `symbol=${symbol}&timestamp=${timestamp}&limit=${limit}`
    const signature = await this.signRequest(queryString)
    
    const response = await fetch(
      `${this.baseUrl}/v3/allOrders?${queryString}&signature=${signature}`,
      {
        headers: {
          'X-MBX-APIKEY': this.apiKey
        }
      }
    )
    
    return response.json()
  }

  async placeOrder(orderParams: any): Promise {
    const timestamp = Date.now()
    const params = { ...orderParams, timestamp }
    
    const queryString = Object.keys(params)
      .map(key => `${key}=${params[key]}`)
      .join('&')
    
    const signature = await this.signRequest(queryString)
    
    const response = await fetch(
      `${this.baseUrl}/v3/order?${queryString}&signature=${signature}`,
      {
        method: 'POST',
        headers: {
          'X-MBX-APIKEY': this.apiKey
        }
      }
    )
    
    return response.json()
  }

  async getTickerPrices(): Promise {
    const response = await fetch(`${this.baseUrl}/v3/ticker/price`)
    return response.json()
  }
}

serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  try {
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
    )

    const authHeader = req.headers.get('Authorization')
    if (!authHeader) {
      throw new Error('No authorization header')
    }

    const { data: { user }, error: authError } = await supabase.auth.getUser(
      authHeader.replace('Bearer ', '')
    )

    if (authError || !user) {
      throw new Error('Invalid user')
    }

    const { action, accountId, ...params } = await req.json()

    // Get trading account credentials
    const { data: account, error: accountError } = await supabase
      .from('trading_accounts')
      .select('*')
      .eq('id', accountId)
      .eq('user_id', user.id)
      .single()

    if (accountError || !account) {
      throw new Error('Trading account not found')
    }

    // Decrypt API credentials (implement your encryption logic)
    const binanceAPI = new BinanceAPI({
      apiKey: account.api_key_encrypted, // TODO: Decrypt
      apiSecret: account.api_secret_encrypted, // TODO: Decrypt
      testnet: account.is_testnet
    })

    let result

    switch (action) {
      case 'getAccountInfo':
        result = await binanceAPI.getAccountInfo()
        break
      
      case 'getAllOrders':
        result = await binanceAPI.getAllOrders(params.symbol, params.limit)
        break
      
      case 'placeOrder':
        result = await binanceAPI.placeOrder(params)
        
        // Store order in database
        await supabase.from('trading_orders').insert({
          user_id: user.id,
          account_id: accountId,
          binance_order_id: result.orderId,
          symbol: params.symbol,
          side: params.side,
          type: params.type,
          quantity: params.quantity,
          price: params.price,
          status: result.status
        })
        break
      
      case 'getTickerPrices':
        result = await binanceAPI.getTickerPrices()
        
        // Store price data
        const priceInserts = result.map((ticker: any) => ({
          symbol: ticker.symbol,
          price: parseFloat(ticker.price),
          timestamp: new Date().toISOString()
        }))
        
        await supabase.from('price_data').insert(priceInserts)
        break
      
      default:
        throw new Error('Invalid action')
    }

    return new Response(
      JSON.stringify({ success: true, data: result }),
      {
        headers: { ...corsHeaders, 'Content-Type': 'application/json' }
      }
    )

  } catch (error) {
    return new Response(
      JSON.stringify({ error: error.message }),
      {
        status: 400,
        headers: { ...corsHeaders, 'Content-Type': 'application/json' }
      }
    )
  }
})

Frontend Integration: React Trading Dashboard

Create a React component for your trading dashboard:

// TradingDashboard.tsx
import React, { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(process.env.REACT_APP_SUPABASE_URL!, process.env.REACT_APP_SUPABASE_ANON_KEY!)

interface Portfolio {
  asset: string
  free_balance: number
  locked_balance: number
  total_balance: number
  unrealized_pnl: number
}

interface Order {
  id: string
  symbol: string
  side: string
  type: string
  quantity: number
  price: number
  status: string
  created_at: string
}

export const TradingDashboard: React.FC = () => {
  const [portfolio, setPortfolio] = useState<Portfolio[]>([])
  const [orders, setOrders] = useState<Order[]>([])
  const [loading, setLoading] = useState(true)
  const [selectedAccount, setSelectedAccount] = useState<string | null>(null)

  useEffect(() => {
    loadDashboardData()
    setupRealtimeSubscriptions()
  }, [selectedAccount])

  const loadDashboardData = async () => {
    try {
      // Load portfolio holdings
      const { data: portfolioData, error: portfolioError } = await supabase
        .from('portfolio_holdings')
        .select('*')
        .eq('account_id', selectedAccount)
        .order('total_balance', { ascending: false })

      if (portfolioError) throw portfolioError
      setPortfolio(portfolioData || [])

      // Load recent orders
      const { data: ordersData, error: ordersError } = await supabase
        .from('trading_orders')
        .select('*')
        .eq('account_id', selectedAccount)
        .order('created_at', { ascending: false })
        .limit(50)

      if (ordersError) throw ordersError
      setOrders(ordersData || [])

    } catch (error) {
      console.error('Error loading dashboard data:', error)
    } finally {
      setLoading(false)
    }
  }

  const setupRealtimeSubscriptions = () => {
    // Subscribe to portfolio changes
    const portfolioSubscription = supabase
      .channel('portfolio-changes')
      .on('postgres_changes', 
        { 
          event: '*', 
          schema: 'public', 
          table: 'portfolio_holdings',
          filter: `account_id=eq.${selectedAccount}`
        }, 
        (payload) => {
          console.log('Portfolio update:', payload)
          loadDashboardData()
        }
      )
      .subscribe()

    // Subscribe to new orders
    const ordersSubscription = supabase
      .channel('orders-changes')
      .on('postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'trading_orders',
          filter: `account_id=eq.${selectedAccount}`
        },
        (payload) => {
          console.log('New order:', payload)
          setOrders(prev => [payload.new as Order, ...prev])
        }
      )
      .subscribe()

    return () => {
      portfolioSubscription.unsubscribe()
      ordersSubscription.unsubscribe()
    }
  }

  const syncWithBinance = async () => {
    try {
      setLoading(true)
      
      // Call Edge Function to sync account data
      const { data, error } = await supabase.functions.invoke('binance-api-handler', {
        body: {
          action: 'getAccountInfo',
          accountId: selectedAccount
        }
      })

      if (error) throw error

      // Update portfolio holdings
      const balances = data.data.balances.filter((balance: any) => 
        parseFloat(balance.free) > 0 || parseFloat(balance.locked) > 0
      )

      for (const balance of balances) {
        await supabase.from('portfolio_holdings').upsert({
          account_id: selectedAccount,
          asset: balance.asset,
          free_balance: parseFloat(balance.free),
          locked_balance: parseFloat(balance.locked),
          updated_at: new Date().toISOString()
        })
      }

      await loadDashboardData()
      
    } catch (error) {
      console.error('Error syncing with Binance:', error)
    } finally {
      setLoading(false)
    }
  }

  if (loading) {
    return <div className="text-center p-8">Loading dashboard...</div>
  }

  return (
    <div className="max-w-7xl mx-auto p-6">
      <div className="flex justify-between items-center mb-8">
        <h1 className="text-3xl font-bold">Trading Dashboard</h1>
        <button 
          onClick={syncWithBinance}
          className="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-2 rounded"
        >
          Sync with Binance
        </button>
      </div>

      {/* Portfolio Overview */}
      <div className="bg-white rounded-lg shadow mb-8 p-6">
        <h2 className="text-xl font-semibold mb-4">Portfolio Holdings</h2>
        <div className="overflow-x-auto">
          <table className="min-w-full table-auto">
            <thead>
              <tr className="bg-gray-50">
                <th className="px-4 py-2 text-left">Asset</th>
                <th className="px-4 py-2 text-right">Free Balance</th>
                <th className="px-4 py-2 text-right">Locked Balance</th>
                <th className="px-4 py-2 text-right">Total Balance</th>
                <th className="px-4 py-2 text-right">Unrealized P&L</th>
              </tr>
            </thead>
            <tbody>
              {portfolio.map((holding) => (
                <tr key={holding.asset} className="border-t">
                  <td className="px-4 py-2 font-medium">{holding.asset}</td>
                  <td className="px-4 py-2 text-right">{holding.free_balance.toFixed(8)}</td>
                  <td className="px-4 py-2 text-right">{holding.locked_balance.toFixed(8)}</td>
                  <td className="px-4 py-2 text-right font-semibold">{holding.total_balance.toFixed(8)}</td>
                  <td className={`px-4 py-2 text-right font-semibold ${
                    holding.unrealized_pnl >= 0 ? 'text-green-600' : 'text-red-600'
                  }`}>
                    {holding.unrealized_pnl.toFixed(2)}%
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>

      {/* Recent Orders */}
      <div className="bg-white rounded-lg shadow p-6">
        <h2 className="text-xl font-semibold mb-4">Recent Orders</h2>
        <div className="overflow-x-auto">
          <table className="min-w-full table-auto">
            <thead>
              <tr className="bg-gray-50">
                <th className="px-4 py-2 text-left">Symbol</th>
                <th className="px-4 py-2 text-left">Side</th>
                <th className="px-4 py-2 text-left">Type</th>
                <th className="px-4 py-2 text-right">Quantity</th>
                <th className="px-4 py-2 text-right">Price</th>
                <th className="px-4 py-2 text-left">Status</th>
                <th className="px-4 py-2 text-left">Time</th>
              </tr>
            </thead>
            <tbody>
              {orders.map((order) => (
                <tr key={order.id} className="border-t">
                  <td className="px-4 py-2 font-medium">{order.symbol}</td>
                  <td className={`px-4 py-2 font-semibold ${
                    order.side === 'BUY' ? 'text-green-600' : 'text-red-600'
                  }`}>
                    {order.side}
                  </td>
                  <td className="px-4 py-2">{order.type}</td>
                  <td className="px-4 py-2 text-right">{order.quantity}</td>
                  <td className="px-4 py-2 text-right">{order.price}</td>
                  <td className="px-4 py-2">
                    <span className={`px-2 py-1 rounded-full text-xs ${
                      order.status === 'FILLED' ? 'bg-green-100 text-green-800' :
                      order.status === 'CANCELED' ? 'bg-red-100 text-red-800' :
                      'bg-yellow-100 text-yellow-800'
                    }`}>
                      {order.status}
                    </span>
                  </td>
                  <td className="px-4 py-2 text-sm text-gray-600">
                    {new Date(order.created_at).toLocaleString()}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  )
}

Real-time Price Monitoring with WebSockets

For live price data, you can use Binance WebSocket streams:

// PriceStreamService.ts
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(process.env.REACT_APP_SUPABASE_URL!, process.env.REACT_APP_SUPABASE_ANON_KEY!)

export class PriceStreamService {
  private ws: WebSocket | null = null
  private symbols: string[] = []

  constructor(symbols: string[]) {
    this.symbols = symbols.map(s => s.toLowerCase())
    this.connect()
  }

  private connect() {
    const streams = this.symbols.map(symbol => `${symbol}@ticker`)
    const wsUrl = `wss://stream.binance.com:9443/ws/${streams.join('/')}`

    this.ws = new WebSocket(wsUrl)

    this.ws.onopen = () => {
      console.log('Connected to Binance WebSocket')
    }

    this.ws.onmessage = async (event) => {
      try {
        const data = JSON.parse(event.data)
        
        // Store price data in Supabase
        await supabase.from('price_data').insert({
          symbol: data.s,
          price: parseFloat(data.c),
          volume: parseFloat(data.v),
          timestamp: new Date(data.E).toISOString()
        })

        // Broadcast to subscribers
        this.onPriceUpdate?.(data)

      } catch (error) {
        console.error('Error processing price data:', error)
      }
    }

    this.ws.onerror = (error) => {
      console.error('WebSocket error:', error)
    }

    this.ws.onclose = () => {
      console.log('WebSocket connection closed, reconnecting...')
      setTimeout(() => this.connect(), 5000)
    }
  }

  public onPriceUpdate?: (data: any) => void

  public disconnect() {
    if (this.ws) {
      this.ws.close()
      this.ws = null
    }
  }
}

// Usage
const priceStream = new PriceStreamService(['BTCUSDT', 'ETHUSDT', 'ADAUSDT'])

priceStream.onPriceUpdate = (data) => {
  console.log(`${data.s}: ${data.c} (${data.P}%)`)
}

Automating Trading Strategies

Create a simple trading strategy with Moving Averages:

// TradingStrategy.ts
interface TradingSignal {
  symbol: string
  action: 'BUY' | 'SELL' | 'HOLD'
  confidence: number
  reason: string
}

export class MovingAverageStrategy {
  private supabase: any
  private symbol: string
  private shortPeriod: number
  private longPeriod: number

  constructor(supabase: any, symbol: string, shortPeriod = 10, longPeriod = 30) {
    this.supabase = supabase
    this.symbol = symbol
    this.shortPeriod = shortPeriod
    this.longPeriod = longPeriod
  }

  async analyze(): Promise<TradingSignal> {
    // Get recent price data
    const { data: priceData, error } = await this.supabase
      .from('price_data')
      .select('price, timestamp')
      .eq('symbol', this.symbol)
      .order('timestamp', { ascending: false })
      .limit(this.longPeriod)

    if (error || !priceData || priceData.length < this.longPeriod) {
      return {
        symbol: this.symbol,
        action: 'HOLD',
        confidence: 0,
        reason: 'Insufficient data'
      }
    }

    // Calculate moving averages
    const prices = priceData.map(d => parseFloat(d.price)).reverse()
    const shortMA = this.calculateMA(prices.slice(-this.shortPeriod))
    const longMA = this.calculateMA(prices.slice(-this.longPeriod))
    const prevShortMA = this.calculateMA(prices.slice(-this.shortPeriod - 1, -1))
    const prevLongMA = this.calculateMA(prices.slice(-this.longPeriod - 1, -1))

    // Determine signal
    if (shortMA > longMA && prevShortMA <= prevLongMA) {
      return {
        symbol: this.symbol,
        action: 'BUY',
        confidence: Math.min((shortMA - longMA) / longMA * 100, 100),
        reason: `Short MA (${shortMA.toFixed(2)}) crossed above Long MA (${longMA.toFixed(2)})`
      }
    } else if (shortMA < longMA && prevShortMA >= prevLongMA) {
      return {
        symbol: this.symbol,
        action: 'SELL',
        confidence: Math.min((longMA - shortMA) / shortMA * 100, 100),
        reason: `Short MA (${shortMA.toFixed(2)}) crossed below Long MA (${longMA.toFixed(2)})`
      }
    }

    return {
      symbol: this.symbol,
      action: 'HOLD',
      confidence: 50,
      reason: `Short MA: ${shortMA.toFixed(2)}, Long MA: ${longMA.toFixed(2)} - No clear signal`
    }
  }

  private calculateMA(prices: number[]): number {
    return prices.reduce((sum, price) => sum + price, 0) / prices.length
  }
}

// Usage
const strategy = new MovingAverageStrategy(supabase, 'BTCUSDT')
const signal = await strategy.analyze()

if (signal.action !== 'HOLD' && signal.confidence > 70) {
  console.log(`Strong ${signal.action} signal for ${signal.symbol}: ${signal.reason}`)
  // Execute trade via Binance API
}

Risk Management and Performance Tracking

Implement a Risk Management System:

// RiskManager.ts
export class RiskManager {
  private maxPositionSize: number = 0.1 // 10% of portfolio
  private maxDailyLoss: number = 0.05 // 5% daily loss limit
  private stopLossPercentage: number = 0.02 // 2% stop loss

  async checkTradeRisk(
    userId: string, 
    accountId: string, 
    symbol: string, 
    side: 'BUY' | 'SELL',
    quantity: number,
    price: number
  ): Promise<{ allowed: boolean, reason?: string }> {
    
    // Check portfolio size limit
    const portfolioValue = await this.getPortfolioValue(userId, accountId)
    const tradeValue = quantity * price
    
    if (tradeValue > portfolioValue * this.maxPositionSize) {
      return {
        allowed: false,
        reason: `Trade size (${(tradeValue/portfolioValue*100).toFixed(1)}%) exceeds maximum position size (${this.maxPositionSize*100}%)`
      }
    }

    // Check daily loss limit
    const dailyPnL = await this.getDailyPnL(userId, accountId)
    if (dailyPnL < -portfolioValue * this.maxDailyLoss) {
      return {
        allowed: false,
        reason: `Daily loss limit reached (${(dailyPnL/portfolioValue*100).toFixed(1)}%)`
      }
    }

    return { allowed: true }
  }

  async setStopLoss(orderId: string, currentPrice: number, side: 'BUY' | 'SELL') {
    const stopPrice = side === 'BUY' 
      ? currentPrice * (1 - this.stopLossPercentage)
      : currentPrice * (1 + this.stopLossPercentage)

    // Create stop loss order via Binance API
    console.log(`Setting stop loss for order ${orderId} at ${stopPrice}`)
  }

  private async getPortfolioValue(userId: string, accountId: string): Promise<number> {
    // Calculate total portfolio value in USDT
    // Implementation depends on your price conversion logic
    return 10000 // Placeholder
  }

  private async getDailyPnL(userId: string, accountId: string): Promise<number> {
    // Calculate daily profit/loss
    // Implementation depends on your PnL calculation logic
    return -50 // Placeholder
  }
}

Performance Analytics Dashboard

Create detailed performance metrics:

// PerformanceAnalytics.ts
export class PerformanceAnalytics {
  private supabase: any

  constructor(supabase: any) {
    this.supabase = supabase
  }

  async getPerformanceMetrics(userId: string, accountId: string, period: string = '30d') {
    const endDate = new Date()
    const startDate = new Date()
    startDate.setDate(endDate.getDate() - parseInt(period))

    // Get all trades in period
    const { data: trades } = await this.supabase
      .from('trading_orders')
      .select('*')
      .eq('user_id', userId)
      .eq('account_id', accountId)
      .eq('status', 'FILLED')
      .gte('executed_at', startDate.toISOString())
      .lte('executed_at', endDate.toISOString())

    if (!trades || trades.length === 0) {
      return null
    }

    // Calculate metrics
    const totalTrades = trades.length
    const winningTrades = trades.filter(t => this.isWinningTrade(t)).length
    const losingTrades = totalTrades - winningTrades
    const winRate = (winningTrades / totalTrades) * 100

    const totalPnL = trades.reduce((sum, trade) => sum + this.calculateTradePnL(trade), 0)
    const avgWin = winningTrades > 0 
      ? trades.filter(t => this.isWinningTrade(t)).reduce((sum, t) => sum + this.calculateTradePnL(t), 0) / winningTrades
      : 0
    const avgLoss = losingTrades > 0
      ? Math.abs(trades.filter(t => !this.isWinningTrade(t)).reduce((sum, t) => sum + this.calculateTradePnL(t), 0) / losingTrades)
      : 0

    const profitFactor = avgLoss > 0 ? (avgWin * winningTrades) / (avgLoss * losingTrades) : 0
    const sharpeRatio = this.calculateSharpeRatio(trades)

    return {
      period,
      totalTrades,
      winningTrades,
      losingTrades,
      winRate: parseFloat(winRate.toFixed(2)),
      totalPnL: parseFloat(totalPnL.toFixed(2)),
      avgWin: parseFloat(avgWin.toFixed(2)),
      avgLoss: parseFloat(avgLoss.toFixed(2)),
      profitFactor: parseFloat(profitFactor.toFixed(2)),
      sharpeRatio: parseFloat(sharpeRatio.toFixed(2)),
      bestTrade: Math.max(...trades.map(t => this.calculateTradePnL(t))),
      worstTrade: Math.min(...trades.map(t => this.calculateTradePnL(t)))
    }
  }

  private isWinningTrade(trade: any): boolean {
    return this.calculateTradePnL(trade) > 0
  }

  private calculateTradePnL(trade: any): number {
    // Simplified PnL calculation
    // In practice, you'd need to track entry/exit prices more carefully
    return trade.side === 'BUY' ? 10 : -5 // Placeholder
  }

  private calculateSharpeRatio(trades: any[]): number {
    const returns = trades.map(t => this.calculateTradePnL(t))
    const avgReturn = returns.reduce((sum, r) => sum + r, 0) / returns.length
    const volatility = Math.sqrt(
      returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / returns.length
    )
    
    return volatility > 0 ? avgReturn / volatility : 0
  }
}

Deployment and Production Setup

Environment Variables

# .env.local
REACT_APP_SUPABASE_URL=your-supabase-url
REACT_APP_SUPABASE_ANON_KEY=your-anon-key

# Supabase Edge Functions .env
BINANCE_API_KEY=your-binance-api-key
BINANCE_API_SECRET=your-binance-secret
SUPABASE_URL=your-supabase-url
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

Deploy Edge Functions

# Terminal
supabase functions deploy binance-api-handler

Security Aspects and Best Practices

API Key Encryption:

  • Never use plaintext for API keys
  • Implement AES-256 encryption for sensitive data
  • Rotate API keys regularly

Rate Limiting:

  • Implement rate limiting for API calls
  • Use exponential backoff on errors
  • Cache frequently accessed data

Monitoring and Alerting:

  • Monitor API limits and errors
  • Set alerts for unusual trading activities
  • Log all critical actions

Advanced Features

Backtesting System:

With your stored price data, you can test strategies against historical data before deploying them live.

Multi-Exchange Support:

Extend the system to other exchanges like Coinbase Pro, Kraken, or KuCoin with similar API patterns.

Social Trading:

Share strategies with other users and implement copy-trading features.

Conclusion

The combination of Binance API and Supabase provides you with a powerful, scalable foundation for professional trading applications. You now have:

  • ✅ Complete Binance API integration
  • ✅ Structured data storage in PostgreSQL
  • ✅ Real-time trading dashboard
  • ✅ Automated trading strategies
  • ✅ Risk management system
  • ✅ Performance analytics
  • ✅ Production-ready deployment

Start with the testnet, implement your first strategies, and then scale to live trading. With this robust architecture, you're well-equipped for professional cryptocurrency trading.

Disclaimer: Trading cryptocurrency involves high risks. Only invest what you can afford to lose and thoroughly test all strategies on testnet first.

📚 Recommended Books for Deep Dive:

Algorithmic Trading:

  • Algorithmic Trading: Winning Strategies and Their Rationale - Comprehensive introduction to algorithmic trading
  • Quantitative Trading: How to Build Your Own Algorithmic Trading Business - Practical guide for building trading systems

Python for Finance:

  • Python for Finance: Mastering Data-Driven Finance - Financial analysis with Python
  • Hands-On Machine Learning for Algorithmic Trading - ML strategies for trading

Tags

  • Trading
  • Crypto
  • Supabase

Comments

About text formats

Restricted HTML

  • Allowed HTML tags: <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.

Related articles

How to Build a Cryptocurrency Trading Bot: Complete Guide
Grid Trading Strategy Implementation: Build Your First Profitable Crypto Bot
Crypto Trading Bot Backtesting Framework: Validate Strategies Before Risking Real Money
Paper Trading Implementation: Bridge From Backtest to Live Trading
Risk Management and Logging Systems: Production-Ready Crypto Trading Bots

About the author

Nikolai Fischer is the founder of Kommune3 (since 2007) and a leading expert in Drupal development and tech entrepreneurship. With 17+ years of experience, he has led hundreds of projects and achieved #1 on Hacker News. As host of the "Kommit mich" podcast and founder of skillution, he combines technical expertise with entrepreneurial thinking. His articles about Supabase, modern web development, and systematic problem-solving have influenced thousands of developers worldwide.

Ihre Anmeldung konnte nicht gespeichert werden. Bitte versuchen Sie es erneut.
Ihre Anmeldung war erfolgreich.

Newsletter

Join a growing community of friendly readers. From time to time I share my thoughts about rational thinking, productivity and life.

Nikolai Fischer

✌ Hi, I'm Niko
Entrepreneur, developer & podcaster

Contact me:

  • E-Mail
  • Phone
  • LinkedIn

My Reading List

  • Quantitative Trading: How to Build Your Own Algorithmic Trading Business (Wiley Trading) - Ernest P. Chan
  • Hands-On Machine Learning for Algorithmic Trading: Design and implement investment strategies based on smart algorithms that learn from data using Python - Stefan Jansen
  • Algorithmic Trading - Ernie Chan
  • Let Me Tell You a Story: Tales Along the Road to Happiness - Jorge Bucay
  • Mindset: The New Psychology of Success - Carol S. Dweck
more
RSS feed