npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

order-profit-analyzer

v1.0.2

Published

Analyze profit/loss from cryptocurrency trading bot orders with FIFO matching algorithm

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-analyzer

Quick 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$ 5000

API 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: false
  • groupByStrategy: If true, groups analysis results by strategy tag. Default: true
  • quantityTolerance: 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:

  1. Orders are sorted by timestamp
  2. 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
  3. Mark used orders to prevent reuse
  4. 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:

  1. 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 earlier
  2. Last 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_buy and unmatched_sell warnings 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_timespan warnings

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 unmatched

Strict 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:

  1. The sell timestamp is after the buy timestamp
  2. The time difference is within matchingWindow (default: 5 seconds)
  3. 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 lint

License

MIT