From e16abd94c0e82c09cca36c0cca59615ef62ef710 Mon Sep 17 00:00:00 2001
From: Ken Kunz
Date: Thu, 12 Sep 2024 16:24:11 -0500
Subject: [PATCH 1/8] refactor TradeDirection (enum -> simple union)
---
src/lib/trade-executor/state/trade-info.ts | 26 +++++++++++--------
.../trade-executor/state/trading-pair-info.ts | 12 ---------
.../PositionTable.svelte | 2 +-
3 files changed, 16 insertions(+), 24 deletions(-)
diff --git a/src/lib/trade-executor/state/trade-info.ts b/src/lib/trade-executor/state/trade-info.ts
index 403e5033f..2834ecb50 100644
--- a/src/lib/trade-executor/state/trade-info.ts
+++ b/src/lib/trade-executor/state/trade-info.ts
@@ -8,10 +8,7 @@
import type { TradeExecution } from './trade';
import type { USDollarAmount } from './utility-types';
-export enum TradeDirection {
- Enter,
- Exit
-}
+export type TradeDirection = 'enter' | 'exit';
/**
* Prototype object that can be applied to a TradeExecution object to enrich
@@ -49,8 +46,15 @@ const tradeInfoPrototype = {
},
// Determine trade direction (enter|exit) based on planned_quantity
- get direction() {
- return this.planned_quantity > 0 ? TradeDirection.Enter : TradeDirection.Exit;
+ get direction(): TradeDirection {
+ return this.planned_quantity > 0 ? 'enter' : 'exit';
+ },
+
+ get directionLabel() {
+ if (this.isCreditTrade) {
+ return this.direction === 'enter' ? 'Supply' : 'Withdraw';
+ }
+ return this.direction === 'enter' ? 'Buy' : 'Sell';
},
get pricingPair() {
@@ -58,7 +62,7 @@ const tradeInfoPrototype = {
},
get actionLabel() {
- return this.pair.getActionLabel(this.direction);
+ return `${this.directionLabel} ${this.pair.actionSymbol}`;
},
get isCreditTrade() {
@@ -78,10 +82,10 @@ const tradeInfoPrototype = {
},
get positionImpact() {
- // NOTE: order of flags is significant since trades may have multiple flags
- for (const flag of ['open', 'close', 'increase', 'reduce'] as const) {
- if (this.flags?.includes(flag)) return flag;
- }
+ // order of flags is significant since trades may have multiple flags
+ const impacts = ['open', 'close', 'increase', 'reduce'] as const;
+ // return first matching impact flag (if any)
+ return impacts.find((flag) => this.flags?.includes(flag));
}
} satisfies ThisType>;
diff --git a/src/lib/trade-executor/state/trading-pair-info.ts b/src/lib/trade-executor/state/trading-pair-info.ts
index fee64580b..e1e6c3545 100644
--- a/src/lib/trade-executor/state/trading-pair-info.ts
+++ b/src/lib/trade-executor/state/trading-pair-info.ts
@@ -6,7 +6,6 @@
*
*/
import type { TradingPairIdentifier } from './identifier';
-import { TradeDirection } from './trade-info';
const kindShortLabels = {
spot_market_hold: 'spot',
@@ -57,17 +56,6 @@ const tradingPairInfoPrototype = {
get kindShortLabel() {
return kindShortLabels[this.kind];
- },
-
- getDirectionLabel(direction: TradeDirection) {
- if (direction === TradeDirection.Enter) {
- return this.isCreditSupply ? 'Supply' : 'Buy';
- }
- return this.isCreditSupply ? 'Withdraw' : 'Sell';
- },
-
- getActionLabel(direction: TradeDirection) {
- return `${this.getDirectionLabel(direction)} ${this.actionSymbol}`;
}
} satisfies ThisType>;
diff --git a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/PositionTable.svelte b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/PositionTable.svelte
index 1d3e47fd1..b53065115 100644
--- a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/PositionTable.svelte
+++ b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/PositionTable.svelte
@@ -72,7 +72,7 @@
table.column({
header: 'Frozen on',
id: 'frozen_on',
- accessor: (position) => position.lastTrade?.direction
+ accessor: (position) => position.lastTrade?.directionLabel
}),
table.column({
header: 'Value',
From 2e22663438290047cc8c47a42adcb575773ff082 Mon Sep 17 00:00:00 2001
From: Ken Kunz
Date: Thu, 12 Sep 2024 16:26:35 -0500
Subject: [PATCH 2/8] fix broken position remarks tooltip links
---
.../[status=positionStatus]-positions/FlagCell.svelte | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/FlagCell.svelte b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/FlagCell.svelte
index 932555268..19e7977a5 100644
--- a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/FlagCell.svelte
+++ b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/FlagCell.svelte
@@ -18,7 +18,7 @@
Position can still have a profitable close if a trailing or dynamic stop loss was used.
See more
@@ -47,7 +47,7 @@
See more
{/if}
From 1f8f2f877371d7e9f827b51110f6898db65aa9cc Mon Sep 17 00:00:00 2001
From: Ken Kunz
Date: Thu, 12 Sep 2024 17:10:29 -0500
Subject: [PATCH 3/8] improve failed trade/position indicators & alerts
---
src/lib/explorer/TradingDescription.svelte | 24 ++++----
.../[position=integer]/+page.svelte | 56 ++++++++++---------
.../[position=integer]/TradeTable.svelte | 2 +-
.../trade-[trade=integer]/+page.svelte | 2 +-
4 files changed, 46 insertions(+), 38 deletions(-)
diff --git a/src/lib/explorer/TradingDescription.svelte b/src/lib/explorer/TradingDescription.svelte
index 6643878ff..e8aa84307 100644
--- a/src/lib/explorer/TradingDescription.svelte
+++ b/src/lib/explorer/TradingDescription.svelte
@@ -20,24 +20,26 @@ Used in DataTable context (vs. standard svelte component context).
import { DataBadge } from '$lib/components';
export let label: string;
- export let modifier: string | undefined;
+ export let modifier = '';
export let isTest = false;
+ export let failed = false;
{label}
- {#if modifier}
-
- {modifier}
-
- {/if}
- {#if isTest}
-
+
+ {modifier}
+
+
+ {#if isTest}
Test
-
- {/if}
+ {/if}
+ {#if failed}
+ Failed
+ {/if}
+
diff --git a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/[position=integer]/TradeTable.svelte b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/[position=integer]/TradeTable.svelte
index 361ce6649..eb12460bd 100644
--- a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/[position=integer]/TradeTable.svelte
+++ b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/[position=integer]/TradeTable.svelte
@@ -28,7 +28,7 @@
table.column({
id: 'description',
header: 'Trade',
- accessor: (t) => ({ label: t.actionLabel, modifier: t.positionImpact, isTest: t.isTest }),
+ accessor: (t) => ({ label: t.actionLabel, modifier: t.positionImpact, isTest: t.isTest, failed: t.failed }),
cell: ({ value }) => createRender(TradingDescription, value)
}),
table.column({
diff --git a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/[position=integer]/trade-[trade=integer]/+page.svelte b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/[position=integer]/trade-[trade=integer]/+page.svelte
index 7c65e1fed..f3619ff07 100644
--- a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/[position=integer]/trade-[trade=integer]/+page.svelte
+++ b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/[position=integer]/trade-[trade=integer]/+page.svelte
@@ -52,7 +52,7 @@
{#if trade.failed}
-
+
- Failure reason: {trade.failedTx?.revert_reason ?? 'unknown'}
{#if trade.failedTx?.tx_hash}
From d663006d0e97a63b19e54262c82bdac82bee9a56 Mon Sep 17 00:00:00 2001
From: Ken Kunz
Date: Fri, 13 Sep 2024 10:53:59 -0500
Subject: [PATCH 4/8] add "profitability data inconsistent" remark
- displayed on strategy positions table for rows with inconsistent data
- only displayed for users with admin role
---
src/lib/trade-executor/state/position-info.ts | 38 +++++++++++++++++++
.../+page.svelte | 3 +-
.../FlagCell.svelte | 32 ++++++++++++++++
.../PositionTable.svelte | 3 +-
4 files changed, 74 insertions(+), 2 deletions(-)
diff --git a/src/lib/trade-executor/state/position-info.ts b/src/lib/trade-executor/state/position-info.ts
index 16f3d7919..667b8604e 100644
--- a/src/lib/trade-executor/state/position-info.ts
+++ b/src/lib/trade-executor/state/position-info.ts
@@ -10,6 +10,7 @@ import type { State } from './state';
import type { PositionStatistics } from './statistics';
import type { TimeBucket } from '$lib/chart';
import { type PositionStatus, type TradingPosition, tradingPositionTooltips } from './position';
+import type { TradeDirection } from './trade-info';
/**
* English tooltips for the datapoints
@@ -215,6 +216,43 @@ const tradingPositionInfoPrototype = {
return this.latestStats?.profitability;
},
+ valueForTradeDirection(direction: TradeDirection) {
+ return this.trades.reduce((acc, t) => {
+ if (t.direction === direction) acc += t.executedValue;
+ return acc;
+ }, 0);
+ },
+
+ get totalEnteredValue() {
+ return this.valueForTradeDirection('enter');
+ },
+
+ get totalExitedValue() {
+ return this.valueForTradeDirection('exit');
+ },
+
+ get profitabilityFromTradeTotals() {
+ if (!this.closed) return;
+
+ const totalEntered = this.totalEnteredValue;
+ const totalExited = this.totalExitedValue;
+ const profitability = (totalExited - totalEntered) / totalEntered;
+ return Number.isFinite(profitability) ? profitability : 0;
+ },
+
+ get hasInconsistentProfitability() {
+ const fromStats = this.profitability;
+ const fromTradeTotals = this.profitabilityFromTradeTotals;
+
+ if (!this.closed || fromStats === undefined || fromTradeTotals === undefined) {
+ return false;
+ }
+
+ const difference = fromTradeTotals - fromStats;
+ const percentChange = difference / fromStats;
+ return Math.abs(percentChange) > 0.01 && Math.abs(difference) > 0.001;
+ },
+
get candleTimeBucket(): TimeBucket {
return this.durationSeconds > 7 * 24 * 3600 ? '1d' : '1h';
},
diff --git a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/+page.svelte b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/+page.svelte
index 5ced65b79..9687c8661 100644
--- a/src/routes/strategies/[strategy]/[status=positionStatus]-positions/+page.svelte
+++ b/src/routes/strategies/[strategy]/[status=positionStatus]-positions/+page.svelte
@@ -7,7 +7,7 @@
import { capitalize } from '$lib/helpers/formatters';
export let data;
- $: ({ positions, status, strategy } = data);
+ $: ({ admin, positions, status, strategy } = data);
$: q = $page.url.searchParams;
$: options = {
@@ -39,6 +39,7 @@
{/if}
import type { TradingPositionInfo } from 'trade-executor/state/position-info';
import type { TradeInfo } from 'trade-executor/state/trade-info';
+ import { Alert } from '$lib/components';
import PositionFlag from './PositionFlag.svelte';
+ import { formatNumber, formatPercent } from '$lib/helpers/formatters';
+ export let admin = false;
export let position: TradingPositionInfo;
export let baseUrl: string;
@@ -51,6 +54,35 @@
{/if}
+
+ {#if admin && position.hasInconsistentProfitability}
+
+
+ This info is only displayed to admin users.
+
+
+ This position has inconsistent profitability data. The value from position stats does not match the result of
+ comparing all entry and exit trades.
+
+
+ - Profitability value from position stats: {formatPercent(position.profitability, 2)}
+ -
+ Profitability value from entry/exit trades:
+ {formatPercent(position.profitabilityFromTradeTotals, 2)}
+
+ - Sum of entry trades: ${formatNumber(position.totalEnteredValue)}
+ - Sum of exit trades: ${formatNumber(position.totalExitedValue)}
+ -
+ (${formatNumber(position.totalExitedValue)} - ${formatNumber(position.totalEnteredValue)}) / ${formatNumber(
+ position.totalEnteredValue
+ )} =
+ {formatPercent(position.profitabilityFromTradeTotals, 2)}
+
+
+
+
+
+ {/if}