[BOT] Diverse anpassungen und vorbereitungen für live betrieb

This commit is contained in:
darkeye 2025-02-07 16:53:45 +01:00
parent 0f0b453648
commit 399204aad3
26 changed files with 528 additions and 56 deletions

View File

@ -401,7 +401,7 @@ export default class BinanceApiClient extends APIClient {
"symbol": symbol,
"orderId": orderId,
}).then(resp => {
resp.forEach(trade => event.fills.push(new TransactionFill(parseFloat(trade.price), parseFloat(trade.qty))));
resp.forEach(trade => event.fills.push(new TransactionFill(parseFloat(trade.price), parseFloat(trade.qty), parseFloat(trade.commission), trade.commissionAsset)));
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});

View File

@ -9,6 +9,9 @@ export default class TradingPairs {
static ETHBTC = new TradingPair(Assets.ETH, Assets.BTC);
static ALGOUSDT = new TradingPair(Assets.ALGO, Assets.USDT);
static TRXUSDT = new TradingPair(Assets.TRX, Assets.USDT);
static ADAUSDT = new TradingPair(Assets.ADA, Assets.USDT);
static XRPUSDT = new TradingPair(Assets.XRP, Assets.USDT);
static DOGEUSDT = new TradingPair(Assets.DOGE, Assets.USDT);
static TRADING_PAIRS = [
this.BTCUSDT,
@ -17,6 +20,9 @@ export default class TradingPairs {
this.ETHBTC,
this.ALGOUSDT,
this.TRXUSDT,
this.ADAUSDT,
this.XRPUSDT,
this.DOGEUSDT,
];
/**

View File

@ -9,8 +9,20 @@ export default class TransactionFill {
*/
quantity = 0;
constructor(price, quantity){
/**
* @type {number}
*/
commission = 0;
/**
* @type {string}
*/
commissionAsset = null;
constructor(price, quantity, commission, commissionAsset){
this.price = price;
this.quantity = quantity;
this.commission = commission;
this.commissionAsset = commissionAsset;
}
}

View File

@ -21,22 +21,30 @@ import PeakDetector from "./tradingbot/data/asset/PeakDetector.js";
import OrderBookEntry from "./apiwrapper/orderbook/OrderBookEntry.js";
import TestStrategy from "./tradingbot/strategy/TestStrategy.js";
import AvgMinMaxStrategy from "./tradingbot/strategy/AvgMinMaxStrategy.js";
import AvgEmaStrategy from "./tradingbot/strategy/AvgEmaStrategy.js";
import SingleTransactionTestWorker from "./tradingbot/worker/SingleTransactionTestWorker.js";
try{
const tradingBot = new TradingBot("C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data/conf", "config-test.sjson");
const galaWorker = new DefaultWorker("gala_minmax", TradingPairs.GALAUSDT, new AvgMinMaxStrategy());
const tradingBot = new TradingBot("C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data/conf", "config-main.sjson");
const galaWorker = new DefaultWorker("gala_avgema", TradingPairs.GALAUSDT, new AvgEmaStrategy());
const statWorker = new StatisticWorker("gala_stats", TradingPairs.GALAUSDT, new TestStrategy());
const trxWorker = new DefaultWorker("trx_minmax", TradingPairs.TRXUSDT, new AvgMinMaxStrategy());
const trxStatWorker = new StatisticWorker("trx_stats", TradingPairs.TRXUSDT, new TestStrategy());
const algoWorker = new DefaultWorker("algo_minmax", TradingPairs.ALGOUSDT, new AvgMinMaxStrategy());
const algoStatWorker = new StatisticWorker("algo_stats", TradingPairs.ALGOUSDT, new TestStrategy());
const algoWorker = new DefaultWorker("algo_avgema", TradingPairs.ALGOUSDT, new AvgEmaStrategy());
const dogeWorker = new DefaultWorker("doge_avgema", TradingPairs.DOGEUSDT, new AvgEmaStrategy());
const xrpWorker = new DefaultWorker("xrp_avgema", TradingPairs.XRPUSDT, new AvgEmaStrategy());
const adaWorker = new DefaultWorker("ada_avgema", TradingPairs.ADAUSDT, new AvgEmaStrategy());
//const testWorker = new SingleTransactionTestWorker("gala_test", TradingPairs.GALAUSDT, new AvgEmaStrategy());
//tradingBot.registerWorker(testWorker);
//tradingBot.registerWorker(galaWorker);
//tradingBot.registerWorker(statWorker);
tradingBot.registerWorker(trxWorker);
tradingBot.registerWorker(trxStatWorker);
tradingBot.registerWorker(algoWorker);
tradingBot.registerWorker(algoStatWorker);
//tradingBot.registerWorker(dogeWorker);
//tradingBot.registerWorker(xrpWorker);
//tradingBot.registerWorker(algoWorker);
//tradingBot.registerWorker(adaWorker);
tradingBot.start();
} catch (e){
console.log(e);
@ -123,14 +131,14 @@ setTimeout(() => {api.requestBuy(transaction);}, 5000);*/
// ############################
/*const path = 'C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data/conf';
const conf = new BotConfig();
conf.setApiConfig(BinanceApiConfig.createTestNetConfig());
conf.setApiConfig(BinanceApiConfig.createMainNetConfig());
conf.setWorkingDirectory('C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data');
conf.setRelativeDataPath('data');
conf.setRelativeLogPath('logs');
conf.setRelativeTmpPath('tmp');
conf.setUsdTradeLimit(500);
conf.setPerTradeLimit(30);
SerializableHelper.save(path, "config-test.sjson", conf);*/
conf.setPerTradeLimit(50);
SerializableHelper.save(path, "config-main.sjson", conf);*/
// ############################
// ## SERIALIZER TESTS ##

View File

@ -1,24 +1,26 @@
import winston from "winston";
import 'winston-daily-rotate-file';
import SingleLevelRotateTransport from "./SingleLevelRotateTransport.js";
export default class Logger {
#internLogger = null;
/*
#levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6
*/
verbose: 3,
debug: 4,
silly: 5,
event: 101
}
/**
* @param {string} logDir
*/
constructor(logDir) {
this.#internLogger = winston.createLogger({
levels: this.#levels,
level: 'debug',
format: winston.format.combine(
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
@ -26,7 +28,8 @@ export default class Logger {
),
transports: [
this.#getCombinedLogTransport(logDir),
this.#getErrorLogTransport(logDir)
this.#getErrorLogTransport(logDir),
this.#getEventLogTransport(logDir)
],
});
}
@ -48,7 +51,8 @@ export default class Logger {
datePattern: 'YYYY-MM-DD',
zippedArchive: true,
maxSize: '10m',
maxFiles: '7d'
maxFiles: '7d',
level: 'silly'
});
}
@ -68,6 +72,22 @@ export default class Logger {
});
}
/**
* @param {string} logPath
* @returns {winston.transport}
*/
#getEventLogTransport(logPath){
return new SingleLevelRotateTransport({
filename: 'event-%DATE%.log',
dirname: logPath,
datePattern: 'YYYY-MM-DD',
zippedArchive: false,
maxSize: '20m',
maxFiles: '7d',
level: 'event'
});
}
/**
* Logs to file
* @param {...any} obj objects to log
@ -112,8 +132,8 @@ export default class Logger {
* Logs to separate file
* @param {...any} obj objects to log
*/
remark(...obj) {
this.#writeLog('verbose', obj);
event(...obj) {
this.#writeLog('event', obj);
}
#writeLog(level, params) {

View File

@ -0,0 +1,20 @@
import winston from "winston";
import 'winston-daily-rotate-file';
export default class SingleLevelRotateTransport extends winston.transports.DailyRotateFile{
/**
*
* @param {import("winston-daily-rotate-file").DailyRotateFileTransportOptions} options
*/
constructor(options = null){
super(options)
}
log(info, callback){
if(info.level.toLowerCase() == this.level.toLowerCase()){
super.log(info, callback);
} else {
callback(null, false);
}
}
}

View File

@ -184,6 +184,20 @@ export default class TradingBot {
return this.#transactionHistory.filter(t => t.symbol.getKey().toLowerCase() == tradingPair.getKey().toLowerCase());
}
/**
* @returns {Transaction[]}
*/
getTransactions(){
return this.#transactions;
}
/**
* @returns {Transaction[]}
*/
getClosedTransactions(){
return this.#transactionHistory;
}
/**
* @returns {AssetDataCollector}
*/
@ -268,7 +282,7 @@ export default class TradingBot {
this.#worker.forEach(worker => {
if (worker.getTradingPair().getKey() === assetUpdateEvent.tradingPair.getKey()) {
worker.onUpdate(this.#dataCollector.getAssetData(worker.getTradingPair()), this.#transactions.filter(t => t.initiator = worker.getId()), this.#config.getMaxTransactionsPerWorker());
worker.onUpdate(this.#dataCollector.getAssetData(worker.getTradingPair()), this.#transactions.filter(t => t.initiator == worker.getId()), this.#config.getMaxTransactionsPerWorker());
}
});
}
@ -301,10 +315,10 @@ export default class TradingBot {
const nextTransactionPhase = TransactionPhase.getPhaseForStatus(transaction.phase, event.status, event.fills.length > 0);
if (transaction.phase !== nextTransactionPhase) {
if (TransactionPhase.BUY_DONE === nextTransactionPhase) {
this.#calculateTradeValues(transaction.buySettings, event.fills);
this.#calculateTradeValues(transaction.symbol, transaction.buySettings, event.fills);
} else if (TransactionPhase.isFinalPhase(nextTransactionPhase)) {
if(TransactionPhase.CANCELED != nextTransactionPhase) {
this.#calculateTradeValues(transaction.sellSettings, event.fills);
this.#calculateTradeValues(transaction.symbol, transaction.sellSettings, event.fills);
transaction.result = (transaction.sellSettings.quantity * transaction.sellSettings.price) - (transaction.buySettings.quantity * transaction.buySettings.price)
}
this.#transactionHistory.push(transaction);
@ -320,20 +334,29 @@ export default class TradingBot {
}
/**
* @param {TradingPair} tradingpair
* @param {TransactionSettings} taSettings
* @param {TransactionFill[]} fills
*/
#calculateTradeValues(taSettings, fills) {
#calculateTradeValues(tradingpair, taSettings, fills) {
let priceSum = 0;
let quantity = 0;
let commissionAsset1 = 0;
let commissionAsset2 = 0;
fills.forEach(fill => {
priceSum += fill.price * fill.quantity;
quantity += fill.quantity;
if(fill.commissionAsset != null && fill.commissionAsset.toLowerCase() == tradingpair.getFirstCurrency().getSymbol().toLowerCase()){
commissionAsset1 += fill.commission;
} else if(fill.commissionAsset != null && fill.commissionAsset.toLowerCase() == tradingpair.getSecondCurrency().getSymbol().toLowerCase()){
commissionAsset2 += fill.commission;
}
});
taSettings.quantity = quantity;
taSettings.price = quantity != 0 ? priceSum / quantity : 0;
taSettings.quantity = quantity - commissionAsset1;
taSettings.price = quantity != 0 ? (priceSum - commissionAsset2) / taSettings.quantity : 0;
}
/**

View File

@ -45,17 +45,25 @@ export default class TradingBotRestDataProvider extends WebViewDataProvider {
* @param {string} symbol
* @returns {Transaction[]}
*/
getActiveTransactions(symbol) {
return this.#tradingBot.getTransactionsByAsset(TradingPairs.fromString(symbol)).sort((t1, t2) => t1.timestamp - t2.timestamp);
getActiveTransactions(symbol = null) {
if(symbol == null){
return this.#tradingBot.getTransactions().sort((t1, t2) => t1.timestamp - t2.timestamp);
} else {
return this.#tradingBot.getTransactionsByAsset(TradingPairs.fromString(symbol)).sort((t1, t2) => t1.timestamp - t2.timestamp);
}
}
/**
* @param {string} symbol
* @returns {Transaction[]}
*/
getAllTransactions(symbol) {
getAllTransactions(symbol = null) {
const tp = TradingPairs.fromString(symbol);
return this.#tradingBot.getTransactionsByAsset(tp).concat(this.#tradingBot.getClosedTransactionsByAsset(tp)).sort((t1, t2) => t1.timestamp - t2.timestamp);
if(symbol == null){
return this.#tradingBot.getTransactions().concat(this.#tradingBot.getClosedTransactions()).sort((t1, t2) => t1.timestamp - t2.timestamp);
} else {
return this.#tradingBot.getTransactionsByAsset(tp).concat(this.#tradingBot.getClosedTransactionsByAsset(tp)).sort((t1, t2) => t1.timestamp - t2.timestamp);
}
}
/**

View File

@ -32,7 +32,7 @@ export default class BotConfig extends Serializable {
/**
* @type {string} chrono unit string; Time until trading should start
*/
#rampupTime = "15m";
#rampupTime = "16m";
/**
* @type {number} max amount of USD, that can be used for trading

View File

@ -77,6 +77,26 @@ export default class AggregatedDataPoint {
*/
peakEnds = 0;
/**
* @type {number}
*/
sma15min = 0;
/**
* @type {number}
*/
ema15min = 0;
/**
* @type {number}
*/
sma5min = 0;
/**
* @type {number}
*/
ema5min = 0;
/**
* @type {number[]}
*/
@ -95,6 +115,10 @@ export default class AggregatedDataPoint {
* @param {OrderBookEntry} ask
*/
pushAsk(ask){
if(ask == null){
return;
}
if(this.bestAsk == null || this.bestAsk.price < ask.price){
this.bestAsk = ask;
}
@ -104,6 +128,10 @@ export default class AggregatedDataPoint {
* @param {OrderBookEntry} bid
*/
pushBid(bid){
if(bid == null){
return;
}
if(!this.hasValues){
this.startPrice = bid.price;
this.minPrice = bid.price;

View File

@ -179,7 +179,7 @@ export default class AssetData {
this.#assetDataAggregator.pushTrade(time, price, quantity);
}
getAggregatedData(duration, timeOffset = -1){
return this.#assetDataAggregator.getData(duration, timeOffset);
getAggregatedData(duration, timeOffset = -1, returnUnfinished = false){
return this.#assetDataAggregator.getData(duration, timeOffset, returnUnfinished);
}
}

View File

@ -1,4 +1,5 @@
import OrderBookEntry from "../../../apiwrapper/orderbook/OrderBookEntry.js";
import AssetInstrumentCalculator from "../../util/AssetInstrumentCalculator.js";
import UnitHelper from "../../util/UnitHelper.js";
import AggregatedDataPoint from "./AggregatedDataPoint.js";
@ -54,6 +55,14 @@ export default class AssetDataAggregator {
tmpSlot.addTrades(dp.tradeCount, dp.tradeVolume);
}
tmpSlot.end(timestamp + AssetDataAggregator.#SLOT_1_MIN);
if(this.#perMinuteDataPoints.length > 0){
tmpSlot.sma5min = AssetInstrumentCalculator.calculateSMA(this.#perMinuteDataPoints.slice(-1 * Math.min(5, this.#perMinuteDataPoints.length)));
tmpSlot.ema5min = AssetInstrumentCalculator.calculateEMA(this.#perMinuteDataPoints.slice(-1 * Math.min(5, this.#perMinuteDataPoints.length)));
tmpSlot.sma15min = AssetInstrumentCalculator.calculateSMA(this.#perMinuteDataPoints.slice(-1 * Math.min(15, this.#perMinuteDataPoints.length)));
tmpSlot.ema15min = AssetInstrumentCalculator.calculateEMA(this.#perMinuteDataPoints.slice(-1 * Math.min(15, this.#perMinuteDataPoints.length)));
} else {
tmpSlot.sma5min = tmpSlot.ema5min = tmpSlot.sma15min = tmpSlot.ema15min = tmpSlot.avgPrice;
}
this.#lastOneMinUpdate = timestamp;
this.#pushSlotAndFillUp(this.#perMinuteDataPoints, tmpSlot, AssetDataAggregator.#SLOT_1_MIN, timestamp);
@ -121,9 +130,10 @@ export default class AssetDataAggregator {
*
* @param {string} duration chrono unit (eg. 1000 or 1s or 15m)
* @param {number} start start timestamp
* @param {boolean} returnUnfinished if true, unfinished timeslots will be returned (minutes only!)
* @returns {AggregatedDataPoint[]}
*/
getData(duration, start = -1){
getData(duration, start = -1, returnUnfinished = false){
let result = [];
const durationInMs = UnitHelper.formatedDurationToMs(duration);
@ -136,8 +146,9 @@ export default class AssetDataAggregator {
} else {
const dpCount = Math.round(durationInMs / AssetDataAggregator.#SLOT_1_MIN);
const pointOffset = start == -1 ? 0 : Math.round(start / AssetDataAggregator.#SLOT_1_MIN);
if(pointOffset + dpCount <= this.#perMinuteDataPoints.length){
result = this.#perMinuteDataPoints.slice(-1 * (pointOffset + dpCount), pointOffset > 0 ? -1 * pointOffset : this.#perMinuteDataPoints.length);
if(pointOffset + dpCount <= this.#perMinuteDataPoints.length || returnUnfinished){
const start = pointOffset + dpCount <= this.#perMinuteDataPoints.length ? -1 * (pointOffset + dpCount) : -1 * this.#perMinuteDataPoints.length;
result = this.#perMinuteDataPoints.slice(start, pointOffset > 0 ? -1 * pointOffset : this.#perMinuteDataPoints.length);
}
}

View File

@ -47,6 +47,10 @@ export default class PeakDetector {
return;
}
if(entry == null || entry.price == null){
return;
}
if(this.#min > entry.price){
this.#min = entry.price;
this.#minTime = Date.now();

View File

@ -1,4 +1,5 @@
import Transaction from "../../apiwrapper/transaction/Transaction.js";
import TransactionPhase from "../../apiwrapper/transaction/TransactionPhase.js";
import Logger from "../../logging/Logger.js";
import Serializable from "../../util/Serializable.js";
import AssetData from "../data/asset/AssetData.js";
@ -56,6 +57,21 @@ export default class AbstractStrategy extends Serializable {
return false;
}
/**
* @param {Transaction} transaction
* @param {number} winPercent
*/
isAllowedToSell(transaction, winPercent){
return transaction != null && !transaction.lockedForUpdate && winPercent > 0.4;
}
/**
* @param {Transaction} transaction
*/
isAllowedToCancel(transaction){
return transaction != null && !transaction.lockedForUpdate && transaction.phase != TransactionPhase.CANCELED;
}
/**
* @returns {boolean}
*/

View File

@ -0,0 +1,138 @@
import Transaction from "../../apiwrapper/transaction/Transaction.js";
import TransactionPhase from "../../apiwrapper/transaction/TransactionPhase.js";
import AbstractStrategy from "./AbstractStrategy.js";
export default class AvgEmaStrategy extends AbstractStrategy {
#lastBuySuggestionTime = 0;
#waitingMultiplier = 1;
#buyRecomendationRequired = false;
#sellRecomendationRequired = false;
constructor(){
super();
this.#lastBuySuggestionTime = Date.now();
}
/**
* @param {Transaction} transaction
* @returns {boolean}
*/
shouldSell(transaction) {
const winPercent = (this.getAssetData().getOrderBook().getBestBid().price * 100) / transaction.buySettings.price - 100;
const elapsedTime = Date.now() - transaction.timestamp;
if(!this.isAllowedToSell(transaction, winPercent)){
return false;
}
if(elapsedTime < 600000){
return true;
}
if(winPercent > 2.5){
return true;
}
const aggDataPoints = this.getAssetData().getAggregatedData('15m');
const price = this.getAssetData().getOrderBook().getBestBid().price;
let isEmaHigher = false;
for(let i = 10; i < aggDataPoints.length; i++){
isEmaHigher |= aggDataPoints[i].ema15min > aggDataPoints[i].avgPrice;
}
if(isEmaHigher){
this.#waitingMultiplier = 1;
this.getLogger().event("[SELL][" + this.constructor.name + "] recomendet sell for " + transaction.id + " for " + price.toFixed(6));
return true;
}
return false;
}
/**
* @returns {boolean}
*/
shouldBuy() {
if(Date.now() < this.#lastBuySuggestionTime + (60000 * this.#waitingMultiplier)){
return false;
}
this.#waitingMultiplier = 1;
const aggDataPoints = this.getAssetData().getAggregatedData('15m');
const aggDataPoints1h = this.getAssetData().getAggregatedData('1h', -1, true);
if(aggDataPoints == null || aggDataPoints.length < 10 || aggDataPoints1h.length < 30){
return false;
}
if(aggDataPoints1h[0].ema15min > aggDataPoints1h[aggDataPoints1h.length - 1].ema15min){
return false;
}
const price = this.getAssetData().getOrderBook().getBestBid().price;
const priceChange15Min = 100 - (aggDataPoints[aggDataPoints.length - 1].minPrice * 100) / aggDataPoints[0].maxPrice;
let isEmaHigher = true;
let wasEmaHigher = false;
for(let i = 10; i < aggDataPoints.length; i++){
if(!wasEmaHigher && aggDataPoints[i].ema15min > aggDataPoints[i].avgPrice){
wasEmaHigher =true;
continue;
}
if(wasEmaHigher && aggDataPoints[i].ema15min < aggDataPoints[i].avgPrice){
isEmaHigher = false;
break;
}
}
if(priceChange15Min > 0.4 && !isEmaHigher){
this.#lastBuySuggestionTime = Date.now();
this.#waitingMultiplier = 15;
this.getLogger().event("[BUY][" + this.constructor.name + "][" + this.getAssetData().getTradingPair().getKey() + "] recomendet buy for " + price.toFixed(6));
return true;
}
return false;
}
/**
* @param {Transaction} transaction
* @returns {boolean}
*/
shouldCancle(transaction) {
if(!this.isAllowedToCancel(transaction)){
return false;
}
const price = this.getAssetData().getOrderBook().getBestBid().price;
if(transaction.buySettings != null && transaction.phase == TransactionPhase.BUY_IN_PROGRESS){
if(transaction.buySettings.orderPrice * 1.002 < price){
this.getLogger().event("[CANCEL][" + this.constructor.name + "] recomendet cancel for " + transaction.id);
return true;
}
}
return false;
}
/**
* @returns {string} string representation of this object
*/
toJson() {
const obj = JSON.parse(super.toJson());
return JSON.stringify(obj);
}
/**
* @param {string} objString
* @returns {AvgEmaStrategy}
*/
static fromJson(objString) {
return new AvgEmaStrategy();
}
}

View File

@ -17,15 +17,24 @@ export default class AvgMinMaxStrategy extends AbstractStrategy {
shouldSell(transaction) {
const winPercent = (this.getAssetData().getOrderBook().getBestBid().price / transaction.buySettings.price) * 100 - 100;
if(winPercent < 0.6){
if(!this.isAllowedToSell(transaction, winPercent)){
return false;
}
if(winPercent > 2.5){
return true;
}
const aggDataPoints = this.getAssetData().getAggregatedData('15m');
const price = this.getAssetData().getOrderBook().getBestBid().price;
const avg15m = aggDataPoints.length > 0 ? aggDataPoints.map(v => v.avgPrice).reduce((a, b) => a+b, 0) / aggDataPoints.length : 0;
return avg15m != 0 && avg15m * 1.005 <= price;
if(avg15m != 0 && avg15m * 1.005 <= price){
this.getLogger().event("[SELL][" + this.constructor.name + "] recomendet sell for " + transaction.id + " for " + price.toFixed(6));
return true;
}
return false;
}
/**
@ -42,6 +51,7 @@ export default class AvgMinMaxStrategy extends AbstractStrategy {
if(avg15m != 0 && avg15m * 0.995 >= price){
this.#lastBuySuggestionTime = Date.now();
this.getLogger().event("[BUY][" + this.constructor.name + "] recomendet buy for " + price.toFixed(6));
return true;
}
@ -53,10 +63,17 @@ export default class AvgMinMaxStrategy extends AbstractStrategy {
* @returns {boolean}
*/
shouldCancle(transaction) {
if(!this.isAllowedToCancel(transaction)){
return false;
}
const price = this.getAssetData().getOrderBook().getBestBid().price;
if(transaction.buySettings != null && transaction.phase == TransactionPhase.BUY_IN_PROGRESS){
return transaction.buySettings.orderPrice * 1.002 < price;
if(transaction.buySettings.orderPrice * 1.002 < price){
this.getLogger().event("[CANCEL][" + this.constructor.name + "] recomendet cancel for " + transaction.id);
return true;
}
}
return false;

View File

@ -14,6 +14,12 @@ export default class PossiblePeakStrategy extends AbstractStrategy {
* @returns {boolean}
*/
shouldSell(transaction) {
const winPercent = (this.getAssetData().getOrderBook().getBestBid().price / transaction.buySettings.price) * 100 - 100;
if(!this.isAllowedToSell(transaction, winPercent)){
return false;
}
return this.getAssetData().getOrderBook().getBestBid().price / transaction.buySettings.price >= 1.015;
}

View File

@ -15,14 +15,14 @@ export default class Simple24hStrategy extends AbstractStrategy {
*/
shouldSell(transaction) {
const winPercent = (this.getAssetData().getOrderBook().getBestBid().price / transaction.buySettings.price) * 100 - 100;
if(!this.isAllowedToSell(transaction, winPercent)){
return false;
}
if(winPercent >= 3){
return true;
}
if(winPercent < 0.6){
return false;
}
const grades = this.#getGrades();

View File

@ -1,6 +1,7 @@
import Transaction from "../../apiwrapper/transaction/Transaction.js";
import AbstractStrategy from "./AbstractStrategy.js";
import AvgMinMaxStrategy2 from "./AvgMinMaxStrategy.js";
import AvgEmaStrategy from "./AvgEmaStrategy.js";
import AvgMinMaxStrategy from "./AvgMinMaxStrategy.js";
export default class TestStrategy extends AbstractStrategy {
@ -21,7 +22,8 @@ export default class TestStrategy extends AbstractStrategy {
constructor(){
super();
this.#strategies.push(new AvgMinMaxStrategy2());
this.#strategies.push(new AvgMinMaxStrategy());
this.#strategies.push(new AvgEmaStrategy());
}
/**

View File

@ -1,4 +1,4 @@
import AggregatedDataPoint from "../data/asset/AggregatedDataPoint";
import AggregatedDataPoint from "../data/asset/AggregatedDataPoint.js";
export default class AssetInstrumentCalculator{
@ -24,10 +24,10 @@ export default class AssetInstrumentCalculator{
* @param {AggregatedDataPoint[]} dataPoints
* @returns {number}
*/
static caclulateEMA(dataPoints) {
static calculateEMA(dataPoints) {
const sma = AssetInstrumentCalculator.calculateSMA(dataPoints);
const multiplier = 2 / (dataPoints.length + 1);
return sma * (1 - multiplier) + dataPoints[dataPoints.length - 1] * multiplier;
return sma * (1 - multiplier) + dataPoints[dataPoints.length - 1].avgPrice * multiplier;
}
}

View File

@ -29,7 +29,13 @@ export default class DefaultWorker extends AbstractWorker{
*/
onUpdate(assetData, transactions, transactionlimit){
this.getStrategy().setAssetData(assetData);
const price = parseFloat(this.getStrategy().getAssetData().getOrderBook().getBestBid().price);
const bestBid = this.getStrategy().getAssetData().getOrderBook().getBestBid();
if(bestBid == null) {
return;
}
const price = parseFloat(bestBid.price);
if(transactionlimit > transactions.length && this.getStrategy().shouldBuy()){
this.getApiProvider().requestBuy(this, Math.round(50 / price), price);

View File

@ -0,0 +1,75 @@
import TradingPair from "../../apiwrapper/assets/TradingPair.js";
import Transaction from "../../apiwrapper/transaction/Transaction.js";
import TransactionPhase from "../../apiwrapper/transaction/TransactionPhase.js";
import AssetData from "../data/asset/AssetData.js";
import AbstractStrategy from "../strategy/AbstractStrategy.js";
import AbstractWorker from "./AbstractWorker.js";
export default class SingleTransactionTestWorker extends AbstractWorker{
buyDone = false;
sellDone = false;
starttime = -1;
/**
* @param {string} name
* @param {TradingPair} tradingPair
* @param {AbstractStrategy} strategy
*/
constructor(name, tradingPair, strategy){
super(name, tradingPair, strategy);
this.starttime = Date.now();
}
/**
* @param {string} dataPath path to data directory
*/
onInit(dataPath){
this.getLogger().debug("Worker " + this.getName() + " initialized!");
}
/**
* @param {AssetData} assetData
* @param {Transaction[]} transactions
* @param {number} transactionlimit
*/
onUpdate(assetData, transactions, transactionlimit){
if(Date.now() - this.starttime < 30000){
return;
}
this.getStrategy().setAssetData(assetData);
const bestBid = this.getStrategy().getAssetData().getOrderBook().getBestBid();
if(bestBid == null) {
return;
}
const price = parseFloat(bestBid.price);
if(!this.buyDone){
this.getApiProvider().requestBuy(this, Math.round(10 / price), price);
this.starttime = Date.now();
this.buyDone = true;
}
if(!this.sellDone && transactions.length > 0 && transactions[0].phase == TransactionPhase.BUY_DONE){
this.getApiProvider().requestSell(this, transactions[0], assetData.getOrderBook().getBestAsk().price);
this.sellDone = true;
}
}
/**
* @returns {string} string representation of this object
*/
toJson() {
return super.toJson();
}
/**
* @param {string} objString
* @returns {AbstractWorker}
*/
static fromJson(objString) {
return super.fromJson(objString, new SingleTransactionTestWorker());
}
}

View File

@ -47,6 +47,26 @@ export default class StatisticWorker extends AbstractWorker {
*/
#sells = [];
/**
* @type {[number, number][]}
*/
#sma5Min = [];
/**
* @type {[number, number][]}
*/
#sma15Min = [];
/**
* @type {[number, number][]}
*/
#ema5Min = [];
/**
* @type {[number, number][]}
*/
#ema15Min = [];
/**
* @type {number}
*/
@ -175,6 +195,10 @@ export default class StatisticWorker extends AbstractWorker {
peakEnd: this.#peakEnd[i][1],
buys: this.#buys[i][1],
sells: this.#sells[i][1],
sma5min: this.#sma5Min[i][1],
sma15min: this.#sma15Min[i][1],
ema5min: this.#ema5Min[i][1],
ema15min: this.#ema15Min[i][1],
});
}
@ -206,6 +230,10 @@ export default class StatisticWorker extends AbstractWorker {
this.#peakStart.push([timestamp, false]);
this.#buys.push([timestamp, this.#buyCount]);
this.#sells.push([timestamp, this.#sellCount]);
this.#sma5Min.push([timestamp, lastMinAssetData.sma5min]);
this.#sma15Min.push([timestamp, lastMinAssetData.sma15min]);
this.#ema5Min.push([timestamp, lastMinAssetData.ema5min]);
this.#ema15Min.push([timestamp, lastMinAssetData.ema15min]);
peaks.sort((p1, p2) => p1.startTime - p2.startTime);
let lastPeakIndex = 0;

View File

@ -6,12 +6,14 @@ import Transaction from "../apiwrapper/transaction/Transaction.js";
import TransactionSettings from "../apiwrapper/transaction/TransactionSettings.js";
import BotConfig from "../tradingbot/config/BotConfig.js";
import AbstractStrategy from "../tradingbot/strategy/AbstractStrategy.js";
import AvgEmaStrategy from "../tradingbot/strategy/AvgEmaStrategy.js";
import AvgMinMaxStrategy from "../tradingbot/strategy/AvgMinMaxStrategy.js";
import PossiblePeakStrategy from "../tradingbot/strategy/PossiblePeakStrategy.js";
import Simple24hStrategy from "../tradingbot/strategy/Simple24hStrategy.js";
import TestStrategy from "../tradingbot/strategy/TestStrategy.js";
import AbstractWorker from "../tradingbot/worker/AbstractWorker.js";
import DefaultWorker from "../tradingbot/worker/DefaultWorker.js";
import SingleTransactionTestWorker from "../tradingbot/worker/SingleTransactionTestWorker.js";
import StatisticWorker from "../tradingbot/worker/StatisticWorker.js";
export default class Serializables {
@ -31,5 +33,7 @@ export default class Serializables {
['Simple24hStrategy', Simple24hStrategy],
['PossiblePeakStrategy', PossiblePeakStrategy],
['AvgMinMaxStrategy', AvgMinMaxStrategy],
['AvgEmaStrategy', AvgEmaStrategy],
['SingleTransactionTestWorker', SingleTransactionTestWorker],
]);
}

View File

@ -61,6 +61,14 @@ export default class RestClient {
res.json(this.#getAllOrderBooks());
});
this.#service.get("/assets/transactions", (req, res, next) => {
res.json(this.#getAllTransactions(req.query.type));
});
this.#service.get("/assets/balance", (req, res, next) => {
res.json(this.#getBalances());
});
this.#service.get("/asset/:symbol/orderbook", (req, res, next) => {
res.json(this.#getAssetOrderBook(req.params.symbol));
});
@ -73,6 +81,10 @@ export default class RestClient {
res.json(this.#getAggregatedAssetData(req.params.symbol, req.query.startTime, req.query.duration));
});
this.#service.get("/asset/:symbol/balance", (req, res, next) => {
res.json(this.#getBalances(req.params.symbol));
});
this.#service.get("/worker/:name/attribute/:attr", (req, res, next) => {
res.json(this.#getWorkerAttribute(req.params.name, req.params.attr));
});
@ -99,6 +111,26 @@ export default class RestClient {
return result;
}
#getBalances(asset = null){
/**
* @type {Map<string, number>}
*/
const balances = this.#dataProvider.getBalances();
const result = [];
if(asset == null){
balances.forEach((value, key) => result.push({symbol: key, balance: value}));
return result;
} else {
const balance = balances.get(asset);
if(balance == null){
return null;
} else {
return {symbol: asset, balance: balance};
}
}
}
#getAssetOrderBook(asset){
const orderBook = this.#dataProvider.getOrderBook(asset);
@ -113,6 +145,14 @@ export default class RestClient {
return transactions.map(t => transactionConverter.toRestObject(t));
}
#getAllTransactions(type = null){
const allTransactions = type == "all";
const transactions = allTransactions ? this.#dataProvider.getAllTransactions() : this.#dataProvider.getActiveTransactions();
const transactionConverter = new TransactionConverter();
return transactions.map(t => transactionConverter.toRestObject(t));
}
#getAllOrderBooks(){
const orderBooks = [];

View File

@ -37,7 +37,7 @@ export default class WebViewDataProvider {
* @param {string} symbol
* @returns {Transaction[]}
*/
getActiveTransactions(symbol) {
getActiveTransactions(symbol = null) {
return [];
}
@ -45,7 +45,7 @@ export default class WebViewDataProvider {
* @param {string} symbol
* @returns {Transaction[]}
*/
getAllTransactions(symbol) {
getAllTransactions(symbol = null) {
return [];
}