From 159f69c141e7089ae2cccf75ca3ecb224e2c3b54 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 21 Jun 2022 19:51:47 +0200 Subject: [PATCH] Add profitdistribution chart --- .../charts/ProfitDistributionChart.vue | 134 ++++++++++++++++++ src/shared/charts/binCount.ts | 19 +++ src/stores/layout.ts | 7 +- src/views/Dashboard.vue | 21 ++- tests/unit/bincount.spec.ts | 48 +++++++ 5 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 src/components/charts/ProfitDistributionChart.vue create mode 100644 src/shared/charts/binCount.ts create mode 100644 tests/unit/bincount.spec.ts diff --git a/src/components/charts/ProfitDistributionChart.vue b/src/components/charts/ProfitDistributionChart.vue new file mode 100644 index 00000000..290a56a9 --- /dev/null +++ b/src/components/charts/ProfitDistributionChart.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/src/shared/charts/binCount.ts b/src/shared/charts/binCount.ts new file mode 100644 index 00000000..0a10b317 --- /dev/null +++ b/src/shared/charts/binCount.ts @@ -0,0 +1,19 @@ +export function binData(data: number[], bins: number) { + const minimum = Math.min(...data); + const maximum = Math.max(...data); + const binSize = ((maximum - minimum) * 1.01) / bins; + // console.log(`data ranges from ${minimum} to ${maximum}, binsize ${binSize}`); + // Count occurances an array with [bucketStart, count in this bucket] + const baseBins = [...Array(bins).keys()].map((i) => [ + Math.round((minimum + i * binSize) * 1000) / 1000, + 0, + ]); + // console.log(baseBins); + for (let i = 0; i < data.length; i++) { + const index = Math.min(Math.floor((data[i] - minimum) / binSize), bins - 1); + // console.log(data[i], index) + baseBins[index][1]++; + } + + return baseBins; +} diff --git a/src/stores/layout.ts b/src/stores/layout.ts index 872ab798..8eeffe72 100644 --- a/src/stores/layout.ts +++ b/src/stores/layout.ts @@ -15,6 +15,7 @@ export enum DashboardLayout { allOpenTrades = 'g-allOpenTrades', cumChartChart = 'g-cumChartChart', allClosedTrades = 'g-allClosedTrades', + profitDistributionChart = 'g-profitDistributionChart', tradesLogChart = 'g-TradesLogChart', } @@ -42,6 +43,7 @@ const DEFAULT_DASHBOARD_LAYOUT: GridItemData[] = [ { i: DashboardLayout.allOpenTrades, x: 0, y: 6, w: 8, h: 6 }, { i: DashboardLayout.cumChartChart, x: 8, y: 6, w: 4, h: 6 }, { i: DashboardLayout.allClosedTrades, x: 0, y: 12, w: 8, h: 6 }, + { i: DashboardLayout.profitDistributionChart, x: 8, y: 12, w: 4, h: 6 }, { i: DashboardLayout.tradesLogChart, x: 0, y: 18, w: 12, h: 4 }, ]; @@ -50,8 +52,9 @@ const DEFAULT_DASHBOARD_LAYOUT_SM: GridItemData[] = [ { i: DashboardLayout.allOpenTrades, x: 0, y: 6, w: 12, h: 8 }, { i: DashboardLayout.dailyChart, x: 0, y: 14, w: 12, h: 6 }, { i: DashboardLayout.cumChartChart, x: 0, y: 20, w: 12, h: 6 }, - { i: DashboardLayout.tradesLogChart, x: 0, y: 26, w: 12, h: 4 }, - { i: DashboardLayout.allClosedTrades, x: 0, y: 30, w: 12, h: 8 }, + { i: DashboardLayout.profitDistributionChart, x: 0, y: 26, w: 12, h: 6 }, + { i: DashboardLayout.tradesLogChart, x: 0, y: 32, w: 12, h: 4 }, + { i: DashboardLayout.allClosedTrades, x: 0, y: 36, w: 12, h: 8 }, ]; const STORE_LAYOUTS = 'ftLayoutSettings'; diff --git a/src/views/Dashboard.vue b/src/views/Dashboard.vue index 8e193cfc..501c03be 100644 --- a/src/views/Dashboard.vue +++ b/src/views/Dashboard.vue @@ -93,6 +93,20 @@ /> + + + + + { return findGridLayout(gridLayout.value, DashboardLayout.cumChartChart); }); - + const gridLayoutProfitDistribution = computed((): GridItemData => { + return findGridLayout(gridLayout.value, DashboardLayout.profitDistributionChart); + }); const gridLayoutTradesLogChart = computed((): GridItemData => { return findGridLayout(gridLayout.value, DashboardLayout.tradesLogChart); }); @@ -218,6 +236,7 @@ export default defineComponent({ gridLayoutAllOpenTrades, gridLayoutAllClosedTrades, gridLayoutCumChart, + gridLayoutProfitDistribution, gridLayoutTradesLogChart, responsiveGridLayouts, }; diff --git a/tests/unit/bincount.spec.ts b/tests/unit/bincount.spec.ts new file mode 100644 index 00000000..7caf6797 --- /dev/null +++ b/tests/unit/bincount.spec.ts @@ -0,0 +1,48 @@ +import { binData } from '@/shared/charts/binCount'; + +describe.only('binCount.ts', () => { + it('Bins data as expected', () => { + const testData = [1, 1, 2, 3, 5, 6, 8, 10]; + const res = binData(testData, 3); + expect(res.length).toEqual(3); + expect(res).toEqual([ + [1, 4], + [4.03, 2], + [7.06, 2], + ]); + expect(res.map((v) => v[1]).reduce((a, b) => a + b)).toEqual(testData.length); + const res1 = binData(testData, 5); + // expect(res1.length).toEqual(5); + expect(res1).toEqual([ + [1, 3], + [2.818, 1], + [4.636, 2], + [6.454, 1], + [8.272, 1], + ]); + expect(res1.map((v) => v[1]).reduce((a, b) => a + b)).toEqual(testData.length); + }); + + it('Bins data with negatives', () => { + const testData = [1, 1, 2, 3, 5, 6, 8, -1, -3, -5, -4]; + const res = binData(testData, 3); + expect(res.length).toEqual(3); + expect(res.map((v) => v[1]).reduce((a, b) => a + b)).toEqual(testData.length); + expect(res).toEqual([ + [-5, 4], + [-0.623, 4], + [3.753, 3], + ]); + }); + it('Bins data performant', () => { + const randomSize = 20000; + const randomData = Array.from({ length: randomSize }, () => Math.floor(Math.random() * 10)); + const startTime = performance.now(); + const res = binData(randomData, 5); + + const endTime = performance.now(); + expect(endTime - startTime).toBeLessThan(20); + + expect(res.map((v) => v[1]).reduce((a, b) => a + b)).toEqual(randomData.length); + }); +});