rework market trends

This commit is contained in:
Sirttas
2026-06-22 12:30:28 +02:00
parent 35b2c9539a
commit 925d9eef73
9 changed files with 373 additions and 125 deletions
@@ -1,12 +1,9 @@
<script setup lang="ts">
import {LoadingSpinner, Tooltip} from '@/components';
import {formatIsk} from '@/formaters';
import {getHistory, getHistoryQuartils} from '@/market';
import {ArrowTrendingDownIcon, ArrowTrendingUpIcon} from '@heroicons/vue/24/outline';
import {getQuartiles, type HistoryQuartiles, MarketTrendIcon} from '@/market';
import {computedAsync} from '@vueuse/core';
import {ref, watchEffect} from 'vue';
const trendingScale = 3;
import {computed, ref} from 'vue';
interface Props {
id: number;
@@ -17,42 +14,36 @@ interface Props {
const props = defineProps<Props>();
const open = ref(false);
const loading = ref(false);
const q1 = ref(0);
const median = ref(0);
const q3 = ref(0);
const lineColor = ref('');
const history = computedAsync(() => getHistory(props.id), []);
const quartiles = computedAsync<HistoryQuartiles | null>(
() => open.value && props.id ? getQuartiles(props.id) : null,
null,
loading,
);
watchEffect(async () => {
if (!open.value || !props.id) {
return;
const lineColor = computed(() => {
if (!quartiles.value) {
return '';
}
const quartils = getHistoryQuartils(history.value);
q1.value = quartils.q1;
median.value = quartils.median;
q3.value = quartils.q3;
if (props.buy >= quartils.q3) {
lineColor.value = 'line-blue';
} else if (props.sell >= quartils.q3) {
lineColor.value = 'line-green';
} else {
lineColor.value = '';
if (props.buy >= quartiles.value.q3) {
return 'line-blue';
}
})
if (props.sell >= quartiles.value.q3) {
return 'line-green';
}
return '';
});
</script>
<template>
<Tooltip v-model:open="open" class="tooltip">
<template #header>
<LoadingSpinner v-if="history.length < trendingScale" />
<ArrowTrendingUpIcon v-else-if="history[0].average > history[trendingScale - 1].average" />
<ArrowTrendingDownIcon v-else />
<LoadingSpinner v-if="loading || !quartiles" />
<MarketTrendIcon v-else :trend="quartiles.trend" />
</template>
<template #default>
<div class="bg-slate-500 -left-1/2 relative tooltip-content" v-if="history.length > 0">
<div class="bg-slate-500 -left-1/2 relative tooltip-content" v-if="quartiles">
<table>
<thead>
<tr>
@@ -63,9 +54,9 @@ watchEffect(async () => {
</thead>
<tbody>
<tr :class="lineColor">
<td class="text-right text-nowrap">{{ formatIsk(q1) }}</td>
<td class="text-right text-nowrap">{{ formatIsk(median) }}</td>
<td class="text-right text-nowrap">{{ formatIsk(q3) }}</td>
<td class="text-right text-nowrap">{{ formatIsk(quartiles.q1) }}</td>
<td class="text-right text-nowrap">{{ formatIsk(quartiles.median) }}</td>
<td class="text-right text-nowrap">{{ formatIsk(quartiles.q3) }}</td>
</tr>
</tbody>
</table>
@@ -5,7 +5,7 @@ import {MinusIcon, PlusIcon} from '@heroicons/vue/24/outline';
import {useStorage} from '@vueuse/core';
import {computed, ref} from 'vue';
import {AcquiredType} from './AcquiredType';
import AcquisitionQuantilsTooltip from './AcquisitionQuantilsTooltip.vue';
import AcquisitionQuartilesTooltip from './AcquisitionQuartilesTooltip.vue';
import {formatEveDate, formatIsk, percentFormater} from "@/formaters.ts";
type Result = {
@@ -189,7 +189,7 @@ const total = computed(() => {
<td v-if="showColumn('name')">
<div class="flex">
<MarketTypeLabel :id="r.data.type.id" :name="r.data.name" />
<AcquisitionQuantilsTooltip :id="r.data.type.id" :buy="r.data.buy" :sell="r.data.sell" />
<AcquisitionQuartilesTooltip :id="r.data.type.id" :buy="r.data.buy" :sell="r.data.sell" />
</div>
</td>
<td v-if="showColumn('buy')" class="text-right">{{ formatIsk(r.data.buy) }}</td>
-59
View File
@@ -1,59 +0,0 @@
import { MarketHistory } from "@/market";
export type HistoryQuartils = {
totalVolume: number,
q1: number,
median: number,
q3: number,
}
export const getHistoryQuartils = (history: MarketHistory[], days?: number): HistoryQuartils => {
const now = Date.now();
const volumes = history
.flatMap(h => {
const volume = h.volume;
if (volume === 0 || (days && new Date(h.date).getTime() < now - days * 24 * 60 * 60 * 1000)) {
return [];
}
const e = estimateVolume(h);
return [[h.highest, e], [h.lowest, volume - e]];
})
.filter(h => h[1] > 0)
.sort((a, b) => a[0] - b[0]);
const totalVolume = volumes.reduce((acc, [_, v]) => acc + v, 0);
const quartilVolume = totalVolume / 4;
const quartils: [number, number, number] = [0, 0, 0];
let currentVolume = 0;
let quartil = 0;
for (const [price, volume] of volumes) {
currentVolume += volume;
if (currentVolume >= quartilVolume * (quartil + 1)) {
quartils[quartil] = price;
if (quartil === 2) {
break;
}
quartil++;
}
}
return {
totalVolume,
q1: quartils[0],
median: quartils[1],
q3: quartils[2],
};
}
const estimateVolume = (history: MarketHistory): number => {
if (history.volume === 0) {
return 0;
}
return Math.max(1, Math.round(history.volume * ((history.average - history.lowest) / (history.highest - history.lowest))));
}
-7
View File
@@ -1,7 +0,0 @@
import { MarketHistoryResponse } from "@/generated/mammon";
import { marketApi } from "@/mammon";
export type MarketHistory = MarketHistoryResponse;
export const getHistory = async (typeId: number): Promise<MarketHistory[]> =>
(await marketApi.findHistory(typeId)).data;
+16
View File
@@ -0,0 +1,16 @@
<script setup lang="ts">
import {type MarketTrend, MarketTrends} from '@/market';
import {ArrowLongRightIcon, ArrowTrendingDownIcon, ArrowTrendingUpIcon} from '@heroicons/vue/24/outline';
interface Props {
trend: MarketTrend;
}
defineProps<Props>();
</script>
<template>
<ArrowTrendingUpIcon v-if="trend === MarketTrends.Up" />
<ArrowTrendingDownIcon v-else-if="trend === MarketTrends.Down" />
<ArrowLongRightIcon v-else />
</template>
+13
View File
@@ -0,0 +1,13 @@
import {
HistoryQuartilesResponse,
HistoryQuartilesResponseTrendEnum,
MarketScanResponseTrendEnum
} from "@/generated/mammon";
import {marketApi} from "@/mammon";
export type HistoryQuartiles = HistoryQuartilesResponse;
export type MarketTrend = MarketScanResponseTrendEnum | HistoryQuartilesResponseTrendEnum;
export const MarketTrends = MarketScanResponseTrendEnum as const;
export const getQuartiles = async (typeId: number, days?: number): Promise<HistoryQuartiles> => (await marketApi.findQuartiles(typeId, days)).data;
+3 -2
View File
@@ -1,2 +1,3 @@
export * from './MarketHistory';
export * from './HistoryQuartils';
export * from './history.ts';
export { default as MarketTrendIcon } from './MarketTrendIcon.vue';