diff --git a/src/constants/trading.constants.ts b/src/constants/trading.constants.ts index bc6b232..bf5bbb8 100644 --- a/src/constants/trading.constants.ts +++ b/src/constants/trading.constants.ts @@ -9,8 +9,11 @@ export enum Side { export const SIDES = [Side.Buy, Side.Close, Side.Long, Side.Sell, Side.Short]; export enum TradingMode { - Reverse = 'reverse', - Overflow = 'overflow' + Reverse = 'reverse' + // Overflow = 'overflow' } -export const TRADING_MODES = [TradingMode.Overflow, TradingMode.Reverse]; +export const TRADING_MODES = [ + // TradingMode.Overflow, + TradingMode.Reverse +]; diff --git a/src/entities/trade.entities.ts b/src/entities/trade.entities.ts index c2a0d74..4174617 100644 --- a/src/entities/trade.entities.ts +++ b/src/entities/trade.entities.ts @@ -5,7 +5,12 @@ import { Matches, ValidateIf } from 'class-validator'; -import { SIDES, Side } from '../constants/trading.constants'; +import { + SIDES, + Side, + TradingMode, + TRADING_MODES +} from '../constants/trading.constants'; export class Trade { @IsString() @@ -19,10 +24,10 @@ export class Trade { @IsOptional() max?: string; - // @IsString() - // @IsIn(TRADING_MODES) - // @IsOptional() - // mode?: TradingMode; + @IsString() + @IsIn(TRADING_MODES) + @IsOptional() + mode?: TradingMode; @IsString() @Matches(/.*(PERP|USD).*/) diff --git a/src/services/exchanges/base/base.exchange.service.ts b/src/services/exchanges/base/base.exchange.service.ts index 2c9b975..6195159 100644 --- a/src/services/exchanges/base/base.exchange.service.ts +++ b/src/services/exchanges/base/base.exchange.service.ts @@ -41,7 +41,7 @@ import { OpenPositionError, OrderSizeError } from '../../../errors/trading.errors'; -import { Side } from '../../../constants/trading.constants'; +import { Side, TradingMode } from '../../../constants/trading.constants'; import { IBalance, ISession @@ -79,11 +79,11 @@ export abstract class BaseExchangeService { trade: Trade ): Promise; - // abstract handleReverseOrder( - // account: Account, - // ticker: Ticker, - // trade: Trade - // ): Promise; + abstract handleReverseOrder( + account: Account, + ticker: Ticker, + trade: Trade + ): Promise; // abstract handleOverflow( // account: Account, @@ -230,23 +230,6 @@ export abstract class BaseExchangeService { return availableFunds; }; - // handleOrderModes = async ( - // account: Account, - // ticker: Ticker, - // trade: Trade - // ): Promise => { - // const { mode } = trade; - // if (mode === TradingMode.Reverse) { - // await this.handleReverseOrder(account, ticker, trade); - // } else if (mode === TradingMode.Overflow) { - // const isOverflowing = await this.handleOverflow(account, ticker, trade); - // if (isOverflowing) { - // return false; // on overflow we only close position - // } - // } - // return true; - // }; - getOpenOrderOptions = async ( account: Account, ticker: Ticker, @@ -267,6 +250,7 @@ export abstract class BaseExchangeService { await this.handleMaxBudget(account, ticker, trade, funds); } } + // if (isSpotExchange(ticker, this.exchangeId) && orderSize > funds) { // // TODO create dedicated error // throw new Error('Insufficient funds'); @@ -284,29 +268,37 @@ export abstract class BaseExchangeService { } }; + handleOrderModes = async ( + account: Account, + ticker: Ticker, + trade: Trade + ): Promise => { + const { mode } = trade; + let isAllowed = true; + if (mode === TradingMode.Reverse && this.exchangeId === ExchangeId.FTX) { + isAllowed = await this.handleReverseOrder(account, ticker, trade); + } + return isAllowed; + }; + createOrder = async (account: Account, trade: Trade): Promise => { await this.refreshSession(account); const { symbol, direction } = trade; const accountId = getAccountId(account); + const side = getSide(direction); try { const ticker = await this.getTicker(symbol); - // const isOrderAllowed = await this.handleOrderModes( - // account, - // ticker, - // trade - // ); - // if (isOrderAllowed) { - // TODO refacto - // close on sell spot order - if ( - getSide(direction) === Side.Sell && - isSpotExchange(ticker, this.exchangeId) - ) { - return await this.createCloseOrder(account, trade, ticker); - } else { + const isOrderAllowed = await this.handleOrderModes( + account, + ticker, + trade + ); + if (isOrderAllowed) { + if (isSpotExchange(ticker, this.exchangeId) && side === Side.Sell) { + return await this.createCloseOrder(account, trade, ticker); + } return await this.createOpenOrder(account, trade, ticker); } - // } } catch (err) { error(CREATE_ORDER_ERROR(this.exchangeId, accountId, trade), err); throw new CreateOrderError( diff --git a/src/services/exchanges/base/spot.exchange.service.ts b/src/services/exchanges/base/spot.exchange.service.ts index f7a7fdf..c994766 100644 --- a/src/services/exchanges/base/spot.exchange.service.ts +++ b/src/services/exchanges/base/spot.exchange.service.ts @@ -111,4 +111,12 @@ export abstract class SpotExchangeService extends BaseExchangeService { : balance // default 100% }; }; + + handleReverseOrder( + _account: Account, + _ticker: Ticker, + _trade: Trade + ): Promise { + throw new Error('Method not implemented.'); + } } diff --git a/src/services/exchanges/binance-usdm.futures.exchange.service.ts b/src/services/exchanges/binance-usdm.futures.exchange.service.ts index 81b5f3c..0f3247f 100644 --- a/src/services/exchanges/binance-usdm.futures.exchange.service.ts +++ b/src/services/exchanges/binance-usdm.futures.exchange.service.ts @@ -101,4 +101,11 @@ export class BinanceFuturesUSDMExchangeService extends FuturesExchangeService { // } // return false; // }; + handleReverseOrder( + _account: Account, + _ticker: Ticker, + _trade: Trade + ): Promise { + throw new Error('Method not implemented.'); + } } diff --git a/src/services/exchanges/ftx.exchange.service.ts b/src/services/exchanges/ftx.exchange.service.ts index 6432626..0103226 100644 --- a/src/services/exchanges/ftx.exchange.service.ts +++ b/src/services/exchanges/ftx.exchange.service.ts @@ -4,10 +4,13 @@ import { getAccountId } from '../../utils/account.utils'; import { Exchange, Ticker } from 'ccxt'; import { Side } from '../../constants/trading.constants'; import { IOrderOptions } from '../../interfaces/trading.interfaces'; -import { error } from '../logger.service'; +import { error, info } from '../logger.service'; import { Trade } from '../../entities/trade.entities'; import { isFTXSpot } from '../../utils/exchanges/ftx.utils'; -import { OPEN_TRADE_ERROR_MAX_SIZE } from '../../messages/trading.messages'; +import { + OPEN_TRADE_ERROR_MAX_SIZE, + REVERSING_TRADE +} from '../../messages/trading.messages'; import { OpenPositionError } from '../../errors/trading.errors'; import { CompositeExchangeService } from './base/composite.exchange.service'; import { IFTXFuturesPosition } from '../../interfaces/exchanges/ftx.exchange.interfaces'; @@ -16,7 +19,11 @@ import { getOrderCost, getRelativeOrderSize } from '../../utils/trading/conversion.utils'; -import { getInvertedSide, getSide } from '../../utils/trading/side.utils'; +import { + getInvertedSide, + getSide, + isSideDifferent +} from '../../utils/trading/side.utils'; export class FTXExchangeService extends CompositeExchangeService { constructor() { @@ -109,26 +116,35 @@ export class FTXExchangeService extends CompositeExchangeService { } }; - // handleReverseOrder = async ( - // account: Account, - // ticker: Ticker, - // trade: Trade - // ): Promise => { - // const { direction } = trade; - // const accountId = getAccountId(account); - // try { - // const position = (await this.getTickerPosition( - // account, - // ticker - // )) as IFTXFuturesPosition; - // if (position && isSideDifferent(position.side as Side, direction)) { - // info(REVERSING_TRADE(this.exchangeId, accountId, ticker.symbol)); - // await this.closeOrder(account, trade, ticker); - // } - // } catch (err) { - // // ignore throw - // } - // }; + handleReverseOrder = async ( + account: Account, + ticker: Ticker, + trade: Trade + ): Promise => { + const { direction } = trade; + const accountId = getAccountId(account); + try { + const position = (await this.getTickerPosition( + account, + ticker + )) as IFTXFuturesPosition; + if (position) { + if (!isSideDifferent(position.side as Side, direction)) { + return false; + } else { + info(REVERSING_TRADE(this.exchangeId, accountId, ticker.symbol)); + await this.createCloseOrder( + account, + { ...trade, size: '100%' }, + ticker + ); + } + } + } catch (err) { + // ignore throw + } + return true; + }; // handleOverflow = async ( // account: Account, diff --git a/src/utils/trading/__tests__/conversion.utils.test.ts b/src/utils/trading/__tests__/conversion.utils.test.ts index 2fad77a..8baa3d1 100644 --- a/src/utils/trading/__tests__/conversion.utils.test.ts +++ b/src/utils/trading/__tests__/conversion.utils.test.ts @@ -17,9 +17,6 @@ describe('Conversion utils', () => { it('should throw if range is incorrect', () => { expect(() => getRelativeOrderSize(70, '0%')).toThrowError(OrderSizeError); - expect(() => getRelativeOrderSize(70, '100.0000000001')).toThrowError( - OrderSizeError - ); expect(() => getRelativeOrderSize(70, '-0.001%')).toThrowError( OrderSizeError ); diff --git a/src/utils/trading/conversion.utils.ts b/src/utils/trading/conversion.utils.ts index 6484c65..1d471d4 100644 --- a/src/utils/trading/conversion.utils.ts +++ b/src/utils/trading/conversion.utils.ts @@ -12,7 +12,7 @@ import { getTickerPrice } from './ticker.utils'; export const getRelativeOrderSize = (balance: number, size: string): number => { const percent = Number(size.replace(/%/g, '')); - if (percent <= 0 || percent > 100) { + if (percent <= 0) { error(TRADE_ERROR_SIZE(size)); throw new OrderSizeError(TRADE_ERROR_SIZE(size)); }