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

accountstate

v0.0.23

Published

Simple, reusable, in-memory account state store for working with exchange positions

Downloads

42

Readme

Description

AccountState store

A simple utility class to store & manage simple account state in-memory.

The majority of account/position/trade/balance state is easily restorable and can be fetched via REST APIs after restarting a process.

This storage class is a way to cache this information in-memory, with utility functions to simplify accessing and manipulating that state.

Installation

$ npm install accountstate

# or
$ yarn add accountstate

Usage

TODO

Custom data

This storage class also supports per-symbol "metadata". This is a key:value object you can use to store any information related to that symbol's position.

This is typically custom data that an exchange might not have any knowledge of.

Some examples:

  • How many entries have happened on the short side of a symbol's position.
  • When did this position first open.
  • What state is the trailing SL mechanism in.
  • What price did the last position for this symbol close.

You can store anything custom here. However, if you do rely on this metadata,

Persistence

The primary purpose of this module is to cache this state in-memory. Most of this can easily be fetched via the REST API, so persistence for the majority of this data is no concern.

However, the concept of per-symbol "metadata" is a custom one that cannot be easily restored once lost. If you use any of the metadata-related set/delete methods in the module, isPendingPersist() will automatically be set to return true.

This is a good way to check if there's a state change to persist somewhere, but it's up to you to implement the persistence mechanism based on your own needs. One way is to debounce an action to getAllSymbolMetadata(), persist it somewhere, and finally call setIsPendingPersist(false).

There's no wrong way to do this. Here's a high level example that extends the account state store to automatically persist to Redis on a timer, if the stored metadata changed:

const PERSIST_ACCOUNT_POSITION_METADATA_EVERY_MS = 250;

export interface EnginePositionMetadata {
  leaderId: string;
  leaderName: string;
  entryCountLong: number;
  entryCountShort: number;
}

/**
 * This abstraction layer extends the open source "account state store" class,
 * adding a persistence mechanism so nothing is lost after restart.
 *
 * Data is stored in Redis, keyed by the accountId.
 *
 * The RedisPersistanceAPI is a custom implementation around the ioredis client.
 */
export class PersistedAccountStateStore extends AccountStateStore<EnginePositionMetadata> {
  private redisAPI: RedisPersistanceAPI<'positionMetadata'>;

  private didRestorePositionMetadata = false;

  private accountId: string;

  constructor(accountId: string, redisAPI: RedisPersistanceAPI) {
    super();

    this.redisAPI = redisAPI;
    this.accountId = accountId;

    /** Start the persistence timer and also fetch any initial state, if any is found **/
    this.startPersistPositionMetadataTimer();
  }

  /** Call this during bootstrap to ensure we've rehydrated before resuming */
  async restorePersistedData(): Promise<void> {
    // Query persisted position metadata from redis
    const storedDataResult = await this.redisAPI.fetchJSONForAccountKey(
      'positionMetadata',
      this.accountId,
    );

    if (storedDataResult?.data && typeof storedDataResult.data === 'object') {
      this.setAllSymbolMetadata(storedDataResult.data);
    } else {
      console.log(
        `No state data in redis for "${this.accountId}" - nothing to restore`,
      );
    }

    // Overwrite local store with restored data
    this.didRestorePositionMetadata = true;
  }

  private startPersistPositionMetadataTimer(): void {
    setInterval(async () => {
      if (!this.didRestorePositionMetadata) {
        await this.restorePersistedData();
      }

      if (!this.isPendingPersist()) {
        return;
      }

      try {
        this.setIsPendingPersist(false);
        await this.redisAPI.writeJSONForAccountKey(
          'positionMetadata',
          this.accountId,
          this.getAllSymbolMetadata(),
        );

        console.log(`Saved position metadata to redis`);
      } catch (e) {
        console.error(
          `Exception writing position metadata to redis: ${sanitiseError(e)}`,
        );
        this.setIsPendingPersist(true);
      }
    }, PERSIST_ACCOUNT_POSITION_METADATA_EVERY_MS);
  }
}