Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d2b5926bb | ||
|
|
cc3bdccd9a |
+81
-29
@@ -363,6 +363,58 @@ paths:
|
|||||||
text/plain:
|
text/plain:
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
/market/{marketTypeId}/scan:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- market
|
||||||
|
summary: "Scan a single market type, returning its volume-weighted price quartiles"
|
||||||
|
operationId: scanMarketType
|
||||||
|
parameters:
|
||||||
|
- name: marketTypeId
|
||||||
|
in: path
|
||||||
|
description: The market type id to scan
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
- name: days
|
||||||
|
in: query
|
||||||
|
description: Number of most recent days of history to analyse
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
default: 365
|
||||||
|
minimum: 1
|
||||||
|
- name: brokerFee
|
||||||
|
in: query
|
||||||
|
description: "Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both\
|
||||||
|
\ buy and sell orders"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
default: 0.015
|
||||||
|
maximum: 1
|
||||||
|
minimum: 0
|
||||||
|
- name: salesTax
|
||||||
|
in: query
|
||||||
|
description: "Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell\
|
||||||
|
\ orders"
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
default: 0.036
|
||||||
|
maximum: 1
|
||||||
|
minimum: 0
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: The scan result for the requested market type
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/MarketScanResponse"
|
||||||
|
"400":
|
||||||
|
description: The days parameter is not greater than 0
|
||||||
/market/{marketTypeId}/history:
|
/market/{marketTypeId}/history:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@@ -802,35 +854,6 @@ components:
|
|||||||
required:
|
required:
|
||||||
- memberLedgerIds
|
- memberLedgerIds
|
||||||
- name
|
- name
|
||||||
MarketHistoryResponse:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
marketTypeId:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
date:
|
|
||||||
type: string
|
|
||||||
format: date
|
|
||||||
average:
|
|
||||||
type: number
|
|
||||||
highest:
|
|
||||||
type: number
|
|
||||||
lowest:
|
|
||||||
type: number
|
|
||||||
orderCount:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
volume:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
required:
|
|
||||||
- average
|
|
||||||
- date
|
|
||||||
- highest
|
|
||||||
- lowest
|
|
||||||
- marketTypeId
|
|
||||||
- orderCount
|
|
||||||
- volume
|
|
||||||
MarketScanResponse:
|
MarketScanResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -864,6 +887,35 @@ components:
|
|||||||
- score
|
- score
|
||||||
- sell
|
- sell
|
||||||
- totalVolume
|
- totalVolume
|
||||||
|
MarketHistoryResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
marketTypeId:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
date:
|
||||||
|
type: string
|
||||||
|
format: date
|
||||||
|
average:
|
||||||
|
type: number
|
||||||
|
highest:
|
||||||
|
type: number
|
||||||
|
lowest:
|
||||||
|
type: number
|
||||||
|
orderCount:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
volume:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
required:
|
||||||
|
- average
|
||||||
|
- date
|
||||||
|
- highest
|
||||||
|
- lowest
|
||||||
|
- marketTypeId
|
||||||
|
- orderCount
|
||||||
|
- volume
|
||||||
MarketPriceResponse:
|
MarketPriceResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@@ -1392,6 +1392,55 @@ export const MarketApiAxiosParamCreator = function (configuration?: Configuratio
|
|||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Scan a single market type, returning its volume-weighted price quartiles
|
||||||
|
* @param {number} marketTypeId The market type id to scan
|
||||||
|
* @param {number} [days] Number of most recent days of history to analyse
|
||||||
|
* @param {number} [brokerFee] Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both buy and sell orders
|
||||||
|
* @param {number} [salesTax] Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell orders
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
scanMarketType: async (marketTypeId: number, days?: number, brokerFee?: number, salesTax?: number, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'marketTypeId' is not null or undefined
|
||||||
|
assertParamExists('scanMarketType', 'marketTypeId', marketTypeId)
|
||||||
|
const localVarPath = `/market/{marketTypeId}/scan`
|
||||||
|
.replace('{marketTypeId}', encodeURIComponent(String(marketTypeId)));
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
if (days !== undefined) {
|
||||||
|
localVarQueryParameter['days'] = days;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (brokerFee !== undefined) {
|
||||||
|
localVarQueryParameter['brokerFee'] = brokerFee;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (salesTax !== undefined) {
|
||||||
|
localVarQueryParameter['salesTax'] = salesTax;
|
||||||
|
}
|
||||||
|
|
||||||
|
localVarHeaderParameter['Accept'] = '*/*';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
url: toPathString(localVarUrlObj),
|
url: toPathString(localVarUrlObj),
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
@@ -1448,6 +1497,22 @@ export const MarketApiFp = function(configuration?: Configuration) {
|
|||||||
const localVarOperationServerBasePath = operationServerMap['MarketApi.scanMarket']?.[localVarOperationServerIndex]?.url;
|
const localVarOperationServerBasePath = operationServerMap['MarketApi.scanMarket']?.[localVarOperationServerIndex]?.url;
|
||||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Scan a single market type, returning its volume-weighted price quartiles
|
||||||
|
* @param {number} marketTypeId The market type id to scan
|
||||||
|
* @param {number} [days] Number of most recent days of history to analyse
|
||||||
|
* @param {number} [brokerFee] Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both buy and sell orders
|
||||||
|
* @param {number} [salesTax] Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell orders
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async scanMarketType(marketTypeId: number, days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<MarketScanResponse>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.scanMarketType(marketTypeId, days, brokerFee, salesTax, options);
|
||||||
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
|
const localVarOperationServerBasePath = operationServerMap['MarketApi.scanMarketType']?.[localVarOperationServerIndex]?.url;
|
||||||
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1490,6 +1555,19 @@ export const MarketApiFactory = function (configuration?: Configuration, basePat
|
|||||||
scanMarket(days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig): AxiosPromise<Array<MarketScanResponse>> {
|
scanMarket(days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig): AxiosPromise<Array<MarketScanResponse>> {
|
||||||
return localVarFp.scanMarket(days, brokerFee, salesTax, options).then((request) => request(axios, basePath));
|
return localVarFp.scanMarket(days, brokerFee, salesTax, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Scan a single market type, returning its volume-weighted price quartiles
|
||||||
|
* @param {number} marketTypeId The market type id to scan
|
||||||
|
* @param {number} [days] Number of most recent days of history to analyse
|
||||||
|
* @param {number} [brokerFee] Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both buy and sell orders
|
||||||
|
* @param {number} [salesTax] Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell orders
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
scanMarketType(marketTypeId: number, days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig): AxiosPromise<MarketScanResponse> {
|
||||||
|
return localVarFp.scanMarketType(marketTypeId, days, brokerFee, salesTax, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1532,6 +1610,20 @@ export class MarketApi extends BaseAPI {
|
|||||||
public scanMarket(days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig) {
|
public scanMarket(days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig) {
|
||||||
return MarketApiFp(this.configuration).scanMarket(days, brokerFee, salesTax, options).then((request) => request(this.axios, this.basePath));
|
return MarketApiFp(this.configuration).scanMarket(days, brokerFee, salesTax, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @summary Scan a single market type, returning its volume-weighted price quartiles
|
||||||
|
* @param {number} marketTypeId The market type id to scan
|
||||||
|
* @param {number} [days] Number of most recent days of history to analyse
|
||||||
|
* @param {number} [brokerFee] Broker fee as a fraction (e.g. 0.015 for 1.5%), paid on both buy and sell orders
|
||||||
|
* @param {number} [salesTax] Sales tax as a fraction (e.g. 0.036 for 3.6%), paid on sell orders
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
public scanMarketType(marketTypeId: number, days?: number, brokerFee?: number, salesTax?: number, options?: RawAxiosRequestConfig) {
|
||||||
|
return MarketApiFp(this.configuration).scanMarketType(marketTypeId, days, brokerFee, salesTax, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-28
@@ -1,4 +1,4 @@
|
|||||||
import { getHistory, getHistoryQuartils, HistoryQuartils, MarketType, MarketTypePrice } from "@/market";
|
import { MarketType } from "@/market";
|
||||||
import { MarketScanResponse } from "@/generated/mammon";
|
import { MarketScanResponse } from "@/generated/mammon";
|
||||||
|
|
||||||
export type ScanResult = {
|
export type ScanResult = {
|
||||||
@@ -13,14 +13,6 @@ export type ScanResult = {
|
|||||||
score: number;
|
score: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirrors mammon's MarketScoreCalculator so the client-side path matches the backend scan.
|
|
||||||
export const calculateScore = (quartils: HistoryQuartils, profit: number, orderCount: number, days: number): number => {
|
|
||||||
if (profit <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return Math.sqrt((Math.pow(quartils.totalVolume, 1.1) * Math.pow(quartils.q1, 1.2) * Math.pow(profit, 0.5) * Math.pow(Math.max(1, orderCount), -0.7)) / days);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const toScanResult = (res: MarketScanResponse, type: MarketType): ScanResult => ({
|
export const toScanResult = (res: MarketScanResponse, type: MarketType): ScanResult => ({
|
||||||
type,
|
type,
|
||||||
buy: res.buy,
|
buy: res.buy,
|
||||||
@@ -32,22 +24,3 @@ export const toScanResult = (res: MarketScanResponse, type: MarketType): ScanRes
|
|||||||
profit: res.profit,
|
profit: res.profit,
|
||||||
score: res.score,
|
score: res.score,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Client-side scan result for a single type (used where the scan endpoint can't be queried per-type).
|
|
||||||
export const buildScanResult = async (price: MarketTypePrice, days: number, calculateProfit: (buy: number, sell: number) => number): Promise<ScanResult> => {
|
|
||||||
const history = await getHistory(price.type.id);
|
|
||||||
const quartils = getHistoryQuartils(history, days);
|
|
||||||
const profit = quartils.q1 === 0 || quartils.q3 === 0 ? 0 : calculateProfit(quartils.q1, quartils.q3);
|
|
||||||
|
|
||||||
return {
|
|
||||||
type: price.type,
|
|
||||||
buy: price.buy,
|
|
||||||
sell: price.sell,
|
|
||||||
q1: quartils.q1,
|
|
||||||
median: quartils.median,
|
|
||||||
q3: quartils.q3,
|
|
||||||
totalVolume: quartils.totalVolume,
|
|
||||||
profit,
|
|
||||||
score: calculateScore(quartils, profit, price.orderCount, days),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
import {ClipboardButton} from '@/components';
|
import {ClipboardButton} from '@/components';
|
||||||
import {getMarketType, MarketType, MarketTypeInput, useApraisalStore, useMarketTaxStore} from "@/market";
|
import {getMarketType, MarketType, MarketTypeInput, useApraisalStore, useMarketTaxStore} from "@/market";
|
||||||
import {AcquisitionResultTable, BuyModal, useAcquiredTypesStore} from '@/market/acquisition';
|
import {AcquisitionResultTable, BuyModal, useAcquiredTypesStore} from '@/market/acquisition';
|
||||||
import {buildScanResult, ScanResultTable} from '@/market/scan';
|
import {ScanResultTable, toScanResult} from '@/market/scan';
|
||||||
|
import {marketApi} from "@/mammon";
|
||||||
import {ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
import {ShoppingCartIcon} from '@heroicons/vue/24/outline';
|
||||||
import log from "loglevel";
|
import log from "loglevel";
|
||||||
import {computed, ref, watch} from "vue";
|
import {computed, ref, watch} from "vue";
|
||||||
@@ -21,7 +22,20 @@ const apraisalStore = useApraisalStore();
|
|||||||
const marketTaxStore = useMarketTaxStore();
|
const marketTaxStore = useMarketTaxStore();
|
||||||
const days = useStorage('market-scan-days', 365);
|
const days = useStorage('market-scan-days', 365);
|
||||||
const price = computedAsync(() => item.value ? apraisalStore.getPrice(item.value) : undefined);
|
const price = computedAsync(() => item.value ? apraisalStore.getPrice(item.value) : undefined);
|
||||||
const result = computedAsync(async () => price.value ? await buildScanResult(price.value, days.value, marketTaxStore.calculateProfit) : undefined);
|
const result = computedAsync(async () => {
|
||||||
|
if (!item.value) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await marketApi.scanMarketType(
|
||||||
|
item.value.id,
|
||||||
|
days.value,
|
||||||
|
marketTaxStore.brokerFee / 100,
|
||||||
|
marketTaxStore.scc / 100
|
||||||
|
);
|
||||||
|
|
||||||
|
return toScanResult(data, item.value);
|
||||||
|
});
|
||||||
const acquiredTypesStore = useAcquiredTypesStore();
|
const acquiredTypesStore = useAcquiredTypesStore();
|
||||||
|
|
||||||
const acquisitions = computed(() => {
|
const acquisitions = computed(() => {
|
||||||
|
|||||||
Reference in New Issue
Block a user