🎸
🚀 Beta Running
PYNGUP: Rebellion against toxic productivity
Beta limited to 100 spots. Tasks become social commitments instead of lonely to-dos.
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.
Binance API provides access to the world's largest crypto exchange with:
Supabase as a backend delivers:
Start with a new Supabase project at database.new and create Binance API keys in your Binance Account.
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);
Create a new Edge Function for Binance API integration:
# Terminal
supabase functions new binance-api-handler
// 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}×tamp=${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' }
}
)
}
})
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>
)
}
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}%)`)
}
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
}
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
}
}
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
}
}
# .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
# Terminal
supabase functions deploy binance-api-handler
API Key Encryption:
Rate Limiting:
Monitoring and Alerting:
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.
The combination of Binance API and Supabase provides you with a powerful, scalable foundation for professional trading applications. You now have:
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.
Algorithmic Trading:
Python for Finance:
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.
Comments