From 02171a0a33cddea2625419ecbf211ec6d79964e8 Mon Sep 17 00:00:00 2001 From: bitrinjani Date: Tue, 5 Dec 2017 18:31:25 +0900 Subject: [PATCH] Revert "major refactoring" This reverts commit b68dd72dcacbde5e58f1533ba68ab4e0246b88ff. --- src/AppRoot.ts | 16 +-- src/ArbitragerImpl.ts | 125 +++++++++++------- src/Bitflyer/BrokerAdapterImpl.ts | 9 +- src/Bitflyer/BrokerApi.ts | 2 +- src/Bitflyer/{types.ts => type.ts} | 0 src/BrokerAdapterRouterImpl.ts | 4 +- src/BrokerPosition.ts | 2 +- src/Coincheck/BrokerAdapterImpl.ts | 9 +- src/Coincheck/BrokerApi.ts | 2 +- src/Coincheck/mapper.ts | 2 +- src/Coincheck/{types.ts => type.ts} | 0 src/ConfigValidatorImpl.ts | 9 +- src/Execution.ts | 2 +- src/JsonConfigStore.ts | 2 +- src/LimitCheckerFactoryImpl.ts | 16 --- src/LimitCheckerImpl.ts | 123 ----------------- src/Order.ts | 6 +- src/PositionServiceImpl.ts | 8 +- src/Quoine/BrokerAdapterImpl.ts | 9 +- src/Quoine/BrokerApi.ts | 2 +- src/Quoine/{types.ts => type.ts} | 0 src/Quote.ts | 2 +- src/QuoteAggregatorImpl.ts | 4 +- src/SpreadAnalyzerImpl.ts | 21 +-- src/WebClient.ts | 3 +- src/__tests__/ArbitragerImpl.test.ts | 96 +++++--------- .../Bitflyer/BrokerAdapterImpl.test.ts | 4 +- src/__tests__/Bitflyer/BrokerApi.test.ts | 2 +- src/__tests__/BrokerAdapterRouterImpl.test.ts | 2 +- src/__tests__/BrokerPosition.test.ts | 2 +- .../Coincheck/BrokerAdapterImpl.test.ts | 6 +- src/__tests__/Coincheck/BrokerApi.test.ts | 2 +- src/__tests__/Coincheck/mapper.test.ts | 4 +- src/__tests__/ConfigValidatorImpl.test.ts | 2 +- src/__tests__/JsonConfigStore.test.ts | 2 +- src/__tests__/Order.test.ts | 2 +- src/__tests__/PositionServiceImpl.test.ts | 5 +- .../Quoine/BrokerAdapterImpl.test.ts | 4 +- src/__tests__/Quoine/BrokerApi.test.ts | 2 +- src/__tests__/QuoteAggregatorImpl.test.ts | 2 +- src/__tests__/SpreadAnalyzerImpl.test.ts | 2 +- src/__tests__/logger/LineIntegration.test.ts | 2 +- src/__tests__/logger/LoggerFactory.test.ts | 2 +- src/__tests__/logger/SlackIntegration.test.ts | 2 +- src/__tests__/util.test.ts | 7 +- src/bitflyer/index.ts | 1 - src/coincheck/index.ts | 1 - src/constants.ts | 1 - src/container.ts | 17 +-- src/logger/LineIntegration.ts | 6 +- src/logger/LoggerFactory.ts | 7 +- src/logger/SlackIntegration.ts | 6 +- src/logger/index.ts | 3 +- src/quoine/index.ts | 1 - src/symbols.ts | 3 +- src/{types.ts => type.ts} | 14 +- src/util.ts | 26 ++-- 57 files changed, 224 insertions(+), 392 deletions(-) rename src/Bitflyer/{types.ts => type.ts} (100%) rename src/Coincheck/{types.ts => type.ts} (100%) delete mode 100644 src/LimitCheckerFactoryImpl.ts delete mode 100644 src/LimitCheckerImpl.ts rename src/Quoine/{types.ts => type.ts} (100%) delete mode 100644 src/bitflyer/index.ts delete mode 100644 src/coincheck/index.ts delete mode 100644 src/constants.ts delete mode 100644 src/quoine/index.ts rename src/{types.ts => type.ts} (93%) diff --git a/src/AppRoot.ts b/src/AppRoot.ts index a14b12df..0969ab49 100644 --- a/src/AppRoot.ts +++ b/src/AppRoot.ts @@ -3,13 +3,11 @@ import intl from './intl'; import 'reflect-metadata'; import container from './container'; import symbols from './symbols'; -import { Arbitrager, QuoteAggregator, PositionService } from './types'; +import { Arbitrager } from './type'; import { Container } from 'inversify'; export default class AppRoot { - private log = getLogger(this.constructor.name); - private quoteAggregator: QuoteAggregator; - private positionService: PositionService; + private log = getLogger('AppRoot'); private arbitrager: Arbitrager; constructor(private readonly ioc: Container = container) { } @@ -17,10 +15,6 @@ export default class AppRoot { async start(): Promise { try { this.log.info(intl.t('StartingTheService')); - this.quoteAggregator = this.ioc.get(symbols.QuoteAggregator); - await this.quoteAggregator.start(); - this.positionService = this.ioc.get(symbols.PositionService); - await this.positionService.start(); this.arbitrager = this.ioc.get(symbols.Arbitrager); await this.arbitrager.start(); this.log.info(intl.t('SuccessfullyStartedTheService')); @@ -36,12 +30,6 @@ export default class AppRoot { if (this.arbitrager) { await this.arbitrager.stop(); } - if (this.positionService) { - await this.positionService.stop(); - } - if (this.quoteAggregator) { - await this.quoteAggregator.stop(); - } this.log.info(intl.t('SuccessfullyStoppedTheService')); } catch (ex) { this.log.error(ex.message); diff --git a/src/ArbitragerImpl.ts b/src/ArbitragerImpl.ts index 0b058397..4f66d6cb 100644 --- a/src/ArbitragerImpl.ts +++ b/src/ArbitragerImpl.ts @@ -5,19 +5,18 @@ import Order from './Order'; import { BrokerAdapterRouter, ConfigStore, PositionService, QuoteAggregator, SpreadAnalyzer, Arbitrager, SpreadAnalysisResult, - OrderType, QuoteSide, OrderSide, LimitCheckerFactory -} from './types'; + OrderType, QuoteSide, OrderSide, OrderStatus, BrokerConfig +} from './type'; import intl from './intl'; -import { padEnd, hr, delay, calculateCommission, findBrokerConfig } from './util'; +import { padEnd, hr, delay, calculateCommission } from './util'; import Quote from './Quote'; import symbols from './symbols'; -import { LOT_MIN_DECIMAL_PLACE } from './constants'; const t = s => intl.t(s); @injectable() export default class ArbitragerImpl implements Arbitrager { - private log = getLogger(this.constructor.name); + private log = getLogger('Arbitrager'); private activeOrders: Order[] = []; constructor( @@ -25,8 +24,7 @@ export default class ArbitragerImpl implements Arbitrager { @inject(symbols.ConfigStore) private readonly configStore: ConfigStore, @inject(symbols.PositionService) private readonly positionService: PositionService, @inject(symbols.BrokerAdapterRouter) private readonly brokerAdapterRouter: BrokerAdapterRouter, - @inject(symbols.SpreadAnalyzer) private readonly spreadAnalyzer: SpreadAnalyzer, - @inject(symbols.LimitCheckerFactory) private readonly limitCheckerFactory: LimitCheckerFactory + @inject(symbols.SpreadAnalyzer) private readonly spreadAnalyzer: SpreadAnalyzer ) { } status: string = 'Init'; @@ -35,49 +33,77 @@ export default class ArbitragerImpl implements Arbitrager { this.status = 'Starting'; this.log.info(t('StartingArbitrager')); this.quoteAggregator.onQuoteUpdated = (quotes: Quote[]) => this.quoteUpdated(quotes); + await this.quoteAggregator.start(); + await this.positionService.start(); this.status = 'Started'; this.log.info(t('StartedArbitrager')); } async stop(): Promise { this.status = 'Stopping'; - this.log.info('Stopping Arbitrager...'); + if (this.positionService) { + await this.positionService.stop(); + } if (this.quoteAggregator) { + await this.quoteAggregator.stop(); this.quoteAggregator.onQuoteUpdated = undefined; } - this.log.info('Stopped Arbitrager.'); this.status = 'Stopped'; } private async arbitrage(quotes: Quote[]): Promise { this.status = 'Arbitraging'; + if (!this.positionService.isStarted) { + this.status = 'Waiting Position Service'; + this.log.info(t('WaitingForPositionService')); + return; + } this.log.info(t('LookingForOpportunity')); const { config } = this.configStore; - let spreadAnalysisResult: SpreadAnalysisResult; + if (this.isMaxExposureBreached()) { + this.status = 'Max exposure breached'; + this.log.error(t('NetExposureIsLargerThanMaxNetExposure')); + return; + } + let result: SpreadAnalysisResult; try { - spreadAnalysisResult = await this.spreadAnalyzer.analyze(quotes, this.positionService.positionMap); + result = await this.spreadAnalyzer.analyze(quotes, this.positionService.positionMap); } catch (ex) { this.status = 'Spread analysis failed'; this.log.warn(t('FailedToGetASpreadAnalysisResult'), ex.message); this.log.debug(ex.stack); return; } - this.printSnapshot(spreadAnalysisResult); - const limitChecker = this.limitCheckerFactory.create(spreadAnalysisResult); - const limitCheckResult = limitChecker.check(); - if (!limitCheckResult.success) { - if (limitCheckResult.reason) { - this.status = limitCheckResult.reason; - } + + this.printSnapshot(result); + const { bestBid, bestAsk, invertedSpread, + targetVolume, targetProfit } = result; + if (invertedSpread <= 0) { + this.status = 'Spread not inverted'; + this.log.info(t('NoArbitrageOpportunitySpreadIsNotInverted')); + return; + } + const minTargetProfit = _.max([ + config.minTargetProfit, + config.minTargetProfitPercent !== undefined ? + _.round((config.minTargetProfitPercent / 100) * _.mean([bestAsk.price, bestBid.price]) * targetVolume) : + 0 + ]) as number; + if (targetProfit < minTargetProfit) { + this.status = 'Too small profit'; + this.log.info(t('TargetProfitIsSmallerThanMinProfit')); + return; + } + if (config.demoMode) { + this.status = 'Demo mode'; + this.log.info(t('ThisIsDemoModeNotSendingOrders')); return; } + this.log.info(t('FoundArbitrageOppotunity')); try { - const { bestBid, bestAsk, targetVolume } = spreadAnalysisResult; - await Promise.all([ - this.sendOrder(bestAsk, targetVolume, OrderType.Limit), - this.sendOrder(bestBid, targetVolume, OrderType.Limit) - ]); + await this.sendOrder(bestAsk, targetVolume, OrderType.Limit); + await this.sendOrder(bestBid, targetVolume, OrderType.Limit); this.status = 'Sent'; await this.checkOrderState(); } catch (ex) { @@ -91,16 +117,20 @@ export default class ArbitragerImpl implements Arbitrager { } private printSnapshot(result: SpreadAnalysisResult) { - const columnWidth = 17; - this.log.info('%s: %s', padEnd(t('BestAsk'), columnWidth), result.bestAsk); - this.log.info('%s: %s', padEnd(t('BestBid'), columnWidth), result.bestBid); - this.log.info('%s: %s', padEnd(t('Spread'), columnWidth), -result.invertedSpread); - this.log.info('%s: %s', padEnd(t('AvailableVolume'), columnWidth), result.availableVolume); - this.log.info('%s: %s', padEnd(t('TargetVolume'), columnWidth), result.targetVolume); + this.log.info('%s: %s', padEnd(t('BestAsk'), 17), result.bestAsk); + this.log.info('%s: %s', padEnd(t('BestBid'), 17), result.bestBid); + this.log.info('%s: %s', padEnd(t('Spread'), 17), -result.invertedSpread); + this.log.info('%s: %s', padEnd(t('AvailableVolume'), 17), result.availableVolume); + this.log.info('%s: %s', padEnd(t('TargetVolume'), 17), result.targetVolume); const midNotional = _.mean([result.bestAsk.price, result.bestBid.price]) * result.targetVolume; - const profitPercentAgainstNotional = _.round((result.targetProfit / midNotional) * 100, LOT_MIN_DECIMAL_PLACE); - this.log.info('%s: %s (%s%%)', padEnd(t('ExpectedProfit'), columnWidth), - result.targetProfit, profitPercentAgainstNotional); + const profitPercentAgainstNotional = _.round((result.targetProfit / midNotional) * 100, 2); + this.log.info('%s: %s (%s%%)', padEnd(t('ExpectedProfit'), 17), result.targetProfit, profitPercentAgainstNotional); + } + + private isMaxExposureBreached(): boolean { + this.log.debug(`Net exposure ${this.positionService.netExposure}, ` + + `Max exposure ${this.configStore.config.maxNetExposure}`); + return Math.abs(this.positionService.netExposure) > this.configStore.config.maxNetExposure; } private async checkOrderState(): Promise { @@ -112,23 +142,21 @@ export default class ArbitragerImpl implements Arbitrager { this.log.info(t('OrderCheckAttempt'), i); this.log.info(t('CheckingIfBothLegsAreDoneOrNot')); try { - await Promise.all([ - this.brokerAdapterRouter.refresh(buyOrder), - this.brokerAdapterRouter.refresh(sellOrder) - ]); + await this.brokerAdapterRouter.refresh(buyOrder); + await this.brokerAdapterRouter.refresh(sellOrder); } catch (ex) { this.log.warn(ex.message); this.log.debug(ex.stack); } - if (!buyOrder.filled) { + if (!this.isFilled(buyOrder)) { this.log.warn(t('BuyLegIsNotFilledYetPendingSizeIs'), buyOrder.pendingSize); } - if (!sellOrder.filled) { + if (!this.isFilled(sellOrder)) { this.log.warn(t('SellLegIsNotFilledYetPendingSizeIs'), sellOrder.pendingSize); } - if (buyOrder.filled && sellOrder.filled) { + if (this.isFilled(buyOrder) && this.isFilled(sellOrder)) { this.status = 'Filled'; const commission = _([buyOrder, sellOrder]).sumBy(o => this.calculateFilledOrderCommission(o)); const profit = _.round(sellOrder.filledSize * sellOrder.averageFilledPrice - @@ -146,24 +174,26 @@ export default class ArbitragerImpl implements Arbitrager { if (i === config.maxRetryCount) { this.status = 'MaxRetryCount breached'; this.log.warn(t('MaxRetryCountReachedCancellingThePendingOrders')); - const promises: Promise[] = []; - if (!buyOrder.filled) { - promises.push(this.brokerAdapterRouter.cancel(buyOrder)); + if (!this.isFilled(buyOrder)) { + await this.brokerAdapterRouter.cancel(buyOrder); } - if (!sellOrder.filled) { - promises.push(this.brokerAdapterRouter.cancel(sellOrder)); + if (!this.isFilled(sellOrder)) { + await this.brokerAdapterRouter.cancel(sellOrder); } - await Promise.all(promises); break; } } } private calculateFilledOrderCommission(order: Order): number { - const brokerConfig = findBrokerConfig(this.configStore.config, order.broker); + const brokerConfig = _.find(this.configStore.config.brokers, b => b.broker === order.broker) as BrokerConfig; return calculateCommission(order.averageFilledPrice, order.filledSize, brokerConfig.commissionPercent); } + private isFilled(order: Order): boolean { + return order.status === OrderStatus.Filled; + } + private async quoteUpdated(quotes: Quote[]): Promise { this.positionService.print(); this.log.info(hr(20) + 'ARBITRAGER' + hr(20)); @@ -173,7 +203,8 @@ export default class ArbitragerImpl implements Arbitrager { private async sendOrder(quote: Quote, targetVolume: number, orderType: OrderType): Promise { this.log.info(t('SendingOrderTargettingQuote'), quote); - const brokerConfig = findBrokerConfig(this.configStore.config, quote.broker); + const brokerConfig = _.find(this.configStore.config.brokers, + x => x.broker === quote.broker) as BrokerConfig; const orderSide = quote.side === QuoteSide.Ask ? OrderSide.Buy : OrderSide.Sell; const { cashMarginType, leverageLevel } = brokerConfig; const order = new Order(quote.broker, orderSide, targetVolume, diff --git a/src/Bitflyer/BrokerAdapterImpl.ts b/src/Bitflyer/BrokerAdapterImpl.ts index 96a4a332..11d330af 100644 --- a/src/Bitflyer/BrokerAdapterImpl.ts +++ b/src/Bitflyer/BrokerAdapterImpl.ts @@ -1,7 +1,7 @@ import { BrokerAdapter, BrokerConfig, ConfigStore, OrderStatus, OrderType, TimeInForce, OrderSide, CashMarginType, QuoteSide, Broker -} from '../types'; +} from '../type'; import { getLogger } from '../logger'; import { injectable, inject } from 'inversify'; import symbols from '../symbols'; @@ -12,9 +12,9 @@ import BrokerApi from './BrokerApi'; import { SendChildOrderResponse, ChildOrdersParam, SendChildOrderRequest, ChildOrder, BoardResponse -} from './types'; +} from './type'; import Execution from '../Execution'; -import { eRound, findBrokerConfig } from '../util'; +import { eRound } from '../util'; @injectable() export default class BrokerAdapterImpl implements BrokerAdapter { @@ -24,7 +24,8 @@ export default class BrokerAdapterImpl implements BrokerAdapter { broker = Broker.Bitflyer; constructor( @inject(symbols.ConfigStore) configStore: ConfigStore) { - this.config = findBrokerConfig(configStore.config, this.broker); + this.config = _.find(configStore.config.brokers, + (b: BrokerConfig) => b.broker === this.broker) as BrokerConfig; this.brokerApi = new BrokerApi(this.config.key, this.config.secret); } diff --git a/src/Bitflyer/BrokerApi.ts b/src/Bitflyer/BrokerApi.ts index 5af509f1..40d073f1 100644 --- a/src/Bitflyer/BrokerApi.ts +++ b/src/Bitflyer/BrokerApi.ts @@ -5,7 +5,7 @@ import { CancelChildOrderRequest, CancelChildOrderResponse, ChildOrdersParam, ChildOrdersResponse, ExecutionsResponse, ExecutionsParam, Execution, BalanceResponse, BoardResponse, ChildOrder, Balance -} from './types'; +} from './type'; import * as querystring from 'querystring'; export default class BrokerApi { diff --git a/src/Bitflyer/types.ts b/src/Bitflyer/type.ts similarity index 100% rename from src/Bitflyer/types.ts rename to src/Bitflyer/type.ts diff --git a/src/BrokerAdapterRouterImpl.ts b/src/BrokerAdapterRouterImpl.ts index ba2e111b..e2de4ca8 100644 --- a/src/BrokerAdapterRouterImpl.ts +++ b/src/BrokerAdapterRouterImpl.ts @@ -1,4 +1,4 @@ -import { BrokerAdapterRouter, Broker, BrokerAdapter, BrokerMap } from './types'; +import { BrokerAdapterRouter, Broker, BrokerAdapter, BrokerMap } from './type'; import { getLogger } from './logger'; import * as _ from 'lodash'; import Order from './Order'; @@ -8,7 +8,7 @@ import symbols from './symbols'; @injectable() export default class BrokerAdapterRouterImpl implements BrokerAdapterRouter { - private log = getLogger(this.constructor.name); + private log = getLogger('BrokerAdapterRouter'); private brokerAdapterMap: BrokerMap; constructor(@multiInject(symbols.BrokerAdapter) brokerAdapters: BrokerAdapter[]) { diff --git a/src/BrokerPosition.ts b/src/BrokerPosition.ts index c0286e42..1b60f798 100644 --- a/src/BrokerPosition.ts +++ b/src/BrokerPosition.ts @@ -1,4 +1,4 @@ -import { Broker } from './types'; +import { Broker } from './type'; import * as _ from 'lodash'; import intl from './intl'; import { padStart, padEnd } from './util'; diff --git a/src/Coincheck/BrokerAdapterImpl.ts b/src/Coincheck/BrokerAdapterImpl.ts index 5f9a388a..f86e464f 100644 --- a/src/Coincheck/BrokerAdapterImpl.ts +++ b/src/Coincheck/BrokerAdapterImpl.ts @@ -10,10 +10,10 @@ import Execution from '../Execution'; import { CashMarginType, ConfigStore, BrokerConfig, Broker, BrokerAdapter, QuoteSide, OrderStatus, OrderSide -} from '../types'; -import { OrderBooksResponse, NewOrderRequest, LeveragePosition } from './types'; +} from '../type'; +import { OrderBooksResponse, NewOrderRequest, LeveragePosition } from './type'; import { getBrokerOrderType } from './mapper'; -import { eRound, almostEqual, findBrokerConfig } from '../util'; +import { eRound, almostEqual } from '../util'; @injectable() export default class BrokerAdapterImpl implements BrokerAdapter { @@ -25,7 +25,8 @@ export default class BrokerAdapterImpl implements BrokerAdapter { constructor( @inject(symbols.ConfigStore) configStore: ConfigStore ) { - this.config = findBrokerConfig(configStore.config, this.broker); + this.config = _.find(configStore.config.brokers, + (b: BrokerConfig) => b.broker === this.broker) as BrokerConfig; this.brokerApi = new BrokerApi(this.config.key, this.config.secret); } diff --git a/src/Coincheck/BrokerApi.ts b/src/Coincheck/BrokerApi.ts index d44e768c..e1e7a3a4 100644 --- a/src/Coincheck/BrokerApi.ts +++ b/src/Coincheck/BrokerApi.ts @@ -6,7 +6,7 @@ import { AccountsBalanceResponse, LeveragePositionsRequest, LeveragePositionsResponse, LeveragePosition, OrderBooksResponse, NewOrderRequest, NewOrderResponse, CancelOrderResponse, OpenOrdersResponse, TransactionsResponse, Pagination, Transaction -} from './types'; +} from './type'; export default class BrokerApi { key: string; diff --git a/src/Coincheck/mapper.ts b/src/Coincheck/mapper.ts index 685a3e14..1a1f47cc 100644 --- a/src/Coincheck/mapper.ts +++ b/src/Coincheck/mapper.ts @@ -1,5 +1,5 @@ import Order from '../Order'; -import { CashMarginType, OrderSide, OrderType } from '../types'; +import { CashMarginType, OrderSide, OrderType } from '../type'; export function getBrokerOrderType(order: Order): string { switch (order.cashMarginType) { diff --git a/src/Coincheck/types.ts b/src/Coincheck/type.ts similarity index 100% rename from src/Coincheck/types.ts rename to src/Coincheck/type.ts diff --git a/src/ConfigValidatorImpl.ts b/src/ConfigValidatorImpl.ts index 163aad84..a2c8cdda 100644 --- a/src/ConfigValidatorImpl.ts +++ b/src/ConfigValidatorImpl.ts @@ -1,8 +1,7 @@ -import { ConfigRoot, ConfigValidator, BrokerConfig, Broker, CashMarginType } from './types'; +import { ConfigRoot, ConfigValidator, BrokerConfig, Broker, CashMarginType } from './type'; import intl from './intl'; import * as _ from 'lodash'; import { injectable } from 'inversify'; -import { findBrokerConfig } from './util'; @injectable() export default class ConfigValidatorImpl implements ConfigValidator { @@ -20,14 +19,14 @@ export default class ConfigValidatorImpl implements ConfigValidator { this.mustBePositive(config.priceMergeSize, 'priceMergeSize'); this.mustBePositive(config.sleepAfterSend, 'sleepAfterSend'); - const bitflyer = findBrokerConfig(config, Broker.Bitflyer); + const bitflyer = _.find(config.brokers, b => b.broker === Broker.Bitflyer) as BrokerConfig; if (this.isEnabled(bitflyer)) { this.throwIf(bitflyer.cashMarginType !== CashMarginType.Cash, 'CashMarginType must be Cash for Bitflyer.'); this.validateBrokerConfigCommon(bitflyer); } - const coincheck = findBrokerConfig(config, Broker.Coincheck); + const coincheck = _.find(config.brokers, b => b.broker === Broker.Coincheck) as BrokerConfig; if (this.isEnabled(coincheck)) { const allowedCashMarginType = [CashMarginType.Cash, CashMarginType.MarginOpen, CashMarginType.NetOut]; this.throwIf(!_.includes(allowedCashMarginType, coincheck.cashMarginType), @@ -35,7 +34,7 @@ export default class ConfigValidatorImpl implements ConfigValidator { this.validateBrokerConfigCommon(coincheck); } - const quoine = findBrokerConfig(config, Broker.Quoine); + const quoine = _.find(config.brokers, b => b.broker === Broker.Quoine) as BrokerConfig; if (this.isEnabled(quoine)) { this.throwIf(quoine.cashMarginType !== CashMarginType.NetOut, 'CashMarginType must be NetOut for Quoine.'); diff --git a/src/Execution.ts b/src/Execution.ts index efc9abd5..da1c6bce 100644 --- a/src/Execution.ts +++ b/src/Execution.ts @@ -1,6 +1,6 @@ import { v4 as uuid } from 'uuid'; import Order from './Order'; -import { Broker, CashMarginType, OrderSide } from './types'; +import { Broker, CashMarginType, OrderSide } from './type'; export default class Execution { constructor(order: Order) { diff --git a/src/JsonConfigStore.ts b/src/JsonConfigStore.ts index 10ec6523..f9814132 100644 --- a/src/JsonConfigStore.ts +++ b/src/JsonConfigStore.ts @@ -1,5 +1,5 @@ import { injectable, inject } from 'inversify'; -import { ConfigStore, ConfigRoot, ConfigValidator } from './types'; +import { ConfigStore, ConfigRoot, ConfigValidator } from './type'; import symbols from './symbols'; import { readJsonFileSync } from './util'; diff --git a/src/LimitCheckerFactoryImpl.ts b/src/LimitCheckerFactoryImpl.ts deleted file mode 100644 index b7acd860..00000000 --- a/src/LimitCheckerFactoryImpl.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { injectable, inject } from 'inversify'; -import symbols from './symbols'; -import { LimitCheckerFactory, ConfigStore, PositionService, SpreadAnalysisResult, LimitChecker } from './types'; -import LimitCheckerImpl from './LimitCheckerImpl'; - -@injectable() -export default class LimitCheckerFactoryImpl implements LimitCheckerFactory { - constructor( - @inject(symbols.ConfigStore) private readonly configStore: ConfigStore, - @inject(symbols.PositionService) private readonly positionService: PositionService - ) { } - - create(spreadAnalysisResult: SpreadAnalysisResult): LimitChecker { - return new LimitCheckerImpl(this.configStore, this.positionService, spreadAnalysisResult); - } -} \ No newline at end of file diff --git a/src/LimitCheckerImpl.ts b/src/LimitCheckerImpl.ts deleted file mode 100644 index 24528c36..00000000 --- a/src/LimitCheckerImpl.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { - LimitChecker, LimitCheckResult, PositionService, ConfigStore, SpreadAnalysisResult -} from './types'; -import { getLogger } from './logger'; -import * as _ from 'lodash'; -import intl from './intl'; - -const t = s => intl.t(s); - -export default class LimitCheckerImpl implements LimitChecker { - private log = getLogger(this.constructor.name); - private limits: Limit[]; - - constructor( - configStore: ConfigStore, - positionService: PositionService, - spreadAnalysisResult: SpreadAnalysisResult - ) { - this.limits = [ - new MaxNetExposureLimit(configStore, positionService), - new InvertedSpreadLimit(spreadAnalysisResult), - new TargetProfitLimit(configStore, spreadAnalysisResult), - new DemoModeLimit(configStore) - ]; - } - - check(): LimitCheckResult { - for (const limit of this.limits) { - this.log.debug(`checking ${limit.constructor.name}...`); - const result = limit.check(); - this.log.debug(`The result is %o.`, result); - if (!result.success) { - return result; - } - } - return { success: true }; - } -} - -interface Limit { - check(): LimitCheckResult; -} - -class MaxNetExposureLimit implements Limit { - private log = getLogger('MaxNetExposureLimit'); - - constructor( - private readonly configStore: ConfigStore, - private readonly positionService: PositionService - ) { } - - check() { - const success = Math.abs(this.positionService.netExposure) <= this.configStore.config.maxNetExposure; - if (success) { - return { success }; - } - const reason = 'Max exposure breached'; - this.log.info(t('NetExposureIsLargerThanMaxNetExposure')); - return { success, reason }; - } -} - -class InvertedSpreadLimit implements Limit { - private log = getLogger('InvertedSpreadLimit'); - - constructor(private readonly spreadAnalysisResult: SpreadAnalysisResult) { } - - check() { - const success = this.spreadAnalysisResult.invertedSpread > 0; - if (success) { - return { success }; - } - const reason = 'Spread not inverted'; - this.log.info(t('NoArbitrageOpportunitySpreadIsNotInverted')); - return { success, reason }; - } -} - -class TargetProfitLimit implements Limit { - private log = getLogger('TargetProfitLimit'); - - constructor( - private readonly configStore: ConfigStore, - private readonly spreadAnalysisResult: SpreadAnalysisResult) { } - - check() { - const success = this.isTargetProfitLargeEnough(); - if (success) { - return { success }; - } - const reason = 'Too small profit'; - this.log.info(t('TargetProfitIsSmallerThanMinProfit')); - return { success, reason }; - } - - private isTargetProfitLargeEnough(): boolean { - const config = this.configStore.config; - const { bestBid, bestAsk, targetVolume, targetProfit } = this.spreadAnalysisResult; - const minTargetProfit = _.max([ - config.minTargetProfit, - config.minTargetProfitPercent !== undefined ? - _.round((config.minTargetProfitPercent / 100) * _.mean([bestAsk.price, bestBid.price]) * targetVolume) : - 0 - ]) as number; - return targetProfit >= minTargetProfit; - } -} - -class DemoModeLimit implements Limit { - private log = getLogger('DemoModeLimit'); - - constructor(private readonly configStore: ConfigStore) { } - - check() { - const success = !this.configStore.config.demoMode; - if (success) { - return { success }; - } - const reason = 'Demo mode'; - this.log.info(t('ThisIsDemoModeNotSendingOrders')); - return { success, reason }; - } -} \ No newline at end of file diff --git a/src/Order.ts b/src/Order.ts index 0fb32872..89b48902 100644 --- a/src/Order.ts +++ b/src/Order.ts @@ -3,7 +3,7 @@ import { v4 as uuid } from 'uuid'; import { OrderSide, CashMarginType, OrderType, TimeInForce, OrderStatus, Broker -} from './types'; +} from './type'; import Execution from './Execution'; import { eRound } from './util'; @@ -36,10 +36,6 @@ export default class Order { : eRound(_.sumBy(this.executions, x => x.size * x.price) / _.sumBy(this.executions, x => x.size)); } - get filled(): boolean { - return this.status === OrderStatus.Filled; - } - toString(): string { return JSON.stringify(this); } diff --git a/src/PositionServiceImpl.ts b/src/PositionServiceImpl.ts index 24244f8a..161617a2 100644 --- a/src/PositionServiceImpl.ts +++ b/src/PositionServiceImpl.ts @@ -2,7 +2,7 @@ import { injectable, inject } from 'inversify'; import { PositionService, BrokerAdapterRouter, ConfigStore, BrokerConfig, BrokerMap -} from './types'; +} from './type'; import { getLogger } from './logger'; import * as _ from 'lodash'; // tslint:disable-next-line:import-name @@ -13,7 +13,7 @@ import symbols from './symbols'; @injectable() export default class PositionServiceImpl implements PositionService { - private log = getLogger(this.constructor.name); + private log = getLogger('PositionService'); private timer; private isRefreshing: boolean; private _positionMap: BrokerMap; @@ -28,6 +28,7 @@ export default class PositionServiceImpl implements PositionService { this.timer = setInterval(() => this.refresh(), this.configStore.config.positionRefreshInterval); await this.refresh(); this.log.debug('Started PositionService.'); + this.isStarted = true; } async stop(): Promise { @@ -39,6 +40,7 @@ export default class PositionServiceImpl implements PositionService { } print(): void { + if (!this.isStarted) { return; } this.log.info(hr(21) + 'POSITION' + hr(21)); this.log.info(`Net Exposure: ${_.round(this.netExposure, 3)} BTC`); _.each(this.positionMap, (position: BrokerPosition) => @@ -48,6 +50,8 @@ export default class PositionServiceImpl implements PositionService { this.log.debug(JSON.stringify(this.positionMap)); } + isStarted: boolean = false; + get netExposure() { return eRound(_.sumBy(_.values(this.positionMap), (p: BrokerPosition) => p.btc)); } diff --git a/src/Quoine/BrokerAdapterImpl.ts b/src/Quoine/BrokerAdapterImpl.ts index ee2fe0e7..c52b9bb5 100644 --- a/src/Quoine/BrokerAdapterImpl.ts +++ b/src/Quoine/BrokerAdapterImpl.ts @@ -1,7 +1,7 @@ import { BrokerAdapter, BrokerConfig, ConfigStore, OrderStatus, OrderType, OrderSide, CashMarginType, QuoteSide, Broker -} from '../types'; +} from '../type'; import BrokerApi from './BrokerApi'; import { getLogger } from '../logger'; import { injectable, inject } from 'inversify'; @@ -9,9 +9,9 @@ import symbols from '../symbols'; import * as _ from 'lodash'; import Order from '../Order'; import Quote from '../Quote'; -import { PriceLevelsResponse, SendOrderRequest, OrdersResponse } from './types'; +import { PriceLevelsResponse, SendOrderRequest, OrdersResponse } from './type'; import Execution from '../Execution'; -import { timestampToDate, findBrokerConfig } from '../util'; +import { timestampToDate } from '../util'; // tslint:disable-next-line:import-name import Decimal from 'decimal.js'; @@ -23,7 +23,8 @@ export default class BrokerAdapterImpl implements BrokerAdapter { broker = Broker.Quoine; constructor( @inject(symbols.ConfigStore) configStore: ConfigStore) { - this.config = findBrokerConfig(configStore.config, this.broker); + this.config = _.find(configStore.config.brokers, + (b: BrokerConfig) => b.broker === this.broker) as BrokerConfig; this.brokerApi = new BrokerApi(this.config.key, this.config.secret); } diff --git a/src/Quoine/BrokerApi.ts b/src/Quoine/BrokerApi.ts index ba6e529d..e4e72583 100644 --- a/src/Quoine/BrokerApi.ts +++ b/src/Quoine/BrokerApi.ts @@ -3,7 +3,7 @@ import WebClient from '../WebClient'; import { SendOrderResponse, SendOrderRequest, CancelOrderResponse, OrdersResponse, TradingAccountsResponse, PriceLevelsResponse, TradingAccount -} from './types'; +} from './type'; import * as querystring from 'querystring'; import * as jwt from 'jsonwebtoken'; diff --git a/src/Quoine/types.ts b/src/Quoine/type.ts similarity index 100% rename from src/Quoine/types.ts rename to src/Quoine/type.ts diff --git a/src/Quote.ts b/src/Quote.ts index 0e2e746d..edb2e777 100644 --- a/src/Quote.ts +++ b/src/Quote.ts @@ -1,5 +1,5 @@ import * as _ from 'lodash'; -import { Broker, QuoteSide } from './types'; +import { Broker, QuoteSide } from './type'; import { padStart, padEnd } from './util'; export default class Quote { diff --git a/src/QuoteAggregatorImpl.ts b/src/QuoteAggregatorImpl.ts index 14f0589e..64d5e87b 100644 --- a/src/QuoteAggregatorImpl.ts +++ b/src/QuoteAggregatorImpl.ts @@ -2,7 +2,7 @@ import { QuoteAggregator, ConfigStore, BrokerConfig, QuoteSide, BrokerAdapterRouter, Broker -} from './types'; +} from './type'; import { getLogger } from './logger'; import * as _ from 'lodash'; import Quote from './Quote'; @@ -10,7 +10,7 @@ import symbols from './symbols'; @injectable() export default class QuoteAggregatorImpl implements QuoteAggregator { - private log = getLogger(this.constructor.name); + private log = getLogger('QuoteAggregator'); private timer; private isRunning: boolean; private _quotes: Quote[] = []; diff --git a/src/SpreadAnalyzerImpl.ts b/src/SpreadAnalyzerImpl.ts index 43a2f9a8..fa5f6d6b 100644 --- a/src/SpreadAnalyzerImpl.ts +++ b/src/SpreadAnalyzerImpl.ts @@ -1,7 +1,7 @@ import { injectable, inject } from 'inversify'; import { - SpreadAnalyzer, ConfigStore, QuoteSide, SpreadAnalysisResult, BrokerMap -} from './types'; + SpreadAnalyzer, ConfigStore, QuoteSide, SpreadAnalysisResult, BrokerMap, BrokerConfig +} from './type'; import { getLogger } from './logger'; import * as _ from 'lodash'; import Quote from './Quote'; @@ -10,13 +10,12 @@ import BrokerPosition from './BrokerPosition'; import symbols from './symbols'; // tslint:disable-next-line:import-name import Decimal from 'decimal.js'; -import { calculateCommission, findBrokerConfig } from './util'; -import { LOT_MIN_DECIMAL_PLACE } from './constants'; +import { calculateCommission } from './util'; const t = s => intl.t(s); @injectable() export default class SpreadAnalyzerImpl implements SpreadAnalyzer { - private log = getLogger(this.constructor.name); + private log = getLogger('SpreadAnalyzer'); constructor( @inject(symbols.ConfigStore) private readonly configStore: ConfigStore @@ -42,11 +41,13 @@ export default class SpreadAnalyzerImpl implements SpreadAnalyzer { } const invertedSpread = bestBid.price - bestAsk.price; - const availableVolume = _.floor(_.min([bestBid.volume, bestAsk.volume]) as number, LOT_MIN_DECIMAL_PLACE); + const availableVolume = _.floor(_.min([bestBid.volume, bestAsk.volume]) as number, 2); const allowedShortSize = positionMap[bestBid.broker].allowedShortSize; const allowedLongSize = positionMap[bestAsk.broker].allowedLongSize; + this.log.debug(`allowedShortSize: ${allowedShortSize}`); + this.log.debug(`allowedLongSize: ${allowedLongSize}`); let targetVolume = _.min([availableVolume, config.maxSize, allowedShortSize, allowedLongSize]) as number; - targetVolume = _.floor(targetVolume, LOT_MIN_DECIMAL_PLACE); + targetVolume = _.floor(targetVolume, 2); const commission = this.calculateTotalCommission([bestBid, bestAsk], targetVolume); const targetProfit = _.round(invertedSpread * targetVolume - commission); const spreadAnalysisResult = { @@ -62,10 +63,12 @@ export default class SpreadAnalyzerImpl implements SpreadAnalyzer { } private calculateTotalCommission(quotes: Quote[], targetVolume: number): number { - return _(quotes).sumBy((q) => { - const brokerConfig = findBrokerConfig(this.configStore.config, q.broker); + const config = this.configStore.config; + const commissions = _(quotes).map((q) => { + const brokerConfig = config.brokers.find(x => x.broker === q.broker) as BrokerConfig; return calculateCommission(q.price, targetVolume, brokerConfig.commissionPercent); }); + return commissions.sum(); } private isAllowedByCurrentPosition(q: Quote, pos: BrokerPosition): boolean { diff --git a/src/WebClient.ts b/src/WebClient.ts index ccf19569..eb9c6534 100644 --- a/src/WebClient.ts +++ b/src/WebClient.ts @@ -11,8 +11,7 @@ export default class WebClient { async fetch( path: string, init: FetchRequestInit = { timeout: WebClient.fetchTimeout }, - verbose: boolean = true - ): Promise { + verbose: boolean = true): Promise { const url = this.baseUrl + path; this.log.debug(`Sending HTTP request... URL: ${url} Request: ${JSON.stringify(init)}`); const res = await fetch(url, init); diff --git a/src/__tests__/ArbitragerImpl.test.ts b/src/__tests__/ArbitragerImpl.test.ts index c7417a1a..f06a4f33 100644 --- a/src/__tests__/ArbitragerImpl.test.ts +++ b/src/__tests__/ArbitragerImpl.test.ts @@ -2,16 +2,14 @@ import { QuoteAggregator, Broker, QuoteSide, ConfigRoot, ConfigStore, PositionService, BrokerAdapterRouter, CashMarginType, OrderStatus, OrderSide -} from '../types'; +} from '../type'; import ArbitragerImpl from '../ArbitragerImpl'; import Quote from '../Quote'; -import LimitCheckerFactoryImpl from '../LimitCheckerFactoryImpl'; let quoteAggregator, config: ConfigRoot, configStore, positionMap, positionService, - baRouter, spreadAnalyzer, quotes, - limitCheckerFactory; + baRouter, spreadAnalyzer, quotes; beforeEach(() => { quoteAggregator = { @@ -64,8 +62,6 @@ beforeEach(() => { analyze: jest.fn() }; - limitCheckerFactory = new LimitCheckerFactoryImpl(configStore, positionService); - quotes = [ new Quote(Broker.Coincheck, QuoteSide.Ask, 3, 1), new Quote(Broker.Coincheck, QuoteSide.Bid, 2, 2), @@ -77,11 +73,13 @@ beforeEach(() => { describe('Arbitrager', () => { test('start/stop', async () => { const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); await arbitrager.start(); + expect(positionService.start).toBeCalled(); expect(quoteAggregator.onQuoteUpdated).not.toBeUndefined(); expect(arbitrager.status).toBe('Started'); await arbitrager.stop(); + expect(positionService.stop).toBeCalled(); expect(arbitrager.status).toBe('Stopped'); }); @@ -91,40 +89,42 @@ describe('Arbitrager', () => { expect(arbitrager.status).toBe('Stopped'); }); - test('SpreadAnalyzer throws', async () => { - config.maxNetExposure = 10; - spreadAnalyzer.analyze.mockImplementation(() => { throw new Error(); }); + test('positionService is not ready', async () => { const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); - positionService.isStarted = true; + positionService, baRouter, spreadAnalyzer); await arbitrager.start(); + positionService.isStarted = false; await quoteAggregator.onQuoteUpdated([]); - expect(spreadAnalyzer.analyze).toBeCalled(); + expect(spreadAnalyzer.analyze).not.toBeCalled(); expect(baRouter.send).not.toBeCalled(); - expect(arbitrager.status).toBe('Spread analysis failed'); + expect(arbitrager.status).toBe('Waiting Position Service'); }); test('violate maxNetExposure', async () => { - spreadAnalyzer.analyze.mockImplementation(() => { - return { - bestBid: new Quote(Broker.Quoine, QuoteSide.Bid, 400, 4), - bestAsk: new Quote(Broker.Coincheck, QuoteSide.Ask, 500, 1), - invertedSpread: -100, - targetVolume: 1, - targetProfit: -100 - }; - }); config.maxNetExposure = 0.1; positionService.netExposure = 0.2; const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); - expect(spreadAnalyzer.analyze).toBeCalled(); + expect(spreadAnalyzer.analyze).not.toBeCalled(); expect(baRouter.send).not.toBeCalled(); expect(arbitrager.status).toBe('Max exposure breached'); }); + test('SpreadAnalyzer throws', async () => { + config.maxNetExposure = 10; + spreadAnalyzer.analyze.mockImplementation(() => { throw new Error(); }); + const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, + positionService, baRouter, spreadAnalyzer); + positionService.isStarted = true; + await arbitrager.start(); + await quoteAggregator.onQuoteUpdated([]); + expect(spreadAnalyzer.analyze).toBeCalled(); + expect(baRouter.send).not.toBeCalled(); + expect(arbitrager.status).toBe('Spread analysis failed'); + }); + test('Spread not inverted', async () => { spreadAnalyzer.analyze.mockImplementation(() => { return { @@ -136,7 +136,7 @@ describe('Arbitrager', () => { }; }); const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -158,7 +158,7 @@ describe('Arbitrager', () => { }; }); const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -180,7 +180,7 @@ describe('Arbitrager', () => { }; }); const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -202,7 +202,7 @@ describe('Arbitrager', () => { }; }); const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -223,7 +223,7 @@ describe('Arbitrager', () => { }; }); const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -250,7 +250,7 @@ describe('Arbitrager', () => { }; }); const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -276,7 +276,7 @@ describe('Arbitrager', () => { }; }); const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -296,7 +296,7 @@ describe('Arbitrager', () => { }; }); const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -317,7 +317,7 @@ describe('Arbitrager', () => { }); baRouter.send = () => { throw new Error('Mock refresh error.'); }; const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -338,7 +338,7 @@ describe('Arbitrager', () => { }); baRouter.refresh = () => { throw new Error('Mock refresh error.'); }; const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); @@ -360,31 +360,7 @@ describe('Arbitrager', () => { }; }); const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); - positionService.isStarted = true; - await arbitrager.start(); - await quoteAggregator.onQuoteUpdated([]); - expect(arbitrager.status).toBe('Filled'); - }); - - test('Send and filled with commission', async () => { - baRouter.refresh.mockImplementation((order) => order.status = OrderStatus.Filled); - config.maxRetryCount = 3; - config.brokers[0].commissionPercent = 0.1; - config.brokers[1].commissionPercent = 0.2; - config.brokers[2].commissionPercent = 0.3; - spreadAnalyzer.analyze.mockImplementation(() => { - return { - bestBid: new Quote(Broker.Quoine, QuoteSide.Bid, 600, 4), - bestAsk: new Quote(Broker.Coincheck, QuoteSide.Ask, 500, 1), - invertedSpread: 100, - availableVolume: 1, - targetVolume: 1, - targetProfit: 100 - }; - }); - const arbitrager = new ArbitragerImpl(quoteAggregator, configStore, - positionService, baRouter, spreadAnalyzer, limitCheckerFactory); + positionService, baRouter, spreadAnalyzer); positionService.isStarted = true; await arbitrager.start(); await quoteAggregator.onQuoteUpdated([]); diff --git a/src/__tests__/Bitflyer/BrokerAdapterImpl.test.ts b/src/__tests__/Bitflyer/BrokerAdapterImpl.test.ts index c241353d..5c10719b 100644 --- a/src/__tests__/Bitflyer/BrokerAdapterImpl.test.ts +++ b/src/__tests__/Bitflyer/BrokerAdapterImpl.test.ts @@ -1,8 +1,8 @@ // tslint:disable import * as nock from 'nock'; import * as _ from 'lodash'; -import BrokerAdapterImpl from '../../bitflyer/BrokerAdapterImpl'; -import { OrderStatus, Broker, CashMarginType, OrderSide, OrderType } from '../../types'; +import BrokerAdapterImpl from '../../Bitflyer/BrokerAdapterImpl'; +import { OrderStatus, Broker, CashMarginType, OrderSide, OrderType } from '../../type'; import nocksetup from './nocksetup'; import Order from '../../Order'; diff --git a/src/__tests__/Bitflyer/BrokerApi.test.ts b/src/__tests__/Bitflyer/BrokerApi.test.ts index f4b31a28..00d73ac4 100644 --- a/src/__tests__/Bitflyer/BrokerApi.test.ts +++ b/src/__tests__/Bitflyer/BrokerApi.test.ts @@ -1,7 +1,7 @@ // tslint:disable import * as nock from 'nock'; import * as _ from 'lodash'; -import BrokerApi from '../../bitflyer/BrokerApi'; +import BrokerApi from '../../Bitflyer/BrokerApi'; import nocksetup from './nocksetup'; nocksetup(); diff --git a/src/__tests__/BrokerAdapterRouterImpl.test.ts b/src/__tests__/BrokerAdapterRouterImpl.test.ts index b7d42f1b..3911655a 100644 --- a/src/__tests__/BrokerAdapterRouterImpl.test.ts +++ b/src/__tests__/BrokerAdapterRouterImpl.test.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; import Order from '../Order'; -import { CashMarginType, OrderType, OrderSide, Broker } from '../types'; +import { CashMarginType, OrderType, OrderSide, Broker } from '../type'; import BrokerAdapterRouterImpl from '../BrokerAdapterRouterImpl'; const baBitflyer = { diff --git a/src/__tests__/BrokerPosition.test.ts b/src/__tests__/BrokerPosition.test.ts index f9588f9e..3abe9c2f 100644 --- a/src/__tests__/BrokerPosition.test.ts +++ b/src/__tests__/BrokerPosition.test.ts @@ -1,5 +1,5 @@ import BrokerPosition from '../BrokerPosition'; -import { Broker } from '../types'; +import { Broker } from '../type'; describe('BrokerPosition', () => { test('toString format', () => { diff --git a/src/__tests__/Coincheck/BrokerAdapterImpl.test.ts b/src/__tests__/Coincheck/BrokerAdapterImpl.test.ts index bc1bfe23..9c1c4cc3 100644 --- a/src/__tests__/Coincheck/BrokerAdapterImpl.test.ts +++ b/src/__tests__/Coincheck/BrokerAdapterImpl.test.ts @@ -1,11 +1,11 @@ // tslint:disable import * as nock from 'nock'; import * as _ from 'lodash'; -import BrokerAdapterImpl from '../../coincheck/BrokerAdapterImpl'; -import { OrderStatus, Broker, CashMarginType, OrderSide, OrderType } from '../../types'; +import BrokerAdapterImpl from '../../Coincheck/BrokerAdapterImpl'; +import { OrderStatus, Broker, CashMarginType, OrderSide, OrderType } from '../../type'; import nocksetup from './nocksetup'; import Order from '../../Order'; -import { NewOrderRequest } from '../../coincheck/types'; +import { NewOrderRequest } from '../../Coincheck/type'; nocksetup(); diff --git a/src/__tests__/Coincheck/BrokerApi.test.ts b/src/__tests__/Coincheck/BrokerApi.test.ts index e17193e8..d1dd441e 100644 --- a/src/__tests__/Coincheck/BrokerApi.test.ts +++ b/src/__tests__/Coincheck/BrokerApi.test.ts @@ -2,7 +2,7 @@ // tslint:disable:max-line-length import * as nock from 'nock'; import * as _ from 'lodash'; -import BrokerApi from '../../coincheck/BrokerApi'; +import BrokerApi from '../../Coincheck/BrokerApi'; import nocksetup from './nocksetup'; nocksetup(); diff --git a/src/__tests__/Coincheck/mapper.test.ts b/src/__tests__/Coincheck/mapper.test.ts index d460e877..773b069a 100644 --- a/src/__tests__/Coincheck/mapper.test.ts +++ b/src/__tests__/Coincheck/mapper.test.ts @@ -1,5 +1,5 @@ -import { getBrokerOrderType } from '../../coincheck/mapper'; -import { CashMarginType, OrderSide, OrderType } from '../../types'; +import { getBrokerOrderType } from '../../Coincheck/mapper'; +import { CashMarginType, OrderSide, OrderType } from '../../type'; describe('getBrokerOrderType', () => { test('cash market buy', () => { diff --git a/src/__tests__/ConfigValidatorImpl.test.ts b/src/__tests__/ConfigValidatorImpl.test.ts index eb8f43a9..1bb9db50 100644 --- a/src/__tests__/ConfigValidatorImpl.test.ts +++ b/src/__tests__/ConfigValidatorImpl.test.ts @@ -1,5 +1,5 @@ import ConfigValidatorImpl from '../ConfigValidatorImpl'; -import { ConfigRoot } from '../types'; +import { ConfigRoot } from '../type'; const config: ConfigRoot = require('./config_test.json'); diff --git a/src/__tests__/JsonConfigStore.test.ts b/src/__tests__/JsonConfigStore.test.ts index 0ebbce0a..65df6b54 100644 --- a/src/__tests__/JsonConfigStore.test.ts +++ b/src/__tests__/JsonConfigStore.test.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; import JsonConfigStore from '../JsonConfigStore'; -import { ConfigStore, ConfigRoot, Broker } from '../types'; +import { ConfigStore, ConfigRoot, Broker } from '../type'; test('JsonConfigStore', () => { const path = `${__dirname}/config_test.json`; diff --git a/src/__tests__/Order.test.ts b/src/__tests__/Order.test.ts index df9936b4..d2eb3c06 100644 --- a/src/__tests__/Order.test.ts +++ b/src/__tests__/Order.test.ts @@ -1,5 +1,5 @@ import Order from '../Order'; -import { Broker, OrderSide, OrderType, CashMarginType } from '../types'; +import { Broker, OrderSide, OrderType, CashMarginType } from '../type'; import Execution from '../Execution'; describe('Order', () => { diff --git a/src/__tests__/PositionServiceImpl.test.ts b/src/__tests__/PositionServiceImpl.test.ts index 65f5e0cb..eafb4b07 100644 --- a/src/__tests__/PositionServiceImpl.test.ts +++ b/src/__tests__/PositionServiceImpl.test.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; import PositionServiceImpl from '../PositionServiceImpl'; -import { Broker } from '../types'; +import { Broker } from '../type'; import * as _ from 'lodash'; import { delay } from '../util'; @@ -28,6 +28,7 @@ const baRouter = { describe('Position Service', () => { test('positions', async () => { const ps = new PositionServiceImpl(configStore, baRouter); + ps.print(); await ps.start(); const positions = _.values(ps.positionMap); const exposure = ps.netExposure; @@ -52,6 +53,7 @@ describe('Position Service', () => { test('positions throws', async () => { const baRouterThrows = { getBtcPosition: async () => { throw new Error('Mock refresh error.'); } }; const ps = new PositionServiceImpl(configStore, baRouterThrows); + ps.print(); await ps.start(); expect(ps.positionMap).toBeUndefined(); expect(ps.netExposure).toBe(0); @@ -63,6 +65,7 @@ describe('Position Service', () => { getBtcPosition: broker => broker === Broker.Quoine ? 0.000002 : -0.3 }; const ps = new PositionServiceImpl(configStore, baRouter); + ps.print(); await ps.start(); const positions = _.values(ps.positionMap); const exposure = ps.netExposure; diff --git a/src/__tests__/Quoine/BrokerAdapterImpl.test.ts b/src/__tests__/Quoine/BrokerAdapterImpl.test.ts index 4459ac6c..202e9d97 100644 --- a/src/__tests__/Quoine/BrokerAdapterImpl.test.ts +++ b/src/__tests__/Quoine/BrokerAdapterImpl.test.ts @@ -1,8 +1,8 @@ // tslint:disable import * as nock from 'nock'; import * as _ from 'lodash'; -import BrokerAdapterImpl from '../../quoine/BrokerAdapterImpl'; -import { OrderStatus, Broker, CashMarginType, OrderSide, OrderType } from '../../types'; +import BrokerAdapterImpl from '../../Quoine/BrokerAdapterImpl'; +import { OrderStatus, Broker, CashMarginType, OrderSide, OrderType } from '../../type'; import nocksetup from './nocksetup'; import Order from '../../Order'; diff --git a/src/__tests__/Quoine/BrokerApi.test.ts b/src/__tests__/Quoine/BrokerApi.test.ts index 8e7f3767..51b7768b 100644 --- a/src/__tests__/Quoine/BrokerApi.test.ts +++ b/src/__tests__/Quoine/BrokerApi.test.ts @@ -2,7 +2,7 @@ // tslint:disable:max-line-length import * as nock from 'nock'; import * as _ from 'lodash'; -import BrokerApi from '../../quoine/BrokerApi'; +import BrokerApi from '../../Quoine/BrokerApi'; import nocksetup from './nocksetup'; nocksetup(); diff --git a/src/__tests__/QuoteAggregatorImpl.test.ts b/src/__tests__/QuoteAggregatorImpl.test.ts index eef87d58..4ac7902d 100644 --- a/src/__tests__/QuoteAggregatorImpl.test.ts +++ b/src/__tests__/QuoteAggregatorImpl.test.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; import QuoteAggregatorImpl from '../QuoteAggregatorImpl'; -import { Broker, QuoteSide, QuoteAggregator } from '../types'; +import { Broker, QuoteSide, QuoteAggregator } from '../type'; import * as _ from 'lodash'; import { delay } from '../util'; import BrokerAdapterRouterImpl from '../BrokerAdapterRouterImpl'; diff --git a/src/__tests__/SpreadAnalyzerImpl.test.ts b/src/__tests__/SpreadAnalyzerImpl.test.ts index 2764a4ff..20542415 100644 --- a/src/__tests__/SpreadAnalyzerImpl.test.ts +++ b/src/__tests__/SpreadAnalyzerImpl.test.ts @@ -1,6 +1,6 @@ import 'reflect-metadata'; import SpreadAnalyzerImpl from '../SpreadAnalyzerImpl'; -import { Broker, QuoteSide, ConfigStore } from '../types'; +import { Broker, QuoteSide, ConfigStore } from '../type'; import * as _ from 'lodash'; import Quote from '../Quote'; diff --git a/src/__tests__/logger/LineIntegration.test.ts b/src/__tests__/logger/LineIntegration.test.ts index ef32261e..4463b484 100644 --- a/src/__tests__/logger/LineIntegration.test.ts +++ b/src/__tests__/logger/LineIntegration.test.ts @@ -1,5 +1,5 @@ import LineIntegration from '../../logger/LineIntegration'; -import { LineConfig } from '../../types'; +import { LineConfig } from '../../type'; import * as nock from 'nock'; const lineUrl = 'https://notify-api.line.me/api/notify'; diff --git a/src/__tests__/logger/LoggerFactory.test.ts b/src/__tests__/logger/LoggerFactory.test.ts index a03e884c..6de4ed13 100644 --- a/src/__tests__/logger/LoggerFactory.test.ts +++ b/src/__tests__/logger/LoggerFactory.test.ts @@ -1,6 +1,6 @@ import * as nock from 'nock'; import LoggerFactory from '../../logger/LoggerFactory'; -import { ConfigRoot } from '../../types'; +import { ConfigRoot } from '../../type'; const slackUrl = 'https://hooks.slack.com/services'; const slackApi = nock(slackUrl); diff --git a/src/__tests__/logger/SlackIntegration.test.ts b/src/__tests__/logger/SlackIntegration.test.ts index 0ac41984..fa23fc49 100644 --- a/src/__tests__/logger/SlackIntegration.test.ts +++ b/src/__tests__/logger/SlackIntegration.test.ts @@ -1,5 +1,5 @@ import SlackIntegration from '../../logger/SlackIntegration'; -import { SlackConfig } from '../../types'; +import { SlackConfig } from '../../type'; import * as nock from 'nock'; const slackUrl = 'https://hooks.slack.com/services'; diff --git a/src/__tests__/util.test.ts b/src/__tests__/util.test.ts index e120d7b7..04e99f5a 100644 --- a/src/__tests__/util.test.ts +++ b/src/__tests__/util.test.ts @@ -1,5 +1,4 @@ import * as util from '../util'; -import { Broker } from '../types'; test('timestampToDate', () => { const dt = util.timestampToDate(1509586252); @@ -45,8 +44,4 @@ test('readJsonFileSync with BOM', () => { test('readJsonFileSync with no BOM', () => { const config = util.readJsonFileSync('./src/__tests__/config_test.json'); expect(config.language).toBe('en'); -}); - -test('findBrokerConfig with no config', () => { - expect(() => util.findBrokerConfig({brokers: []}, Broker.Bitflyer)).toThrow(); -}) \ No newline at end of file +}); \ No newline at end of file diff --git a/src/bitflyer/index.ts b/src/bitflyer/index.ts deleted file mode 100644 index 9b1cccab..00000000 --- a/src/bitflyer/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as BrokerAdapterImpl } from './BrokerAdapterImpl'; \ No newline at end of file diff --git a/src/coincheck/index.ts b/src/coincheck/index.ts deleted file mode 100644 index 9b1cccab..00000000 --- a/src/coincheck/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as BrokerAdapterImpl } from './BrokerAdapterImpl'; \ No newline at end of file diff --git a/src/constants.ts b/src/constants.ts deleted file mode 100644 index ff2b00a4..00000000 --- a/src/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const LOT_MIN_DECIMAL_PLACE = 2; \ No newline at end of file diff --git a/src/container.ts b/src/container.ts index 7fbd3001..43243576 100644 --- a/src/container.ts +++ b/src/container.ts @@ -2,8 +2,7 @@ import { Container } from 'inversify'; import symbols from './symbols'; import ArbitragerImpl from './ArbitragerImpl'; import { Arbitrager, ConfigStore, QuoteAggregator, PositionService, - BrokerAdapterRouter, SpreadAnalyzer, ConfigValidator, BrokerAdapter, - LimitCheckerFactory } from './types'; + BrokerAdapterRouter, SpreadAnalyzer, ConfigValidator, BrokerAdapter } from './type'; import JsonConfigStore from './JsonConfigStore'; import QuoteAggregatorImpl from './QuoteAggregatorImpl'; import PositionServiceImpl from './PositionServiceImpl'; @@ -11,10 +10,9 @@ import BrokerAdapterRouterImpl from './BrokerAdapterRouterImpl'; import SpreadAnalyzerImpl from './SpreadAnalyzerImpl'; import ConfigValidatorImpl from './ConfigValidatorImpl'; // tslint:disable:import-name -import * as bitflyer from './bitflyer'; -import * as coincheck from './coincheck'; -import * as quoine from './quoine'; -import LimitCheckerFactoryImpl from './LimitCheckerFactoryImpl'; +import BitflyerBrokerAdapterImpl from './Bitflyer/BrokerAdapterImpl'; +import QuoineBrokerAdapterImpl from './Quoine/BrokerAdapterImpl'; +import CoincheckBrokerAdapterImpl from './Coincheck/BrokerAdapterImpl'; const container = new Container(); container.bind(symbols.Arbitrager).to(ArbitragerImpl); @@ -23,9 +21,8 @@ container.bind(symbols.QuoteAggregator).to(QuoteAggregatorImpl) container.bind(symbols.PositionService).to(PositionServiceImpl).inSingletonScope(); container.bind(symbols.BrokerAdapterRouter).to(BrokerAdapterRouterImpl); container.bind(symbols.SpreadAnalyzer).to(SpreadAnalyzerImpl); -container.bind(symbols.BrokerAdapter).to(bitflyer.BrokerAdapterImpl); -container.bind(symbols.BrokerAdapter).to(coincheck.BrokerAdapterImpl); -container.bind(symbols.BrokerAdapter).to(quoine.BrokerAdapterImpl); +container.bind(symbols.BrokerAdapter).to(BitflyerBrokerAdapterImpl); +container.bind(symbols.BrokerAdapter).to(CoincheckBrokerAdapterImpl); +container.bind(symbols.BrokerAdapter).to(QuoineBrokerAdapterImpl); container.bind(symbols.ConfigValidator).to(ConfigValidatorImpl); -container.bind(symbols.LimitCheckerFactory).to(LimitCheckerFactoryImpl); export default container; diff --git a/src/logger/LineIntegration.ts b/src/logger/LineIntegration.ts index 0e04ffd5..d29bf2aa 100644 --- a/src/logger/LineIntegration.ts +++ b/src/logger/LineIntegration.ts @@ -1,6 +1,6 @@ // tslint:disable:import-name -import fetch, { RequestInit as FetchRequestInit } from 'node-fetch'; -import { LineConfig } from '../types'; +import fetch from 'node-fetch'; +import { LineConfig } from '../type'; import * as querystring from 'querystring'; export default class LineIntegration { @@ -22,7 +22,7 @@ export default class LineIntegration { const payload = { message }; - const init: FetchRequestInit = { + const init = { method: 'POST', body: querystring.stringify(payload), headers: { diff --git a/src/logger/LoggerFactory.ts b/src/logger/LoggerFactory.ts index 6c2bf100..7bc05957 100644 --- a/src/logger/LoggerFactory.ts +++ b/src/logger/LoggerFactory.ts @@ -6,7 +6,7 @@ import { mkdir, getConfigRoot } from '../util'; import lessSplat from './lessSplat'; import SlackIntegration from './SlackIntegration'; import LineIntegration from './LineIntegration'; -import { SlackConfig, LineConfig, ConfigRoot } from '../types'; +import { SlackConfig, LineConfig, ConfigRoot } from '../type'; interface LogFunc { (msg: any, ...args: any[]): void; @@ -24,7 +24,7 @@ export default class LoggerFactory { private static dateFormat = 'YYYY-MM-DD HH:mm:ss.SSS'; private loggerCache: Map = new Map(); - constructor(private readonly config: ConfigRoot = getConfigRoot()) { + constructor(private readonly config: ConfigRoot | undefined = getConfigRoot()) { mkdir(LoggerFactory.logdir); } @@ -72,8 +72,7 @@ export default class LoggerFactory { private addIntegration( transport: { on: any }, Integration: { new(config: any): SlackIntegration | LineIntegration }, - config: SlackConfig | LineConfig | undefined - ): void { + config: SlackConfig | LineConfig | undefined) { if (config && config.enabled) { const integration = new Integration(config); transport.on('logged', info => integration.handler(info[Symbol.for('message')])); diff --git a/src/logger/SlackIntegration.ts b/src/logger/SlackIntegration.ts index 1445602e..1b9042c4 100644 --- a/src/logger/SlackIntegration.ts +++ b/src/logger/SlackIntegration.ts @@ -1,6 +1,6 @@ // tslint:disable:import-name -import fetch, { RequestInit as FetchRequestInit } from 'node-fetch'; -import { SlackConfig } from '../types'; +import fetch from 'node-fetch'; +import { SlackConfig } from '../type'; export default class SlackIntegration { static fetchTimeout = 5000; @@ -22,7 +22,7 @@ export default class SlackIntegration { channel: this.config.channel, username: this.config.username }; - const init: FetchRequestInit = { + const init = { method: 'POST', body: JSON.stringify(payload), headers: { 'Content-Type': 'application/json' }, diff --git a/src/logger/index.ts b/src/logger/index.ts index 550b54dd..ad27d045 100644 --- a/src/logger/index.ts +++ b/src/logger/index.ts @@ -1,8 +1,7 @@ import LoggerFactory, { Logger } from './LoggerFactory'; -import * as _ from 'lodash'; const factory = new LoggerFactory(); export function getLogger(name: string): Logger { - return factory.create(_.trimEnd(name, 'Impl')); + return factory.create(name); } diff --git a/src/quoine/index.ts b/src/quoine/index.ts deleted file mode 100644 index 9b1cccab..00000000 --- a/src/quoine/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as BrokerAdapterImpl } from './BrokerAdapterImpl'; \ No newline at end of file diff --git a/src/symbols.ts b/src/symbols.ts index 424a94e5..1987209d 100644 --- a/src/symbols.ts +++ b/src/symbols.ts @@ -6,8 +6,7 @@ const symbols = { SpreadAnalyzer: Symbol('SpreadAnalyzer'), ConfigValidator: Symbol('ConfigValidator'), BrokerAdapterRouter: Symbol('BrokerAdapterRouter'), - BrokerAdapter: Symbol('BrokerAdapter'), - LimitCheckerFactory: Symbol('LimitCheckerFactory') + BrokerAdapter: Symbol('BrokerAdapter') }; export default symbols; \ No newline at end of file diff --git a/src/types.ts b/src/type.ts similarity index 93% rename from src/types.ts rename to src/type.ts index 801af05a..18a25463 100644 --- a/src/types.ts +++ b/src/type.ts @@ -39,19 +39,6 @@ export interface SpreadAnalysisResult { targetProfit: number; } -export interface LimitChecker { - check(): LimitCheckResult; -} - -export interface LimitCheckResult { - success: boolean; - reason?: string; -} - -export interface LimitCheckerFactory { - create(spreadAnalysisResult: SpreadAnalysisResult): LimitChecker; -} - export enum OrderSide { Buy = 'Buy', Sell = 'Sell' @@ -111,6 +98,7 @@ export interface PositionService { start(): Promise; stop(): Promise; print(): void; + isStarted: boolean; netExposure: number; positionMap: BrokerMap; } diff --git a/src/util.ts b/src/util.ts index 635abff7..8e05edc1 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,7 +1,7 @@ import * as _ from 'lodash'; import * as crypto from 'crypto'; import * as fs from 'fs'; -import { ConfigRoot, Broker, BrokerConfig } from './types'; +import { ConfigRoot } from './type'; interface ToStringable { toString(): string; @@ -48,11 +48,11 @@ export const nonce: () => string = function () { }; }(); -export function timestampToDate(n: number): Date { +export function timestampToDate(n: number) { return new Date(n * 1000); } -export function mkdir(dir: string): void { +export function mkdir(dir: string) { try { fs.mkdirSync(dir); } catch (err) { @@ -67,7 +67,7 @@ export function calculateCommission(price: number, volume: number, commissionPer price * volume * (commissionPercent / 100) : 0; } -function removeBom(s: string): string { +function removeBom(s: string) { return s.charCodeAt(0) === 0xFEFF ? s.slice(1) : s; } @@ -76,16 +76,12 @@ export function readJsonFileSync(filepath: string): any { return JSON.parse(removeBom(content)); } -export function getConfigRoot(): ConfigRoot { - const configPath = process.env.NODE_ENV !== 'test' ? - `${__dirname}/config.json` : `${__dirname}/__tests__/config_test.json`; - return readJsonFileSync(configPath) as ConfigRoot; -} - -export function findBrokerConfig(configRoot: ConfigRoot, broker: Broker): BrokerConfig { - const found = configRoot.brokers.find(brokerConfig => brokerConfig.broker === broker); - if (found === undefined) { - throw new Error(`Unabled to find ${broker} in config.`); +export function getConfigRoot() { + try { + const configPath = process.env.NODE_ENV !== 'test' ? + `${__dirname}/config.json` : `${__dirname}/__tests__/config_test.json`; + return readJsonFileSync(configPath) as ConfigRoot; + } catch { + return undefined; } - return found; } \ No newline at end of file