Compare commits
5 Commits
2b3a0e3102
...
399204aad3
| Author | SHA1 | Date | |
|---|---|---|---|
| 399204aad3 | |||
| 0f0b453648 | |||
| 4f15b08756 | |||
| 8fcecb1919 | |||
| 83d22f970d |
@ -401,7 +401,7 @@ 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(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);
|
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});
|
||||||
|
|||||||
@ -7,12 +7,22 @@ export default class TradingPairs {
|
|||||||
static ETHUSDT = new TradingPair(Assets.ETH, Assets.USDT);
|
static ETHUSDT = new TradingPair(Assets.ETH, Assets.USDT);
|
||||||
static GALAUSDT = new TradingPair(Assets.GALA, Assets.USDT);
|
static GALAUSDT = new TradingPair(Assets.GALA, Assets.USDT);
|
||||||
static ETHBTC = new TradingPair(Assets.ETH, Assets.BTC);
|
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 = [
|
static TRADING_PAIRS = [
|
||||||
this.BTCUSDT,
|
this.BTCUSDT,
|
||||||
this.ETHUSDT,
|
this.ETHUSDT,
|
||||||
this.GALAUSDT,
|
this.GALAUSDT,
|
||||||
this.ETHBTC,
|
this.ETHBTC,
|
||||||
|
this.ALGOUSDT,
|
||||||
|
this.TRXUSDT,
|
||||||
|
this.ADAUSDT,
|
||||||
|
this.XRPUSDT,
|
||||||
|
this.DOGEUSDT,
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -9,8 +9,20 @@ export default class TransactionFill {
|
|||||||
*/
|
*/
|
||||||
quantity = 0;
|
quantity = 0;
|
||||||
|
|
||||||
constructor(price, quantity){
|
/**
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
commission = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
commissionAsset = null;
|
||||||
|
|
||||||
|
constructor(price, quantity, commission, commissionAsset){
|
||||||
this.price = price;
|
this.price = price;
|
||||||
this.quantity = quantity;
|
this.quantity = quantity;
|
||||||
|
this.commission = commission;
|
||||||
|
this.commissionAsset = commissionAsset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,6 +31,12 @@ export default class TransactionSettings extends Serializable{
|
|||||||
*/
|
*/
|
||||||
apiID = "";
|
apiID = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creation time
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
timestamp = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {number} price default 0
|
* @param {number} price default 0
|
||||||
* @param {number} quantity default 0
|
* @param {number} quantity default 0
|
||||||
@ -39,6 +45,7 @@ export default class TransactionSettings extends Serializable{
|
|||||||
super();
|
super();
|
||||||
this.orderPrice = price;
|
this.orderPrice = price;
|
||||||
this.orderQuantity = quantity;
|
this.orderQuantity = quantity;
|
||||||
|
this.timestamp = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,6 +58,7 @@ export default class TransactionSettings extends Serializable{
|
|||||||
obj.quantity = this.quantity;
|
obj.quantity = this.quantity;
|
||||||
obj.price = this.price;
|
obj.price = this.price;
|
||||||
obj.apiID = this.apiID;
|
obj.apiID = this.apiID;
|
||||||
|
obj.timestamp = this.timestamp;
|
||||||
|
|
||||||
return JSON.stringify(obj);
|
return JSON.stringify(obj);
|
||||||
}
|
}
|
||||||
@ -68,6 +76,7 @@ export default class TransactionSettings extends Serializable{
|
|||||||
obj.quantity = tmpObj.quantity;
|
obj.quantity = tmpObj.quantity;
|
||||||
obj.price = tmpObj.price;
|
obj.price = tmpObj.price;
|
||||||
obj.apiID = tmpObj.apiID;
|
obj.apiID = tmpObj.apiID;
|
||||||
|
obj.timestamp = tmpObj.timestamp;
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,14 +21,30 @@ import PeakDetector from "./tradingbot/data/asset/PeakDetector.js";
|
|||||||
import OrderBookEntry from "./apiwrapper/orderbook/OrderBookEntry.js";
|
import OrderBookEntry from "./apiwrapper/orderbook/OrderBookEntry.js";
|
||||||
import TestStrategy from "./tradingbot/strategy/TestStrategy.js";
|
import TestStrategy from "./tradingbot/strategy/TestStrategy.js";
|
||||||
import AvgMinMaxStrategy from "./tradingbot/strategy/AvgMinMaxStrategy.js";
|
import AvgMinMaxStrategy from "./tradingbot/strategy/AvgMinMaxStrategy.js";
|
||||||
|
import AvgEmaStrategy from "./tradingbot/strategy/AvgEmaStrategy.js";
|
||||||
|
import SingleTransactionTestWorker from "./tradingbot/worker/SingleTransactionTestWorker.js";
|
||||||
|
|
||||||
try{
|
try{
|
||||||
|
|
||||||
const tradingBot = new TradingBot("C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data/conf", "config-test.sjson");
|
const tradingBot = new TradingBot("C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data/conf", "config-main.sjson");
|
||||||
const galaWorker = new DefaultWorker("gala_minmax", TradingPairs.GALAUSDT, new AvgMinMaxStrategy());
|
const galaWorker = new DefaultWorker("gala_avgema", TradingPairs.GALAUSDT, new AvgEmaStrategy());
|
||||||
const statWorker = new StatisticWorker("gala_stats", TradingPairs.GALAUSDT, new TestStrategy());
|
const statWorker = new StatisticWorker("gala_stats", TradingPairs.GALAUSDT, 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(galaWorker);
|
||||||
//tradingBot.registerWorker(statWorker);
|
//tradingBot.registerWorker(statWorker);
|
||||||
|
//tradingBot.registerWorker(dogeWorker);
|
||||||
|
//tradingBot.registerWorker(xrpWorker);
|
||||||
|
//tradingBot.registerWorker(algoWorker);
|
||||||
|
//tradingBot.registerWorker(adaWorker);
|
||||||
|
|
||||||
tradingBot.start();
|
tradingBot.start();
|
||||||
} catch (e){
|
} catch (e){
|
||||||
console.log(e);
|
console.log(e);
|
||||||
@ -115,14 +131,14 @@ setTimeout(() => {api.requestBuy(transaction);}, 5000);*/
|
|||||||
// ############################
|
// ############################
|
||||||
/*const path = 'C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data/conf';
|
/*const path = 'C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data/conf';
|
||||||
const conf = new BotConfig();
|
const conf = new BotConfig();
|
||||||
conf.setApiConfig(BinanceApiConfig.createTestNetConfig());
|
conf.setApiConfig(BinanceApiConfig.createMainNetConfig());
|
||||||
conf.setWorkingDirectory('C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data');
|
conf.setWorkingDirectory('C:/Users/Wlad/Projekte/BinanceBot/binance_bot/data');
|
||||||
conf.setRelativeDataPath('data');
|
conf.setRelativeDataPath('data');
|
||||||
conf.setRelativeLogPath('logs');
|
conf.setRelativeLogPath('logs');
|
||||||
conf.setRelativeTmpPath('tmp');
|
conf.setRelativeTmpPath('tmp');
|
||||||
conf.setUsdTradeLimit(500);
|
conf.setUsdTradeLimit(500);
|
||||||
conf.setPerTradeLimit(30);
|
conf.setPerTradeLimit(50);
|
||||||
SerializableHelper.save(path, "config-test.sjson", conf);*/
|
SerializableHelper.save(path, "config-main.sjson", conf);*/
|
||||||
|
|
||||||
// ############################
|
// ############################
|
||||||
// ## SERIALIZER TESTS ##
|
// ## SERIALIZER TESTS ##
|
||||||
|
|||||||
@ -1,24 +1,26 @@
|
|||||||
import winston from "winston";
|
import winston from "winston";
|
||||||
import 'winston-daily-rotate-file';
|
import 'winston-daily-rotate-file';
|
||||||
|
import SingleLevelRotateTransport from "./SingleLevelRotateTransport.js";
|
||||||
|
|
||||||
export default class Logger {
|
export default class Logger {
|
||||||
#internLogger = null;
|
#internLogger = null;
|
||||||
|
|
||||||
/*
|
#levels = {
|
||||||
error: 0,
|
error: 0,
|
||||||
warn: 1,
|
warn: 1,
|
||||||
info: 2,
|
info: 2,
|
||||||
http: 3,
|
verbose: 3,
|
||||||
verbose: 4,
|
debug: 4,
|
||||||
debug: 5,
|
silly: 5,
|
||||||
silly: 6
|
event: 101
|
||||||
*/
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} logDir
|
* @param {string} logDir
|
||||||
*/
|
*/
|
||||||
constructor(logDir) {
|
constructor(logDir) {
|
||||||
this.#internLogger = winston.createLogger({
|
this.#internLogger = winston.createLogger({
|
||||||
|
levels: this.#levels,
|
||||||
level: 'debug',
|
level: 'debug',
|
||||||
format: winston.format.combine(
|
format: winston.format.combine(
|
||||||
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
||||||
@ -26,7 +28,8 @@ export default class Logger {
|
|||||||
),
|
),
|
||||||
transports: [
|
transports: [
|
||||||
this.#getCombinedLogTransport(logDir),
|
this.#getCombinedLogTransport(logDir),
|
||||||
this.#getErrorLogTransport(logDir)
|
this.#getErrorLogTransport(logDir),
|
||||||
|
this.#getEventLogTransport(logDir)
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -48,7 +51,8 @@ export default class Logger {
|
|||||||
datePattern: 'YYYY-MM-DD',
|
datePattern: 'YYYY-MM-DD',
|
||||||
zippedArchive: true,
|
zippedArchive: true,
|
||||||
maxSize: '10m',
|
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
|
* Logs to file
|
||||||
* @param {...any} obj objects to log
|
* @param {...any} obj objects to log
|
||||||
@ -112,8 +132,8 @@ export default class Logger {
|
|||||||
* Logs to separate file
|
* Logs to separate file
|
||||||
* @param {...any} obj objects to log
|
* @param {...any} obj objects to log
|
||||||
*/
|
*/
|
||||||
remark(...obj) {
|
event(...obj) {
|
||||||
this.#writeLog('verbose', obj);
|
this.#writeLog('event', obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
#writeLog(level, params) {
|
#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());
|
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}
|
* @returns {AssetDataCollector}
|
||||||
*/
|
*/
|
||||||
@ -268,7 +282,7 @@ export default class TradingBot {
|
|||||||
|
|
||||||
this.#worker.forEach(worker => {
|
this.#worker.forEach(worker => {
|
||||||
if (worker.getTradingPair().getKey() === assetUpdateEvent.tradingPair.getKey()) {
|
if (worker.getTradingPair().getKey() === assetUpdateEvent.tradingPair.getKey()) {
|
||||||
worker.onUpdate(this.#dataCollector.getAssetData(worker.getTradingPair()), this.#transactions.filter(t => t.initiator = worker.getId()));
|
worker.onUpdate(this.#dataCollector.getAssetData(worker.getTradingPair()), this.#transactions.filter(t => t.initiator == worker.getId()), this.#config.getMaxTransactionsPerWorker());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -301,10 +315,12 @@ export default class TradingBot {
|
|||||||
const nextTransactionPhase = TransactionPhase.getPhaseForStatus(transaction.phase, event.status, event.fills.length > 0);
|
const nextTransactionPhase = TransactionPhase.getPhaseForStatus(transaction.phase, event.status, event.fills.length > 0);
|
||||||
if (transaction.phase !== nextTransactionPhase) {
|
if (transaction.phase !== nextTransactionPhase) {
|
||||||
if (TransactionPhase.BUY_DONE === 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)) {
|
} else if (TransactionPhase.isFinalPhase(nextTransactionPhase)) {
|
||||||
this.#calculateTradeValues(transaction.sellSettings, event.fills);
|
if(TransactionPhase.CANCELED != nextTransactionPhase) {
|
||||||
transaction.result = (transaction.sellSettings.quantity * transaction.sellSettings.price) - (transaction.buySettings.quantity * transaction.buySettings.price)
|
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);
|
this.#transactionHistory.push(transaction);
|
||||||
this.#transactions.splice(index, 1);
|
this.#transactions.splice(index, 1);
|
||||||
}
|
}
|
||||||
@ -318,20 +334,29 @@ export default class TradingBot {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param {TradingPair} tradingpair
|
||||||
* @param {TransactionSettings} taSettings
|
* @param {TransactionSettings} taSettings
|
||||||
* @param {TransactionFill[]} fills
|
* @param {TransactionFill[]} fills
|
||||||
*/
|
*/
|
||||||
#calculateTradeValues(taSettings, fills) {
|
#calculateTradeValues(tradingpair, taSettings, fills) {
|
||||||
let priceSum = 0;
|
let priceSum = 0;
|
||||||
let quantity = 0;
|
let quantity = 0;
|
||||||
|
let commissionAsset1 = 0;
|
||||||
|
let commissionAsset2 = 0;
|
||||||
|
|
||||||
fills.forEach(fill => {
|
fills.forEach(fill => {
|
||||||
priceSum += fill.price * fill.quantity;
|
priceSum += fill.price * fill.quantity;
|
||||||
quantity += 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.quantity = quantity - commissionAsset1;
|
||||||
taSettings.price = quantity != 0 ? priceSum / quantity : 0;
|
taSettings.price = quantity != 0 ? (priceSum - commissionAsset2) / taSettings.quantity : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -45,17 +45,25 @@ export default class TradingBotRestDataProvider extends WebViewDataProvider {
|
|||||||
* @param {string} symbol
|
* @param {string} symbol
|
||||||
* @returns {Transaction[]}
|
* @returns {Transaction[]}
|
||||||
*/
|
*/
|
||||||
getActiveTransactions(symbol) {
|
getActiveTransactions(symbol = null) {
|
||||||
return this.#tradingBot.getTransactionsByAsset(TradingPairs.fromString(symbol)).sort((t1, t2) => t1.timestamp - t2.timestamp);
|
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
|
* @param {string} symbol
|
||||||
* @returns {Transaction[]}
|
* @returns {Transaction[]}
|
||||||
*/
|
*/
|
||||||
getAllTransactions(symbol) {
|
getAllTransactions(symbol = null) {
|
||||||
const tp = TradingPairs.fromString(symbol);
|
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
|
* @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
|
* @type {number} max amount of USD, that can be used for trading
|
||||||
@ -44,6 +44,11 @@ export default class BotConfig extends Serializable {
|
|||||||
*/
|
*/
|
||||||
#maxUSDPerTrade = 50.0;
|
#maxUSDPerTrade = 50.0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number} max amount of active transactions per worker
|
||||||
|
*/
|
||||||
|
#maxTransactionsPerWorker = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {boolean} if rest interface should be enabled
|
* @type {boolean} if rest interface should be enabled
|
||||||
*/
|
*/
|
||||||
@ -219,6 +224,20 @@ export default class BotConfig extends Serializable {
|
|||||||
this.#rampupTime = chronoString;
|
this.#rampupTime = chronoString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
getMaxTransactionsPerWorker(){
|
||||||
|
return this.#maxTransactionsPerWorker;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} max
|
||||||
|
*/
|
||||||
|
setMaxTransactionsPerWorker(max){
|
||||||
|
this.#maxTransactionsPerWorker = max;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {string} string representation of this object
|
* @returns {string} string representation of this object
|
||||||
*/
|
*/
|
||||||
@ -234,6 +253,7 @@ export default class BotConfig extends Serializable {
|
|||||||
obj.restInterfaceEnabled = this.#enableRestInterface;
|
obj.restInterfaceEnabled = this.#enableRestInterface;
|
||||||
obj.restInterfacePort = this.#restInterfacePort;
|
obj.restInterfacePort = this.#restInterfacePort;
|
||||||
obj.rampupTime = this.#rampupTime;
|
obj.rampupTime = this.#rampupTime;
|
||||||
|
obj.maxTransactionsPerWorker = this.#maxTransactionsPerWorker;
|
||||||
|
|
||||||
return JSON.stringify(obj);
|
return JSON.stringify(obj);
|
||||||
}
|
}
|
||||||
@ -256,6 +276,7 @@ export default class BotConfig extends Serializable {
|
|||||||
conf.#enableRestInterface = obj.restInterfaceEnabled;
|
conf.#enableRestInterface = obj.restInterfaceEnabled;
|
||||||
conf.#restInterfacePort = obj.restInterfacePort;
|
conf.#restInterfacePort = obj.restInterfacePort;
|
||||||
conf.#rampupTime = obj.rampupTime;
|
conf.#rampupTime = obj.rampupTime;
|
||||||
|
conf.#maxTransactionsPerWorker = obj.maxTransactionsPerWorker;
|
||||||
|
|
||||||
return conf;
|
return conf;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,6 +77,26 @@ export default class AggregatedDataPoint {
|
|||||||
*/
|
*/
|
||||||
peakEnds = 0;
|
peakEnds = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
sma15min = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
ema15min = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
sma5min = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
ema5min = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {number[]}
|
* @type {number[]}
|
||||||
*/
|
*/
|
||||||
@ -95,6 +115,10 @@ export default class AggregatedDataPoint {
|
|||||||
* @param {OrderBookEntry} ask
|
* @param {OrderBookEntry} ask
|
||||||
*/
|
*/
|
||||||
pushAsk(ask){
|
pushAsk(ask){
|
||||||
|
if(ask == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(this.bestAsk == null || this.bestAsk.price < ask.price){
|
if(this.bestAsk == null || this.bestAsk.price < ask.price){
|
||||||
this.bestAsk = ask;
|
this.bestAsk = ask;
|
||||||
}
|
}
|
||||||
@ -104,6 +128,10 @@ export default class AggregatedDataPoint {
|
|||||||
* @param {OrderBookEntry} bid
|
* @param {OrderBookEntry} bid
|
||||||
*/
|
*/
|
||||||
pushBid(bid){
|
pushBid(bid){
|
||||||
|
if(bid == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!this.hasValues){
|
if(!this.hasValues){
|
||||||
this.startPrice = bid.price;
|
this.startPrice = bid.price;
|
||||||
this.minPrice = bid.price;
|
this.minPrice = bid.price;
|
||||||
|
|||||||
@ -179,7 +179,7 @@ export default class AssetData {
|
|||||||
this.#assetDataAggregator.pushTrade(time, price, quantity);
|
this.#assetDataAggregator.pushTrade(time, price, quantity);
|
||||||
}
|
}
|
||||||
|
|
||||||
getAggregatedData(duration, timeOffset = -1){
|
getAggregatedData(duration, timeOffset = -1, returnUnfinished = false){
|
||||||
return this.#assetDataAggregator.getData(duration, timeOffset);
|
return this.#assetDataAggregator.getData(duration, timeOffset, returnUnfinished);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import OrderBookEntry from "../../../apiwrapper/orderbook/OrderBookEntry.js";
|
import OrderBookEntry from "../../../apiwrapper/orderbook/OrderBookEntry.js";
|
||||||
|
import AssetInstrumentCalculator from "../../util/AssetInstrumentCalculator.js";
|
||||||
import UnitHelper from "../../util/UnitHelper.js";
|
import UnitHelper from "../../util/UnitHelper.js";
|
||||||
import AggregatedDataPoint from "./AggregatedDataPoint.js";
|
import AggregatedDataPoint from "./AggregatedDataPoint.js";
|
||||||
|
|
||||||
@ -54,6 +55,14 @@ export default class AssetDataAggregator {
|
|||||||
tmpSlot.addTrades(dp.tradeCount, dp.tradeVolume);
|
tmpSlot.addTrades(dp.tradeCount, dp.tradeVolume);
|
||||||
}
|
}
|
||||||
tmpSlot.end(timestamp + AssetDataAggregator.#SLOT_1_MIN);
|
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.#lastOneMinUpdate = timestamp;
|
||||||
|
|
||||||
this.#pushSlotAndFillUp(this.#perMinuteDataPoints, tmpSlot, AssetDataAggregator.#SLOT_1_MIN, 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 {string} duration chrono unit (eg. 1000 or 1s or 15m)
|
||||||
* @param {number} start start timestamp
|
* @param {number} start start timestamp
|
||||||
|
* @param {boolean} returnUnfinished if true, unfinished timeslots will be returned (minutes only!)
|
||||||
* @returns {AggregatedDataPoint[]}
|
* @returns {AggregatedDataPoint[]}
|
||||||
*/
|
*/
|
||||||
getData(duration, start = -1){
|
getData(duration, start = -1, returnUnfinished = false){
|
||||||
let result = [];
|
let result = [];
|
||||||
const durationInMs = UnitHelper.formatedDurationToMs(duration);
|
const durationInMs = UnitHelper.formatedDurationToMs(duration);
|
||||||
|
|
||||||
@ -136,8 +146,9 @@ export default class AssetDataAggregator {
|
|||||||
} else {
|
} else {
|
||||||
const dpCount = Math.round(durationInMs / AssetDataAggregator.#SLOT_1_MIN);
|
const dpCount = Math.round(durationInMs / AssetDataAggregator.#SLOT_1_MIN);
|
||||||
const pointOffset = start == -1 ? 0 : Math.round(start / AssetDataAggregator.#SLOT_1_MIN);
|
const pointOffset = start == -1 ? 0 : Math.round(start / AssetDataAggregator.#SLOT_1_MIN);
|
||||||
if(pointOffset + dpCount <= this.#perMinuteDataPoints.length){
|
if(pointOffset + dpCount <= this.#perMinuteDataPoints.length || returnUnfinished){
|
||||||
result = this.#perMinuteDataPoints.slice(-1 * (pointOffset + dpCount), pointOffset > 0 ? -1 * pointOffset : this.#perMinuteDataPoints.length);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(entry == null || entry.price == null){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(this.#min > entry.price){
|
if(this.#min > entry.price){
|
||||||
this.#min = entry.price;
|
this.#min = entry.price;
|
||||||
this.#minTime = Date.now();
|
this.#minTime = Date.now();
|
||||||
|
|||||||
@ -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 Logger from "../../logging/Logger.js";
|
import Logger from "../../logging/Logger.js";
|
||||||
import Serializable from "../../util/Serializable.js";
|
import Serializable from "../../util/Serializable.js";
|
||||||
import AssetData from "../data/asset/AssetData.js";
|
import AssetData from "../data/asset/AssetData.js";
|
||||||
@ -56,6 +57,21 @@ export default class AbstractStrategy extends Serializable {
|
|||||||
return false;
|
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}
|
* @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) {
|
shouldSell(transaction) {
|
||||||
const winPercent = (this.getAssetData().getOrderBook().getBestBid().price / transaction.buySettings.price) * 100 - 100;
|
const winPercent = (this.getAssetData().getOrderBook().getBestBid().price / transaction.buySettings.price) * 100 - 100;
|
||||||
|
|
||||||
if(winPercent < 0.6){
|
if(!this.isAllowedToSell(transaction, winPercent)){
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(winPercent > 2.5){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
const aggDataPoints = this.getAssetData().getAggregatedData('15m');
|
const aggDataPoints = this.getAssetData().getAggregatedData('15m');
|
||||||
const price = this.getAssetData().getOrderBook().getBestBid().price;
|
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;
|
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){
|
if(avg15m != 0 && avg15m * 0.995 >= price){
|
||||||
this.#lastBuySuggestionTime = Date.now();
|
this.#lastBuySuggestionTime = Date.now();
|
||||||
|
this.getLogger().event("[BUY][" + this.constructor.name + "] recomendet buy for " + price.toFixed(6));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -53,10 +63,17 @@ export default class AvgMinMaxStrategy extends AbstractStrategy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
shouldCancle(transaction) {
|
shouldCancle(transaction) {
|
||||||
|
if(!this.isAllowedToCancel(transaction)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const price = this.getAssetData().getOrderBook().getBestBid().price;
|
const price = this.getAssetData().getOrderBook().getBestBid().price;
|
||||||
|
|
||||||
if(transaction.buySettings != null && transaction.phase == TransactionPhase.BUY_IN_PROGRESS){
|
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;
|
return false;
|
||||||
|
|||||||
@ -14,6 +14,12 @@ export default class PossiblePeakStrategy extends AbstractStrategy {
|
|||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
shouldSell(transaction) {
|
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;
|
return this.getAssetData().getOrderBook().getBestBid().price / transaction.buySettings.price >= 1.015;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,14 +15,14 @@ export default class Simple24hStrategy extends AbstractStrategy {
|
|||||||
*/
|
*/
|
||||||
shouldSell(transaction) {
|
shouldSell(transaction) {
|
||||||
const winPercent = (this.getAssetData().getOrderBook().getBestBid().price / transaction.buySettings.price) * 100 - 100;
|
const winPercent = (this.getAssetData().getOrderBook().getBestBid().price / transaction.buySettings.price) * 100 - 100;
|
||||||
|
|
||||||
|
if(!this.isAllowedToSell(transaction, winPercent)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if(winPercent >= 3){
|
if(winPercent >= 3){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(winPercent < 0.6){
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const grades = this.#getGrades();
|
const grades = this.#getGrades();
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import Transaction from "../../apiwrapper/transaction/Transaction.js";
|
import Transaction from "../../apiwrapper/transaction/Transaction.js";
|
||||||
import AbstractStrategy from "./AbstractStrategy.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 {
|
export default class TestStrategy extends AbstractStrategy {
|
||||||
|
|
||||||
@ -21,7 +22,8 @@ export default class TestStrategy extends AbstractStrategy {
|
|||||||
|
|
||||||
constructor(){
|
constructor(){
|
||||||
super();
|
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{
|
export default class AssetInstrumentCalculator{
|
||||||
|
|
||||||
@ -24,10 +24,10 @@ export default class AssetInstrumentCalculator{
|
|||||||
* @param {AggregatedDataPoint[]} dataPoints
|
* @param {AggregatedDataPoint[]} dataPoints
|
||||||
* @returns {number}
|
* @returns {number}
|
||||||
*/
|
*/
|
||||||
static caclulateEMA(dataPoints) {
|
static calculateEMA(dataPoints) {
|
||||||
const sma = AssetInstrumentCalculator.calculateSMA(dataPoints);
|
const sma = AssetInstrumentCalculator.calculateSMA(dataPoints);
|
||||||
const multiplier = 2 / (dataPoints.length + 1);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,8 +139,9 @@ export default class AbstractWorker extends Serializable {
|
|||||||
/**
|
/**
|
||||||
* @param {AssetData} assetData
|
* @param {AssetData} assetData
|
||||||
* @param {Transaction[]} transactions
|
* @param {Transaction[]} transactions
|
||||||
|
* @param {number} transactionlimit
|
||||||
*/
|
*/
|
||||||
onUpdate(assetData, transactions){
|
onUpdate(assetData, transactions, transactionlimit){
|
||||||
// TODO implement update
|
// TODO implement update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -25,12 +25,19 @@ export default class DefaultWorker extends AbstractWorker{
|
|||||||
/**
|
/**
|
||||||
* @param {AssetData} assetData
|
* @param {AssetData} assetData
|
||||||
* @param {Transaction[]} transactions
|
* @param {Transaction[]} transactions
|
||||||
|
* @param {number} transactionlimit
|
||||||
*/
|
*/
|
||||||
onUpdate(assetData, transactions){
|
onUpdate(assetData, transactions, transactionlimit){
|
||||||
this.getStrategy().setAssetData(assetData);
|
this.getStrategy().setAssetData(assetData);
|
||||||
const price = parseFloat(this.getStrategy().getAssetData().getOrderBook().getBestBid().price);
|
const bestBid = this.getStrategy().getAssetData().getOrderBook().getBestBid();
|
||||||
|
|
||||||
if(this.getStrategy().shouldBuy()){
|
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);
|
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 = [];
|
#sells = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {[number, number][]}
|
||||||
|
*/
|
||||||
|
#sma5Min = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {[number, number][]}
|
||||||
|
*/
|
||||||
|
#sma15Min = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {[number, number][]}
|
||||||
|
*/
|
||||||
|
#ema5Min = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {[number, number][]}
|
||||||
|
*/
|
||||||
|
#ema15Min = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {number}
|
* @type {number}
|
||||||
*/
|
*/
|
||||||
@ -103,8 +123,9 @@ export default class StatisticWorker extends AbstractWorker {
|
|||||||
/**
|
/**
|
||||||
* @param {AssetData} assetData
|
* @param {AssetData} assetData
|
||||||
* @param {Transaction[]} transactions
|
* @param {Transaction[]} transactions
|
||||||
|
* @param {number} transactionlimit
|
||||||
*/
|
*/
|
||||||
onUpdate(assetData, transactions) {
|
onUpdate(assetData, transactions, transactionlimit) {
|
||||||
const strategy = this.getStrategy();
|
const strategy = this.getStrategy();
|
||||||
if(strategy != null){
|
if(strategy != null){
|
||||||
strategy.setAssetData(assetData);
|
strategy.setAssetData(assetData);
|
||||||
@ -174,6 +195,10 @@ export default class StatisticWorker extends AbstractWorker {
|
|||||||
peakEnd: this.#peakEnd[i][1],
|
peakEnd: this.#peakEnd[i][1],
|
||||||
buys: this.#buys[i][1],
|
buys: this.#buys[i][1],
|
||||||
sells: this.#sells[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],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +230,10 @@ export default class StatisticWorker extends AbstractWorker {
|
|||||||
this.#peakStart.push([timestamp, false]);
|
this.#peakStart.push([timestamp, false]);
|
||||||
this.#buys.push([timestamp, this.#buyCount]);
|
this.#buys.push([timestamp, this.#buyCount]);
|
||||||
this.#sells.push([timestamp, this.#sellCount]);
|
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);
|
peaks.sort((p1, p2) => p1.startTime - p2.startTime);
|
||||||
let lastPeakIndex = 0;
|
let lastPeakIndex = 0;
|
||||||
|
|||||||
@ -6,12 +6,14 @@ import Transaction from "../apiwrapper/transaction/Transaction.js";
|
|||||||
import TransactionSettings from "../apiwrapper/transaction/TransactionSettings.js";
|
import TransactionSettings from "../apiwrapper/transaction/TransactionSettings.js";
|
||||||
import BotConfig from "../tradingbot/config/BotConfig.js";
|
import BotConfig from "../tradingbot/config/BotConfig.js";
|
||||||
import AbstractStrategy from "../tradingbot/strategy/AbstractStrategy.js";
|
import AbstractStrategy from "../tradingbot/strategy/AbstractStrategy.js";
|
||||||
|
import AvgEmaStrategy from "../tradingbot/strategy/AvgEmaStrategy.js";
|
||||||
import AvgMinMaxStrategy from "../tradingbot/strategy/AvgMinMaxStrategy.js";
|
import AvgMinMaxStrategy from "../tradingbot/strategy/AvgMinMaxStrategy.js";
|
||||||
import PossiblePeakStrategy from "../tradingbot/strategy/PossiblePeakStrategy.js";
|
import PossiblePeakStrategy from "../tradingbot/strategy/PossiblePeakStrategy.js";
|
||||||
import Simple24hStrategy from "../tradingbot/strategy/Simple24hStrategy.js";
|
import Simple24hStrategy from "../tradingbot/strategy/Simple24hStrategy.js";
|
||||||
import TestStrategy from "../tradingbot/strategy/TestStrategy.js";
|
import TestStrategy from "../tradingbot/strategy/TestStrategy.js";
|
||||||
import AbstractWorker from "../tradingbot/worker/AbstractWorker.js";
|
import AbstractWorker from "../tradingbot/worker/AbstractWorker.js";
|
||||||
import DefaultWorker from "../tradingbot/worker/DefaultWorker.js";
|
import DefaultWorker from "../tradingbot/worker/DefaultWorker.js";
|
||||||
|
import SingleTransactionTestWorker from "../tradingbot/worker/SingleTransactionTestWorker.js";
|
||||||
import StatisticWorker from "../tradingbot/worker/StatisticWorker.js";
|
import StatisticWorker from "../tradingbot/worker/StatisticWorker.js";
|
||||||
|
|
||||||
export default class Serializables {
|
export default class Serializables {
|
||||||
@ -31,5 +33,7 @@ export default class Serializables {
|
|||||||
['Simple24hStrategy', Simple24hStrategy],
|
['Simple24hStrategy', Simple24hStrategy],
|
||||||
['PossiblePeakStrategy', PossiblePeakStrategy],
|
['PossiblePeakStrategy', PossiblePeakStrategy],
|
||||||
['AvgMinMaxStrategy', AvgMinMaxStrategy],
|
['AvgMinMaxStrategy', AvgMinMaxStrategy],
|
||||||
|
['AvgEmaStrategy', AvgEmaStrategy],
|
||||||
|
['SingleTransactionTestWorker', SingleTransactionTestWorker],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
@ -61,6 +61,14 @@ export default class RestClient {
|
|||||||
res.json(this.#getAllOrderBooks());
|
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) => {
|
this.#service.get("/asset/:symbol/orderbook", (req, res, next) => {
|
||||||
res.json(this.#getAssetOrderBook(req.params.symbol));
|
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));
|
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) => {
|
this.#service.get("/worker/:name/attribute/:attr", (req, res, next) => {
|
||||||
res.json(this.#getWorkerAttribute(req.params.name, req.params.attr));
|
res.json(this.#getWorkerAttribute(req.params.name, req.params.attr));
|
||||||
});
|
});
|
||||||
@ -99,6 +111,26 @@ export default class RestClient {
|
|||||||
return result;
|
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){
|
#getAssetOrderBook(asset){
|
||||||
const orderBook = this.#dataProvider.getOrderBook(asset);
|
const orderBook = this.#dataProvider.getOrderBook(asset);
|
||||||
|
|
||||||
@ -113,6 +145,14 @@ export default class RestClient {
|
|||||||
return transactions.map(t => transactionConverter.toRestObject(t));
|
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(){
|
#getAllOrderBooks(){
|
||||||
const orderBooks = [];
|
const orderBooks = [];
|
||||||
|
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export default class WebViewDataProvider {
|
|||||||
* @param {string} symbol
|
* @param {string} symbol
|
||||||
* @returns {Transaction[]}
|
* @returns {Transaction[]}
|
||||||
*/
|
*/
|
||||||
getActiveTransactions(symbol) {
|
getActiveTransactions(symbol = null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ export default class WebViewDataProvider {
|
|||||||
* @param {string} symbol
|
* @param {string} symbol
|
||||||
* @returns {Transaction[]}
|
* @returns {Transaction[]}
|
||||||
*/
|
*/
|
||||||
getAllTransactions(symbol) {
|
getAllTransactions(symbol = null) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user