[BOT] Bugfixes, erweiterungen ... nach testnet test

This commit is contained in:
darkeye 2025-01-15 12:31:13 +01:00
parent ccc1945ee4
commit c98ac43d52
11 changed files with 233 additions and 76 deletions

View File

@ -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);
}
} }

View File

@ -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;
} }
} }

View File

@ -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);

View File

@ -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;
} }
} }

View File

@ -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);*/

View File

@ -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',

View File

@ -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);
} }

View File

@ -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;
} }

View File

@ -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);
} }
} }

View File

@ -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!
} }

View File

@ -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;