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

♻️ Refactoring exchanges services #15

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
:contruction: wip Fixing trading modes
  • Loading branch information
thibaultyou committed Aug 8, 2021
commit 4294de22cd31a2e88e3f4f5594fcbae910301d62
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,6 @@ You can use the bot with :

Feel free to submit [Github issues](https://github.com/thibaultyou/tradingview-alerts-processor/issues) if you find anything you want me to add, fix or improve.

Best way to show your support to this tool is by hitting the star button [![Stars](https://img.shields.io/github/stars/thibaultyou/tradingview-alerts-processor?style=social)](https://github.com/thibaultyou/tradingview-alerts-processor/stargazers), you can also [!["Buy Me A Coffee"](https://img.shields.io/badge/-buy_me_a%C2%A0coffee-gray?logo=buy-me-a-coffee)](https://www.buymeacoffee.com/thibaultyou).
Best way to show your support to this tool is by hitting the star button [![Stars](https://img.shields.io/github/stars/thibaultyou/tradingview-alerts-processor?style=social)](https://github.com/thibaultyou/tradingview-alerts-processor/stargazers), you can also [!["Buy Me A Coffee"](https://img.shields.io/badge/-buy_me_a%C2%A0coffee-gray?logo=buy-me-a-coffee)](https://www.buymeacoffee.com/thibaultyou).

You can join us on the Jackrabbit Discord server [![Discord](https://img.shields.io/discord/664206005881536512)](https://discord.gg/mNMVWXpAGd) where I'll be happy to answer questions there, you'll also find great strategies to use with this tool.
7 changes: 2 additions & 5 deletions src/interfaces/exchanges/base/spot.exchange.interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { Ticker } from 'ccxt';
import { Account } from '../../../entities/account.entities';
/* eslint-disable @typescript-eslint/no-empty-interface */

export interface ISpotExchange {
getTickerBalance(account: Account, ticker: Ticker): Promise<number>;
}
export interface ISpotExchange {}
48 changes: 35 additions & 13 deletions src/services/exchanges/base/base.exchange.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
EXCHANGE_INIT_SUCCESS,
MARKETS_READ_ERROR,
MARKETS_READ_SUCCESS,
TICKER_BALANCE_READ_ERROR,
TICKER_BALANCE_READ_SUCCESS,
TICKER_READ_ERROR,
TICKER_READ_SUCCESS
} from '../../../messages/exchanges.messages';
Expand Down Expand Up @@ -50,12 +52,12 @@ import {
getExchangeOptions,
isSpotExchange
} from '../../../utils/exchanges/common.utils';
import { getSpotQuote } from '../../../utils/trading/symbol.utils';
import { getQuote, getSymbol } from '../../../utils/trading/symbol.utils';
import { getSide } from '../../../utils/trading/side.utils';
import {
getOrderCost,
getRelativeOrderSize,
getTokensAmount
getTokensAmount,
getTokensPrice
} from '../../../utils/trading/conversion.utils';
import { getTickerPrice } from '../../../utils/trading/ticker.utils';
import { filterBalances } from '../../../utils/trading/balance.utils';
Expand Down Expand Up @@ -168,6 +170,28 @@ export abstract class BaseExchangeService implements IBaseExchange {
}
};

getTickerBalance = async (
account: Account,
ticker: Ticker
): Promise<number> => {
const accountId = getAccountId(account);
const symbol = getSymbol(ticker.symbol);
try {
const balances = await this.getBalances(account);
const balance = balances.filter((b) => b.coin === symbol).pop();
const size = Number(balance.free);
debug(
TICKER_BALANCE_READ_SUCCESS(this.exchangeId, accountId, symbol, balance)
);
return size;
} catch (err) {
error(TICKER_BALANCE_READ_ERROR(this.exchangeId, accountId, symbol, err));
throw new TickerFetchError(
TICKER_BALANCE_READ_ERROR(this.exchangeId, accountId, symbol, err)
);
}
};

getMarkets = async (): Promise<IMarket[]> => {
try {
const markets: ccxt.Market[] = await this.defaultExchange.fetchMarkets();
Expand Down Expand Up @@ -198,7 +222,7 @@ export abstract class BaseExchangeService implements IBaseExchange {
): Promise<number> => {
const { symbol } = ticker;
const accountId = getAccountId(account);
const quote = getSpotQuote(symbol);
const quote = getQuote(symbol);
// TODO refacto
let availableFunds = 0;
if (this.exchangeId === ExchangeId.FTX && !isFTXSpot(ticker)) {
Expand All @@ -207,9 +231,7 @@ export abstract class BaseExchangeService implements IBaseExchange {
).result;
availableFunds = Number(accountInfos.freeCollateral);
} else {
const balances = await this.getBalances(account);
const balance = balances.filter((b) => b.coin === quote).pop();
availableFunds = Number(balance.free);
availableFunds = await this.getTickerBalance(account, ticker);
}
info(AVAILABLE_FUNDS(accountId, this.exchangeId, quote, availableFunds));
return availableFunds;
Expand All @@ -236,14 +258,14 @@ export abstract class BaseExchangeService implements IBaseExchange {
}
};

// TODO refacto
openOrder = async (account: Account, trade: Trade): Promise<Order> => {
await this.refreshSession(account);
const { symbol, size, direction, max, mode } = trade;
const accountId = getAccountId(account);
try {
const ticker = await this.getTicker(symbol);
// TODO refacto
// handling sell on spot
// handling sell if spot exchange
if (
getSide(direction) === Side.Sell &&
isSpotExchange(ticker, this.exchangeId)
Expand All @@ -252,7 +274,7 @@ export abstract class BaseExchangeService implements IBaseExchange {
}
// handling size / budget
if (size.includes('%') || max) {
const funds = await this.getAvailableFunds(account, ticker); // avoid this call if possible
const funds = await this.getAvailableFunds(account, ticker);
if (size.includes('%')) {
trade = {
...trade,
Expand All @@ -278,10 +300,10 @@ export abstract class BaseExchangeService implements IBaseExchange {
ticker,
trade
);
const cost = getOrderCost(ticker, this.exchangeId, size);
const order: Order = await this.sessions
.get(accountId)
.exchange.createMarketOrder(symbol, side, size);
const cost = getTokensPrice(ticker, this.exchangeId, size);
side === Side.Buy
? long(
OPEN_LONG_TRADE_SUCCESS(
Expand Down Expand Up @@ -326,7 +348,7 @@ export abstract class BaseExchangeService implements IBaseExchange {
ticker,
trade
);
const cost = getOrderCost(ticker, this.exchangeId, size);
const price = getTokensPrice(ticker, this.exchangeId, size);
const order = await this.sessions
.get(accountId)
.exchange.createMarketOrder(symbol, side, size);
Expand All @@ -336,7 +358,7 @@ export abstract class BaseExchangeService implements IBaseExchange {
accountId,
symbol,
size,
cost.toFixed(2)
price.toFixed(2)
)
);
return order;
Expand Down
38 changes: 4 additions & 34 deletions src/services/exchanges/base/composite.exchange.service.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,7 @@
import { Ticker } from 'ccxt';
import { Account } from '../../../entities/account.entities';
import { TickerFetchError } from '../../../errors/exchange.errors';
import {
TICKER_BALANCE_READ_SUCCESS,
TICKER_BALANCE_READ_ERROR
} from '../../../messages/exchanges.messages';
import { getAccountId } from '../../../utils/account.utils';
import { getSpotSymbol } from '../../../utils/trading/symbol.utils';
import { debug, error } from '../../logger.service';
import { ISpotExchange } from '../../../interfaces/exchanges/base/spot.exchange.interfaces';
import { FuturesExchangeService } from './futures.exchange.service';

// FIXME can be replaced by a mixin
export abstract class CompositeExchangeService extends FuturesExchangeService {
getTickerBalance = async (
account: Account,
ticker: Ticker
): Promise<number> => {
const accountId = getAccountId(account);
const symbol = getSpotSymbol(ticker.symbol);
try {
const balances = await this.getBalances(account);
const balance = balances.filter((b) => b.coin === symbol).pop();
const size = Number(balance.free);
debug(
TICKER_BALANCE_READ_SUCCESS(this.exchangeId, accountId, symbol, balance)
);
return size;
} catch (err) {
error(TICKER_BALANCE_READ_ERROR(this.exchangeId, accountId, symbol, err));
throw new TickerFetchError(
TICKER_BALANCE_READ_ERROR(this.exchangeId, accountId, symbol, err)
);
}
};
// above declaration is the same as SpotExchangeService since I'm not playing with mixins for now
}
export abstract class CompositeExchangeService
extends FuturesExchangeService
implements ISpotExchange {}
67 changes: 44 additions & 23 deletions src/services/exchanges/base/futures.exchange.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,31 @@ import { TradingMode } from '../../../constants/trading.constants';
import { Account } from '../../../entities/account.entities';
import { Trade } from '../../../entities/trade.entities';
import { PositionsFetchError } from '../../../errors/exchange.errors';
import {
NoOpenPositionError,
OpenPositionError
} from '../../../errors/trading.errors';
import { OpenPositionError } from '../../../errors/trading.errors';
import { IFuturesExchange } from '../../../interfaces/exchanges/base/futures.exchange.interfaces';
import {
NO_CURRENT_POSITION,
POSITIONS_READ_ERROR,
POSITIONS_READ_SUCCESS,
POSITION_READ_SUCCESS
} from '../../../messages/exchanges.messages';
import {
OPEN_TRADE_ERROR_MAX_SIZE,
REVERSING_TRADE,
TRADE_OVERFLOW
} from '../../../messages/trading.messages';
import { OPEN_TRADE_ERROR_MAX_SIZE } from '../../../messages/trading.messages';
import { FuturesPosition } from '../../../types/exchanges.types';
import { getAccountId } from '../../../utils/account.utils';
import { getRelativeOrderSize } from '../../../utils/trading/conversion.utils';
import {
getRelativeOrderSize,
getTokensAmount
} from '../../../utils/trading/conversion.utils';
import {
filterPosition,
filterPositions,
getPositionSize
} from '../../../utils/trading/position.utils';
import { getSide } from '../../../utils/trading/side.utils';
import { getSide, getInvertedSide } from '../../../utils/trading/side.utils';
import { debug, error, info } from '../../logger.service';
import { BaseExchangeService } from './base.exchange.service';
import { getTickerPrice } from '../../../utils/trading/ticker.utils';
import { IOrderOptions } from '../../../interfaces/trading.interfaces';

export abstract class FuturesExchangeService
extends BaseExchangeService
Expand Down Expand Up @@ -77,11 +75,14 @@ export abstract class FuturesExchangeService
const position = filterPosition(positions, this.exchangeId, ticker);
if (!position) {
info(NO_CURRENT_POSITION(accountId, this.exchangeId, symbol));
throw new NoOpenPositionError(
NO_CURRENT_POSITION(accountId, this.exchangeId, symbol)
// throw new NoOpenPositionError(
// NO_CURRENT_POSITION(accountId, this.exchangeId, symbol)
// );
} else {
debug(
POSITION_READ_SUCCESS(accountId, this.exchangeId, symbol, position)
);
}
debug(POSITION_READ_SUCCESS(accountId, this.exchangeId, symbol, position));
return position;
};

Expand All @@ -90,7 +91,33 @@ export abstract class FuturesExchangeService
ticker: Ticker
): Promise<number> => {
const position = await this.getTickerPosition(account, ticker);
return getPositionSize(position, this.exchangeId);
return position ? getPositionSize(position, this.exchangeId) : 0;
};

getCloseOrderOptions = async (
account: Account,
ticker: Ticker,
trade: Trade
): Promise<IOrderOptions> => {
const { size, direction } = trade;
const { symbol } = ticker;
const price = getTickerPrice(ticker, this.exchangeId);
const position = await this.getTickerPosition(account, ticker);
const current = getPositionSize(position, this.exchangeId);

let orderSize = 0;
if (size && size.includes('%')) {
orderSize = getRelativeOrderSize(current, size); // relative
} else if (!size || Number(size) > current) {
orderSize = current; // 100%
} else {
orderSize = Number(size); // absolute
}

return {
size: getTokensAmount(symbol, price, orderSize),
side: getInvertedSide(direction) as 'sell' | 'buy'
};
};

handleOrderModes = async (
Expand All @@ -117,16 +144,10 @@ export abstract class FuturesExchangeService
const { symbol, max, direction, size } = trade;
const accountId = getAccountId(account);
const side = getSide(direction);
let current = 0;
// TODO refacto
try {
current = await this.getTickerPositionSize(account, ticker);
} catch (err) {
// silent
}
const current = await this.getTickerPositionSize(account, ticker);
if (
Math.abs(current) +
(size.includes('%') // add the required position cost
(size.includes('%')
? getRelativeOrderSize(balance, size)
: Number(size)) >
Number(max)
Expand Down
36 changes: 4 additions & 32 deletions src/services/exchanges/base/spot.exchange.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,19 @@ import { Ticker } from 'ccxt';
import { Side } from '../../../constants/trading.constants';
import { Account } from '../../../entities/account.entities';
import { Trade } from '../../../entities/trade.entities';
import { TickerFetchError } from '../../../errors/exchange.errors';
import { OpenPositionError } from '../../../errors/trading.errors';
import { ISpotExchange } from '../../../interfaces/exchanges/base/spot.exchange.interfaces';
import { IOrderOptions } from '../../../interfaces/trading.interfaces';
import {
TICKER_BALANCE_READ_SUCCESS,
TICKER_BALANCE_READ_ERROR
} from '../../../messages/exchanges.messages';
import { OPEN_TRADE_ERROR_MAX_SIZE } from '../../../messages/trading.messages';
import { getAccountId } from '../../../utils/account.utils';
import {
getOrderCost,
getTokensPrice,
getRelativeOrderSize,
getTokensAmount
} from '../../../utils/trading/conversion.utils';
import { getSide } from '../../../utils/trading/side.utils';
import { getSpotSymbol } from '../../../utils/trading/symbol.utils';
import { getTickerPrice } from '../../../utils/trading/ticker.utils';
import { debug, error } from '../../logger.service';
import { error } from '../../logger.service';
import { BaseExchangeService } from './base.exchange.service';

export abstract class SpotExchangeService
Expand All @@ -40,8 +34,8 @@ export abstract class SpotExchangeService
const side = getSide(direction);
const current = await this.getTickerBalance(account, ticker);
if (
getOrderCost(ticker, this.exchangeId, current) + // get cost of current position
(size.includes('%') // add the required position cost
getTokensPrice(ticker, this.exchangeId, current) +
(size.includes('%')
? getRelativeOrderSize(balance, size)
: Number(size)) >
Number(max)
Expand All @@ -55,28 +49,6 @@ export abstract class SpotExchangeService
}
};

getTickerBalance = async (
account: Account,
ticker: Ticker
): Promise<number> => {
const accountId = getAccountId(account);
const symbol = getSpotSymbol(ticker.symbol);
try {
const balances = await this.getBalances(account);
const balance = balances.filter((b) => b.coin === symbol).pop();
const size = Number(balance.free);
debug(
TICKER_BALANCE_READ_SUCCESS(this.exchangeId, accountId, symbol, balance)
);
return size;
} catch (err) {
error(TICKER_BALANCE_READ_ERROR(this.exchangeId, accountId, symbol, err));
throw new TickerFetchError(
TICKER_BALANCE_READ_ERROR(this.exchangeId, accountId, symbol, err)
);
}
};

getCloseOrderOptions = async (
account: Account,
ticker: Ticker,
Expand Down
Loading