[BOT] Bugfixes, erweiterungen ... nach testnet test
This commit is contained in:
parent
ccc1945ee4
commit
c98ac43d52
@ -29,6 +29,11 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
*/
|
*/
|
||||||
#restClient = null;
|
#restClient = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Map<string, import("binance").SymbolExchangeInfo>}
|
||||||
|
*/
|
||||||
|
#assetPermissions = new Map();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {BinanceApiConfig} config
|
* @param {BinanceApiConfig} config
|
||||||
* @param {Logger} logger
|
* @param {Logger} logger
|
||||||
@ -37,6 +42,7 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
super(config, logger);
|
super(config, logger);
|
||||||
this.#wsClient = new WebsocketClient(config.getWSClientConfig());
|
this.#wsClient = new WebsocketClient(config.getWSClientConfig());
|
||||||
this.#restClient = new MainClient(config.getRestClientConfig());
|
this.#restClient = new MainClient(config.getRestClientConfig());
|
||||||
|
this.#loadAssetInfo();
|
||||||
this.#wsClient.on('formattedMessage', this.#wsMessageHandler.bind(this));
|
this.#wsClient.on('formattedMessage', this.#wsMessageHandler.bind(this));
|
||||||
this.#wsClient.on('open', this.#connectionOpened.bind(this));
|
this.#wsClient.on('open', this.#connectionOpened.bind(this));
|
||||||
this.#wsClient.on('reconnecting', this.#reconnected.bind(this));
|
this.#wsClient.on('reconnecting', this.#reconnected.bind(this));
|
||||||
@ -98,8 +104,8 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const event = new TradeEvent(tradingPair);
|
const event = new TradeEvent(tradingPair);
|
||||||
event.price = data.price;
|
event.price = parseFloat(data.price);
|
||||||
event.quantity = data.quantity;
|
event.quantity = parseFloat(data.quantity);
|
||||||
|
|
||||||
this.dispatchAssetEvent(event);
|
this.dispatchAssetEvent(event);
|
||||||
}
|
}
|
||||||
@ -115,12 +121,12 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
const event = new OrderBookUpdateEvent(tradingPair);
|
const event = new OrderBookUpdateEvent(tradingPair);
|
||||||
event.updateId = data.lastUpdateId;
|
event.updateId = data.lastUpdateId;
|
||||||
if(data.bids && data.asks){
|
if(data.bids && data.asks){
|
||||||
data.bids.forEach(b => event.bids.push(new OrderBookEntry(b[0], b[1])));
|
data.bids.forEach(b => event.bids.push(new OrderBookEntry(parseFloat(b[0]), parseFloat(b[1]))));
|
||||||
data.asks.forEach(a => event.asks.push(new OrderBookEntry(a[0], a[1])));
|
data.asks.forEach(a => event.asks.push(new OrderBookEntry(parseFloat(a[0]), parseFloat(a[1]))));
|
||||||
event.isReset = true;
|
event.isReset = true;
|
||||||
} else {
|
} else {
|
||||||
data.bidDepthDelta.forEach(b => event.bids.push(new OrderBookEntry(b.price, b.quantity)));
|
data.bidDepthDelta.forEach(b => event.bids.push(new OrderBookEntry(parseFloat(b.price), parseFloat(b.quantity))));
|
||||||
data.askDepthDelta .forEach(a => event.asks.push(new OrderBookEntry(a.price, a.quantity)));
|
data.askDepthDelta .forEach(a => event.asks.push(new OrderBookEntry(parseFloat(a.price), parseFloat(a.quantity))));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -136,16 +142,16 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const event = new Ticker24hEvent(tradingPair);
|
const event = new Ticker24hEvent(tradingPair);
|
||||||
event.priceChange = data.priceChange;
|
event.priceChange = parseFloat(data.priceChange);
|
||||||
event.priceChangePercent = data.priceChangePercent;
|
event.priceChangePercent = parseFloat(data.priceChangePercent);
|
||||||
event.weightedAveragePrice = data.weightedAveragePrice;
|
event.weightedAveragePrice = parseFloat(data.weightedAveragePrice);
|
||||||
event.bestBidPrice = data.bestBid;
|
event.bestBidPrice = parseFloat(data.bestBid);
|
||||||
event.bestBidQuantity = data.bestBidQuantity;
|
event.bestBidQuantity = parseFloat(data.bestBidQuantity);
|
||||||
event.bestAskPrice = data.bestAskPrice;
|
event.bestAskPrice = parseFloat(data.bestAskPrice);
|
||||||
event.bestAskQuantity = data.bestAskQuantity;
|
event.bestAskQuantity = parseFloat(data.bestAskQuantity);
|
||||||
event.high = data.high;
|
event.high = parseFloat(data.high);
|
||||||
event.low = data.low;
|
event.low = parseFloat(data.low);
|
||||||
event.trades = data.trades;
|
event.trades = parseFloat(data.trades);
|
||||||
|
|
||||||
this.dispatchAssetEvent(event);
|
this.dispatchAssetEvent(event);
|
||||||
}
|
}
|
||||||
@ -158,10 +164,10 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const event = new KLineEvent(tradingPair, data.kline.high, data.kline.low, data.kline.open, data.kline.close);
|
const event = new KLineEvent(tradingPair, parseFloat(data.kline.high), parseFloat(data.kline.low), parseFloat(data.kline.open), parseFloat(data.kline.close));
|
||||||
event.isClosed = data.kline.final;
|
event.isClosed = data.kline.final;
|
||||||
event.startTime = data.kline.startTime;
|
event.startTime = parseFloat(data.kline.startTime);
|
||||||
event.endTime = data.kline.endTime;
|
event.endTime = parseFloat(data.kline.endTime);
|
||||||
|
|
||||||
this.dispatchAssetEvent(event);
|
this.dispatchAssetEvent(event);
|
||||||
}
|
}
|
||||||
@ -172,7 +178,7 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
async #fireAccountBalanceChangeEvent(data) {
|
async #fireAccountBalanceChangeEvent(data) {
|
||||||
const event = new BalanceChangeEvent();
|
const event = new BalanceChangeEvent();
|
||||||
data.balances.forEach(balanceUpdate => {
|
data.balances.forEach(balanceUpdate => {
|
||||||
event.setChangedBalance(balanceUpdate.asset, balanceUpdate.availableBalance);
|
event.setChangedBalance(balanceUpdate.asset, parseFloat(balanceUpdate.availableBalance));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.dispatchAccountEvent(event);
|
this.dispatchAccountEvent(event);
|
||||||
@ -234,7 +240,7 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
const event = new BalanceChangeEvent();
|
const event = new BalanceChangeEvent();
|
||||||
resp.forEach(coinInfo => {
|
resp.forEach(coinInfo => {
|
||||||
if (coinInfo.free > 0) {
|
if (coinInfo.free > 0) {
|
||||||
event.setChangedBalance(coinInfo.coin, coinInfo.free);
|
event.setChangedBalance(coinInfo.coin, parseFloat(coinInfo.free));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -260,17 +266,18 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
"symbol": transaction.symbol.getKey(),
|
"symbol": transaction.symbol.getKey(),
|
||||||
"side": "BUY",
|
"side": "BUY",
|
||||||
"type": "LIMIT",
|
"type": "LIMIT",
|
||||||
"price": transaction.buySettings.orderPrice,
|
"price": this.#correctPriceForAsset(transaction.buySettings.orderPrice, transaction.symbol.getKey()),
|
||||||
"quantity" : transaction.buySettings.orderQuantity,
|
"quantity": this.#correctQuantityForAsset(transaction.buySettings.orderQuantity, transaction.symbol.getKey()),
|
||||||
"newClientOrderId": transaction.id,
|
"newClientOrderId": transaction.id,
|
||||||
"timeInForce": "GTC",
|
"timeInForce": "GTC",
|
||||||
"timestamp": Date.now()
|
"timestamp": Date.now()
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
transaction.buySettings.apiID = resp.orderId;
|
transaction.buySettings.apiID = resp.orderId;
|
||||||
transaction.phase = TransactionPhase.BUY_IN_PROGRESS;
|
transaction.phase = TransactionPhase.BUY_IN_PROGRESS;
|
||||||
|
transaction.lockedForUpdate = false;
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.getLogger().error("Unable to create buy order for " + transaction.id, {code: e.code, msg: e.message, url: e.requestUrl});
|
this.getLogger().error("Unable to create buy order for " + transaction.id, {code: e.code, msg: e.message, url: e.requestUrl});
|
||||||
const event = new TransactionUpdateEvent(transaction.id, transaction.buySettings.apiID, TransactionStatus.CANCELED)
|
const event = new TransactionUpdateEvent(transaction.id, transaction.buySettings.apiID, TransactionStatus.CANCELED);
|
||||||
this.dispatchAccountEvent(event);
|
this.dispatchAccountEvent(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -283,17 +290,18 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
"symbol": transaction.symbol.getKey(),
|
"symbol": transaction.symbol.getKey(),
|
||||||
"side": "SELL",
|
"side": "SELL",
|
||||||
"type": "LIMIT",
|
"type": "LIMIT",
|
||||||
"price": transaction.sellSettings.orderPrice,
|
"price": this.#correctPriceForAsset(transaction.sellSettings.orderPrice, transaction.symbol.getKey()),
|
||||||
"quantity" : transaction.sellSettings.orderQuantity,
|
"quantity" : this.#correctQuantityForAsset(transaction.sellSettings.orderQuantity, transaction.symbol.getKey()),
|
||||||
"newClientOrderId": transaction.id,
|
"newClientOrderId": transaction.id,
|
||||||
"timeInForce": "GTC",
|
"timeInForce": "GTC",
|
||||||
"timestamp": Date.now()
|
"timestamp": Date.now()
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
transaction.sellSettings.apiID = resp.orderId;
|
transaction.sellSettings.apiID = resp.orderId;
|
||||||
transaction.phase = TransactionPhase.SELL_IN_PROGRESS;
|
transaction.phase = TransactionPhase.SELL_IN_PROGRESS;
|
||||||
|
transaction.lockedForUpdate = false;
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.getLogger().error("Unable to create sell order for " + transaction.id, {code: e.code, msg: e.message, url: e.requestUrl});
|
this.getLogger().error("Unable to create sell order for " + transaction.id, {code: e.code, msg: e.message, url: e.requestUrl});
|
||||||
const event = new TransactionUpdateEvent(transaction.id, transaction.sellSettings.apiID, TransactionStatus.CANCELED)
|
const event = new TransactionUpdateEvent(transaction.id, transaction.sellSettings.apiID, TransactionStatus.CANCELED);
|
||||||
this.dispatchAccountEvent(event);
|
this.dispatchAccountEvent(event);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -302,29 +310,41 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
*/
|
*/
|
||||||
requestBuyCancel(transaction){
|
requestBuyCancel(transaction){
|
||||||
this.#restClient.cancelOrder({
|
this.#requestCancel(transaction.buySettings.apiID, transaction.id, transaction.symbol.getKey());
|
||||||
"symbol": transaction.symbol.getKey(),
|
|
||||||
"orderId": transaction.buySettings.apiID,
|
|
||||||
"origClientOrderId": transaction.id
|
|
||||||
}).then(resp =>{
|
|
||||||
this.requestTransactionUpdate(transaction, "buy");
|
|
||||||
}).catch(e => {
|
|
||||||
this.getLogger().error("Unable to cancle buy order for " + transaction.id, {code: e.code, msg: e.message, url: e.requestUrl});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
*/
|
*/
|
||||||
requestSellCancel(transaction){
|
requestSellCancel(transaction){
|
||||||
this.#restClient.cancelOrder({
|
this.#requestCancel(transaction.sellSettings.apiID, transaction.id, transaction.symbol.getKey());
|
||||||
"symbol": transaction.symbol.getKey(),
|
}
|
||||||
"orderId": transaction.sellSettings.apiID,
|
|
||||||
"origClientOrderId": transaction.id
|
/**
|
||||||
}).then(resp =>{
|
* @param {string} orderId
|
||||||
this.requestTransactionUpdate(transaction, "sell");
|
* @param {string} clientId
|
||||||
|
* @param {string} symbol
|
||||||
|
*/
|
||||||
|
#requestCancel(orderId, clientId, symbol){
|
||||||
|
/**
|
||||||
|
* @type {import("binance").CancelOrderParams}
|
||||||
|
*/
|
||||||
|
const orderParams = {
|
||||||
|
"symbol": symbol,
|
||||||
|
};
|
||||||
|
|
||||||
|
if(clientId != null && clientId != ""){
|
||||||
|
orderParams.origClientOrderId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(orderId != null && orderId != ""){
|
||||||
|
orderParams.orderId = orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#restClient.cancelOrder(orderParams).then(resp =>{
|
||||||
|
this.#requestOrderUpdate(orderId, clientId, symbol);
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.getLogger().error("Unable to cancle sell order for " + transaction.id, {code: e.code, msg: e.message, url: e.requestUrl});
|
this.getLogger().error("Unable to cancle order for " + clientId, {code: e.code, msg: e.message, url: e.requestUrl});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,13 +363,28 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
* @param {string} symbol
|
* @param {string} symbol
|
||||||
*/
|
*/
|
||||||
#requestOrderUpdate(orderId, clientId, symbol){
|
#requestOrderUpdate(orderId, clientId, symbol){
|
||||||
this.#restClient.getOrder({
|
/**
|
||||||
|
* @type {import("binance").GetOrderParams}
|
||||||
|
*/
|
||||||
|
const orderParams = {
|
||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
"orderId": orderId,
|
};
|
||||||
"origClientOrderId": clientId,
|
|
||||||
}).then(resp => {
|
if(clientId != null && clientId != ""){
|
||||||
|
orderParams.origClientOrderId = clientId;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(orderId != null && orderId != ""){
|
||||||
|
orderParams.orderId = orderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#restClient.getOrder(orderParams).then(resp => {
|
||||||
const event = new TransactionUpdateEvent(resp.clientOrderId, resp.orderId, resp.status, resp.updateTime);
|
const event = new TransactionUpdateEvent(resp.clientOrderId, resp.orderId, resp.status, resp.updateTime);
|
||||||
this.#requestTransactionFills(orderId, clientId, symbol, event);
|
if(resp.status == "CANCELED" && parseFloat(resp.executedQty) <= 0){
|
||||||
|
this.dispatchAccountEvent(event);
|
||||||
|
} else {
|
||||||
|
this.#requestTransactionFills(orderId, clientId, symbol, event);
|
||||||
|
}
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.getLogger().error("Unable to get order status for " + clientId + "(" + orderId + ")", {code: e.code, msg: e.message, url: e.requestUrl});
|
this.getLogger().error("Unable to get order status for " + clientId + "(" + orderId + ")", {code: e.code, msg: e.message, url: e.requestUrl});
|
||||||
});
|
});
|
||||||
@ -366,10 +401,69 @@ export default class BinanceApiClient extends APIClient {
|
|||||||
"symbol": symbol,
|
"symbol": symbol,
|
||||||
"orderId": orderId,
|
"orderId": orderId,
|
||||||
}).then(resp => {
|
}).then(resp => {
|
||||||
resp.forEach(trade => event.fills.push(new TransactionFill(trade.price, trade.qty)));
|
resp.forEach(trade => event.fills.push(new TransactionFill(parseFloat(trade.price), parseFloat(trade.qty))));
|
||||||
this.dispatchAccountEvent(event);
|
this.dispatchAccountEvent(event);
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
this.getLogger().error("Unable to get order fills for " + clientId + "(" + orderId + ")", {code: e.code, msg: e.message, url: e.requestUrl});
|
this.getLogger().error("Unable to get order fills for " + clientId + "(" + orderId + ")", {code: e.code, msg: e.message, url: e.requestUrl});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#loadAssetInfo(){
|
||||||
|
this.#restClient.getExchangeInfo().then(exchangeInfo => {
|
||||||
|
exchangeInfo.symbols.forEach(symbolInfo => {
|
||||||
|
this.#assetPermissions.set(symbolInfo.symbol, symbolInfo);
|
||||||
|
});
|
||||||
|
}).catch(e => {
|
||||||
|
this.getLogger().error("Unable to get exchange info!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} price
|
||||||
|
* @param {string} asset
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
#correctPriceForAsset(price, asset){
|
||||||
|
if(!this.#assetPermissions.has(asset)){
|
||||||
|
return price;
|
||||||
|
}
|
||||||
|
|
||||||
|
const symbolInfo = this.#assetPermissions.get(asset);
|
||||||
|
const priceFilter = symbolInfo.filters.find(filter => filter.filterType == 'PRICE_FILTER');
|
||||||
|
let newPrice = parseFloat(price);
|
||||||
|
|
||||||
|
if(priceFilter != null){
|
||||||
|
newPrice = this.#toPrecision(newPrice, priceFilter.tickSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} quantity
|
||||||
|
* @param {string} asset
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
#correctQuantityForAsset(quantity, asset){
|
||||||
|
if(!this.#assetPermissions.has(asset)){
|
||||||
|
return quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
const symbolInfo = this.#assetPermissions.get(asset);
|
||||||
|
const lotFilter = symbolInfo.filters.find(filter => filter.filterType == 'LOT_SIZE');
|
||||||
|
let newQuantity = parseFloat(quantity);
|
||||||
|
|
||||||
|
if(lotFilter != null){
|
||||||
|
newQuantity = this.#toPrecision(newQuantity, lotFilter.stepSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
return newQuantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toPrecision(value, precision){
|
||||||
|
let precisionDigits = Math.log(Math.round(1/parseFloat(precision))) * Math.LOG10E + 1 | 0;
|
||||||
|
precisionDigits = precisionDigits > 0 && precision >= 1 ? precisionDigits - 1 : precisionDigits;
|
||||||
|
|
||||||
|
return parseFloat(value).toFixed(precisionDigits);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ export default class TransactionUpdateEvent extends APIEvent{
|
|||||||
super(APIEventType.TRANSACTION_UPDATE);
|
super(APIEventType.TRANSACTION_UPDATE);
|
||||||
this.apiId = apiId;
|
this.apiId = apiId;
|
||||||
this.transactionId = transactionId;
|
this.transactionId = transactionId;
|
||||||
this.statis = status;
|
this.status = status;
|
||||||
this.timestamp = timestamp;
|
this.timestamp = timestamp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,6 +45,11 @@ export default class Transaction extends Serializable {
|
|||||||
*/
|
*/
|
||||||
timestamp = 0;
|
timestamp = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean} indicates, that the transaction was locked for sell/buy/update and will be unlocked, when the action is over
|
||||||
|
*/
|
||||||
|
lockedForUpdate = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} initiator
|
* @param {string} initiator
|
||||||
* @param {TradingPair} tradingPair
|
* @param {TradingPair} tradingPair
|
||||||
@ -70,6 +75,7 @@ export default class Transaction extends Serializable {
|
|||||||
obj.id = this.id;
|
obj.id = this.id;
|
||||||
obj.result = this.result;
|
obj.result = this.result;
|
||||||
obj.phase = this.phase;
|
obj.phase = this.phase;
|
||||||
|
obj.timestamp = this.timestamp;
|
||||||
obj.symbol = SerializableHelper.serialize(this.symbol);
|
obj.symbol = SerializableHelper.serialize(this.symbol);
|
||||||
obj.buySettings = SerializableHelper.serialize(this.buySettings);
|
obj.buySettings = SerializableHelper.serialize(this.buySettings);
|
||||||
obj.sellSettings = SerializableHelper.serialize(this.sellSettings);
|
obj.sellSettings = SerializableHelper.serialize(this.sellSettings);
|
||||||
@ -89,6 +95,7 @@ export default class Transaction extends Serializable {
|
|||||||
obj.id = tmpObj.id;
|
obj.id = tmpObj.id;
|
||||||
obj.result = tmpObj.result;
|
obj.result = tmpObj.result;
|
||||||
obj.phase = tmpObj.phase;
|
obj.phase = tmpObj.phase;
|
||||||
|
obj.timestamp = tmpObj.timestamp;
|
||||||
obj.symbol = SerializableHelper.deserialize(tmpObj.symbol);
|
obj.symbol = SerializableHelper.deserialize(tmpObj.symbol);
|
||||||
obj.buySettings = SerializableHelper.deserialize(tmpObj.buySettings);
|
obj.buySettings = SerializableHelper.deserialize(tmpObj.buySettings);
|
||||||
obj.sellSettings = SerializableHelper.deserialize(tmpObj.sellSettings);
|
obj.sellSettings = SerializableHelper.deserialize(tmpObj.sellSettings);
|
||||||
|
|||||||
@ -129,7 +129,7 @@ export default class TransactionPhase {
|
|||||||
return phase;
|
return phase;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.isBuyPhase(currentPhase)){
|
if(TransactionPhase.INITIAL == currentPhase || TransactionPhase.BUY_IN_PROGRESS == currentPhase){
|
||||||
if(TransactionStatus.isCancled(status) && !hasFills){
|
if(TransactionStatus.isCancled(status) && !hasFills){
|
||||||
return this.CANCELED;
|
return this.CANCELED;
|
||||||
}
|
}
|
||||||
@ -137,9 +137,7 @@ export default class TransactionPhase {
|
|||||||
if((TransactionStatus.FILLED == status) || (TransactionStatus.isCancled(status) && hasFills)){
|
if((TransactionStatus.FILLED == status) || (TransactionStatus.isCancled(status) && hasFills)){
|
||||||
return this.BUY_DONE;
|
return this.BUY_DONE;
|
||||||
}
|
}
|
||||||
}
|
} else if (TransactionPhase.BUY_DONE == currentPhase || TransactionPhase.SELL_IN_PROGRESS == currentPhase){
|
||||||
|
|
||||||
if(this.isSellPhase(currentPhase)){
|
|
||||||
if(TransactionStatus.isCancled(status)){
|
if(TransactionStatus.isCancled(status)){
|
||||||
return this.BUY_DONE;
|
return this.BUY_DONE;
|
||||||
}
|
}
|
||||||
@ -147,8 +145,10 @@ export default class TransactionPhase {
|
|||||||
if(TransactionStatus.FILLED == status){
|
if(TransactionStatus.FILLED == status){
|
||||||
return this.FINISHED;
|
return this.FINISHED;
|
||||||
}
|
}
|
||||||
|
} else if (TransactionPhase.SELL_DONE == currentPhase){
|
||||||
|
return this.FINISHED;
|
||||||
}
|
}
|
||||||
|
|
||||||
return phase;
|
return currentPhase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -34,6 +34,24 @@ try{
|
|||||||
console.log(e);
|
console.log(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*const logger = new Logger("C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data/log");
|
||||||
|
const api = new BinanceApiClient(BinanceApiConfig.createTestNetConfig(), logger);
|
||||||
|
const listener = {
|
||||||
|
handleAssetUpdate: function(accountUpdateEvent){
|
||||||
|
//;
|
||||||
|
},
|
||||||
|
|
||||||
|
handleAccountUpdate: function(accountUpdateEvent){
|
||||||
|
console.log(accountUpdateEvent);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
api.registerAccountEventListener(listener);
|
||||||
|
api.registerAssetEventListener(listener);
|
||||||
|
const transaction = new Transaction("test", TradingPairs.GALAUSDT);
|
||||||
|
transaction.buySettings = new TransactionSettings(0.0343, 874.89064);
|
||||||
|
setTimeout(() => {api.requestBuy(transaction);}, 5000);*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ export default class Logger {
|
|||||||
return new winston.transports.DailyRotateFile({
|
return new winston.transports.DailyRotateFile({
|
||||||
filename: 'application-%DATE%.log',
|
filename: 'application-%DATE%.log',
|
||||||
dirname: logPath,
|
dirname: logPath,
|
||||||
datePattern: 'YYYY-MM-DD-HH',
|
datePattern: 'YYYY-MM-DD',
|
||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: '10m',
|
maxSize: '10m',
|
||||||
maxFiles: '7d'
|
maxFiles: '7d'
|
||||||
@ -60,7 +60,7 @@ export default class Logger {
|
|||||||
return new winston.transports.DailyRotateFile({
|
return new winston.transports.DailyRotateFile({
|
||||||
filename: 'error-%DATE%.log',
|
filename: 'error-%DATE%.log',
|
||||||
dirname: logPath,
|
dirname: logPath,
|
||||||
datePattern: 'YYYY-MM-DD-HH',
|
datePattern: 'YYYY-MM-DD',
|
||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: '20m',
|
maxSize: '20m',
|
||||||
maxFiles: '7d',
|
maxFiles: '7d',
|
||||||
|
|||||||
@ -126,8 +126,11 @@ export default class TradingBot {
|
|||||||
worker.init(this.#logger, this.#config.getDataDirectory(), this);
|
worker.init(this.#logger, this.#config.getDataDirectory(), this);
|
||||||
});
|
});
|
||||||
this.#transactions.forEach(transaction => {
|
this.#transactions.forEach(transaction => {
|
||||||
const type = transaction.phase === (TransactionPhase.INITIAL || transaction.phase === TransactionPhase.BUY_IN_PROGRESS) ? "buy" : "sell";
|
if(transaction.buySettings != null && (transaction.phase === TransactionPhase.INITIAL || transaction.phase === TransactionPhase.BUY_IN_PROGRESS)){
|
||||||
this.#api.requestTransactionUpdate(transaction, type);
|
this.#api.requestTransactionUpdate(transaction, 'buy');
|
||||||
|
} else if(transaction.sellSettings != null){
|
||||||
|
this.#api.requestTransactionUpdate(transaction, 'sell');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.#startTime = Date.now();
|
this.#startTime = Date.now();
|
||||||
@ -282,7 +285,10 @@ export default class TradingBot {
|
|||||||
let index = -1;
|
let index = -1;
|
||||||
|
|
||||||
this.#transactions.forEach((v, i) => {
|
this.#transactions.forEach((v, i) => {
|
||||||
if (v.id == event.transactionId || v.buySettings.apiID == event.apiId || v.sellSettings.apiID == event.apiId) {
|
if (v.id == event.transactionId
|
||||||
|
|| (v.buySettings != null && v.buySettings.apiID == event.apiId)
|
||||||
|
|| (v.sellSettings != null && v.sellSettings.apiID == event.apiId)
|
||||||
|
) {
|
||||||
transaction = v;
|
transaction = v;
|
||||||
index = i;
|
index = i;
|
||||||
}
|
}
|
||||||
@ -299,14 +305,17 @@ export default class TradingBot {
|
|||||||
this.#calculateTradeValues(transaction.buySettings, event.fills);
|
this.#calculateTradeValues(transaction.buySettings, event.fills);
|
||||||
} else if (TransactionPhase.isFinalPhase(nextTransactionPhase)) {
|
} else if (TransactionPhase.isFinalPhase(nextTransactionPhase)) {
|
||||||
this.#calculateTradeValues(transaction.sellSettings, event.fills);
|
this.#calculateTradeValues(transaction.sellSettings, event.fills);
|
||||||
transaction.result = (sellSettings.quantity * sellSettings.price) - (buySettings.quantity * buySettings.price)
|
transaction.result = (transaction.sellSettings.quantity * transaction.sellSettings.price) - (transaction.buySettings.quantity * transaction.buySettings.price)
|
||||||
this.#transactionHistory.push(transaction);
|
this.#transactionHistory.push(transaction);
|
||||||
this.#transactions.splice(index, 1);
|
this.#transactions.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.phase = nextTransactionPhase;
|
transaction.phase = nextTransactionPhase;
|
||||||
|
transaction.lockedForUpdate = false;
|
||||||
this.#saveTransactions();
|
this.#saveTransactions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction.lockedForUpdate = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -323,7 +332,7 @@ export default class TradingBot {
|
|||||||
});
|
});
|
||||||
|
|
||||||
taSettings.quantity = quantity;
|
taSettings.quantity = quantity;
|
||||||
taSettings.price = priceSum / quantity;
|
taSettings.price = quantity != 0 ? priceSum / quantity : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -349,8 +358,8 @@ export default class TradingBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const transaction = new Transaction(worker.getId(), worker.getTradingPair());
|
const transaction = new Transaction(worker.getId(), worker.getTradingPair());
|
||||||
transaction.buySettings.orderPrice = price;
|
transaction.buySettings = new TransactionSettings(price, this.#config.getPerTradeLimit() / price);
|
||||||
transaction.buySettings.orderQuantity = Math.round(this.#config.getPerTradeLimit() / price);
|
transaction.lockedForUpdate = true;
|
||||||
this.#transactions.push(transaction);
|
this.#transactions.push(transaction);
|
||||||
this.#saveTransactions();
|
this.#saveTransactions();
|
||||||
|
|
||||||
@ -360,8 +369,15 @@ export default class TradingBot {
|
|||||||
/**
|
/**
|
||||||
* @param {AbstractWorker} worker
|
* @param {AbstractWorker} worker
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
|
* @param {number} price
|
||||||
*/
|
*/
|
||||||
requestSell(worker, transaction){
|
requestSell(worker, transaction, price){
|
||||||
|
if(transaction.lockedForUpdate){
|
||||||
|
this.#logger.warn("Attempt to sell a locked transaction!", transaction.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
transaction.lockedForUpdate = true;
|
||||||
|
transaction.sellSettings = new TransactionSettings(price, parseFloat(transaction.buySettings.quantity));
|
||||||
this.#api.requestSell(transaction);
|
this.#api.requestSell(transaction);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -370,12 +386,26 @@ export default class TradingBot {
|
|||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
*/
|
*/
|
||||||
requestCancle(worker, transaction){
|
requestCancle(worker, transaction){
|
||||||
// not implemented as this class is only an interface!
|
if(transaction.lockedForUpdate){
|
||||||
|
this.#logger.warn("Attempt to cancel a locked transaction!", transaction.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(transaction.buySettings != null && transaction.phase == TransactionPhase.BUY_IN_PROGRESS){
|
||||||
|
transaction.lockedForUpdate = true;
|
||||||
|
this.#api.requestBuyCancel(transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(transaction.sellSettings != null && transaction.phase == TransactionPhase.SELL_IN_PROGRESS){
|
||||||
|
transaction.lockedForUpdate = true;
|
||||||
|
this.#api.requestSellCancel(transaction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#loadTransactions() {
|
#loadTransactions() {
|
||||||
try {
|
try {
|
||||||
this.#transactions = SerializableHelper.load(this.#config.getDataDirectory(), 'transactions.sjson', Transaction);
|
this.#transactions = SerializableHelper.load(this.#config.getDataDirectory(), 'transactions.sjson', Transaction);
|
||||||
|
this.#transactions = this.#transactions.filter(t => t.phase != TransactionPhase.INITIAL);
|
||||||
|
this.#transactions.forEach(t => t.lockedForUpdate = false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.#logger.warn("Bot was unable to load any transactions. Assuming no active transactions existing!", e);
|
this.#logger.warn("Bot was unable to load any transactions. Assuming no active transactions existing!", e);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import Transaction from "../../apiwrapper/transaction/Transaction.js";
|
import Transaction from "../../apiwrapper/transaction/Transaction.js";
|
||||||
|
import TransactionPhase from "../../apiwrapper/transaction/TransactionPhase.js";
|
||||||
import AbstractStrategy from "./AbstractStrategy.js";
|
import AbstractStrategy from "./AbstractStrategy.js";
|
||||||
|
|
||||||
export default class AvgMinMaxStrategy extends AbstractStrategy {
|
export default class AvgMinMaxStrategy extends AbstractStrategy {
|
||||||
@ -52,6 +53,12 @@ export default class AvgMinMaxStrategy extends AbstractStrategy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
shouldCancle(transaction) {
|
shouldCancle(transaction) {
|
||||||
|
const price = this.getAssetData().getOrderBook().getBestBid().price;
|
||||||
|
|
||||||
|
if(transaction.buySettings != null && transaction.phase == TransactionPhase.BUY_IN_PROGRESS){
|
||||||
|
return transaction.buySettings.orderPrice * 1.002 < price;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export default class DefaultWorker extends AbstractWorker{
|
|||||||
*/
|
*/
|
||||||
onUpdate(assetData, transactions){
|
onUpdate(assetData, transactions){
|
||||||
this.getStrategy().setAssetData(assetData);
|
this.getStrategy().setAssetData(assetData);
|
||||||
const price = this.getStrategy().getAssetData().getOrderBook().getBestBid().price;
|
const price = parseFloat(this.getStrategy().getAssetData().getOrderBook().getBestBid().price);
|
||||||
|
|
||||||
if(this.getStrategy().shouldBuy()){
|
if(this.getStrategy().shouldBuy()){
|
||||||
this.getApiProvider().requestBuy(this, Math.round(50 / price), price);
|
this.getApiProvider().requestBuy(this, Math.round(50 / price), price);
|
||||||
@ -36,8 +36,8 @@ export default class DefaultWorker extends AbstractWorker{
|
|||||||
|
|
||||||
for(let transaction of transactions){
|
for(let transaction of transactions){
|
||||||
if(transaction.phase == TransactionPhase.BUY_DONE && this.getStrategy().shouldSell(transaction)){
|
if(transaction.phase == TransactionPhase.BUY_DONE && this.getStrategy().shouldSell(transaction)){
|
||||||
this.getApiProvider().requestSell(this, transaction);
|
this.getApiProvider().requestSell(this, transaction, price);
|
||||||
} else if(this.getStrategy().shouldCancle()){
|
} else if(this.getStrategy().shouldCancle(transaction)){
|
||||||
this.getApiProvider().requestCancle(this, transaction);
|
this.getApiProvider().requestCancle(this, transaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,11 +11,12 @@ export default class WorkerTradingApiProvider {
|
|||||||
// not implemented as this class is only an interface!
|
// not implemented as this class is only an interface!
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {AbstractWorker} worker
|
* @param {AbstractWorker} worker
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
|
* @param {number} price
|
||||||
*/
|
*/
|
||||||
requestSell(worker, transaction){
|
requestSell(worker, transaction, price){
|
||||||
// not implemented as this class is only an interface!
|
// not implemented as this class is only an interface!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -24,8 +24,8 @@ export default class TransactionConverter extends AbstractConverter {
|
|||||||
phase: obj.phase,
|
phase: obj.phase,
|
||||||
timestamp:obj.timestamp,
|
timestamp:obj.timestamp,
|
||||||
result: obj.result,
|
result: obj.result,
|
||||||
buySettings: tsConverter.toRestObject(obj.buySettings),
|
buySettings: obj.buySettings == null ? null : tsConverter.toRestObject(obj.buySettings),
|
||||||
sellSettings: tsConverter.toRestObject(obj.sellSettings),
|
sellSettings: obj.sellSettings == null ? null : tsConverter.toRestObject(obj.sellSettings),
|
||||||
symbol: obj.symbol.getKey()
|
symbol: obj.symbol.getKey()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -43,8 +43,8 @@ export default class TransactionConverter extends AbstractConverter {
|
|||||||
t.phase = obj.phase;
|
t.phase = obj.phase;
|
||||||
t.timestamp = obj.timestamp;
|
t.timestamp = obj.timestamp;
|
||||||
t.result = obj.result;
|
t.result = obj.result;
|
||||||
t.buySettings = tsConverter.fromRestObject(obj.buySettings);
|
t.buySettings = obj.buySettings == null ? null : tsConverter.fromRestObject(obj.buySettings);
|
||||||
t.sellSettings = tsConverter.fromRestObject(obj.sellSettings);
|
t.sellSettings = obj.sellSettings == null ? null : tsConverter.fromRestObject(obj.sellSettings);
|
||||||
t.symbol = TradingPairs.fromString(obj.symbol);
|
t.symbol = TradingPairs.fromString(obj.symbol);
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user