diff --git a/.vscode/settings.json b/.vscode/settings.json
index a6fb976..2e6dad4 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,6 @@
{
- "eslint.enable": true
+ "eslint.enable": true,
+ "sonarlint.connectedMode.project": {
+ "projectKey": "icyfry_serverless-bot"
+ }
}
\ No newline at end of file
diff --git a/README.md b/README.md
index a4c494a..468a8b7 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,8 @@
![GitHub top language](https://img.shields.io/github/languages/top/icyfry/serverless-bot) [![Build](https://github.com/icyfry/serverless-bot/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/icyfry/serverless-bot/actions/workflows/build.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=icyfry_serverless-bot&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=icyfry_serverless-bot)
+> ⚠️ this is a experimentation project, DO NOT use it in a real use case, performance will most probably be negative.
+
A serverless bot to trigger automatic orders on dYdX
@@ -19,7 +21,7 @@ A serverless bot to trigger automatic orders on dYdX
### Setup and troubleshooting
-`zlib-sync` lib may cause segmentation fault on local development and unit tests
+`zlib-sync` may cause segmentation fault on local development and unit tests when not using mock for the discord module
commands
* `task test` Run unit tests
diff --git a/src/bot.ts b/src/bot.ts
index 22f1884..77d04e7 100644
--- a/src/bot.ts
+++ b/src/bot.ts
@@ -1,45 +1,50 @@
-import { OrderExecution, OrderSide, OrderTimeInForce, OrderType } from '@dydxprotocol/v4-client-js';
import { GetSecretValueCommand, SecretsManagerClient } from "@aws-sdk/client-secrets-manager";
-import { Context } from 'aws-lambda';
+import { APIGatewayProxyResult, Context } from 'aws-lambda';
import { Strat } from './strategy/strat';
import { Discord } from './communication/discord';
import { CallbackResponseParams } from './main';
+import { BroadcastTxAsyncResponse, BroadcastTxSyncResponse } from '@cosmjs/tendermint-rpc/build/tendermint37';
+import { IndexedTx} from '@cosmjs/stargate';
+import { OrderExecution, OrderSide, OrderTimeInForce, OrderType } from '@dydxprotocol/v4-client-js';
-/**
- * Input of the bot
- */
-export class Input {
- public market = "BTC-USD";
- public price = 0;
- public source: InputSource = InputSource.Mock;
- public details: InputDetails = {};
- public emitKey = "nokey";
- public dryrun = false;
- public roundingFactor = 100000000; // 8 decimals
- public interval = 60; // 1 minute
- constructor(event: string) {
- Object.assign(this, JSON.parse(event));
+// Transaction response
+export type TxResponse = BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx;
+
+// Error that should not interrupt the bot
+export class Warning extends Error {
+ constructor(message?: string) {
+ super(message);
+ this.name = 'Warning';
}
}
-/**
- * Output of the bot
- */
-export class Output {
- public order: BotOrder;
- constructor(order: BotOrder) {
- this.order = order;
- }
- toString() : string { return JSON.stringify(
- {
- "order" : this.order,
- },
- null, 2); }
+// Configuration of the broker connected to the bot
+export interface BrokerConfig {
+ TESTNET_MNEMONIC: string;
+ MAINNET_MNEMONIC: string;
+ DISCORD_TOKEN: string;
}
-/**
- * Trade order
- */
+// A position on the broker
+export interface Position {
+ market: string,
+ status: string,
+ side: string,
+ size: number,
+ maxSize: number,
+ entryPrice: number,
+ exitPrice: number,
+ realizedPnl: number, // in usd
+ unrealizedPnl: number, // in usd
+ createdAt: Date,
+ createdAtHeight: number,
+ closedAt: Date,
+ sumOpen: number, // in crypto
+ sumClose: number, // in crypto
+ netFunding: number
+}
+
+// An order to place on the broker
export class BotOrder {
public market = "BTC-USD"; // perpertual market id
public type:OrderType = OrderType.LIMIT; // order type
@@ -55,30 +60,68 @@ export class BotOrder {
public goodTillTime = 86400; // goodTillTime in seconds
}
-export interface BrokerConfig {
- TESTNET_MNEMONIC: string;
- MAINNET_MNEMONIC: string;
- DISCORD_TOKEN: string;
-}
-
-export interface InputDetails {
+// Output of the bot
+export class Output {
+ public order: BotOrder;
+ constructor(order: BotOrder) {
+ this.order = order;
+ }
+ toString() : string { return JSON.stringify(
+ {
+ "order" : this.order,
+ },
+ null, 2); }
}
-export interface SuperTrendDetails extends InputDetails{
- action: string; // BUY or SELL
- limit: number;
+// Input of the bot
+export class Input {
+ public market = "BTC-USD";
+ public price = 0;
+ public source: InputSource = InputSource.Mock;
+ public details: InputDetails = {};
+ public emitKey = "nokey";
+ public dryrun = false;
+ public roundingFactor = 100000000; // 8 decimals
+ public interval = 60; // 1 minute
+ constructor(event: string) {
+ Object.assign(this, JSON.parse(event));
+ // Rounding the prices
+ this.price = Math.round(this.price*100)/100;
+ }
}
-export interface SMCDetails extends InputDetails{
- type: string;
+// Commons details of the input
+export interface InputDetails {
+ plots?: string[];
}
+// Available sources of input
export enum InputSource {
SuperTrend = "SUPER_TREND",
SMC = "SMART_MONEY_CONCEPTS",
Mock = "MOCK"
}
+// Details of the SuperTrend input
+export class SuperTrendDetails implements InputDetails {
+ public action = "BUY"; // BUY or SELL
+ public limit = 0;
+ public plots?: string[] = [];
+ constructor(details: string) {
+ Object.assign(this, JSON.parse(details));
+ // Rounding the prices
+ this.limit = Math.round(this.limit*100)/100;
+ }
+}
+
+// Details of the SMC input
+export interface SMCDetails extends InputDetails{
+ type: string;
+}
+
+/**
+ * Bot abstract class
+ */
export abstract class Bot {
static readonly NETWORK_MAINNET: string = "mainnet";
@@ -95,38 +138,38 @@ export abstract class Bot {
}
/**
- * Place an order on the exchange
+ * Place an order on the broker
* @param order Order to place
* @returns transaction response
*/
- public abstract placeOrder(order:BotOrder): Promise;
+ public abstract placeOrder(order:BotOrder): Promise;
/**
- * Close a position on the exchange
+ * Close a position on the broker
* @param market the market to close
* @param hasToBeSide the side the position has to be before closing (LONG or SHORT)
- * @param refPrice reference price to close the position
+ * @param refPrice reference price used to close the position
* @returns transaction response and position closed
*/
- public abstract closePosition(market: string, hasToBeSide?: OrderSide, refPrice?: number): Promise<{tx: any, position: any}>;
+ public abstract closePosition(market: string, hasToBeSide?: OrderSide, refPrice?: number): Promise<{tx: TxResponse, position: Position}>;
/**
- * Connect to the exchange
+ * Connect to the broker
* @returns address of the connected account
*/
public abstract connect() : Promise;
/**
- * Disconnect from the exchange
+ * Disconnect from the broker
*/
public abstract disconnect(): Promise;
/**
- * Cancel order
+ * Cancel an order
* @param market Market to cancel the order
- * @param clientId id of the order
+ * @param clientId id of the order to cancel
*/
- public abstract cancelOrdersForMarket(market: string, clientId: number): Promise;
+ public abstract cancelOrdersForMarket(market: string, clientId: number): Promise;
/**
* Read config from the AWS Secrets Manager
@@ -149,23 +192,25 @@ export abstract class Bot {
};
/**
- * Process the lambda event
+ * Process the lambda input event
* @param input input of the lambda
* @param strategy the strategy to apply
- * @param context the context
+ * @param _context the context
* @returns the response
*/
public async process(input: Input, strategy: Strat, context?: Context): Promise {
+ console.log(context?.awsRequestId);
+
// Return of the process
const response: CallbackResponseParams = {
- response_error: null,
+ response_error: undefined,
response_success: {
statusCode: 200,
headers: {
'Content-Type': 'application/json; charset=utf-8',
},
- body: {"message": "no message"}
+ body: '{"message": "no message"}'
}
};
@@ -176,30 +221,32 @@ export abstract class Bot {
// Order
const order: BotOrder = strategy.getStatelessOrderBasedOnInput(input);
-
+
// Close previous position on this market
try{
- const closingTransaction: any = await this.closePosition(order.market, order.side === OrderSide.BUY ? OrderSide.SELL : OrderSide.BUY, order.price);
- await this.discord.sendMessageClosePosition(order.market, closingTransaction.position, closingTransaction.tx);
- } catch(e: any) {
- // Position not closed
- console.warn(e);
- await this.discord.sendError(e);
+ const closingTransaction: {tx: TxResponse, position: Position} = await this.closePosition(order.market, order.side === OrderSide.BUY ? OrderSide.SELL : OrderSide.BUY, order.price);
+ await this.discord.sendMessageClosePosition(order.market, closingTransaction.position, closingTransaction.tx);
+ } catch(error) {
+ if(error instanceof Warning) {
+ console.warn(error);
+ await this.discord.sendError(error); // Position not closed
+ } else throw error;
}
- const orderTransaction: any = await this.placeOrder(order);
+ // Open new position
+ const orderTransaction: TxResponse = await this.placeOrder(order);
await this.discord.sendMessageOrder(order, input, strategy, orderTransaction);
// Output
const output: Output = new Output(order);
console.log("Output "+output);
- response.response_success.body = JSON.stringify({"message": "process done"});
+ (response.response_success as APIGatewayProxyResult).body = JSON.stringify({"message": "process done"});
}
- catch(e: any) {
- await this.discord.sendError(e);
- response.response_success=null;
- response.response_error=new Error(e);
+ catch(error) {
+ await this.discord.sendError(error as Error);
+ response.response_success = undefined;
+ response.response_error= error as Error;
}
return response;
diff --git a/src/communication/discord.ts b/src/communication/discord.ts
index 264601e..0575f06 100644
--- a/src/communication/discord.ts
+++ b/src/communication/discord.ts
@@ -1,9 +1,16 @@
import { Client, ColorResolvable, EmbedBuilder, GatewayIntentBits, Message, TextChannel } from "discord.js";
-import { BotOrder, Input, Output } from "../bot";
+import { BotOrder, Position, Input, Output, Warning } from "../bot";
import { OrderSide } from "@dydxprotocol/v4-client-js";
-import { Position } from "../dydx/dydx-bot";
import { Strat } from "../strategy/strat";
+import { BroadcastTxAsyncResponse, BroadcastTxSyncResponse } from "@cosmjs/tendermint-rpc";
+import { IndexedTx } from "@cosmjs/stargate";
+// Transaction response
+type TxResponse = BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx;
+
+/**
+ * Discord interactions
+ */
export class Discord {
public client?: Client;
@@ -19,7 +26,10 @@ export class Discord {
}
- private getTxEmbedField(tx: any): any {
+ /**
+ * Get the tx embed field
+ */
+ private getTxEmbedField(tx: TxResponse): {name: string, value: string, inline: boolean} {
let hash: string;
if (tx?.hash instanceof Uint8Array) {
hash = Buffer.from(tx?.hash).toString('hex');
@@ -31,7 +41,7 @@ export class Discord {
return { name: `tx`, value: `${hash}`, inline: true };
}
- private getChaoslabsUrl(address: string,subAccount: number): any {
+ private getChaoslabsUrl(address: string,subAccount: number): string {
return `https://community.chaoslabs.xyz/dydx-v4/risk/accounts/${address}/subAccount/${subAccount}/overview`;
}
@@ -53,6 +63,9 @@ export class Discord {
}
+ /**
+ * Disconnect from discord
+ */
public async logout(): Promise {
if(this.client === undefined) throw new Error("Client not initialized");
this.client.removeAllListeners();
@@ -76,8 +89,8 @@ export class Discord {
if(!this.client?.isReady) throw new Error("Client not ready");
return this.channel.send({ embeds: [embed] });
}
-
- public sendMessageClosePosition(market: string, position: Position, tx?: any): Promise {
+
+ public sendMessageClosePosition(market: string, position: Position, tx?: TxResponse): Promise {
const embed = new EmbedBuilder()
.setTitle(`${market}`)
@@ -96,11 +109,11 @@ export class Discord {
// Performance at close
const perf = Math.round((pnl/(position.sumOpen * position.entryPrice))*100)/100;
let perfIcon: string;
- if(perf > -0.02 && perf < 0.01) {
+ if(perf > -0.02 && perf < 0.02) {
perfIcon = "😑";
- }else if(perf >= 0.05) {
+ }else if(perf >= 0.1) {
perfIcon = "🚀";
- }else if(perf <= -0.02) {
+ }else if(perf <= -0.05) {
perfIcon = "😡";
}else {
perfIcon = "😐";
@@ -114,7 +127,7 @@ export class Discord {
}
- public sendMessageOrder(order: BotOrder, input?: Input, strategy?: Strat, tx?: any): Promise {
+ public sendMessageOrder(order: BotOrder, input?: Input, strategy?: Strat, tx?: TxResponse): Promise {
// Color of the embed
let color: ColorResolvable;
@@ -127,7 +140,7 @@ export class Discord {
}
// size in USD
- let usdSize = Math.round((order.price*order.size)*100)/100;
+ const usdSize = Math.round((order.price*order.size)*100)/100;
const embed = new EmbedBuilder()
.setTitle(`${order.market}`)
@@ -155,8 +168,9 @@ export class Discord {
return this.sendMessage(message);
}
- public sendError(message: string): Promise {
- return this.sendMessage("⚠️ "+message);
+ public sendError(message: Error): Promise {
+ if(message instanceof Warning) return this.sendMessage("⚠️ "+message);
+ else return this.sendMessage("❌ "+message);
}
}
diff --git a/src/dydx/dydx-bot.ts b/src/dydx/dydx-bot.ts
index 91dab20..1ae66b8 100644
--- a/src/dydx/dydx-bot.ts
+++ b/src/dydx/dydx-bot.ts
@@ -1,28 +1,9 @@
import { BECH32_PREFIX,CompositeClient, LocalWallet, Network, OrderFlags, OrderSide, OrderTimeInForce, OrderType, SubaccountClient } from "@dydxprotocol/v4-client-js";
-import { BotOrder, Bot, BrokerConfig } from "../bot";
-import { BroadcastTxAsyncResponse, BroadcastTxSyncResponse } from '@cosmjs/tendermint-rpc/build/tendermint37';
-import { IndexedTx} from '@cosmjs/stargate';
-
-export interface Position {
- market: string,
- status: string,
- side: string,
- size: number,
- maxSize: number,
- entryPrice: number,
- exitPrice: number,
- realizedPnl: number, // in usd
- unrealizedPnl: number, // in usd
- createdAt: Date,
- createdAtHeight: number,
- closedAt: Date,
- sumOpen: number, // in crypto
- sumClose: number, // in crypto
- netFunding: number
-}
-
-export type TxResponse = BroadcastTxAsyncResponse | BroadcastTxSyncResponse | IndexedTx;
+import { BotOrder, Bot, BrokerConfig, Warning, TxResponse, Position } from "../bot";
+/**
+ * Bot implementation for dYdX
+ */
export class DYDXBot extends Bot {
public client?: CompositeClient;
@@ -87,7 +68,7 @@ export class DYDXBot extends Bot {
const position: Position | undefined = await this.client.indexerClient.account.getSubaccountPerpetualPositions(this.subaccount.address, this.SUBACCOUNT_NUMBER).then((result) => {
return result.positions.find((position: Position) => position.market === market && position.status === "OPEN");
});
- if(position === undefined) throw new Error(`Trying to close a positon that does not exist on ${market}`);
+ if(position === undefined) throw new Warning(`Trying to close a positon that does not exist on ${market}`);
// Check if position side is correct
if(hasToBeSide !== undefined && position.side !== (hasToBeSide === OrderSide.BUY ? Bot.SIDE_LONG : Bot.SIDE_SHORT)) throw new Error(`Trying to close a positon on ${market} but the position is already on the target side ${position.side}`);
@@ -96,8 +77,7 @@ export class DYDXBot extends Bot {
const closingOrder: BotOrder = new BotOrder();
closingOrder.market = market;
closingOrder.clientId = Date.now();
- // closingOrder.reduceOnly = true;
-
+
if(position.side === Bot.SIDE_LONG){
closingOrder.size = position.size;
closingOrder.side = OrderSide.SELL;
@@ -118,6 +98,7 @@ export class DYDXBot extends Bot {
else {
closingOrder.type = OrderType.MARKET;
closingOrder.timeInForce = OrderTimeInForce.FOK;
+ closingOrder.reduceOnly = true;
}
// Send closing order
diff --git a/src/main.ts b/src/main.ts
index db5776c..dbd0da6 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,19 +1,21 @@
import { Network } from "@dydxprotocol/v4-client-js";
import { DYDXBot } from "./dydx/dydx-bot";
import { BasicStrat } from "./strategy/strat-basic";
-import { APIGatewayProxyEvent, Context } from "aws-lambda";
+import { APIGatewayProxyCallback, APIGatewayProxyEvent, APIGatewayProxyResult, Context } from "aws-lambda";
import { Bot, Input } from "./bot";
+/**
+ * Response parameters for the APIGateway callback
+ */
export interface CallbackResponseParams {
- response_error: Error|null;
- response_success: any;
+ response_error?: Error;
+ response_success?: APIGatewayProxyResult;
}
-export type CallbackResponse = (response_error: Error|null, response_success: any) => any;
/**
* Main handler for the lambda function
*/
-exports.handler = function (event: APIGatewayProxyEvent, context: Context, callback: CallbackResponse) {
+exports.handler = function (event: APIGatewayProxyEvent, context: Context, callback: APIGatewayProxyCallback) {
console.log(`Event: ${JSON.stringify(event, null, 2)}`);
console.log(`Context: ${JSON.stringify(context, null, 2)}`);
@@ -34,7 +36,7 @@ exports.handler = function (event: APIGatewayProxyEvent, context: Context, callb
}
}
- catch(e: any) {
+ catch(e) {
console.error(e);
exit(callback, {response_error: new Error("Error"), response_success:undefined} );
}
@@ -46,12 +48,12 @@ exports.handler = function (event: APIGatewayProxyEvent, context: Context, callb
* @param callback callback function
* @param response content returned by the bot
*/
-function exit(callback: CallbackResponse, response: CallbackResponseParams) {
+function exit(callback: APIGatewayProxyCallback, response: CallbackResponseParams) {
console.log(response);
callback(response.response_error, response.response_success);
}
-function DydxHandler(input: Input, context: Context, callback: CallbackResponse) {
+function DydxHandler(input: Input, context: Context, callback: APIGatewayProxyCallback) {
let network: Network;
@@ -71,13 +73,15 @@ function DydxHandler(input: Input, context: Context, callback: CallbackResponse)
bot.process(input, new BasicStrat(), context).then((response: CallbackResponseParams) => {
exit(callback,response);
- }).catch((e: any): void => {
- throw new Error(e);
+ }).catch((e: Error): void => {
+ throw e;
}).finally ((): void => {
bot.disconnect();
});
- }).catch((e: any): void => {
+ }).catch((e: Error): void => {
+ console.error(e);
+ // Return generic error in the body
exit(callback, {response_error: new Error("Error"), response_success:undefined} );
});
diff --git a/test/__mocks__/bot-mock.ts b/test/__mocks__/bot-mock.ts
index 4397804..69ed44b 100644
--- a/test/__mocks__/bot-mock.ts
+++ b/test/__mocks__/bot-mock.ts
@@ -87,7 +87,7 @@ export class MockBot extends Bot {
// Position already in place , ignore input
if((order.side == OrderSide.BUY && this.openPositonsLongs.has(order.market)) ||
(order.side == OrderSide.SELL && this.openPositonsShorts.has(order.market))) {
- return Promise.resolve({response_error:null, response_success:null});
+ return Promise.resolve({response_error:undefined, response_success:undefined});
}
// Close previous position
@@ -96,7 +96,7 @@ export class MockBot extends Bot {
// Open new position
this.placeOrder(order);
- return Promise.resolve({response_error:null, response_success:null});
+ return Promise.resolve({response_error:undefined, response_success:undefined});
}
diff --git a/test/discord.test.ts b/test/discord.test.ts
index b8ce7eb..d16e598 100644
--- a/test/discord.test.ts
+++ b/test/discord.test.ts
@@ -35,7 +35,7 @@ describe("discord", () => {
order.price = 1000;
order.size = 0.1;
order.side = OrderSide.SELL;
- d.sendMessageOrder(order, new Input("{}"), new BasicStrat(), {hash: "0x1234"});
+ d.sendMessageOrder(order, new Input("{}"), new BasicStrat(), undefined);
}, TIMEOUT);
it("output message to discord", async () => {
@@ -44,7 +44,7 @@ describe("discord", () => {
}, TIMEOUT);
it("error message to discord", async () => {
- d.sendError("error message");
+ d.sendError(new Error("error message"));
}, TIMEOUT);
it("close position message to discord", async () => {
@@ -64,7 +64,7 @@ describe("discord", () => {
sumOpen: 0.1,
sumClose: 0.1,
netFunding: 0
- },{hash: "0x1234"})
+ },undefined)
}, TIMEOUT);
});
\ No newline at end of file
diff --git a/test/dydx.test.ts b/test/dydx.test.ts
index ea9ca8b..e177fbb 100644
--- a/test/dydx.test.ts
+++ b/test/dydx.test.ts
@@ -87,7 +87,7 @@ describe("dYdX", () => {
}, TIMEOUT);
it("dryrun", async () => {
- const input : Input = {roundingFactor:1000, interval:60, dryrun:true, emitKey:"", market:"BTC-USD", price:10000, source: InputSource.Mock ,details:{action:"SELL",limit:50000}};
+ const input : Input = {roundingFactor:1000, interval:60, dryrun:true, emitKey:"", market:"BTC-USD", price:10000, source: InputSource.Mock ,details:{}};
try {
await bot.process(input, new BasicStrat(), undefined);
}
diff --git a/test/strat-basic.test.ts b/test/strat-basic.test.ts
index 2eadb5e..f1b46e1 100644
--- a/test/strat-basic.test.ts
+++ b/test/strat-basic.test.ts
@@ -2,7 +2,7 @@
import { MockBot } from "./__mocks__/bot-mock";
import { OrderSide} from "@dydxprotocol/v4-client-js";
-import { BotOrder, Input, InputSource } from "../src/bot";
+import { BotOrder, Input, InputSource, SuperTrendDetails } from "../src/bot";
import { BasicStrat } from "../src/strategy/strat-basic";
import dotenv from 'dotenv';
import fs from 'fs';
@@ -26,14 +26,14 @@ describe("basic strat", () => {
});
it("supertrend BUY", () => {
- const order: BotOrder = strat.getStatelessOrderBasedOnInput({interval:60, roundingFactor:1000, dryrun:false, emitKey:"", market:"BTC-USD",price:10000,source: InputSource.SuperTrend ,details:{action:"BUY",limit:50000}});
+ const order: BotOrder = strat.getStatelessOrderBasedOnInput({interval:60, roundingFactor:1000, dryrun:false, emitKey:"", market:"BTC-USD",price:10000,source: InputSource.SuperTrend ,details:({action:"BUY",limit:50000} as SuperTrendDetails)});
expect(order.size).toBe(0.1);
expect(order.side).toBe(OrderSide.BUY);
expect(order.price).toBe(50000);
});
it("supertrend SELL", () => {
- const order: BotOrder = strat.getStatelessOrderBasedOnInput({interval:60, roundingFactor:1000, dryrun:false, emitKey:"", market:"BTC-USD",price:10000,source: InputSource.SuperTrend ,details:{action:"SELL",limit:50000}});
+ const order: BotOrder = strat.getStatelessOrderBasedOnInput({interval:60, roundingFactor:1000, dryrun:false, emitKey:"", market:"BTC-USD",price:10000,source: InputSource.SuperTrend ,details:({action:"SELL",limit:50000} as SuperTrendDetails)});
expect(order.size).toBe(0.1);
expect(order.side).toBe(OrderSide.SELL);
expect(order.price).toBe(50000);
@@ -41,12 +41,12 @@ describe("basic strat", () => {
it("supertrend error", () => {
expect(() => {
- const order: BotOrder = strat.getStatelessOrderBasedOnInput({interval:60, roundingFactor:1000, dryrun:false, emitKey:"", market:"BTC-USD",price:10000,source: InputSource.SuperTrend ,details:{action:"ERR"}});
+ const order: BotOrder = strat.getStatelessOrderBasedOnInput({interval:60, roundingFactor:1000, dryrun:false, emitKey:"", market:"BTC-USD",price:10000,source: InputSource.SuperTrend ,details:({action:"ERR"} as SuperTrendDetails)});
}).toThrow();
});
it("rounding", async () => {
- const input : Input = {interval:60, roundingFactor:0, dryrun:true, emitKey:"", market:"BTC-USD",price:0,source: InputSource.Mock ,details:{action:"BUY",limit:0}};
+ const input : Input = {interval:60, roundingFactor:0, dryrun:true, emitKey:"", market:"BTC-USD",price:0,source: InputSource.Mock ,details:({action:"BUY",limit:0} as SuperTrendDetails)};
let order: BotOrder;
// no rounding size = 0,0999594977723775
@@ -93,7 +93,7 @@ describe("basic strat", () => {
// Successful strategy
expect(bot.getFullBalance()).toBeGreaterThan(INITIAL_BALANCE)
- console.log("performance : " + ((bot.getFullBalance()/INITIAL_BALANCE)-1)*100 + "%");
+ console.log("performance : " + ((bot.getFullBalance()/INITIAL_BALANCE)-1)*100 + " %");
});