Compare commits
2 Commits
ccc1945ee4
...
2b3a0e3102
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b3a0e3102 | |||
| c98ac43d52 |
@ -29,6 +29,11 @@ export default class BinanceApiClient extends APIClient {
|
||||
*/
|
||||
#restClient = null;
|
||||
|
||||
/**
|
||||
* @type {Map<string, import("binance").SymbolExchangeInfo>}
|
||||
*/
|
||||
#assetPermissions = new Map();
|
||||
|
||||
/**
|
||||
* @param {BinanceApiConfig} config
|
||||
* @param {Logger} logger
|
||||
@ -37,6 +42,7 @@ export default class BinanceApiClient extends APIClient {
|
||||
super(config, logger);
|
||||
this.#wsClient = new WebsocketClient(config.getWSClientConfig());
|
||||
this.#restClient = new MainClient(config.getRestClientConfig());
|
||||
this.#loadAssetInfo();
|
||||
this.#wsClient.on('formattedMessage', this.#wsMessageHandler.bind(this));
|
||||
this.#wsClient.on('open', this.#connectionOpened.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);
|
||||
event.price = data.price;
|
||||
event.quantity = data.quantity;
|
||||
event.price = parseFloat(data.price);
|
||||
event.quantity = parseFloat(data.quantity);
|
||||
|
||||
this.dispatchAssetEvent(event);
|
||||
}
|
||||
@ -115,12 +121,12 @@ export default class BinanceApiClient extends APIClient {
|
||||
const event = new OrderBookUpdateEvent(tradingPair);
|
||||
event.updateId = data.lastUpdateId;
|
||||
if(data.bids && data.asks){
|
||||
data.bids.forEach(b => event.bids.push(new OrderBookEntry(b[0], b[1])));
|
||||
data.asks.forEach(a => event.asks.push(new OrderBookEntry(a[0], a[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(parseFloat(a[0]), parseFloat(a[1]))));
|
||||
event.isReset = true;
|
||||
} else {
|
||||
data.bidDepthDelta.forEach(b => event.bids.push(new OrderBookEntry(b.price, b.quantity)));
|
||||
data.askDepthDelta .forEach(a => event.asks.push(new OrderBookEntry(a.price, a.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(parseFloat(a.price), parseFloat(a.quantity))));
|
||||
}
|
||||
|
||||
|
||||
@ -136,16 +142,16 @@ export default class BinanceApiClient extends APIClient {
|
||||
}
|
||||
|
||||
const event = new Ticker24hEvent(tradingPair);
|
||||
event.priceChange = data.priceChange;
|
||||
event.priceChangePercent = data.priceChangePercent;
|
||||
event.weightedAveragePrice = data.weightedAveragePrice;
|
||||
event.bestBidPrice = data.bestBid;
|
||||
event.bestBidQuantity = data.bestBidQuantity;
|
||||
event.bestAskPrice = data.bestAskPrice;
|
||||
event.bestAskQuantity = data.bestAskQuantity;
|
||||
event.high = data.high;
|
||||
event.low = data.low;
|
||||
event.trades = data.trades;
|
||||
event.priceChange = parseFloat(data.priceChange);
|
||||
event.priceChangePercent = parseFloat(data.priceChangePercent);
|
||||
event.weightedAveragePrice = parseFloat(data.weightedAveragePrice);
|
||||
event.bestBidPrice = parseFloat(data.bestBid);
|
||||
event.bestBidQuantity = parseFloat(data.bestBidQuantity);
|
||||
event.bestAskPrice = parseFloat(data.bestAskPrice);
|
||||
event.bestAskQuantity = parseFloat(data.bestAskQuantity);
|
||||
event.high = parseFloat(data.high);
|
||||
event.low = parseFloat(data.low);
|
||||
event.trades = parseFloat(data.trades);
|
||||
|
||||
this.dispatchAssetEvent(event);
|
||||
}
|
||||
@ -158,10 +164,10 @@ export default class BinanceApiClient extends APIClient {
|
||||
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.startTime = data.kline.startTime;
|
||||
event.endTime = data.kline.endTime;
|
||||
event.startTime = parseFloat(data.kline.startTime);
|
||||
event.endTime = parseFloat(data.kline.endTime);
|
||||
|
||||
this.dispatchAssetEvent(event);
|
||||
}
|
||||
@ -172,7 +178,7 @@ export default class BinanceApiClient extends APIClient {
|
||||
async #fireAccountBalanceChangeEvent(data) {
|
||||
const event = new BalanceChangeEvent();
|
||||
data.balances.forEach(balanceUpdate => {
|
||||
event.setChangedBalance(balanceUpdate.asset, balanceUpdate.availableBalance);
|
||||
event.setChangedBalance(balanceUpdate.asset, parseFloat(balanceUpdate.availableBalance));
|
||||
});
|
||||
|
||||
this.dispatchAccountEvent(event);
|
||||
@ -234,7 +240,7 @@ export default class BinanceApiClient extends APIClient {
|
||||
const event = new BalanceChangeEvent();
|
||||
resp.forEach(coinInfo => {
|
||||
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(),
|
||||
"side": "BUY",
|
||||
"type": "LIMIT",
|
||||
"price": transaction.buySettings.orderPrice,
|
||||
"quantity" : transaction.buySettings.orderQuantity,
|
||||
"price": this.#correctPriceForAsset(transaction.buySettings.orderPrice, transaction.symbol.getKey()),
|
||||
"quantity": this.#correctQuantityForAsset(transaction.buySettings.orderQuantity, transaction.symbol.getKey()),
|
||||
"newClientOrderId": transaction.id,
|
||||
"timeInForce": "GTC",
|
||||
"timestamp": Date.now()
|
||||
}).then(resp => {
|
||||
transaction.buySettings.apiID = resp.orderId;
|
||||
transaction.phase = TransactionPhase.BUY_IN_PROGRESS;
|
||||
transaction.lockedForUpdate = false;
|
||||
}).catch(e => {
|
||||
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);
|
||||
});
|
||||
}
|
||||
@ -283,17 +290,18 @@ export default class BinanceApiClient extends APIClient {
|
||||
"symbol": transaction.symbol.getKey(),
|
||||
"side": "SELL",
|
||||
"type": "LIMIT",
|
||||
"price": transaction.sellSettings.orderPrice,
|
||||
"quantity" : transaction.sellSettings.orderQuantity,
|
||||
"price": this.#correctPriceForAsset(transaction.sellSettings.orderPrice, transaction.symbol.getKey()),
|
||||
"quantity" : this.#correctQuantityForAsset(transaction.sellSettings.orderQuantity, transaction.symbol.getKey()),
|
||||
"newClientOrderId": transaction.id,
|
||||
"timeInForce": "GTC",
|
||||
"timestamp": Date.now()
|
||||
}).then(resp => {
|
||||
transaction.sellSettings.apiID = resp.orderId;
|
||||
transaction.phase = TransactionPhase.SELL_IN_PROGRESS;
|
||||
transaction.lockedForUpdate = false;
|
||||
}).catch(e => {
|
||||
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);
|
||||
});
|
||||
}
|
||||
@ -302,29 +310,41 @@ export default class BinanceApiClient extends APIClient {
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
requestBuyCancel(transaction){
|
||||
this.#restClient.cancelOrder({
|
||||
"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});
|
||||
});
|
||||
this.#requestCancel(transaction.buySettings.apiID, transaction.id, transaction.symbol.getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Transaction} transaction
|
||||
*/
|
||||
requestSellCancel(transaction){
|
||||
this.#restClient.cancelOrder({
|
||||
"symbol": transaction.symbol.getKey(),
|
||||
"orderId": transaction.sellSettings.apiID,
|
||||
"origClientOrderId": transaction.id
|
||||
}).then(resp =>{
|
||||
this.requestTransactionUpdate(transaction, "sell");
|
||||
this.#requestCancel(transaction.sellSettings.apiID, transaction.id, transaction.symbol.getKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} orderId
|
||||
* @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 => {
|
||||
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
|
||||
*/
|
||||
#requestOrderUpdate(orderId, clientId, symbol){
|
||||
this.#restClient.getOrder({
|
||||
/**
|
||||
* @type {import("binance").GetOrderParams}
|
||||
*/
|
||||
const orderParams = {
|
||||
"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);
|
||||
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 => {
|
||||
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,
|
||||
"orderId": orderId,
|
||||
}).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);
|
||||
}).catch(e => {
|
||||
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);
|
||||
this.apiId = apiId;
|
||||
this.transactionId = transactionId;
|
||||
this.statis = status;
|
||||
this.status = status;
|
||||
this.timestamp = timestamp;
|
||||
}
|
||||
}
|
||||
@ -45,6 +45,11 @@ export default class Transaction extends Serializable {
|
||||
*/
|
||||
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 {TradingPair} tradingPair
|
||||
@ -70,6 +75,7 @@ export default class Transaction extends Serializable {
|
||||
obj.id = this.id;
|
||||
obj.result = this.result;
|
||||
obj.phase = this.phase;
|
||||
obj.timestamp = this.timestamp;
|
||||
obj.symbol = SerializableHelper.serialize(this.symbol);
|
||||
obj.buySettings = SerializableHelper.serialize(this.buySettings);
|
||||
obj.sellSettings = SerializableHelper.serialize(this.sellSettings);
|
||||
@ -89,6 +95,7 @@ export default class Transaction extends Serializable {
|
||||
obj.id = tmpObj.id;
|
||||
obj.result = tmpObj.result;
|
||||
obj.phase = tmpObj.phase;
|
||||
obj.timestamp = tmpObj.timestamp;
|
||||
obj.symbol = SerializableHelper.deserialize(tmpObj.symbol);
|
||||
obj.buySettings = SerializableHelper.deserialize(tmpObj.buySettings);
|
||||
obj.sellSettings = SerializableHelper.deserialize(tmpObj.sellSettings);
|
||||
|
||||
@ -129,7 +129,7 @@ export default class TransactionPhase {
|
||||
return phase;
|
||||
}
|
||||
|
||||
if(this.isBuyPhase(currentPhase)){
|
||||
if(TransactionPhase.INITIAL == currentPhase || TransactionPhase.BUY_IN_PROGRESS == currentPhase){
|
||||
if(TransactionStatus.isCancled(status) && !hasFills){
|
||||
return this.CANCELED;
|
||||
}
|
||||
@ -137,9 +137,7 @@ export default class TransactionPhase {
|
||||
if((TransactionStatus.FILLED == status) || (TransactionStatus.isCancled(status) && hasFills)){
|
||||
return this.BUY_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
if(this.isSellPhase(currentPhase)){
|
||||
} else if (TransactionPhase.BUY_DONE == currentPhase || TransactionPhase.SELL_IN_PROGRESS == currentPhase){
|
||||
if(TransactionStatus.isCancled(status)){
|
||||
return this.BUY_DONE;
|
||||
}
|
||||
@ -147,8 +145,10 @@ export default class TransactionPhase {
|
||||
if(TransactionStatus.FILLED == status){
|
||||
return this.FINISHED;
|
||||
}
|
||||
} else if (TransactionPhase.SELL_DONE == currentPhase){
|
||||
return this.FINISHED;
|
||||
}
|
||||
|
||||
return phase;
|
||||
|
||||
return currentPhase;
|
||||
}
|
||||
}
|
||||
@ -34,6 +34,24 @@ try{
|
||||
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({
|
||||
filename: 'application-%DATE%.log',
|
||||
dirname: logPath,
|
||||
datePattern: 'YYYY-MM-DD-HH',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '10m',
|
||||
maxFiles: '7d'
|
||||
@ -60,7 +60,7 @@ export default class Logger {
|
||||
return new winston.transports.DailyRotateFile({
|
||||
filename: 'error-%DATE%.log',
|
||||
dirname: logPath,
|
||||
datePattern: 'YYYY-MM-DD-HH',
|
||||
datePattern: 'YYYY-MM-DD',
|
||||
zippedArchive: true,
|
||||
maxSize: '20m',
|
||||
maxFiles: '7d',
|
||||
|
||||
@ -126,8 +126,11 @@ export default class TradingBot {
|
||||
worker.init(this.#logger, this.#config.getDataDirectory(), this);
|
||||
});
|
||||
this.#transactions.forEach(transaction => {
|
||||
const type = transaction.phase === (TransactionPhase.INITIAL || transaction.phase === TransactionPhase.BUY_IN_PROGRESS) ? "buy" : "sell";
|
||||
this.#api.requestTransactionUpdate(transaction, type);
|
||||
if(transaction.buySettings != null && (transaction.phase === TransactionPhase.INITIAL || transaction.phase === TransactionPhase.BUY_IN_PROGRESS)){
|
||||
this.#api.requestTransactionUpdate(transaction, 'buy');
|
||||
} else if(transaction.sellSettings != null){
|
||||
this.#api.requestTransactionUpdate(transaction, 'sell');
|
||||
}
|
||||
});
|
||||
|
||||
this.#startTime = Date.now();
|
||||
@ -235,8 +238,7 @@ export default class TradingBot {
|
||||
handleAccountUpdate(accountUpdateEvent) {
|
||||
switch (accountUpdateEvent.eventType) {
|
||||
case APIEventType.BALANCE_CHANGE:
|
||||
this.#logger.info("Balance update: ", accountUpdateEvent);
|
||||
// TODO update changed balances
|
||||
this.#accountInfo.updateBalances(accountUpdateEvent);
|
||||
break;
|
||||
case APIEventType.TRANSACTION_UPDATE:
|
||||
this.#updateTransaction(accountUpdateEvent);
|
||||
@ -282,7 +284,10 @@ export default class TradingBot {
|
||||
let index = -1;
|
||||
|
||||
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;
|
||||
index = i;
|
||||
}
|
||||
@ -299,14 +304,17 @@ export default class TradingBot {
|
||||
this.#calculateTradeValues(transaction.buySettings, event.fills);
|
||||
} else if (TransactionPhase.isFinalPhase(nextTransactionPhase)) {
|
||||
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.#transactions.splice(index, 1);
|
||||
}
|
||||
|
||||
transaction.phase = nextTransactionPhase;
|
||||
transaction.lockedForUpdate = false;
|
||||
this.#saveTransactions();
|
||||
}
|
||||
|
||||
transaction.lockedForUpdate = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -323,7 +331,7 @@ export default class TradingBot {
|
||||
});
|
||||
|
||||
taSettings.quantity = quantity;
|
||||
taSettings.price = priceSum / quantity;
|
||||
taSettings.price = quantity != 0 ? priceSum / quantity : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -349,8 +357,8 @@ export default class TradingBot {
|
||||
}
|
||||
|
||||
const transaction = new Transaction(worker.getId(), worker.getTradingPair());
|
||||
transaction.buySettings.orderPrice = price;
|
||||
transaction.buySettings.orderQuantity = Math.round(this.#config.getPerTradeLimit() / price);
|
||||
transaction.buySettings = new TransactionSettings(price, this.#config.getPerTradeLimit() / price);
|
||||
transaction.lockedForUpdate = true;
|
||||
this.#transactions.push(transaction);
|
||||
this.#saveTransactions();
|
||||
|
||||
@ -360,8 +368,15 @@ export default class TradingBot {
|
||||
/**
|
||||
* @param {AbstractWorker} worker
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -370,12 +385,26 @@ export default class TradingBot {
|
||||
* @param {Transaction} 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() {
|
||||
try {
|
||||
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) {
|
||||
this.#logger.warn("Bot was unable to load any transactions. Assuming no active transactions existing!", e);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import BalanceChangeEvent from "../../../apiwrapper/event/BalanceChangeEvent.js";
|
||||
import AccountRights from "./AccountRights.js";
|
||||
import AccountStatus from "./AccountStatus.js";
|
||||
|
||||
@ -23,9 +24,14 @@ export default class AccountInfo {
|
||||
*/
|
||||
#balances = new Map();
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
#lastBalanceUpdate = 0;
|
||||
|
||||
constructor() {
|
||||
//super();
|
||||
this.#updateTime = Date.now();
|
||||
this.#lastBalanceUpdate = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,23 +90,19 @@ export default class AccountInfo {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AccountInformation} accountInfo
|
||||
* @param {BalanceChangeEvent} balanceEvent
|
||||
*/
|
||||
update(accountInfo) {
|
||||
// TODO
|
||||
/*if (accountInfo == null) {
|
||||
updateBalances(balanceEvent){
|
||||
if(balanceEvent.timestamp < this.#lastBalanceUpdate){
|
||||
return;
|
||||
}
|
||||
|
||||
this.#updateTime = accountInfo.updateTime;
|
||||
this.#canDeposit = accountInfo.canDeposit;
|
||||
this.#canTrade = accountInfo.canTrade;
|
||||
this.#canWithdraw = accountInfo.canWithdraw;
|
||||
|
||||
for (let balance of accountInfo.balances) {
|
||||
if (balance.free > 0) {
|
||||
this.#balances.set(balance.asset, balance.free);
|
||||
balanceEvent.changedBalances.forEach((balance, symbol) => {
|
||||
if(balance > 0 || this.#balances.has(symbol)){
|
||||
this.#balances.set(symbol, balance);
|
||||
}
|
||||
}*/
|
||||
});
|
||||
|
||||
this.#lastBalanceUpdate = balanceEvent.timestamp;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import Transaction from "../../apiwrapper/transaction/Transaction.js";
|
||||
import TransactionPhase from "../../apiwrapper/transaction/TransactionPhase.js";
|
||||
import AbstractStrategy from "./AbstractStrategy.js";
|
||||
|
||||
export default class AvgMinMaxStrategy extends AbstractStrategy {
|
||||
@ -52,6 +53,12 @@ export default class AvgMinMaxStrategy extends AbstractStrategy {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@ -28,7 +28,7 @@ export default class DefaultWorker extends AbstractWorker{
|
||||
*/
|
||||
onUpdate(assetData, transactions){
|
||||
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()){
|
||||
this.getApiProvider().requestBuy(this, Math.round(50 / price), price);
|
||||
@ -36,8 +36,8 @@ export default class DefaultWorker extends AbstractWorker{
|
||||
|
||||
for(let transaction of transactions){
|
||||
if(transaction.phase == TransactionPhase.BUY_DONE && this.getStrategy().shouldSell(transaction)){
|
||||
this.getApiProvider().requestSell(this, transaction);
|
||||
} else if(this.getStrategy().shouldCancle()){
|
||||
this.getApiProvider().requestSell(this, transaction, price);
|
||||
} else if(this.getStrategy().shouldCancle(transaction)){
|
||||
this.getApiProvider().requestCancle(this, transaction);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,11 +11,12 @@ export default class WorkerTradingApiProvider {
|
||||
// not implemented as this class is only an interface!
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* @param {AbstractWorker} worker
|
||||
* @param {Transaction} transaction
|
||||
* @param {number} price
|
||||
*/
|
||||
requestSell(worker, transaction){
|
||||
requestSell(worker, transaction, price){
|
||||
// not implemented as this class is only an interface!
|
||||
}
|
||||
|
||||
|
||||
@ -24,8 +24,8 @@ export default class TransactionConverter extends AbstractConverter {
|
||||
phase: obj.phase,
|
||||
timestamp:obj.timestamp,
|
||||
result: obj.result,
|
||||
buySettings: tsConverter.toRestObject(obj.buySettings),
|
||||
sellSettings: tsConverter.toRestObject(obj.sellSettings),
|
||||
buySettings: obj.buySettings == null ? null : tsConverter.toRestObject(obj.buySettings),
|
||||
sellSettings: obj.sellSettings == null ? null : tsConverter.toRestObject(obj.sellSettings),
|
||||
symbol: obj.symbol.getKey()
|
||||
};
|
||||
}
|
||||
@ -43,8 +43,8 @@ export default class TransactionConverter extends AbstractConverter {
|
||||
t.phase = obj.phase;
|
||||
t.timestamp = obj.timestamp;
|
||||
t.result = obj.result;
|
||||
t.buySettings = tsConverter.fromRestObject(obj.buySettings);
|
||||
t.sellSettings = tsConverter.fromRestObject(obj.sellSettings);
|
||||
t.buySettings = obj.buySettings == null ? null : tsConverter.fromRestObject(obj.buySettings);
|
||||
t.sellSettings = obj.sellSettings == null ? null : tsConverter.fromRestObject(obj.sellSettings);
|
||||
t.symbol = TradingPairs.fromString(obj.symbol);
|
||||
|
||||
return t;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user