[BOT] Diverse anpassungen und vorbereitungen für live betrieb
This commit is contained in:
parent
0f0b453648
commit
399204aad3
@ -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});
|
||||
|
||||
@ -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,
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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 ##
|
||||
|
||||
@ -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) {
|
||||
|
||||
20
src/js/logging/SingleLevelRotateTransport.js
Normal file
20
src/js/logging/SingleLevelRotateTransport.js
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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}
|
||||
*/
|
||||
|
||||
138
src/js/tradingbot/strategy/AvgEmaStrategy.js
Normal file
138
src/js/tradingbot/strategy/AvgEmaStrategy.js
Normal 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();
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
75
src/js/tradingbot/worker/SingleTransactionTestWorker.js
Normal file
75
src/js/tradingbot/worker/SingleTransactionTestWorker.js
Normal 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());
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -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],
|
||||
]);
|
||||
}
|
||||
@ -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 = [];
|
||||
|
||||
|
||||
@ -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 [];
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user