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 🙏

© 2024 – Pkg Stats / Ryan Hefner

ameba-ts

v1.1.1

Published

A multi-asset backtester for TypeScript

Downloads

106

Readme

Ameba-TS

Ameba-TS is a multi-asset backtester for TypeScript.

Getting started

Creating a trading strategy

The easiest way of writing a trading strategy is composing it from reusable building blocks ("strategy components") with composeStrategy. You can provide a set of entry filters (conditions which need to pass before a trade can be entered), the method to enter trades, and one or many methods to exit the trade.

In the following example, a long position is entered with a market order when the 50-period SMA (simple moving average) is above the 200-period SMA and the 20-period RSI (relative strength index) indicator is below 30. Both the stop loss and take profit order are place 5 times the 20-period ATR (average true range) away from the entry price, so the trades have 1-to-1 risk-to-reward ratio.

const myStrategy: TradingStrategy = composeStrategy({
  filters: [
    gt(sma(50), sma(200)),
    lt(rsi(20), 30)
  ],
  entry: marketBuyEntry,
  exits: [
    atrStopLoss(20, 5),
    atrTakeProfit(20, 5)
  ],
});

Position sizing

To decide how many units to buy when entering a position (or sell when shorting), we need to create a position sizing strategy, a Staker. For common use cases this can be achieved as follows:

const myStaker: Staker = createStaker({
  maxRelativeRisk: 0.01, // Risk 1% of account per trade
  maxRelativeExposure: 1.5, // Use max 50% leverage for all positions in total
  allowFractions: true, // Currencies and cryptos allow non-whole-number position sizes, stocks don't
});

Providing data for the backtest

In order to test the strategy with historical price data, you need to implement a data provider:

const myDataProvider: CandleDataProvider = {
  name: "broker-name",
  getCandles: async ({ symbol, timeframe, from, to }): Promise<Candle[]> => {
    throw Error("Not implemented.");
  },
};

You can implement the getCandles function e.g. by fetching from your broker's API or by reading from your locally stored data set.

Backtesting

Below is an example of testing how the strategy would have performed during the year 2021 when trading BTC and ETH cryptocurrencies (the symbols are specific to the data provider).

const result: BacktestResult = await backtest({
  strategy: withStaker(myStrategy, myStaker),
  dataProvider: myDataProvider,
  initialBalance: 10000,
  symbols: ["BTC", "ETH"],
  timeframe: "1h",
  from: "2021-01-01",
  to: "2022-01-01",
});

console.log(result.stats);

The result tells us how much profit the strategy would have made, among other statistics. You can obtain all of the executed trades in result.trades.

NOTE: The asynchronous backtest function loads price data on demand in batches and clears old data from memory. To keep your code synchronous, you also have the option to load the used data set into memory beforehand and use the backtestSync function instead.

Trading strategy architecture

A trading strategy is a function that receives the current state of things and returns the changes it wishes to make to the open orders. The state includes e.g. the historical price data up to the current moment as Candles (OHLC data) and the currently active positions and orders. The strategy function is called each time when a new candle is completed, and the changes returned by the strategy are activated by closing previous orders and/or opening new ones.

There are three layers of abstraction.

1. Using composeStrategy with a Staker

This allows you to focus on high level trading ideas and decouple trading patterns from position sizing. See the 'Getting started' guide for an example.

Using composeStrategy enables easily experimenting with different kinds of strategy components. Changes such as adding a new entry filter or changing the entry method to limit order at the recent lows require changing only one line of code. Also, if you create your custom strategy component, you can reuse it in multiple strategies without repeating yourself.

2. Writing a TradingStrategy function

You can also write out a TradingStrategy function without utilizing composeStrategy. You can still focus on one asset at a time and decouple position sizing from trading patterns by using a Staker, but the code is more verbose and harder to maintain.

Below is an example of writing out the same strategy as composed in the 'Getting started' guide:

const myStrategy: TradingStrategy = (state: AssetState) => {
  const fastSma = getSma(state, 50);
  const slowSma = getSma(state, 200);
  const rsi = getRsi(state, 20);
  const atr = getAtr(state, 20);
  if (
    fastSma === undefined ||
    slowSma === undefined ||
    rsi === undefined ||
    atr === undefined
  ) {
    // More data needed before the indicators are ready
    return {};
  }
  if (!state.position) {
    if (fastSma > slowSma && rsi < 30) {
      // Not in a position and entry conditions fulfilled
      const currentPrice = state.series[state.series.length - 1].close;
      return {
        entryOrder: {
          side: "buy",
          type: "market",
        },
        // Exit orders set while not in a position will be posted immediately
        // if/when the entry is filled
        stopLoss: currentPrice - atr * 5,
        takeProfit: currentPrice + atr * 5,
      };
    } else {
      // Cancel potentially open entry order (although not really meaningful
      // when the entry is a market order that should fill immediately)
      return {
        entryOrder: null,
      };
    }
  } else {
    // Here we could manage the exit orders while in a position
    return {};
  }
};

3. FullTradingStrategy

The backtester actually takes in a FullTradingStrategy function type (withStaker returns FullTradingStrategy). This is the lowest level of abstraction among the trading strategy APIs. If writing out a FullTradingStrategy, you need to take care of position sizing and simultaneosly handle multiple tradable assets (if provided). This gives you the most control over the trading decisions, at the cost of complexity.