Skip to content

Commit

Permalink
feat(bot-processor): pass candle and candles history to context
Browse files Browse the repository at this point in the history
  • Loading branch information
bludnic committed May 17, 2024
1 parent 372d3ed commit 148141a
Show file tree
Hide file tree
Showing 19 changed files with 145 additions and 16 deletions.
5 changes: 4 additions & 1 deletion packages/backtesting/src/backtesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ export class Backtesting<T extends IBotConfiguration<T>> {
// last candle
await this.processor.stop();
} else {
await this.processor.process();
await this.processor.process({
candle,
candles: candlesticks.slice(0, index + 1),
});
}

const anyOrderPlaced = this.marketSimulator.placeOrders();
Expand Down
26 changes: 26 additions & 0 deletions packages/bot-processor/src/effect-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import type {
cancelSmartTrade,
createSmartTrade,
replaceSmartTrade,
useMarket,
useCandle,
} from "./effects";
import {
BUY,
Expand All @@ -24,6 +26,8 @@ import {
USE_INDICATORS,
USE_SMART_TRADE,
USE_TRADE,
USE_MARKET,
USE_CANDLE,
} from "./effects";

export const effectRunnerMap: Record<
Expand All @@ -42,6 +46,8 @@ export const effectRunnerMap: Record<
[USE_INDICATORS]: () => {
throw new Error("useIndicators() hook is deprecated");
},
[USE_MARKET]: runUseMarketEffect,
[USE_CANDLE]: runUseCandleEffect,
};

async function runUseSmartTradeEffect(
Expand Down Expand Up @@ -193,3 +199,23 @@ async function runUseExchangeEffect(

return ctx.exchange;
}

async function runUseMarketEffect(
_effect: ReturnType<typeof useMarket>,
ctx: TBotContext<any>,
) {
return ctx.market;
}

async function runUseCandleEffect(
effect: ReturnType<typeof useCandle>,
ctx: TBotContext<any>,
) {
const index = effect.payload;

if (index >= 0) {
return ctx.market.candles[index];
}

return ctx.market.candles[ctx.market.candles.length + index];
}
1 change: 1 addition & 0 deletions packages/bot-processor/src/effects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ export * from "./sell";
export * from "./useExchange";
export * from "./useIndicators";
export * from "./useTrade";
export * from "./market";
15 changes: 15 additions & 0 deletions packages/bot-processor/src/effects/market.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { USE_MARKET, USE_CANDLE } from "./types";
import { makeEffect } from "./utils";

export function useMarket() {
return makeEffect(USE_MARKET, undefined, undefined);
}

/**
* Get candle data for the given index.
* If the index is negative, it will return the candle data from the end.
* By default, will return the last candle.
*/
export function useCandle(index = -1) {
return makeEffect(USE_CANDLE, index, undefined);
}
2 changes: 1 addition & 1 deletion packages/bot-processor/src/effects/types/base-effect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EffectType } from "./effect-types";
import type { EffectType } from "./effect-types";

export type BaseEffect<T extends EffectType, P = undefined, R = undefined> = {
type: T;
Expand Down
6 changes: 5 additions & 1 deletion packages/bot-processor/src/effects/types/effect-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const CREATE_SMART_TRADE = "CREATE_SMART_TRADE";
export const CANCEL_SMART_TRADE = "CANCEL_SMART_TRADE";
export const USE_EXCHANGE = "USE_EXCHANGE";
export const USE_INDICATORS = "USE_INDICATORS";
export const USE_MARKET = "USE_MARKET";
export const USE_CANDLE = "USE_CANDLE";

export type EffectType =
| typeof USE_SMART_TRADE
Expand All @@ -19,4 +21,6 @@ export type EffectType =
| typeof CREATE_SMART_TRADE
| typeof CANCEL_SMART_TRADE
| typeof USE_EXCHANGE
| typeof USE_INDICATORS;
| typeof USE_INDICATORS
| typeof USE_MARKET
| typeof USE_CANDLE;
4 changes: 3 additions & 1 deletion packages/bot-processor/src/strategy-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type {
IBotConfiguration,
IBotControl,
IStore,
MarketData,
TBotContext,
} from "./types";
import { createContext } from "./utils/createContext";
Expand Down Expand Up @@ -41,12 +42,13 @@ export class StrategyRunner<T extends IBotConfiguration> {
await this.runTemplate(context);
}

async process() {
async process(market?: MarketData) {
const context = createContext(
this.control,
this.botConfig,
this.exchange,
"process",
market,
);

await this.runTemplate(context);
Expand Down
5 changes: 5 additions & 0 deletions packages/bot-processor/src/types/bot/bot-context.type.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { IExchange } from "@opentrader/exchanges";
import type { MarketData } from "../market";
import type { IBotControl } from "./bot-control.interface";
import type { IBotConfiguration } from "./bot-configuration.interface";

Expand All @@ -22,4 +23,8 @@ export type TBotContext<T extends IBotConfiguration> = {
onStart: boolean;
onStop: boolean;
onProcess: boolean;
/**
* Marked data
*/
market: MarketData;
};
1 change: 1 addition & 0 deletions packages/bot-processor/src/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./bot";
export * from "./smart-trade";
export * from "./store";
export * from "./market";
1 change: 1 addition & 0 deletions packages/bot-processor/src/types/market/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./market-data";
12 changes: 12 additions & 0 deletions packages/bot-processor/src/types/market/market-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { ICandlestick } from "@opentrader/types";

export interface MarketData {
/**
* Lst closed candlestick
*/
candle?: ICandlestick;
/**
* List of previous candles, included last one. Last candle can be accessed by `candles[candles.length - 1]`
*/
candles: ICandlestick[];
}
11 changes: 10 additions & 1 deletion packages/bot-processor/src/utils/createContext.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import type { IExchange } from "@opentrader/exchanges";
import type { IBotConfiguration, IBotControl, TBotContext } from "../types";
import type {
IBotConfiguration,
IBotControl,
MarketData,
TBotContext,
} from "../types";

export function createContext<T extends IBotConfiguration>(
control: IBotControl,
config: T,
exchange: IExchange,
command: "start" | "stop" | "process", // @todo add type in file
market: MarketData = {
candles: [],
},
): TBotContext<T> {
return {
control,
Expand All @@ -15,5 +23,6 @@ export function createContext<T extends IBotConfiguration>(
onStart: command === "start",
onStop: command === "stop",
onProcess: command === "process",
market,
};
}
28 changes: 28 additions & 0 deletions packages/bot-templates/src/templates/test/candle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { IExchange } from "@opentrader/exchanges";
import type { MarketData, TBotContext } from "@opentrader/bot-processor";
import { useMarket, useCandle, useExchange } from "@opentrader/bot-processor";
import { logger } from "@opentrader/logger";

export function* testCandle(ctx: TBotContext<any>) {
const { config: bot, onStart, onStop } = ctx;
const exchange: IExchange = yield useExchange();

if (onStart) {
logger.info(
`[CandleStrategy] Bot started. Using ${exchange.exchangeCode} exchange`,
);
return;
}
if (onStop) {
logger.info(`[CandleStrategy] Bot stopped`);
return;
}

const market: MarketData = yield useMarket();
logger.info(market, `[CandleStrategy] Market data`);

const candle: MarketData["candle"] = yield useCandle();
logger.info(`[CandleStrategy] Candle ${JSON.stringify(candle)}`);

logger.info(`[CandleStrategy] Bot template executed successfully`);
}
1 change: 1 addition & 0 deletions packages/bot-templates/src/templates/test/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./buySell";
export * from "./trade";
export * from "./debug";
export * from "./candle";
9 changes: 7 additions & 2 deletions packages/bot/src/channels/candles/candles.aggregator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { CandlesWatcher } from "./candles.watcher";
* Aggregates 1m candles to higher timeframes.
*
* Emits:
* - candle: `ICandlestick`
* - candle(lastCandle: `ICandlestick`, candlesHistory: ICandlestick[]): void
*/
export class CandlesAggregator extends EventEmitter {
public timeframe: BarSize;
Expand All @@ -21,6 +21,10 @@ export class CandlesAggregator extends EventEmitter {
* Storing 1m candles for further aggregation.
*/
private bucket: ICandlestick[] = [];
/**
* Pushing aggregated candles to history.
*/
private candlesHistory: ICandlestick[] = [];
private candlesWatcher: CandlesWatcher;

constructor(timeframe: BarSize, candlesWatcher: CandlesWatcher) {
Expand All @@ -39,12 +43,13 @@ export class CandlesAggregator extends EventEmitter {
`Bucket length of ${this.symbol} reached ${this.bucket.length}/${this.bucketSize}. Aggregating ${this.timeframe} bucket`,
);
const candle = this.aggregate();
this.candlesHistory.push(candle);

logger.info(
candle,
`Aggregated ${this.symbol} ${this.bucketSize}m candles to ${this.timeframe}: O: ${candle.open}, H: ${candle.high}, L: ${candle.low}, C: ${candle.close} at ${new Date(candle.timestamp).toISOString()}`,
);
this.emit("candle", candle);
this.emit("candle", candle, this.candlesHistory);

return;
}
Expand Down
3 changes: 2 additions & 1 deletion packages/bot/src/channels/candles/candles.channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,11 +63,12 @@ export class CandlesChannel extends EventEmitter {
}

aggregator = new CandlesAggregator(timeframe, watcher);
aggregator.on("candle", (candle: ICandlestick) => {
aggregator.on("candle", (candle: ICandlestick, history: ICandlestick[]) => {
const candleEvent: CandleEvent = {
symbol,
timeframe,
candle,
history,
};

this.emit("candle", candleEvent);
Expand Down
7 changes: 7 additions & 0 deletions packages/bot/src/channels/candles/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,12 @@ import type { BarSize, ICandlestick } from "@opentrader/types";
export type CandleEvent = {
symbol: string;
timeframe: BarSize;
/**
* Last closed candle
*/
candle: ICandlestick;
/**
* Candles history
*/
history: ICandlestick[];
};
7 changes: 5 additions & 2 deletions packages/bot/src/processing/candles.processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class CandlesProcessor {

// @todo maybe queue
private async handleCandle(data: CandleEvent) {
const { candle, symbol, timeframe } = data;
const { candle, history, symbol, timeframe } = data;

logger.info(
`CandlesProcessor: Received candle ${timeframe} for ${symbol}. Start processing.`,
Expand All @@ -85,7 +85,10 @@ export class CandlesProcessor {
continue;
}

await botProcessor.process();
await botProcessor.process({
candle,
candles: history,
});
await botProcessor.placePendingOrders();

logger.info(`Exec bot #${bot.id} template done`);
Expand Down
17 changes: 11 additions & 6 deletions packages/processing/src/bot/bot.processing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { IBotConfiguration } from "@opentrader/bot-processor";
import type { IBotConfiguration, MarketData } from "@opentrader/bot-processor";
import { createStrategyRunner } from "@opentrader/bot-processor";
import { findTemplate } from "@opentrader/bot-templates";
import { exchangeProvider } from "@opentrader/exchanges";
Expand Down Expand Up @@ -69,8 +69,13 @@ export class BotProcessing {
});
}

async processCommand(command: "start" | "stop" | "process") {
console.log(`🤖 Bot #${this.bot.id} command=${command}`);
async processCommand(
command: "start" | "stop" | "process",
market?: MarketData,
) {
console.log(
`🤖 Bot #${this.bot.id} command=${command} candle=${JSON.stringify(market?.candle)} candlesHistory=${market?.candles.length} start`,
);
if (this.isBotProcessing()) {
console.warn(
`Cannot execute "${command}()" command. The bot is busy right now by the previous processing job.`,
Expand All @@ -87,7 +92,7 @@ export class BotProcessing {
} else if (command === "stop") {
await processor.stop();
} else if (command === "process") {
await processor.process();
await processor.process(market);
}
} catch (err) {
await xprisma.bot.setProcessing(false, this.bot.id);
Expand All @@ -107,8 +112,8 @@ export class BotProcessing {
await this.processCommand("stop");
}

async process() {
await this.processCommand("process");
async process(market?: MarketData) {
await this.processCommand("process", market);
}

isBotRunning() {
Expand Down

0 comments on commit 148141a

Please sign in to comment.