Example: Automated Trading Bot
A sophisticated trading bot that implements multiple strategies using Saros SDKs, including arbitrage detection, grid trading, and yield optimization with comprehensive risk management.
Overview
This example demonstrates:
- Multi-strategy automated trading
- Real-time arbitrage detection across pools
- Grid trading for range-bound markets
- Automated yield farming optimization
- Risk management and position sizing
- Performance monitoring and alerts
- Backtesting framework
⚠️ Disclaimer: This is educational code. Never use automated trading with funds you can't afford to lose. Always test thoroughly on devnet first.
Complete Implementation
Project Setup
# Create project
mkdir saros-trading-bot
cd saros-trading-bot
# Initialize with TypeScript
npm init -y
npm install @saros-finance/sdk @saros-finance/dlmm-sdk @solana/web3.js
npm install ws node-cron winston dotenv bn.js
npm install -D @types/node @types/ws typescript ts-node nodemon jest
# Create project structure
mkdir -p src/{strategies,utils,monitoring,backtest}
mkdir -p tests/{unit,integration}
Core Trading Engine
// src/TradingBot.ts
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import {
getSwapAmountSaros,
swapSaros,
getPoolInfo,
getAllPools,
getInfoTokenByMint,
convertBalanceToWei,
convertWeiToBalance
} from '@saros-finance/sdk';
import { WebSocketConnection } from './utils/WebSocketConnection';
import { Logger } from './utils/Logger';
import { RiskManager } from './utils/RiskManager';
import { PerformanceTracker } from './monitoring/PerformanceTracker';
export class SarosTradingBot {
private connection: Connection;
private wallet: Keypair;
private logger: Logger;
private riskManager: RiskManager;
private performanceTracker: PerformanceTracker;
private wsConnection: WebSocketConnection;
private isRunning = false;
private strategies: Map<string, TradingStrategy> = new Map();
private positions: Map<string, Position> = new Map();
private config: BotConfig;
constructor(config: BotConfig) {
this.config = config;
this.connection = new Connection(config.rpcUrl, 'confirmed');
this.wallet = Keypair.fromSecretKey(new Uint8Array(config.privateKey));
this.logger = new Logger(config.logLevel);
this.riskManager = new RiskManager(config.riskManagement);
this.performanceTracker = new PerformanceTracker();
this.wsConnection = new WebSocketConnection(config.wsUrl);
this.logger.info('Trading bot initialized', {
wallet: this.wallet.publicKey.toString(),
strategies: config.enabledStrategies
});
}
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('Bot is already running');
}
try {
this.logger.info('Starting Saros Trading Bot...');
// Initialize strategies
await this.initializeStrategies();
// Start monitoring
await this.startMonitoring();
// Start WebSocket connections
await this.wsConnection.connect();
this.isRunning = true;
this.logger.info('✅ Trading bot started successfully');
// Main trading loop
await this.runTradingLoop();
} catch (error) {
this.logger.error('Failed to start trading bot:', error);
throw error;
}
}
async stop(): Promise<void> {
this.logger.info('Stopping trading bot...');
this.isRunning = false;
// Close WebSocket connections
await this.wsConnection.disconnect();
// Close all positions if configured
if (this.config.closePositionsOnStop) {
await this.closeAllPositions();
}
this.logger.info('✅ Trading bot stopped');
}
private async initializeStrategies(): Promise<void> {
const { enabledStrategies } = this.config;
// Initialize arbitrage strategy
if (enabledStrategies.arbitrage) {
const arbitrage = new ArbitrageStrategy(
this.connection,
this.wallet,
this.config.strategies.arbitrage
);
this.strategies.set('arbitrage', arbitrage);
}
// Initialize grid trading strategy
if (enabledStrategies.gridTrading) {
const gridTrading = new GridTradingStrategy(
this.connection,
this.wallet,
this.config.strategies.gridTrading
);
this.strategies.set('gridTrading', gridTrading);
}
// Initialize yield optimization strategy
if (enabledStrategies.yieldOptimization) {
const yieldOptimization = new YieldOptimizationStrategy(
this.connection,
this.wallet,
this.config.strategies.yieldOptimization
);
this.strategies.set('yieldOptimization', yieldOptimization);
}
this.logger.info(`Initialized ${this.strategies.size} strategies`);
}
private async runTradingLoop(): Promise<void> {
while (this.isRunning) {
try {
// Check portfolio health
await this.performHealthCheck();
// Execute strategies
for (const [name, strategy] of this.strategies) {
if (!this.isRunning) break;
try {
this.logger.debug(`Executing strategy: ${name}`);
const signals = await strategy.analyze();
for (const signal of signals) {
if (await this.validateSignal(signal)) {
await this.executeSignal(signal);
}
}
} catch (error) {
this.logger.error(`Strategy ${name} failed:`, error);
}
}
// Update performance metrics
await this.updatePerformanceMetrics();
// Wait before next iteration
await this.sleep(this.config.loopInterval);
} catch (error) {
this.logger.error('Trading loop error:', error);
await this.sleep(5000); // Wait 5s before retrying
}
}
}
private async validateSignal(signal: TradingSignal): Promise<boolean> {
// Risk management validation
if (!this.riskManager.validateSignal(signal)) {
this.logger.warn('Signal rejected by risk manager:', signal);
return false;
}
// Position size validation
const maxPositionSize = this.config.riskManagement.maxPositionSize;
if (signal.amount > maxPositionSize) {
this.logger.warn('Signal amount exceeds max position size:', signal);
return false;
}
// Balance validation
const balance = await this.getTokenBalance(signal.fromToken);
if (balance < signal.amount) {
this.logger.warn('Insufficient balance for signal:', signal);
return false;
}
return true;
}
private async executeSignal(signal: TradingSignal): Promise<void> {
try {
this.logger.info('Executing trading signal:', signal);
// Get fresh quote
const quote = await this.getSwapQuote(
signal.fromToken,
signal.toToken,
signal.amount,
signal.slippage
);
// Validate quote meets expectations
if (quote.priceImpact > signal.maxPriceImpact) {
this.logger.warn('Price impact too high, skipping signal');
return;
}
// Execute swap
const result = await this.executeSwap(quote);
if (result.success) {
// Record trade
await this.recordTrade({
signal,
quote,
result,
timestamp: new Date()
});
this.logger.info('✅ Trade executed successfully:', {
signature: result.signature,
inputAmount: signal.amount,
outputAmount: result.outputAmount
});
} else {
this.logger.error('❌ Trade execution failed:', result.error);
}
} catch (error) {
this.logger.error('Signal execution error:', error);
}
}
private async getSwapQuote(
fromToken: string,
toToken: string,
amount: number,
slippage: number
): Promise<any> {
// Find pool
const pools = await getAllPools(this.connection);
const pool = pools.find(p =>
(p.tokenA.mint === fromToken && p.tokenB.mint === toToken) ||
(p.tokenA.mint === toToken && p.tokenB.mint === fromToken)
);
if (!pool) {
throw new Error(`No pool found for ${fromToken}/${toToken}`);
}
// Build pool params
const poolParams = {
address: pool.address,
tokens: {
[pool.tokenA.mint]: {
mintAddress: pool.tokenA.mint,
decimals: pool.tokenA.decimals,
addressSPL: 'will-be-resolved'
},
[pool.tokenB.mint]: {
mintAddress: pool.tokenB.mint,
decimals: pool.tokenB.decimals,
addressSPL: 'will-be-resolved'
}
},
tokenIds: [pool.tokenA.mint, pool.tokenB.mint]
};
// Get quote
const quote = await getSwapAmountSaros(
this.connection,
fromToken,
toToken,
amount,
slippage,
poolParams
);
return quote;
}
// ... Additional methods
}
// Strategy interfaces and implementations
export abstract class TradingStrategy {
protected connection: Connection;
protected wallet: Keypair;
protected config: any;
protected logger: Logger;
constructor(connection: Connection, wallet: Keypair, config: any) {
this.connection = connection;
this.wallet = wallet;
this.config = config;
this.logger = new Logger();
}
abstract analyze(): Promise<TradingSignal[]>;
}
export class ArbitrageStrategy extends TradingStrategy {
async analyze(): Promise<TradingSignal[]> {
const signals: TradingSignal[] = [];
try {
// Get all pools
const pools = await getAllPools(this.connection);
// Find arbitrage opportunities
const tokenPairs = this.findTokenPairs(pools);
for (const pair of tokenPairs) {
const arbitrageOpportunity = await this.detectArbitrage(pair);
if (arbitrageOpportunity && arbitrageOpportunity.profitability > this.config.minProfitability) {
signals.push({
type: 'arbitrage',
fromToken: arbitrageOpportunity.fromToken,
toToken: arbitrageOpportunity.toToken,
amount: arbitrageOpportunity.optimalAmount,
expectedProfit: arbitrageOpportunity.profit,
pools: arbitrageOpportunity.pools,
slippage: this.config.slippage,
maxPriceImpact: this.config.maxPriceImpact,
priority: arbitrageOpportunity.profitability > 1.0 ? 'high' : 'medium'
});
}
}
this.logger.info(`Found ${signals.length} arbitrage opportunities`);
return signals;
} catch (error) {
this.logger.error('Arbitrage analysis failed:', error);
return [];
}
}
private async detectArbitrage(tokenPair: TokenPair): Promise<ArbitrageOpportunity | null> {
const { tokenA, tokenB } = tokenPair;
// Find all pools for this token pair
const pools = await this.findPoolsForPair(tokenA, tokenB);
if (pools.length < 2) {
return null; // Need at least 2 pools for arbitrage
}
// Calculate prices in all pools
const prices = await Promise.all(
pools.map(async pool => {
const quote = await this.getPoolPrice(pool, tokenA, tokenB, 1);
return {
pool: pool.address,
price: parseFloat(quote.amountOut),
liquidity: pool.tvl
};
})
);
// Find price discrepancy
prices.sort((a, b) => a.price - b.price);
const lowestPrice = prices[0];
const highestPrice = prices[prices.length - 1];
const priceDifference = highestPrice.price - lowestPrice.price;
const profitability = (priceDifference / lowestPrice.price) * 100;
// Calculate optimal trade amount based on liquidity
const maxAmount = Math.min(
lowestPrice.liquidity * 0.1, // Max 10% of pool liquidity
this.config.maxTradeAmount
);
if (profitability > this.config.minProfitability) {
return {
fromToken: tokenA,
toToken: tokenB,
buyPool: lowestPrice.pool,
sellPool: highestPrice.pool,
profitability,
profit: priceDifference * maxAmount,
optimalAmount: maxAmount,
pools: [lowestPrice.pool, highestPrice.pool]
};
}
return null;
}
private findTokenPairs(pools: any[]): TokenPair[] {
const pairs = new Set<string>();
const result: TokenPair[] = [];
for (const pool of pools) {
const pairKey = [pool.tokenA.mint, pool.tokenB.mint].sort().join('-');
if (!pairs.has(pairKey)) {
pairs.add(pairKey);
result.push({
tokenA: pool.tokenA.mint,
tokenB: pool.tokenB.mint
});
}
}
return result;
}
}
export class GridTradingStrategy extends TradingStrategy {
private gridOrders: Map<string, GridOrder[]> = new Map();
async analyze(): Promise<TradingSignal[]> {
const signals: TradingSignal[] = [];
try {
for (const [symbol, gridConfig] of Object.entries(this.config.grids)) {
const currentPrice = await this.getCurrentPrice(symbol);
const existingOrders = this.gridOrders.get(symbol) || [];
// Check if any grid orders should be executed
for (const order of existingOrders) {
if (this.shouldExecuteOrder(order, currentPrice)) {
signals.push({
type: 'grid',
fromToken: order.side === 'buy' ? 'USDC' : symbol,
toToken: order.side === 'buy' ? symbol : 'USDC',
amount: order.amount,
expectedPrice: order.price,
slippage: this.config.slippage,
maxPriceImpact: 0.5,
priority: 'medium',
gridOrderId: order.id
});
}
}
// Create new grid orders if needed
const newOrders = await this.createGridOrders(symbol, currentPrice, gridConfig);
if (newOrders.length > 0) {
this.gridOrders.set(symbol, [...existingOrders, ...newOrders]);
this.logger.info(`Created ${newOrders.length} new grid orders for ${symbol}`);
}
}
return signals;
} catch (error) {
this.logger.error('Grid trading analysis failed:', error);
return [];
}
}
private async createGridOrders(
symbol: string,
currentPrice: number,
config: GridConfig
): Promise<GridOrder[]> {
const orders: GridOrder[] = [];
const { gridSpacing, numGrids, orderSize } = config;
// Create buy orders below current price
for (let i = 1; i <= numGrids; i++) {
const buyPrice = currentPrice * (1 - (gridSpacing * i / 100));
orders.push({
id: `${symbol}-buy-${i}-${Date.now()}`,
side: 'buy',
price: buyPrice,
amount: orderSize,
status: 'pending',
createdAt: new Date()
});
}
// Create sell orders above current price
for (let i = 1; i <= numGrids; i++) {
const sellPrice = currentPrice * (1 + (gridSpacing * i / 100));
orders.push({
id: `${symbol}-sell-${i}-${Date.now()}`,
side: 'sell',
price: sellPrice,
amount: orderSize,
status: 'pending',
createdAt: new Date()
});
}
return orders;
}
private shouldExecuteOrder(order: GridOrder, currentPrice: number): boolean {
const tolerance = 0.001; // 0.1% tolerance
return (
(order.side === 'buy' && currentPrice <= order.price * (1 + tolerance)) ||
(order.side === 'sell' && currentPrice >= order.price * (1 - tolerance))
);
}
}
export class YieldOptimizationStrategy extends TradingStrategy {
async analyze(): Promise<TradingSignal[]> {
const signals: TradingSignal[] = [];
try {
// Analyze current positions for optimization opportunities
const positions = await this.getCurrentPositions();
for (const position of positions) {
// Check if there are better yielding opportunities
const betterOpportunity = await this.findBetterYieldOpportunity(position);
if (betterOpportunity && betterOpportunity.apyImprovement > this.config.minAPYImprovement) {
signals.push({
type: 'yield_optimization',
fromToken: position.token,
toToken: betterOpportunity.token,
amount: position.amount * this.config.migrationPercentage,
expectedAPYImprovement: betterOpportunity.apyImprovement,
currentAPY: position.apy,
targetAPY: betterOpportunity.apy,
slippage: this.config.slippage,
maxPriceImpact: 1.0,
priority: betterOpportunity.apyImprovement > 5.0 ? 'high' : 'medium'
});
}
}
return signals;
} catch (error) {
this.logger.error('Yield optimization analysis failed:', error);
return [];
}
}
private async findBetterYieldOpportunity(position: CurrentPosition): Promise<YieldOpportunity | null> {
// Get all staking pools for the same token
const stakingPools = await this.getStakingPoolsForToken(position.token);
// Find highest APY pool
const bestPool = stakingPools
.filter(pool => pool.isActive)
.sort((a, b) => b.apy - a.apy)[0];
if (!bestPool || bestPool.apy <= position.apy) {
return null;
}
const apyImprovement = bestPool.apy - position.apy;
return {
token: position.token,
pool: bestPool.address,
apy: bestPool.apy,
apyImprovement,
lockPeriod: bestPool.lockPeriod,
estimatedAdditionalYield: position.amount * (apyImprovement / 100) / 365
};
}
}
Risk Management System
// src/utils/RiskManager.ts
export class RiskManager {
private config: RiskManagementConfig;
private tradeHistory: Trade[] = [];
private currentExposure: Map<string, number> = new Map();
constructor(config: RiskManagementConfig) {
this.config = config;
}
validateSignal(signal: TradingSignal): boolean {
// Check daily loss limit
if (this.getDailyLoss() > this.config.maxDailyLoss) {
return false;
}
// Check position concentration
const tokenExposure = this.currentExposure.get(signal.fromToken) || 0;
if (tokenExposure + signal.amount > this.config.maxTokenExposure) {
return false;
}
// Check price impact
if (signal.maxPriceImpact > this.config.maxPriceImpact) {
return false;
}
// Check trade frequency (anti-spam)
const recentTrades = this.getRecentTrades(5 * 60 * 1000); // 5 minutes
if (recentTrades.length > this.config.maxTradesPerWindow) {
return false;
}
// Check minimum profitability
if (signal.expectedProfit && signal.expectedProfit < this.config.minProfitThreshold) {
return false;
}
return true;
}
recordTrade(trade: Trade): void {
this.tradeHistory.push(trade);
// Update current exposure
const fromExposure = this.currentExposure.get(trade.fromToken) || 0;
const toExposure = this.currentExposure.get(trade.toToken) || 0;
this.currentExposure.set(trade.fromToken, fromExposure - trade.inputAmount);
this.currentExposure.set(trade.toToken, toExposure + trade.outputAmount);
// Cleanup old trades (keep last 1000)
if (this.tradeHistory.length > 1000) {
this.tradeHistory = this.tradeHistory.slice(-1000);
}
}
getDailyPnL(): number {
const dayStart = new Date();
dayStart.setHours(0, 0, 0, 0);
return this.tradeHistory
.filter(trade => trade.timestamp >= dayStart)
.reduce((total, trade) => total + (trade.pnl || 0), 0);
}
private getDailyLoss(): number {
return Math.max(0, -this.getDailyPnL());
}
private getRecentTrades(windowMs: number): Trade[] {
const cutoff = new Date(Date.now() - windowMs);
return this.tradeHistory.filter(trade => trade.timestamp >= cutoff);
}
}
Performance Monitoring
// src/monitoring/PerformanceTracker.ts
export class PerformanceTracker {
private metrics: PerformanceMetrics = {
totalTrades: 0,
successfulTrades: 0,
failedTrades: 0,
totalVolume: 0,
totalPnL: 0,
dailyPnL: 0,
winRate: 0,
avgProfit: 0,
avgLoss: 0,
maxDrawdown: 0,
sharpeRatio: 0,
startTime: new Date(),
lastUpdateTime: new Date()
};
private dailyReturns: number[] = [];
private portfolioValues: number[] = [];
updateMetrics(trade: Trade): void {
this.metrics.totalTrades++;
this.metrics.totalVolume += trade.inputAmount;
if (trade.success) {
this.metrics.successfulTrades++;
this.metrics.totalPnL += trade.pnl || 0;
} else {
this.metrics.failedTrades++;
}
// Update derived metrics
this.metrics.winRate = (this.metrics.successfulTrades / this.metrics.totalTrades) * 100;
this.metrics.lastUpdateTime = new Date();
// Calculate daily PnL
this.updateDailyPnL();
// Update Sharpe ratio
this.updateSharpeRatio();
}
private updateDailyPnL(): void {
const today = new Date().toDateString();
const todayTrades = this.getTodaysTrades();
this.metrics.dailyPnL = todayTrades.reduce((sum, trade) => sum + (trade.pnl || 0), 0);
}
private updateSharpeRatio(): void {
if (this.dailyReturns.length < 30) {
this.metrics.sharpeRatio = 0;
return;
}
const avgReturn = this.dailyReturns.reduce((sum, r) => sum + r, 0) / this.dailyReturns.length;
const variance = this.dailyReturns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / this.dailyReturns.length;
const stdDev = Math.sqrt(variance);
// Assume 3% risk-free rate (annualized)
const riskFreeRate = 0.03 / 365;
this.metrics.sharpeRatio = stdDev > 0 ? (avgReturn - riskFreeRate) / stdDev : 0;
}
getMetrics(): PerformanceMetrics {
return { ...this.metrics };
}
generateReport(): PerformanceReport {
const uptime = Date.now() - this.metrics.startTime.getTime();
const uptimeHours = uptime / (1000 * 60 * 60);
return {
summary: {
totalTrades: this.metrics.totalTrades,
winRate: this.metrics.winRate,
totalPnL: this.metrics.totalPnL,
dailyPnL: this.metrics.dailyPnL,
sharpeRatio: this.metrics.sharpeRatio
},
performance: {
avgTradesPerHour: this.metrics.totalTrades / uptimeHours,
avgVolumePerDay: this.metrics.totalVolume / (uptimeHours / 24),
bestDay: this.getBestDay(),
worstDay: this.getWorstDay()
},
risk: {
maxDrawdown: this.metrics.maxDrawdown,
volatility: this.calculateVolatility(),
consecutiveLosses: this.getMaxConsecutiveLosses()
}
};
}
private getTodaysTrades(): Trade[] {
const today = new Date().toDateString();
return this.getAllTrades().filter(trade =>
trade.timestamp.toDateString() === today
);
}
private getAllTrades(): Trade[] {
// This would be implemented to fetch from storage
return [];
}
}
Configuration & Types
// src/types/index.ts
export interface BotConfig {
rpcUrl: string;
wsUrl?: string;
privateKey: number[];
logLevel: 'debug' | 'info' | 'warn' | 'error';
loopInterval: number;
closePositionsOnStop: boolean;
enabledStrategies: {
arbitrage: boolean;
gridTrading: boolean;
yieldOptimization: boolean;
};
strategies: {
arbitrage: ArbitrageConfig;
gridTrading: GridTradingConfig;
yieldOptimization: YieldOptimizationConfig;
};
riskManagement: RiskManagementConfig;
}
export interface ArbitrageConfig {
minProfitability: number; // Minimum profit percentage
maxTradeAmount: number; // Maximum trade amount
slippage: number;
maxPriceImpact: number;
tokens: string[]; // Tokens to monitor
}
export interface GridTradingConfig {
grids: Record<string, GridConfig>;
slippage: number;
}
export interface GridConfig {
gridSpacing: number; // Percentage spacing between grids
numGrids: number; // Number of grids above/below
orderSize: number; // Size of each grid order
}
export interface YieldOptimizationConfig {
minAPYImprovement: number; // Minimum APY improvement to trigger migration
migrationPercentage: number; // Percentage of position to migrate
slippage: number;
tokens: string[]; // Tokens to optimize yields for
}
export interface RiskManagementConfig {
maxDailyLoss: number; // Maximum daily loss in USD
maxPositionSize: number; // Maximum position size per trade
maxTokenExposure: number; // Maximum exposure per token
maxPriceImpact: number; // Maximum acceptable price impact
maxTradesPerWindow: number; // Max trades per 5-minute window
minProfitThreshold: number; // Minimum profit to execute trade
}
export interface TradingSignal {
type: 'arbitrage' | 'grid' | 'yield_optimization';
fromToken: string;
toToken: string;
amount: number;
slippage: number;
maxPriceImpact: number;
priority: 'low' | 'medium' | 'high';
expectedProfit?: number;
expectedPrice?: number;
gridOrderId?: string;
expectedAPYImprovement?: number;
currentAPY?: number;
targetAPY?: number;
pools?: string[];
}
export interface Trade {
id: string;
signal: TradingSignal;
inputAmount: number;
outputAmount: number;
fromToken: string;
toToken: string;
signature: string;
timestamp: Date;
success: boolean;
pnl?: number;
gasUsed: number;
priceImpact: number;
}
Main Application
// src/main.ts
import dotenv from 'dotenv';
import { SarosTradingBot } from './TradingBot';
import { Logger } from './utils/Logger';
dotenv.config();
const logger = new Logger();
// Bot configuration
const config: BotConfig = {
rpcUrl: process.env.RPC_URL || 'https://api.mainnet-beta.solana.com',
wsUrl: process.env.WS_URL,
privateKey: JSON.parse(process.env.PRIVATE_KEY || '[]'),
logLevel: (process.env.LOG_LEVEL as any) || 'info',
loopInterval: parseInt(process.env.LOOP_INTERVAL || '10000'), // 10 seconds
closePositionsOnStop: process.env.CLOSE_ON_STOP === 'true',
enabledStrategies: {
arbitrage: process.env.ENABLE_ARBITRAGE === 'true',
gridTrading: process.env.ENABLE_GRID_TRADING === 'true',
yieldOptimization: process.env.ENABLE_YIELD_OPT === 'true'
},
strategies: {
arbitrage: {
minProfitability: parseFloat(process.env.ARB_MIN_PROFIT || '0.5'),
maxTradeAmount: parseFloat(process.env.ARB_MAX_AMOUNT || '1000'),
slippage: 0.5,
maxPriceImpact: 1.0,
tokens: ['SOL', 'USDC', 'USDT', 'C98']
},
gridTrading: {
grids: {
'SOL': {
gridSpacing: 2.0, // 2% spacing
numGrids: 5,
orderSize: 10
}
},
slippage: 0.3
},
yieldOptimization: {
minAPYImprovement: 2.0, // 2% APY improvement minimum
migrationPercentage: 0.5, // Migrate 50% at a time
slippage: 0.5,
tokens: ['SAROS', 'SOL', 'USDC']
}
},
riskManagement: {
maxDailyLoss: 100, // $100 max daily loss
maxPositionSize: 500, // $500 max per position
maxTokenExposure: 1000, // $1000 max per token
maxPriceImpact: 2.0, // 2% max price impact
maxTradesPerWindow: 10, // 10 trades per 5 minutes
minProfitThreshold: 0.5 // $0.50 minimum profit
}
};
async function main() {
try {
logger.info('Starting Saros Trading Bot');
logger.info('Configuration:', {
strategies: config.enabledStrategies,
riskLimits: config.riskManagement
});
// Validate configuration
if (!config.privateKey || config.privateKey.length === 0) {
throw new Error('Private key not configured');
}
// Create and start bot
const bot = new SarosTradingBot(config);
// Handle graceful shutdown
process.on('SIGINT', async () => {
logger.info('Received SIGINT, shutting down gracefully...');
await bot.stop();
process.exit(0);
});
process.on('SIGTERM', async () => {
logger.info('Received SIGTERM, shutting down gracefully...');
await bot.stop();
process.exit(0);
});
// Start the bot
await bot.start();
} catch (error) {
logger.error('Failed to start trading bot:', error);
process.exit(1);
}
}
if (require.main === module) {
main();
}
Test Suite
// tests/TradingBot.test.ts
import { SarosTradingBot } from '../src/TradingBot';
import { ArbitrageStrategy } from '../src/strategies/ArbitrageStrategy';
describe('SarosTradingBot', () => {
let bot: SarosTradingBot;
const mockConfig = {
rpcUrl: 'https://api.devnet.solana.com',
privateKey: new Array(64).fill(1), // Mock private key
logLevel: 'error' as const,
loopInterval: 1000,
closePositionsOnStop: false,
enabledStrategies: {
arbitrage: true,
gridTrading: false,
yieldOptimization: false
},
strategies: {
arbitrage: {
minProfitability: 0.1,
maxTradeAmount: 100,
slippage: 0.5,
maxPriceImpact: 1.0,
tokens: ['SOL', 'USDC']
},
gridTrading: {
grids: {},
slippage: 0.5
},
yieldOptimization: {
minAPYImprovement: 1.0,
migrationPercentage: 0.3,
slippage: 0.5,
tokens: []
}
},
riskManagement: {
maxDailyLoss: 10,
maxPositionSize: 50,
maxTokenExposure: 100,
maxPriceImpact: 1.0,
maxTradesPerWindow: 5,
minProfitThreshold: 0.1
}
};
beforeEach(() => {
bot = new SarosTradingBot(mockConfig);
});
test('should initialize without errors', () => {
expect(bot).toBeInstanceOf(SarosTradingBot);
});
test('should validate configuration', async () => {
// Test with invalid config
const invalidConfig = { ...mockConfig, privateKey: [] };
expect(() => new SarosTradingBot(invalidConfig)).toThrow();
});
});
describe('ArbitrageStrategy', () => {
test('should detect arbitrage opportunities', async () => {
const strategy = new ArbitrageStrategy(
mockConnection,
mockWallet,
mockConfig.strategies.arbitrage
);
const signals = await strategy.analyze();
expect(Array.isArray(signals)).toBe(true);
});
});
// Mock implementations
const mockConnection = {
getBalance: jest.fn().mockResolvedValue(1000000),
getTokenAccountsByOwner: jest.fn().mockResolvedValue({ value: [] })
} as any;
const mockWallet = {
publicKey: {
toString: () => 'MockWalletAddress'
}
} as any;
Running the Bot
# Environment setup
cp .env.example .env
# Edit .env with your configuration
# Development mode
npm run dev
# Production mode
npm run build
npm start
# Run tests
npm test
# Run with Docker
npm run docker:build
npm run docker:run
# Backtest strategies
npm run backtest -- --strategy=arbitrage --days=30
Environment Configuration
# .env
RPC_URL=https://api.mainnet-beta.solana.com
WS_URL=wss://api.mainnet-beta.solana.com
PRIVATE_KEY=[1,2,3,4,...] # Your wallet private key as array
LOG_LEVEL=info
# Strategy toggles
ENABLE_ARBITRAGE=true
ENABLE_GRID_TRADING=false
ENABLE_YIELD_OPT=false
# Risk management
ARB_MIN_PROFIT=0.5
ARB_MAX_AMOUNT=1000
LOOP_INTERVAL=10000
CLOSE_ON_STOP=true
# Monitoring
ENABLE_ALERTS=true
TELEGRAM_BOT_TOKEN=your_telegram_bot_token
TELEGRAM_CHAT_ID=your_chat_id
Safety Features
- Paper Trading Mode: Test strategies without real funds
- Position Limits: Automatic position sizing and exposure limits
- Circuit Breakers: Stop trading after significant losses
- Sanity Checks: Validate all operations before execution
- Graceful Shutdown: Safely close positions on bot termination
- Audit Trail: Complete logging of all trades and decisions
Key Learnings
This advanced example showcases:
- Complex SDK Integration: Multiple strategies using all Saros SDK features
- Production-Ready Architecture: Proper error handling, monitoring, and deployment
- Risk Management: Comprehensive risk controls and position management
- Performance Optimization: Caching, batch operations, and efficient data structures
- Testing Coverage: Unit tests, integration tests, and backtesting framework
MashaAllah! This demonstrates the full power of Saros SDKs in a real-world application.