Skip to content

Commit

Permalink
PoC of position closing
Browse files Browse the repository at this point in the history
  • Loading branch information
bitrinjani committed Dec 18, 2017
1 parent 9b423eb commit e30c193
Show file tree
Hide file tree
Showing 6 changed files with 177 additions and 75 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "r2",
"version": "2.0.0",
"version": "2.1.0",
"main": "index.js",
"license": "MIT",
"scripts": {
Expand Down
100 changes: 79 additions & 21 deletions src/ArbitragerImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,18 @@ import { injectable, inject } from 'inversify';
import * as _ from 'lodash';
import Order from './Order';
import {
BrokerAdapterRouter, ConfigStore, PositionService,
QuoteAggregator, SpreadAnalyzer, Arbitrager, SpreadAnalysisResult,
OrderType, QuoteSide, OrderSide, LimitCheckerFactory
BrokerAdapterRouter,
ConfigStore,
PositionService,
QuoteAggregator,
SpreadAnalyzer,
Arbitrager,
SpreadAnalysisResult,
OrderType,
QuoteSide,
OrderSide,
LimitCheckerFactory,
OrderPair
} from './types';
import t from './intl';
import { padEnd, hr, delay, calculateCommission, findBrokerConfig } from './util';
Expand All @@ -15,6 +24,7 @@ import symbols from './symbols';
@injectable()
export default class ArbitragerImpl implements Arbitrager {
private readonly log = getLogger(this.constructor.name);
private activePairs: OrderPair[] = [];

constructor(
@inject(symbols.QuoteAggregator) private readonly quoteAggregator: QuoteAggregator,
Expand All @@ -23,7 +33,7 @@ export default class ArbitragerImpl implements Arbitrager {
@inject(symbols.BrokerAdapterRouter) private readonly brokerAdapterRouter: BrokerAdapterRouter,
@inject(symbols.SpreadAnalyzer) private readonly spreadAnalyzer: SpreadAnalyzer,
@inject(symbols.LimitCheckerFactory) private readonly limitCheckerFactory: LimitCheckerFactory
) { }
) {}

status: string = 'Init';

Expand All @@ -49,17 +59,27 @@ export default class ArbitragerImpl implements Arbitrager {
this.status = 'Arbitraging';
this.log.info(t`LookingForOpportunity`);
const { config } = this.configStore;
let spreadAnalysisResult: SpreadAnalysisResult;
try {
spreadAnalysisResult = 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;

const exitFlag = await this.findClosable(quotes);
let spreadAnalysisResult;
if (exitFlag) {
if (this.spreadAnalyzer.lastResult === undefined) {
throw new Error();
}
spreadAnalysisResult = this.spreadAnalyzer.lastResult;
} else {
try {
spreadAnalysisResult = 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.printSpreadAnalysisResult(spreadAnalysisResult);
const limitChecker = this.limitCheckerFactory.create(spreadAnalysisResult);
const limitChecker = this.limitCheckerFactory.create(spreadAnalysisResult, exitFlag);
const limitCheckResult = limitChecker.check();
if (!limitCheckResult.success) {
if (limitCheckResult.reason) {
Expand All @@ -71,9 +91,9 @@ export default class ArbitragerImpl implements Arbitrager {
try {
const { bestBid, bestAsk, targetVolume } = spreadAnalysisResult;
const sendTasks = [bestAsk, bestBid].map(q => this.sendOrder(q, targetVolume, OrderType.Limit));
const orders = await Promise.all(sendTasks);
const orders = (await Promise.all(sendTasks)) as OrderPair;
this.status = 'Sent';
await this.checkOrderState(orders);
await this.checkOrderState(orders, exitFlag);
} catch (ex) {
this.log.error(ex.message);
this.log.debug(ex.stack);
Expand All @@ -98,7 +118,7 @@ export default class ArbitragerImpl implements Arbitrager {
);
}

private async checkOrderState(orders: Order[]): Promise<void> {
private async checkOrderState(orders: OrderPair, exitFlag: boolean = false): Promise<void> {
const { config } = this.configStore;
for (const i of _.range(1, config.maxRetryCount + 1)) {
await delay(config.orderStatusCheckInterval);
Expand All @@ -116,6 +136,9 @@ export default class ArbitragerImpl implements Arbitrager {

if (orders.every(o => o.filled)) {
this.status = 'Filled';
if (!exitFlag) {
this.activePairs.push(orders);
}
const commission = _(orders).sumBy(o => this.calculateFilledOrderCommission(o));
const profit = _.round(
_(orders).sumBy(o => (o.side === OrderSide.Sell ? 1 : -1) * o.filledNotional) - commission
Expand All @@ -139,7 +162,7 @@ export default class ArbitragerImpl implements Arbitrager {
}

private printOrderSummary(orders: Order[]) {
orders.forEach((o) => {
orders.forEach(o => {
if (o.filled) {
this.log.info(o.toSummary());
} else {
Expand All @@ -160,14 +183,49 @@ export default class ArbitragerImpl implements Arbitrager {
this.log.info(hr(50));
}

private async findClosable(quotes: Quote[]): Promise<boolean> {
const { config } = this.configStore;
this.log.debug(`activePairs: ${this.activePairs}`);
for (const pair of _.reverse(this.activePairs)) {
try {
this.log.debug(`Analyzing pair: ${pair}...`);
const result = await this.spreadAnalyzer.analyze(quotes, this.positionService.positionMap, pair);
this.log.debug(`pair: ${pair}, result: ${JSON.stringify(result)}.`);
const { bestBid, bestAsk, targetVolume, targetProfit } = result;
const targetVolumeNotional = _.mean([bestAsk.price, bestBid.price]) * targetVolume;
const effectiveMinExitTargetProfit = _.max([
config.minExitTargetProfit,
config.minExitTargetProfitPercent !== undefined
? _.round(config.minExitTargetProfitPercent / 100 * targetVolumeNotional)
: Number.MIN_SAFE_INTEGER
]) as number;
this.log.debug(`effectiveMinExitTargetProfit: ${effectiveMinExitTargetProfit}`);
if (targetProfit >= effectiveMinExitTargetProfit) {
this.activePairs = _.without(this.activePairs, pair);
return true;
}
} catch (ex) {
this.log.debug(ex.message);
}
}
return false;
}

private async sendOrder(quote: Quote, targetVolume: number, orderType: OrderType): Promise<Order> {
this.log.info(t`SendingOrderTargettingQuote`, quote);
const brokerConfig = findBrokerConfig(this.configStore.config, quote.broker);
const { cashMarginType, leverageLevel } = brokerConfig;
const { cashMarginType, leverageLevel } = brokerConfig;
const orderSide = quote.side === QuoteSide.Ask ? OrderSide.Buy : OrderSide.Sell;
const order = new Order(quote.broker, orderSide, targetVolume,
quote.price, cashMarginType, orderType, leverageLevel);
const order = new Order(
quote.broker,
orderSide,
targetVolume,
quote.price,
cashMarginType,
orderType,
leverageLevel
);
await this.brokerAdapterRouter.send(order);
return order;
}
}
}
4 changes: 2 additions & 2 deletions src/LimitCheckerFactoryImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default class LimitCheckerFactoryImpl implements LimitCheckerFactory {
@inject(symbols.PositionService) private readonly positionService: PositionService
) { }

create(spreadAnalysisResult: SpreadAnalysisResult): LimitChecker {
return new LimitCheckerImpl(this.configStore, this.positionService, spreadAnalysisResult);
create(spreadAnalysisResult: SpreadAnalysisResult, exitFlag: boolean): LimitChecker {
return new LimitCheckerImpl(this.configStore, this.positionService, spreadAnalysisResult, exitFlag);
}
}
65 changes: 33 additions & 32 deletions src/LimitCheckerImpl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import {
LimitChecker, LimitCheckResult, PositionService, ConfigStore, SpreadAnalysisResult
} from './types';
import { LimitChecker, LimitCheckResult, PositionService, ConfigStore, SpreadAnalysisResult } from './types';
import { getLogger } from './logger';
import * as _ from 'lodash';
import t from './intl';
Expand All @@ -12,15 +10,24 @@ export default class LimitCheckerImpl implements LimitChecker {
constructor(
configStore: ConfigStore,
positionService: PositionService,
spreadAnalysisResult: SpreadAnalysisResult
spreadAnalysisResult: SpreadAnalysisResult,
exitFlag: boolean
) {
this.limits = [
new MaxNetExposureLimit(configStore, positionService),
new InvertedSpreadLimit(spreadAnalysisResult),
new MinTargetProfitLimit(configStore, spreadAnalysisResult),
new MaxTargetProfitLimit(configStore, spreadAnalysisResult),
new DemoModeLimit(configStore)
];
if (exitFlag) {
this.limits = [
new MaxNetExposureLimit(configStore, positionService),
new MaxTargetProfitLimit(configStore, spreadAnalysisResult),
new DemoModeLimit(configStore)
];
} else {
this.limits = [
new MaxNetExposureLimit(configStore, positionService),
new InvertedSpreadLimit(spreadAnalysisResult),
new MinTargetProfitLimit(configStore, spreadAnalysisResult),
new MaxTargetProfitLimit(configStore, spreadAnalysisResult),
new DemoModeLimit(configStore)
];
}
}

check(): LimitCheckResult {
Expand All @@ -39,10 +46,7 @@ export default class LimitCheckerImpl implements LimitChecker {
class MaxNetExposureLimit implements LimitChecker {
private readonly log = getLogger('MaxNetExposureLimit');

constructor(
private readonly configStore: ConfigStore,
private readonly positionService: PositionService
) { }
constructor(private readonly configStore: ConfigStore, private readonly positionService: PositionService) {}

check() {
const success = Math.abs(this.positionService.netExposure) <= this.configStore.config.maxNetExposure;
Expand All @@ -58,7 +62,7 @@ class MaxNetExposureLimit implements LimitChecker {
class InvertedSpreadLimit implements LimitChecker {
private readonly log = getLogger('InvertedSpreadLimit');

constructor(private readonly spreadAnalysisResult: SpreadAnalysisResult) { }
constructor(private readonly spreadAnalysisResult: SpreadAnalysisResult) {}

check() {
const success = this.spreadAnalysisResult.invertedSpread > 0;
Expand All @@ -74,9 +78,7 @@ class InvertedSpreadLimit implements LimitChecker {
class MinTargetProfitLimit implements LimitChecker {
private readonly log = getLogger('TargetProfitLimit');

constructor(
private readonly configStore: ConfigStore,
private readonly spreadAnalysisResult: SpreadAnalysisResult) { }
constructor(private readonly configStore: ConfigStore, private readonly spreadAnalysisResult: SpreadAnalysisResult) {}

check() {
const success = this.isTargetProfitLargeEnough();
Expand All @@ -91,22 +93,21 @@ class MinTargetProfitLimit implements LimitChecker {
private isTargetProfitLargeEnough(): boolean {
const config = this.configStore.config;
const { bestBid, bestAsk, targetVolume, targetProfit } = this.spreadAnalysisResult;
const minTargetProfit = _.max([
const targetVolumeNotional = _.mean([bestAsk.price, bestBid.price]) * targetVolume;
const effectiveMinTargetProfit = _.max([
config.minTargetProfit,
config.minTargetProfitPercent !== undefined ?
_.round((config.minTargetProfitPercent / 100) * _.mean([bestAsk.price, bestBid.price]) * targetVolume) :
0
config.minTargetProfitPercent !== undefined
? _.round(config.minTargetProfitPercent / 100 * targetVolumeNotional)
: 0
]) as number;
return targetProfit >= minTargetProfit;
return targetProfit >= effectiveMinTargetProfit;
}
}

class MaxTargetProfitLimit implements LimitChecker {
private readonly log = getLogger('MaxTargetProfitLimit');

constructor(
private readonly configStore: ConfigStore,
private readonly spreadAnalysisResult: SpreadAnalysisResult) { }
constructor(private readonly configStore: ConfigStore, private readonly spreadAnalysisResult: SpreadAnalysisResult) {}

check() {
const success = this.isProfitSmallerThanLimit();
Expand All @@ -123,9 +124,9 @@ class MaxTargetProfitLimit implements LimitChecker {
const { bestBid, bestAsk, targetVolume, targetProfit } = this.spreadAnalysisResult;
const maxTargetProfit = _.min([
config.maxTargetProfit,
config.maxTargetProfitPercent !== undefined ?
_.round((config.maxTargetProfitPercent / 100) * _.mean([bestAsk.price, bestBid.price]) * targetVolume) :
Number.MAX_SAFE_INTEGER
config.maxTargetProfitPercent !== undefined
? _.round(config.maxTargetProfitPercent / 100 * _.mean([bestAsk.price, bestBid.price]) * targetVolume)
: Number.MAX_SAFE_INTEGER
]) as number;
return targetProfit <= maxTargetProfit;
}
Expand All @@ -134,7 +135,7 @@ class MaxTargetProfitLimit implements LimitChecker {
class DemoModeLimit implements LimitChecker {
private readonly log = getLogger('DemoModeLimit');

constructor(private readonly configStore: ConfigStore) { }
constructor(private readonly configStore: ConfigStore) {}

check() {
const success = !this.configStore.config.demoMode;
Expand All @@ -145,4 +146,4 @@ class DemoModeLimit implements LimitChecker {
this.log.info(t`ThisIsDemoModeNotSendingOrders`);
return { success, reason };
}
}
}
Loading

0 comments on commit e30c193

Please sign in to comment.