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);
+ });
+});