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

backmark

v1.0.2

Published

Backtesting tool for crypto trading

Downloads

5

Readme

BackMark

TypeScript Coverage

BackMark is a lightweight and efficient toolkit for backtesting trading algorithms. Designed with simplicity and flexibility in mind, BackMark enables seamless integration between backtesting strategies and live trading bots, ensuring minimal adjustments are needed when transitioning from simulation to real-world trading.

Why Backmark

  • User-Friendly Metrics: Provides clear, easy-to-understand metrics to evaluate your strategies effectively.
  • Realistic Order Simulation: Simulates partial order fills based on actual volume, giving you a more accurate backtesting experience.
  • Seamless Integration: Includes a unified interface for Exchange/Broker clients, allowing you to copy your backtesting strategy directly into your trading bot with minimal or no modifications.
  • No External Dependencies: BackMark is a self-contained solution, ensuring lightweight and hassle-free integration into your workflow.

Installation

 npm i backmark

How to use

Trading Data Format

The trading data should be provided in a CSV file, sorted by time in ascending order. The file must include the following columns:

  • timestamp: The date and time of the trading data point.
  • open: The opening price for the specified time interval.
  • close: The closing price for the specified time interval.
  • high: The highest price during the time interval.
  • low: The lowest price during the time interval.
  • volume: The total traded volume during the time interval.

For historical trading data at minute-level precision, you can explore and download datasets such as this one on Kaggle:
392 Crypto Currency Pairs at Minute Resolution

Implement you Strategy

To get started, you need to implement the strategy you want to test. Your strategy must extend the Strategy class and utilize the methods provided by the IExchangeClient interface to perform trading operations.

Below is an example in TypeScript to guide you:

import { Strategy } from '../../src/lib/strategy.js';
import type { Account, TradingData } from '../../src/lib/exchange/types.js';
import { FasterSMA } from 'trading-signals';
import { Stop } from '../../src/lib/orders/types.js';
import { IExchangeClient } from '../../src/lib/exchange/IExchangeClient.js';

const SMA_DAYS = 10;
const STOP_PRICE_PERCENTAGE = 25;

export class SmaStrategy extends Strategy {
    private sma: FasterSMA;
    private dailyPrices: number[];
    private previousData: TradingData | undefined = undefined;
    private dayCount: number;
    private tradedToday = false;

    constructor(protected readonly exchangeClient: IExchangeClient) {
        super(exchangeClient);
        this.sma = new FasterSMA(SMA_DAYS);
        this.dailyPrices = [];
        this.dayCount = 0;
    }

    async checkPosition(tradingData: TradingData): Promise<void> {
        const account = await this.exchangeClient.getAccount();

        // Ensure a stop-loss order if holding a position
        this.ensureStopLoss(account, tradingData.price);

        // pass sma days (wee need some days to calculate SMA (i))
        this.dailyPrices.push(tradingData.price);

        if (!this.previousData) {
            this.previousData = tradingData;
            return;
        }

        // Update SMA at the end of the day
        if (this.previousData && this.isNewDay(new Date(tradingData.timestamp), new Date(this.previousData.timestamp))) {
            this.updateSMA(tradingData);
        }

        // Skip trading until sufficient SMA data is available
        if (this.dayCount <= SMA_DAYS) {
            return;
        }

        // Execute buy/sell strategies if not traded today
        if (!this.tradedToday) {
            this.executeTradingStrategy(tradingData, account);
        }

        return;
    }

    private isNewDay = (current: Date, prev: Date): boolean => current.getDay() !== prev.getDay() && current > prev;

    private updateSMA(tradingData: TradingData): void {
        if (this.dailyPrices.length === 0) return;

        const averagePrice = this.dailyPrices.reduce((sum, price) => sum + price, 0) / this.dailyPrices.length;
        this.sma.update(averagePrice);

        this.dailyPrices = [];
        this.previousData = tradingData;
        this.dayCount++;
        this.tradedToday = false;
    }

    private ensureStopLoss(account: Account, assetPrice: number): void {
        const hasStopLoss = this.exchangeClient.getAllOrders().some((order) => order?.stop === Stop.LOSS);

        const stopPrice = assetPrice - (assetPrice * STOP_PRICE_PERCENTAGE) / 100;
        if (!hasStopLoss && account.productQuantity > 0) {
            this.exchangeClient.stopLossOrder(stopPrice, account.productQuantity);
        }
    }

    private executeTradingStrategy(tradingData: TradingData, account: Account): void {
        const smaValue = this.sma.getResult();

        // Buy if price is below SMA
        if (tradingData.price < smaValue * 0.94 && account.balance > 0) {
            this.exchangeClient.marketBuyOrder(account.balance * 0.5);
        }

        // Sell if price is above SMA
        if (tradingData.price > smaValue * 1.02) {
            const bitcoin = account.productQuantity;
            if (bitcoin > 0) {
                this.exchangeClient.marketSellOrder(bitcoin);
            }
        }

        this.tradedToday = true;
    }
}

Testing

ESM JS example:

import { BackTest, Strategy } from 'backmark';

// your strategy
class SmaStrategy extends Strategy {
    constructor(exchangeClient) {
        super(exchangeClient);
    }
    async checkPosition(tradingData) {
        console.log(tradingData);
        this.exchangeClient.marketBuyOrder(300);
    }
}

const options = {
    accountBalance: 1000,
    fee: 1.5,
    productName: 'BTC-USD',
};

const backTest = new BackTest('btcusd.csv', SmaStrategy, options);

backTest
    .run()
    .then(() => {
        console.log(backTest.getResult());
    })
    .catch();
{
  initialBalance: 1000,
  product: 'BTC-USD',
  finalHoldings: 0,
  finalBalance: 1049.6294293755195,
  totalProfit: 49.62942937551952,
  tradeHistory: [
    {
      orderId: 'a072782c-7673-4010-8ef3-92b9f95461ee',
      price: 507.5,
      quantity: 0.0123151169623545,
      createdAt: 2022-02-18T23:00:00.000Z,
      side: 'buy',
      balanceAfterTrade: 492.5,
      holdingsAfterTrade: 0.0123151169623545
    },
    {
      orderId: '666d55c2-a87c-4492-a37f-61a9728386be',
      price: 249.94375,
      quantity: 0.006226935197129856,
      createdAt: 2022-02-20T23:00:00.000Z,
      side: 'buy',
      balanceAfterTrade: 242.55625,
      holdingsAfterTrade: 0.018542052159484356
    },
    {
      orderId: 'e295188b-3bd2-423d-9e14-1859943495c3',
      price: 123.097296875,
      quantity: 0.003174523488585778,
      createdAt: 2022-02-21T23:00:00.000Z,
      side: 'buy',
      balanceAfterTrade: 119.45895312500001,
      holdingsAfterTrade: 0.021716575648070134
    },
    {
      orderId: '19c0a0cd-7703-408b-88d3-14182ca8ab08',
      price: 60.6254187109375,
      quantity: 0.0015493718417645837,
      createdAt: 2022-02-22T23:00:00.000Z,
      side: 'buy',
      balanceAfterTrade: 58.833534414062505,
      holdingsAfterTrade: 0.023265947489834717
    },
    {
      orderId: '26d8eef2-80ef-4550-8381-d55e194ea7b1',
      price: 29.858018715136723,
      quantity: 0.0007689994744868107,
      createdAt: 2022-02-23T23:00:00.000Z,
      side: 'buy',
      balanceAfterTrade: 28.975515698925783,
      holdingsAfterTrade: 0.024034946964321527
    },
    {
      orderId: '7634fde9-998c-4eb8-92bf-1dca3a7052f2',
      price: 1020.6539136765938,
      quantity: 0.024034946964321527,
      createdAt: 2022-02-28T23:00:00.000Z,
      side: 'sell',
      balanceAfterTrade: 1049.6294293755195,
      holdingsAfterTrade: 0
    }
  ],
  profitPercentage: 4.9629429375519525
}

License

MIT