diff --git a/src/js/tradingbot/TradingBotRestDataProvider.js b/src/js/tradingbot/TradingBotRestDataProvider.js index 9b7e4d4..7c4f6d6 100644 --- a/src/js/tradingbot/TradingBotRestDataProvider.js +++ b/src/js/tradingbot/TradingBotRestDataProvider.js @@ -1,6 +1,7 @@ import TradingPairs from "../apiwrapper/assets/TraidingPairs.js"; import Transaction from "../apiwrapper/transaction/Transaction.js"; import WebViewDataProvider from "../webview/WebViewDataProvider.js"; +import AssetData from "./data/asset/AssetData.js"; import TradingBot from "./TradingBot.js"; export default class TradingBotRestDataProvider extends WebViewDataProvider { @@ -29,7 +30,15 @@ export default class TradingBotRestDataProvider extends WebViewDataProvider { * @returns {OrderBook} */ getOrderBook(symbol) { - return this.#tradingBot.getAssetDataCollector().getAssetData(symbol).getOrderBook(); + return this.getAssetData(symbol).getOrderBook(); + } + + /** + * @param {*} symbol + * @returns {AssetData} + */ + getAssetData(symbol){ + return this.#tradingBot.getAssetDataCollector().getAssetData(symbol); } /** diff --git a/src/js/tradingbot/data/asset/AggregatedDataPoint.js b/src/js/tradingbot/data/asset/AggregatedDataPoint.js index eb223bf..1e4a08f 100644 --- a/src/js/tradingbot/data/asset/AggregatedDataPoint.js +++ b/src/js/tradingbot/data/asset/AggregatedDataPoint.js @@ -1,4 +1,3 @@ -import TradeEvent from "../../../apiwrapper/event/TradeEvent.js"; import OrderBookEntry from "../../../apiwrapper/orderbook/OrderBookEntry.js"; export default class AggregatedDataPoint { @@ -61,12 +60,12 @@ export default class AggregatedDataPoint { /** * @type {OrderBookEntry} */ - bestAsk = 0; + bestAsk = null; /** * @type {OrderBookEntry} */ - bestBid = 0; + bestBid = null; /** * @type {number} @@ -107,12 +106,14 @@ export default class AggregatedDataPoint { pushBid(bid){ if(!this.hasValues){ this.startPrice = bid.price; + this.minPrice = bid.price; + this.maxPrice = bid.price; } this.hasValues = true; this.minPrice = Math.min(this.minPrice, bid.price); this.maxPrice = Math.max(this.maxPrice = bid.price); this.endPrice = bid.price; - this.#priceValues.push(bid.price); + this.#priceValues.push(parseFloat(bid.price)); if(this.bestBid == null || this.bestBid.price < bid.price){ this.bestBid = bid; } @@ -148,6 +149,9 @@ export default class AggregatedDataPoint { this.#priceValues = []; } + /** + * @returns {AggregatedDataPoint} returns a deep copy of this object + */ clone(){ const dp = new AggregatedDataPoint(this.startTime, this.duration); dp.startPrice = this.startPrice; @@ -163,5 +167,7 @@ export default class AggregatedDataPoint { dp.tradeVolume = this.tradeVolume; dp.bestAsk = this.bestAsk; dp.bestBid = this.bestBid; + + return dp; } } \ No newline at end of file diff --git a/src/js/tradingbot/data/asset/AssetDataAggregator.js b/src/js/tradingbot/data/asset/AssetDataAggregator.js index bbdc2a7..b7b8f30 100644 --- a/src/js/tradingbot/data/asset/AssetDataAggregator.js +++ b/src/js/tradingbot/data/asset/AssetDataAggregator.js @@ -47,7 +47,7 @@ export default class AssetDataAggregator { const tmpSlot = new AggregatedDataPoint(timestamp - AssetDataAggregator.#SLOT_1_MIN, AssetDataAggregator.#SLOT_1_MIN); - for(let i = this.#perSecondDataPoints.length - 60; i >= 0; i--){ + for(let i = this.#perSecondDataPoints.length - 60; i < this.#perSecondDataPoints.length; i++){ const dp = this.#perSecondDataPoints[i]; tmpSlot.pushAsk(dp.bestAsk); tmpSlot.pushBid(dp.bestBid); @@ -71,10 +71,13 @@ export default class AssetDataAggregator { return; } - const tmpSlot = this.#currentDataPoint; + let tmpSlot = this.#currentDataPoint; if(!tmpSlot.hasValues){ tmpSlot = this.#perSecondDataPoints[this.#perSecondDataPoints.length - 1].clone(); tmpSlot.startTime = timestamp - AssetDataAggregator.#SLOT_1_SEC; + tmpSlot.endTime = timestamp; + tmpSlot.tradeCount = 0; + tmpSlot.tradeVolume = 0; } this.#currentDataPoint = new AggregatedDataPoint(timestamp, AssetDataAggregator.#SLOT_1_SEC); tmpSlot.end(timestamp); @@ -102,6 +105,8 @@ export default class AssetDataAggregator { const newPoint = point.clone(); newPoint.startTime = point.startTime + duration * (i + 1); newPoint.endTime = newPoint.startTime + duration; + newPoint.tradeCount = 0; + newPoint.tradeVolume = 0; targetArr.push(newPoint); } } @@ -119,7 +124,7 @@ export default class AssetDataAggregator { * @returns {AggregatedDataPoint[]} */ getData(duration, start = -1){ - const result = []; + let result = []; const durationInMs = UnitHelper.formatedDurationToMs(duration); // if seconds data required @@ -132,7 +137,7 @@ export default class AssetDataAggregator { 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), -1 * pointOffset); + result = this.#perMinuteDataPoints.slice(-1 * (pointOffset + dpCount), pointOffset > 0 ? -1 * pointOffset : this.#perMinuteDataPoints.length); } } diff --git a/src/js/tradingbot/util/UnitHelper.js b/src/js/tradingbot/util/UnitHelper.js index 04f242d..ba05ef5 100644 --- a/src/js/tradingbot/util/UnitHelper.js +++ b/src/js/tradingbot/util/UnitHelper.js @@ -74,7 +74,7 @@ export default class UnitHelper { if (match) { return match[1] * UnitHelper.#CHRONO_UNIT_TO_MS_MAPPING.get(match[2]); } else { - return 0; + return isNaN(duration) ? 0 : parseFloat(duration); } } } \ No newline at end of file diff --git a/src/js/webview/RestClient.js b/src/js/webview/RestClient.js index c84dc18..45935c4 100644 --- a/src/js/webview/RestClient.js +++ b/src/js/webview/RestClient.js @@ -3,6 +3,7 @@ import bodyParser from "body-parser"; import cors from "cors"; import WebViewDataProvider from "./WebViewDataProvider.js"; import ConverterFactory from "./converter/ConverterFactory.js"; +import AggregatedDataPointConverter from "./converter/converters/AggregatedDataPointConverter.js"; export default class RestClient { @@ -63,6 +64,10 @@ export default class RestClient { res.json(this.#getAssetOrderBook(req.params.symbol)); }); + this.#service.get("/asset/:symbol/aggregated", (req, res, next) => { + res.json(this.#getAggregatedAssetData(req.params.symbol, req.query.startTime, req.query.duration)); + }); + this.#service.get("/worker/:name/attribute/:attr", (req, res, next) => { res.json(this.#getWorkerAttribute(req.params.name, req.params.attr)); }); @@ -76,6 +81,19 @@ export default class RestClient { } + #getAggregatedAssetData(asset, starttime, duration){ + const st = starttime != null ? starttime : -1; + const d = duration != null ? duration : 3600000; + + const data = this.#dataProvider.getAssetData(asset).assetDataAggregator.getData(d, st); + const converter = new AggregatedDataPointConverter(); + const result = []; + + data.forEach(dp => result.push(converter.toRestObject(dp))); + + return result; + } + #getAssetOrderBook(asset){ const orderBook = this.#dataProvider.getOrderBook(asset); diff --git a/src/js/webview/WebViewDataProvider.js b/src/js/webview/WebViewDataProvider.js index 6bd0422..e7f39f5 100644 --- a/src/js/webview/WebViewDataProvider.js +++ b/src/js/webview/WebViewDataProvider.js @@ -1,5 +1,6 @@ import OrderBook from "../apiwrapper/orderbook/OrderBook.js"; import Transaction from "../apiwrapper/transaction/Transaction.js"; +import AssetData from "../tradingbot/data/asset/AssetData.js"; import AbstractWorker from "../tradingbot/worker/AbstractWorker.js"; export default class WebViewDataProvider { @@ -54,4 +55,12 @@ export default class WebViewDataProvider { getWorkers(){ return []; } + + /** + * @param {*} symbol + * @returns {AssetData} + */ + getAssetData(symbol){ + return null; + } } \ No newline at end of file diff --git a/src/js/webview/converter/ConverterFactory.js b/src/js/webview/converter/ConverterFactory.js index 6e90e7e..45f8c4c 100644 --- a/src/js/webview/converter/ConverterFactory.js +++ b/src/js/webview/converter/ConverterFactory.js @@ -1,12 +1,15 @@ import OrderBook from "../../apiwrapper/orderbook/OrderBook.js"; import OrderBookEntry from "../../apiwrapper/orderbook/OrderBookEntry.js"; +import AggregatedDataPoint from "../../tradingbot/data/asset/AggregatedDataPoint.js"; import AbstractConverter from "./AbstractConverter.js"; +import AggregatedDataPointConverter from "./converters/AggregatedDataPointConverter.js"; import OrderBookConverter from "./converters/OrderBookConverter.js"; import OrderBookEntryConverter from "./converters/OrderBookEntryConverter.js"; export default class ConverterFactory { static OBJECT_TYPE_ORDER_BOOK = 'order_book'; static OBJECT_TYPE_ORDER_BOOK_ENTRY = 'order_book_entry'; + static OBJECT_TYPE_AGGREGATED_DATA_POINT = 'aggregated_data_point'; /** * @param {any} obj @@ -25,6 +28,8 @@ export default class ConverterFactory { return new OrderBookConverter(); } else if (obj instanceof OrderBookEntry) { return new OrderBookEntryConverter(); + } else if (obj instanceof AggregatedDataPoint){ + return new AggregatedDataPointConverter(); } throw new Error("No converter defined for: " + JSON.stringify(obj)); @@ -40,6 +45,8 @@ export default class ConverterFactory { return new OrderBookConverter(); case this.OBJECT_TYPE_ORDER_BOOK_ENTRY: return new OrderBookEntryConverter(); + case this.OBJECT_TYPE_AGGREGATED_DATA_POINT: + return new AggregatedDataPointConverter(); default: throw new Error("No converter defined for type: " + type); } diff --git a/src/js/webview/converter/converters/AggregatedDataPointConverter.js b/src/js/webview/converter/converters/AggregatedDataPointConverter.js new file mode 100644 index 0000000..b364107 --- /dev/null +++ b/src/js/webview/converter/converters/AggregatedDataPointConverter.js @@ -0,0 +1,62 @@ +import AggregatedDataPoint from "../../../tradingbot/data/asset/AggregatedDataPoint.js"; +import AbstractConverter from "../AbstractConverter.js"; +import ConverterFactory from "../ConverterFactory.js"; +import OrderBookEntryConverter from "./OrderBookEntryConverter.js"; + +export default class AggregatedDataPointConverter extends AbstractConverter { + + constructor(){ + super(); + } + + /** + * @param {AggregatedDataPoint} obj + * @returns {object} + */ + toRestObject(obj) { + const obeConverter = new OrderBookEntryConverter(); + return { + _objectType: ConverterFactory.OBJECT_TYPE_AGGREGATED_DATA_POINT, + startTime: obj.startTime, + endTime: obj.endTime, + duration: obj.duration, + tradeCount: obj.tradeCount, + tradeVolume: obj.tradeVolume, + minPrice: obj.minPrice, + maxPrice: obj.maxPrice, + avgPrice: obj.avgPrice, + startPrice: obj.startPrice, + endPrice: obj.endPrice, + bestAsk: obeConverter.toRestObject(obj.bestAsk), + bestBid: obeConverter.toRestObject(obj.bestBid), + peakStarts: obj.peakStarts, + peakEnds: obj.peakEnds + }; + } + + /** + * @param {object} obj + * @returns {AggregatedDataPoint} + */ + fromRestObject(obj) { + const obeConverter = new OrderBookEntryConverter(); + const dp = new AggregatedDataPoint(obj.startTime, obj.duration); + + dp.startTime = obj.startTime; + dp.endTime = obj.endTime; + dp.duration = obj.duration; + dp.tradeCount = obj.tradeCount; + dp.tradeVolume = obj.tradeVolume; + dp.minPrice = obj.minPrice; + dp.maxPrice = obj.maxPrice; + dp.avgPrice = obj.avgPrice; + dp.startPrice = obj.startPrice; + dp.endPrice = obj.endPrice; + dp.bestAsk = obeConverter.fromRestObject(obj.bestAsk); + dp.bestBid = obeConverter.fromRestObject(obj.bestBid); + dp.peakStarts = obj.peakStarts; + dp.peakEnds = obj.peakEnds; + + return dp; + } +} \ No newline at end of file