order-profit-analyzer
v1.0.2
Published
Analyze profit/loss from cryptocurrency trading bot orders with FIFO matching algorithm
Maintainers
Readme
order-profit-analyzer
A TypeScript library for analyzing profit/loss (PnL) from cryptocurrency trading bot orders with FIFO matching algorithm.
Features
- FIFO Matching Algorithm: Matches buy and sell orders using First-In-First-Out within a configurable time window
- Partial Order Support: Handles partial fills where multiple buys can match a single sell (and vice versa)
- Strategy Grouping: Group and analyze results by strategy tags (arbitrage, mirror, market-making, etc.)
- Multi-Asset Support: Analyze orders for multiple assets (BTC, ETH, etc.) in a single call
- Comprehensive Warnings: Generates warnings for unmatched orders, losses, and long time spans
- Zero Runtime Dependencies: Lightweight and fast
- TypeScript Native: Full type safety with exported types
- Export Support: Export results to CSV and JSON formats
Installation
npm install order-profit-analyzerQuick Start
import { analyzeOrders, Order } from 'order-profit-analyzer';
const orders: Order[] = [
{
id: '1',
type: 'buy',
asset: 'BTC',
amount: 1,
price: 100000,
timestamp: new Date('2024-01-01T10:00:00'),
},
{
id: '2',
type: 'sell',
asset: 'BTC',
amount: 1,
price: 105000,
timestamp: new Date('2024-01-01T10:00:03'),
},
];
const result = analyzeOrders(orders);
console.log(`Profit: R$ ${result.summary.totalProfitBRL}`);
// Output: Profit: R$ 5000API Reference
analyzeOrders(orders, config?)
Convenience function to analyze orders without creating an instance.
const result = analyzeOrders(orders, {
matchingWindow: 5000,
groupByStrategy: true,
});OrderProfitAnalyzer
Main analyzer class with more control.
import { OrderProfitAnalyzer } from 'order-profit-analyzer';
const analyzer = new OrderProfitAnalyzer({
matchingWindow: 5000, // 5 seconds
strictMode: false,
groupByStrategy: true,
});
const result = analyzer.analyze(orders);
// Export to CSV
const csv = analyzer.exportToCSV(result);
// Export to JSON
const json = analyzer.exportToJSON(result);Interfaces
Order
interface Order {
id: string;
type: 'buy' | 'sell';
asset: string; // 'BTC', 'ETH', etc.
amount: number; // Quantity in asset units
price: number; // Unit price in BRL
timestamp: Date;
strategyTag?: 'arbitrage' | 'mirror' | 'market-making' | string;
exchange?: string; // Optional, useful for arbitrage
}AnalyzerConfig
interface AnalyzerConfig {
matchingWindow?: number; // ms, default: 5000
strictMode?: boolean; // default: false - throws on orphan orders
groupByStrategy?: boolean; // default: true
quantityTolerance?: number; // 0-1, default: 0 (exact match)
}Configuration Details:
matchingWindow: Time window in milliseconds for matching orders. Buy and sell orders must be within this time window to be matched. Default: 5000ms (5 seconds)strictMode: If true, throws an error when orphan (unmatched) orders are found. Default: falsegroupByStrategy: If true, groups analysis results by strategy tag. Default: truequantityTolerance: Percentage tolerance (0-1) for matching order quantities. Useful for bots that may not operate exact amounts due to rounding. Example:0.01= 1% tolerance. Default: 0 (exact match required)
AnalysisResult
interface AnalysisResult {
summary: {
totalProfitBRL: number;
totalVolume: number;
totalBuyVolume: number;
totalSellVolume: number;
matchedVolume: number;
unmatchedBuyVolume: number;
unmatchedSellVolume: number;
averageProfit: number;
profitMargin: number; // percentage
};
trades: MatchedTrade[];
warnings: Warning[];
byStrategy?: {
[tag: string]: StrategyResult;
};
}Matching Algorithm
The library uses a FIFO (First-In-First-Out) matching algorithm:
- Orders are sorted by timestamp
- For each SELL order:
- Find BUY orders that occurred before it (within the time window)
- Match using FIFO: use the oldest buys first
- If a buy doesn't cover the entire sell, use the next buy
- Continue until the sell is fully matched or buys are exhausted
- Mark used orders to prevent reuse
- Generate warnings for unmatched orders
Example
Buys:
- B1: 5 BTC @ R$100k = R$500k (t=0s)
- B2: 5 BTC @ R$101k = R$505k (t=2s)
Sell:
- S1: 10 BTC @ R$105k = R$1,050k (t=4s)
Result:
- Matched trade: 10 BTC
- Total cost: R$500k + R$505k = R$1,005k
- Revenue: R$1,050k
- Profit: R$45k (4.48%)Quantity Tolerance
The quantityTolerance parameter allows matching orders with small quantity differences:
const result = analyzeOrders(orders, {
quantityTolerance: 0.01, // 1% tolerance
});Use cases:
- Trading bots that round amounts (e.g., rounding to avoid orders < $10)
- Small discrepancies that are recovered in subsequent trades
- Handling floating-point precision differences
How it works:
- If the difference between buy and sell amounts is within the tolerance percentage, they can be fully matched
- Example: With 1% tolerance, 1.00 BTC can match with 1.009 BTC (0.9% difference)
- The match uses the larger amount to ensure complete matching
Common Edge Cases
First and Last Orders
⚠️ Important: It's common for the first and last orders in a dataset to remain unmatched. This happens when:
First orders: The matching counterpart order may have occurred before your data range started
Data starts here ↓ [BUY 1 BTC] ← Missing the corresponding SELL that happened earlierLast orders: The matching counterpart order may occur after your data range ended
[SELL 1 BTC] ← Missing the corresponding BUY that will happen later ↑ Data ends here
Recommendations:
- ✅ Expect
unmatched_buyandunmatched_sellwarnings at data boundaries - ✅ Consider these warnings as informational rather than errors
- ✅ Expand your data range when possible to reduce boundary effects
- ✅ Use
strictMode: false(default) to handle these gracefully
Choosing the Right Matching Window
The matchingWindow parameter controls how far apart orders can be while still matching:
Default: 5000ms (5 seconds) - Good for:
- High-frequency trading bots
- Arbitrage strategies with quick execution
- Market-making bots
Consider increasing (e.g., 30000ms = 30s) for:
- Slower strategies with manual intervention
- Markets with lower liquidity
- Grid trading or DCA strategies
Too large window (> 1 hour):
- ❌ May create matches that don't represent real trades
- ❌ Can result in misleading profit calculations
- ⚠️ Generates
long_timespanwarnings
Too small window (< 1 second):
- ❌ May leave many orders unmatched unnecessarily
- ❌ Only suitable for ultra-high-frequency strategies
// Example: Adjust window based on strategy
const result = analyzeOrders(orders, {
matchingWindow: 30000, // 30 seconds for slower strategies
});Warnings
The analyzer generates warnings for various conditions:
| Type | Severity | Description |
|------|----------|-------------|
| unmatched_buy | medium | Buy order with no matching sell |
| unmatched_sell | high | Sell order with no matching buy |
| negative_profit | medium | Trade resulted in a loss |
| long_timespan | low | Trade time span > 10x matching window |
| partial_match | low | Order was only partially matched |
Strategy Support
Group and analyze orders by strategy:
const orders: Order[] = [
{ id: '1', type: 'buy', ..., strategyTag: 'arbitrage' },
{ id: '2', type: 'sell', ..., strategyTag: 'arbitrage' },
{ id: '3', type: 'buy', ..., strategyTag: 'mirror' },
{ id: '4', type: 'sell', ..., strategyTag: 'mirror' },
];
const result = analyzeOrders(orders, { groupByStrategy: true });
console.log(result.byStrategy);
// {
// arbitrage: { profitBRL: 5000, tradeCount: 1, ... },
// mirror: { profitBRL: 2000, tradeCount: 1, ... }
// }Examples
Arbitrage
import { analyzeOrders, Order } from 'order-profit-analyzer';
const orders: Order[] = [
{
id: 'arb-buy',
type: 'buy',
asset: 'BTC',
amount: 0.5,
price: 100000,
timestamp: new Date('2024-01-15T09:00:00'),
strategyTag: 'arbitrage',
exchange: 'binance',
},
{
id: 'arb-sell',
type: 'sell',
asset: 'BTC',
amount: 0.5,
price: 102000, // 2% spread
timestamp: new Date('2024-01-15T09:00:02'),
strategyTag: 'arbitrage',
exchange: 'mercado_bitcoin',
},
];
const result = analyzeOrders(orders, { matchingWindow: 5000 });
console.log(`Arbitrage profit: R$ ${result.summary.totalProfitBRL}`);Handling Partial Fills
const orders: Order[] = [
{ id: '1', type: 'buy', asset: 'BTC', amount: 5, price: 100000, ... },
{ id: '2', type: 'buy', asset: 'BTC', amount: 5, price: 101000, ... },
{ id: '3', type: 'sell', asset: 'BTC', amount: 8, price: 105000, ... },
];
const result = analyzeOrders(orders);
// Trade: 8 BTC matched (5 from order 1, 3 from order 2)
// Remaining: 2 BTC from order 2 unmatchedStrict Mode
import { OrderProfitAnalyzer, AnalysisError } from 'order-profit-analyzer';
const analyzer = new OrderProfitAnalyzer({ strictMode: true });
try {
const result = analyzer.analyze(ordersWithOrphans);
} catch (error) {
if (error instanceof AnalysisError) {
console.log('Unmatched orders found:', error.warnings);
}
}Performance
The library is optimized to handle 10,000+ orders efficiently. Benchmarks show:
- 1,000 orders: < 100ms
- 10,000 orders: < 1s
- 100,000 orders: < 10s
FAQ
Q: Why is my order not matching?
Check that:
- The sell timestamp is after the buy timestamp
- The time difference is within
matchingWindow(default: 5 seconds) - Both orders have the same asset (case-insensitive)
Q: How do I handle orders from different exchanges?
Use the exchange field and strategyTag: 'arbitrage':
{ ..., exchange: 'binance', strategyTag: 'arbitrage' }
{ ..., exchange: 'coinbase', strategyTag: 'arbitrage' }Q: Can I analyze multiple assets at once?
Yes! Orders are automatically grouped by asset. The summary shows combined profit, and individual trades show which asset was traded.
Q: How is profit margin calculated?
profitMargin = (totalProfit / totalBuyCost) * 100
Q: What happens with orders outside the time window?
They become "unmatched" and generate warnings. Increase matchingWindow if your trading strategy has longer delays.
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Build
npm run build
# Lint
npm run lintLicense
MIT
