Skip to content
This repository has been archived by the owner on Dec 1, 2023. It is now read-only.

♻️ Adding back reverse mode for FTX futures #36

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/constants/trading.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
];
15 changes: 10 additions & 5 deletions src/entities/trade.entities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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).*/)
Expand Down
68 changes: 30 additions & 38 deletions src/services/exchanges/base/base.exchange.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -79,11 +79,11 @@ export abstract class BaseExchangeService {
trade: Trade
): Promise<IOrderOptions>;

// abstract handleReverseOrder(
// account: Account,
// ticker: Ticker,
// trade: Trade
// ): Promise<void>;
abstract handleReverseOrder(
account: Account,
ticker: Ticker,
trade: Trade
): Promise<boolean>;

// abstract handleOverflow(
// account: Account,
Expand Down Expand Up @@ -230,23 +230,6 @@ export abstract class BaseExchangeService {
return availableFunds;
};

// handleOrderModes = async (
// account: Account,
// ticker: Ticker,
// trade: Trade
// ): Promise<boolean> => {
// 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,
Expand All @@ -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');
Expand All @@ -284,29 +268,37 @@ export abstract class BaseExchangeService {
}
};

handleOrderModes = async (
account: Account,
ticker: Ticker,
trade: Trade
): Promise<boolean> => {
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<Order> => {
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(
Expand Down
8 changes: 8 additions & 0 deletions src/services/exchanges/base/spot.exchange.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,12 @@ export abstract class SpotExchangeService extends BaseExchangeService {
: balance // default 100%
};
};

handleReverseOrder(
_account: Account,
_ticker: Ticker,
_trade: Trade
): Promise<boolean> {
throw new Error('Method not implemented.');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,4 +101,11 @@ export class BinanceFuturesUSDMExchangeService extends FuturesExchangeService {
// }
// return false;
// };
handleReverseOrder(
_account: Account,
_ticker: Ticker,
_trade: Trade
): Promise<boolean> {
throw new Error('Method not implemented.');
}
}
62 changes: 39 additions & 23 deletions src/services/exchanges/ftx.exchange.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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() {
Expand Down Expand Up @@ -109,26 +116,35 @@ export class FTXExchangeService extends CompositeExchangeService {
}
};

// handleReverseOrder = async (
// account: Account,
// ticker: Ticker,
// trade: Trade
// ): Promise<void> => {
// 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<boolean> => {
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,
Expand Down
3 changes: 0 additions & 3 deletions src/utils/trading/__tests__/conversion.utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
);
Expand Down
2 changes: 1 addition & 1 deletion src/utils/trading/conversion.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would > 100% be allowed?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you're using leverage to trade like a degenerate 😅

error(TRADE_ERROR_SIZE(size));
throw new OrderSizeError(TRADE_ERROR_SIZE(size));
}
Expand Down