Slicing example

Slicing is a simple algorithm, which split one large order into smaller ones and place them during some time. You can see example code below.

import { AbstractTradeAlgorithm, CreateOrderOptions, AlgorithmRun } from '@tradeb0t/core'
import { Job } from 'node-schedule'
import type { StubExchangeApi } from '../exchange'

export interface SlicingInput {
  order: CreateOrderOptions
  parts: number
  minutes: number
}
export interface SlicingState {
  orders_sended: number
  lots_in_orders: number[]
}
export interface SlicingStopData {
  jobs: Job[]
}

export class SlicingAlgorithm extends AbstractTradeAlgorithm<
  SlicingInput,
  SlicingState,
  SlicingStopData,
  StubExchangeApi
> {
  get name(): string {
    return 'slicing'
  }
  get description(): string {
    return 'Slicing algorithm splits defined order into multiple smaller orders and sends them during continuous time interval.'
  }
  get inputs(): any {
    return {
      order: 'OrderDetails',
      parts: 'number',
      minutes: 'number'
    }
  }

  async main(inputs: SlicingInput) {
    const { order, parts, minutes } = inputs
    const { trader } = this
    const lotsInOrder: number = Math.floor(order.lots / parts)
    let lastLots: number = order.lots % parts
    const lotsInOrders: number[] = []

    for (let i = 0; i < parts; i++) {
      lotsInOrders.push(lotsInOrder)
    }
    for (let i = lotsInOrders.length - 1; lastLots > 0; i--) {
      lotsInOrders[i] += 1
      lastLots -= 1
    }

    const algorithmRun = await this.commitStart(inputs, { orders_sended: 0, lots_in_orders: lotsInOrders })

    try {
      await this.initialize(algorithmRun, { orders_sended: 0, lots_in_orders: lotsInOrders, parts, minutes, order })
    } catch (e) {
      return this.commitError(algorithmRun.id, e as Error)
    }

    return algorithmRun
  }
  async resume(id: number) {
    const algorithmRun = await this.loadProgress(id)
    const { order, parts, minutes } = algorithmRun.inputs
    const { orders_sended, lots_in_orders } = algorithmRun.state

    try {
      await this.initialize(algorithmRun, { orders_sended, lots_in_orders, parts, minutes, order })
    } catch (e) {
      return this.commitError(algorithmRun.id, e as Error)
    }

    return this.commitContinue(id)
  }

  private async initialize(
    algorithmRun: AlgorithmRun,
    opts: {
      orders_sended: number
      parts: number
      lots_in_orders: number[]
      minutes: number
      order: CreateOrderOptions
    }
  ) {
    const { trader } = this
    const { orders_sended, parts, lots_in_orders, minutes, order } = opts
    const stopData: SlicingStopData = { jobs: [] }
    const startPoint = this.addSecondsToDate(new Date(), 10)
    const minutesRemain = minutes * (1 - orders_sended / parts)
    for (let i = orders_sended; i < lots_in_orders.length; i++) {
      const lots = lots_in_orders[i]
      const sendOrderTime: Date = this.addMinutesToDate(startPoint, (minutesRemain / (parts - 1)) * i)
      const newJob = trader.scheduleAction(async () => {
        try {
          await trader.sendOrder({ ...order, lots }, this.name, algorithmRun.id)
          if (i < lots_in_orders.length - 1)
            await this.saveProgress(algorithmRun.id, { orders_sended: i + 1, lots_in_orders })
          else await this.commitFinish(algorithmRun.id)
        } catch (e) {
          await this.commitError(algorithmRun.id, e as Error)
        }
      }, sendOrderTime)
      stopData.jobs.push(newJob)
    }
    this.stopState.set(algorithmRun.id, stopData)
    if (parts < 1) throw new Error('Parts must be greater than 0')
  }

  async stop(id: number) {
    const stopData = this.stopState.get(id)
    if (!stopData) throw new Error(`Algorithm run with id:${id} was not found.`)
    stopData.jobs.forEach((job) => {
      job.cancel()
    })
    return await this.commitStop(id)
  }

  private addSecondsToDate(date: Date, seconds: number) {
    return new Date(date.getTime() + seconds * 1000)
  }

  private addMinutesToDate(date: Date, minutes: number) {
    return new Date(date.getTime() + minutes * 60 * 1000)
  }
}