@signals-protocol/v1-sdk 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/share/avatar.d.ts +9 -0
- package/dist/share/avatar.js +167 -0
- package/dist/share/index.d.ts +2 -0
- package/dist/share/index.js +4 -1
- package/dist/share/profile-image.js +491 -148
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -10,4 +10,4 @@ export * as MathUtils from "./utils/math";
|
|
|
10
10
|
export { toWAD, toMicroUSDC } from "./clmsr-sdk";
|
|
11
11
|
export { createCLMSRSDK, createSignalsSDK } from "./clmsr-sdk";
|
|
12
12
|
export * from "./share";
|
|
13
|
-
export declare const VERSION = "1.
|
|
13
|
+
export declare const VERSION = "1.5.0";
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export type AvatarTheme = "light" | "dark";
|
|
2
|
+
export interface AvatarData {
|
|
3
|
+
pattern: boolean[][];
|
|
4
|
+
primary: string;
|
|
5
|
+
secondary: string;
|
|
6
|
+
background: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const AVATAR_COLOR_PALETTE: readonly ["#1444c2", "#2d88ff", "#fdd979", "#8bcaff", "#00BF40", "#F7931A", "#FF4343", "#6366f1", "#8b5cf6", "#ec4899", "#f59e0b", "#10b981", "#fb7185", "#fc97f3", "#5eead4", "#c4b5fd"];
|
|
9
|
+
export declare function getAvatarData(address: string, theme: AvatarTheme): AvatarData;
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AVATAR_COLOR_PALETTE = void 0;
|
|
4
|
+
exports.getAvatarData = getAvatarData;
|
|
5
|
+
exports.AVATAR_COLOR_PALETTE = [
|
|
6
|
+
"#1444c2",
|
|
7
|
+
"#2d88ff",
|
|
8
|
+
"#fdd979",
|
|
9
|
+
"#8bcaff",
|
|
10
|
+
"#00BF40",
|
|
11
|
+
"#F7931A",
|
|
12
|
+
"#FF4343",
|
|
13
|
+
"#6366f1",
|
|
14
|
+
"#8b5cf6",
|
|
15
|
+
"#ec4899",
|
|
16
|
+
"#f59e0b",
|
|
17
|
+
"#10b981",
|
|
18
|
+
"#fb7185",
|
|
19
|
+
"#fc97f3",
|
|
20
|
+
"#5eead4",
|
|
21
|
+
"#c4b5fd",
|
|
22
|
+
];
|
|
23
|
+
const AVATAR_BACKGROUNDS = {
|
|
24
|
+
light: "#f8fafc",
|
|
25
|
+
dark: "#1e1e22",
|
|
26
|
+
};
|
|
27
|
+
function simpleHash(str) {
|
|
28
|
+
let hash = 5381;
|
|
29
|
+
for (let i = 0; i < str.length; i += 1) {
|
|
30
|
+
const char = str.charCodeAt(i);
|
|
31
|
+
hash = (hash << 5) + hash + char;
|
|
32
|
+
}
|
|
33
|
+
return Math.abs(hash);
|
|
34
|
+
}
|
|
35
|
+
function getSecondaryHash(address) {
|
|
36
|
+
let hash = 0;
|
|
37
|
+
const suffix = address.slice(-8);
|
|
38
|
+
for (let i = 0; i < suffix.length; i += 1) {
|
|
39
|
+
hash = ((hash << 3) + hash + suffix.charCodeAt(i)) & 0xffffffff;
|
|
40
|
+
}
|
|
41
|
+
return Math.abs(hash);
|
|
42
|
+
}
|
|
43
|
+
function generateSeedValues(address) {
|
|
44
|
+
const hash1 = simpleHash(address);
|
|
45
|
+
const hash2 = getSecondaryHash(address);
|
|
46
|
+
const values = [];
|
|
47
|
+
let seed = hash1;
|
|
48
|
+
for (let i = 0; i < 10; i += 1) {
|
|
49
|
+
seed = (seed * 1103015245 + 12345 + hash2) & 0x7fffffff;
|
|
50
|
+
values.push(seed);
|
|
51
|
+
}
|
|
52
|
+
return values;
|
|
53
|
+
}
|
|
54
|
+
function generatePixelPattern(address) {
|
|
55
|
+
const seeds = generateSeedValues(address);
|
|
56
|
+
const pattern = Array(5)
|
|
57
|
+
.fill(null)
|
|
58
|
+
.map(() => Array(5).fill(false));
|
|
59
|
+
for (let y = 0; y < 5; y += 1) {
|
|
60
|
+
for (let x = 0; x < 2; x += 1) {
|
|
61
|
+
const index = y * 2 + x;
|
|
62
|
+
const seedIndex = index % seeds.length;
|
|
63
|
+
const shouldFill = seeds[seedIndex] % 100 > 40;
|
|
64
|
+
pattern[y][x] = shouldFill;
|
|
65
|
+
pattern[y][4 - x] = shouldFill;
|
|
66
|
+
}
|
|
67
|
+
const centerIndex = y + 10;
|
|
68
|
+
const centerSeedIndex = centerIndex % seeds.length;
|
|
69
|
+
pattern[y][2] = seeds[centerSeedIndex] % 100 > 40;
|
|
70
|
+
}
|
|
71
|
+
const countFilled = () => pattern.flat().filter(Boolean).length;
|
|
72
|
+
const ensureMinPixels = (minPixels) => {
|
|
73
|
+
let filledCount = countFilled();
|
|
74
|
+
if (filledCount >= minPixels)
|
|
75
|
+
return;
|
|
76
|
+
const candidates = [];
|
|
77
|
+
for (let y = 0; y < 5; y += 1) {
|
|
78
|
+
for (let x = 0; x < 3; x += 1) {
|
|
79
|
+
if (!pattern[y][x]) {
|
|
80
|
+
candidates.push([y, x]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
let i = 0;
|
|
85
|
+
while (filledCount < minPixels && candidates.length > 0) {
|
|
86
|
+
const idx = (seeds[i % seeds.length] + i) % candidates.length;
|
|
87
|
+
const [y, x] = candidates.splice(idx, 1)[0];
|
|
88
|
+
if (!pattern[y][x]) {
|
|
89
|
+
pattern[y][x] = true;
|
|
90
|
+
if (x !== 2) {
|
|
91
|
+
if (!pattern[y][4 - x]) {
|
|
92
|
+
pattern[y][4 - x] = true;
|
|
93
|
+
filledCount += 2;
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
filledCount += 1;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
filledCount += 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
i += 1;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
ensureMinPixels(12);
|
|
107
|
+
const parityCounts = { even: 0, odd: 0 };
|
|
108
|
+
for (let y = 0; y < 5; y += 1) {
|
|
109
|
+
for (let x = 0; x < 5; x += 1) {
|
|
110
|
+
if (pattern[y][x]) {
|
|
111
|
+
if ((y + x) % 2 === 0) {
|
|
112
|
+
parityCounts.even += 1;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
parityCounts.odd += 1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (parityCounts.even === 0 || parityCounts.odd === 0) {
|
|
121
|
+
const missingParity = parityCounts.even === 0 ? 0 : 1;
|
|
122
|
+
const candidates = [];
|
|
123
|
+
for (let y = 0; y < 5; y += 1) {
|
|
124
|
+
for (let x = 0; x < 3; x += 1) {
|
|
125
|
+
if (!pattern[y][x] && (y + x) % 2 === missingParity) {
|
|
126
|
+
candidates.push([y, x]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (candidates.length > 0) {
|
|
131
|
+
const idx = (seeds[(seeds.length - 1 + missingParity) % seeds.length] +
|
|
132
|
+
parityCounts.even +
|
|
133
|
+
parityCounts.odd) %
|
|
134
|
+
candidates.length;
|
|
135
|
+
const [y, x] = candidates[idx];
|
|
136
|
+
pattern[y][x] = true;
|
|
137
|
+
if (x !== 2) {
|
|
138
|
+
pattern[y][4 - x] = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return pattern;
|
|
143
|
+
}
|
|
144
|
+
function generateColors(address) {
|
|
145
|
+
const seeds = generateSeedValues(address);
|
|
146
|
+
const hash1 = simpleHash(address);
|
|
147
|
+
const hash2 = getSecondaryHash(address);
|
|
148
|
+
const primaryColorIndex = Math.abs(hash1 ^ seeds[3]) % exports.AVATAR_COLOR_PALETTE.length;
|
|
149
|
+
let secondaryColorIndex = Math.abs(hash2 ^ seeds[7]) % exports.AVATAR_COLOR_PALETTE.length;
|
|
150
|
+
if (secondaryColorIndex === primaryColorIndex) {
|
|
151
|
+
secondaryColorIndex =
|
|
152
|
+
(secondaryColorIndex + 1) % exports.AVATAR_COLOR_PALETTE.length;
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
primary: exports.AVATAR_COLOR_PALETTE[primaryColorIndex],
|
|
156
|
+
secondary: exports.AVATAR_COLOR_PALETTE[secondaryColorIndex],
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function getAvatarData(address, theme) {
|
|
160
|
+
const colors = generateColors(address);
|
|
161
|
+
return {
|
|
162
|
+
pattern: generatePixelPattern(address),
|
|
163
|
+
primary: colors.primary,
|
|
164
|
+
secondary: colors.secondary,
|
|
165
|
+
background: AVATAR_BACKGROUNDS[theme],
|
|
166
|
+
};
|
|
167
|
+
}
|
package/dist/share/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ export { buildPositionVNode } from "./position-image";
|
|
|
3
3
|
export { buildProfileVNode } from "./profile-image";
|
|
4
4
|
export { buildDistributionVNode } from "./distribution-image";
|
|
5
5
|
export { buildBrandVNode } from "./brand-image";
|
|
6
|
+
export { AVATAR_COLOR_PALETTE, getAvatarData } from "./avatar";
|
|
7
|
+
export type { AvatarData, AvatarTheme } from "./avatar";
|
|
6
8
|
export { h } from "./h";
|
|
7
9
|
export { SIGNALS_LOGO_URI, buildLogoStrokeSVG } from "./assets";
|
|
8
10
|
export { formatUSDC, formatPrice, formatPriceRange, percForm, formatUsdPnl, getPnlColor, } from "./format";
|
package/dist/share/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.formatDistributionPriceRange = exports.getPeakRange = exports.formatXTick = exports.generateXAxisTicks = exports.getSqrtHeight = exports.getBarCategory = exports.getPnlColor = exports.formatUsdPnl = exports.percForm = exports.formatPriceRange = exports.formatPrice = exports.formatUSDC = exports.buildLogoStrokeSVG = exports.SIGNALS_LOGO_URI = exports.h = exports.buildBrandVNode = exports.buildDistributionVNode = exports.buildProfileVNode = exports.buildPositionVNode = void 0;
|
|
3
|
+
exports.formatDistributionPriceRange = exports.getPeakRange = exports.formatXTick = exports.generateXAxisTicks = exports.getSqrtHeight = exports.getBarCategory = exports.getPnlColor = exports.formatUsdPnl = exports.percForm = exports.formatPriceRange = exports.formatPrice = exports.formatUSDC = exports.buildLogoStrokeSVG = exports.SIGNALS_LOGO_URI = exports.h = exports.getAvatarData = exports.AVATAR_COLOR_PALETTE = exports.buildBrandVNode = exports.buildDistributionVNode = exports.buildProfileVNode = exports.buildPositionVNode = void 0;
|
|
4
4
|
// VNode builders
|
|
5
5
|
var position_image_1 = require("./position-image");
|
|
6
6
|
Object.defineProperty(exports, "buildPositionVNode", { enumerable: true, get: function () { return position_image_1.buildPositionVNode; } });
|
|
@@ -10,6 +10,9 @@ var distribution_image_1 = require("./distribution-image");
|
|
|
10
10
|
Object.defineProperty(exports, "buildDistributionVNode", { enumerable: true, get: function () { return distribution_image_1.buildDistributionVNode; } });
|
|
11
11
|
var brand_image_1 = require("./brand-image");
|
|
12
12
|
Object.defineProperty(exports, "buildBrandVNode", { enumerable: true, get: function () { return brand_image_1.buildBrandVNode; } });
|
|
13
|
+
var avatar_1 = require("./avatar");
|
|
14
|
+
Object.defineProperty(exports, "AVATAR_COLOR_PALETTE", { enumerable: true, get: function () { return avatar_1.AVATAR_COLOR_PALETTE; } });
|
|
15
|
+
Object.defineProperty(exports, "getAvatarData", { enumerable: true, get: function () { return avatar_1.getAvatarData; } });
|
|
13
16
|
// Helpers (used by v1-server for its own distribution rendering)
|
|
14
17
|
var h_1 = require("./h");
|
|
15
18
|
Object.defineProperty(exports, "h", { enumerable: true, get: function () { return h_1.h; } });
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.buildProfileVNode = buildProfileVNode;
|
|
4
4
|
const h_1 = require("./h");
|
|
5
5
|
const assets_1 = require("./assets");
|
|
6
|
+
const avatar_1 = require("./avatar");
|
|
6
7
|
const CARD_WIDTH = 1200;
|
|
7
8
|
const CARD_HEIGHT = 630;
|
|
8
9
|
const OUTER_PADDING = 24;
|
|
@@ -15,6 +16,9 @@ const PLOT_RIGHT = 18;
|
|
|
15
16
|
const PLOT_TOP = 14;
|
|
16
17
|
const PLOT_BOTTOM = 24;
|
|
17
18
|
const BAND_WIDTH = 18;
|
|
19
|
+
const DAY_MS = 86400000;
|
|
20
|
+
const AVATAR_SIZE = 96;
|
|
21
|
+
const MAX_RENDERED_POSITIONS = 1000;
|
|
18
22
|
const OG_THEMES = {
|
|
19
23
|
light: {
|
|
20
24
|
pageBg: "#eef7ff", // bg-surface-soft
|
|
@@ -41,13 +45,83 @@ const OG_THEMES = {
|
|
|
41
45
|
neutralBg: "#27272A", // surface-muted
|
|
42
46
|
},
|
|
43
47
|
};
|
|
44
|
-
const
|
|
45
|
-
BRONZE:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
const TIER_CHIP_THEMES = {
|
|
49
|
+
BRONZE: {
|
|
50
|
+
light: {
|
|
51
|
+
from: "#FFE6CC",
|
|
52
|
+
to: "#FFC999",
|
|
53
|
+
border: "#C97E3D",
|
|
54
|
+
icon: "#8B4513",
|
|
55
|
+
},
|
|
56
|
+
dark: {
|
|
57
|
+
from: "#3D2B1A",
|
|
58
|
+
to: "#5C3A1E",
|
|
59
|
+
border: "#8B5E2B",
|
|
60
|
+
icon: "#F5E5D4",
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
SILVER: {
|
|
64
|
+
light: {
|
|
65
|
+
from: "#F2F5F8",
|
|
66
|
+
to: "#E2E7EE",
|
|
67
|
+
border: "#BFC6CE",
|
|
68
|
+
icon: "#6B7280",
|
|
69
|
+
},
|
|
70
|
+
dark: {
|
|
71
|
+
from: "#2A2D31",
|
|
72
|
+
to: "#363A40",
|
|
73
|
+
border: "#6B7280",
|
|
74
|
+
icon: "#F3F4F6",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
GOLD: {
|
|
78
|
+
light: {
|
|
79
|
+
from: "#FFF0B8",
|
|
80
|
+
to: "#FFD875",
|
|
81
|
+
border: "#D9A441",
|
|
82
|
+
icon: "#DA9100",
|
|
83
|
+
},
|
|
84
|
+
dark: {
|
|
85
|
+
from: "#3D3520",
|
|
86
|
+
to: "#4A3D1A",
|
|
87
|
+
border: "#B8892E",
|
|
88
|
+
icon: "#FFE9B3",
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
PLATINUM: {
|
|
92
|
+
light: {
|
|
93
|
+
from: "#E4FFF7",
|
|
94
|
+
to: "#CDEFE5",
|
|
95
|
+
border: "#8FD7C6",
|
|
96
|
+
icon: "#059669",
|
|
97
|
+
},
|
|
98
|
+
dark: {
|
|
99
|
+
from: "#1A3D33",
|
|
100
|
+
to: "#1F3B31",
|
|
101
|
+
border: "#4DAE96",
|
|
102
|
+
icon: "#BAEBD9",
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
DIAMOND: {
|
|
106
|
+
light: {
|
|
107
|
+
from: "#E2F4FF",
|
|
108
|
+
to: "#C9E9FF",
|
|
109
|
+
border: "#89D0FF",
|
|
110
|
+
icon: "#0284C7",
|
|
111
|
+
},
|
|
112
|
+
dark: {
|
|
113
|
+
from: "#1A2F3D",
|
|
114
|
+
to: "#1E3545",
|
|
115
|
+
border: "#5AAFE0",
|
|
116
|
+
icon: "#5EC0F2",
|
|
117
|
+
},
|
|
118
|
+
},
|
|
50
119
|
};
|
|
120
|
+
const PRICE_TICK_STEPS = [
|
|
121
|
+
10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000,
|
|
122
|
+
250000, 500000, 1000000,
|
|
123
|
+
];
|
|
124
|
+
const MAX_MATERIALIZED_PRICE_TICKS = 20;
|
|
51
125
|
function shortenAddress(address) {
|
|
52
126
|
return `${address.slice(0, 6)}…${address.slice(-4)}`;
|
|
53
127
|
}
|
|
@@ -99,6 +173,17 @@ function normalizePositions(positions) {
|
|
|
99
173
|
});
|
|
100
174
|
return normalized;
|
|
101
175
|
}
|
|
176
|
+
function capRenderablePositions(positions) {
|
|
177
|
+
if (positions.length <= MAX_RENDERED_POSITIONS) {
|
|
178
|
+
return positions;
|
|
179
|
+
}
|
|
180
|
+
return positions
|
|
181
|
+
.map((position, index) => ({ position, index }))
|
|
182
|
+
.sort((a, b) => b.position.marketEndTimestampMs - a.position.marketEndTimestampMs ||
|
|
183
|
+
a.index - b.index)
|
|
184
|
+
.slice(0, MAX_RENDERED_POSITIONS)
|
|
185
|
+
.map(({ position }) => position);
|
|
186
|
+
}
|
|
102
187
|
function buildMainText(input) {
|
|
103
188
|
const handle = resolveDisplayName(input.realHandle ?? null);
|
|
104
189
|
if (handle)
|
|
@@ -107,60 +192,111 @@ function buildMainText(input) {
|
|
|
107
192
|
return "A Signals trader";
|
|
108
193
|
return resolveDisplayName(input.displayName) ?? shortenAddress(input.address);
|
|
109
194
|
}
|
|
195
|
+
function formatCallCopy(totalCalls) {
|
|
196
|
+
return `${totalCalls} BTC range ${totalCalls === 1 ? "call" : "calls"}`;
|
|
197
|
+
}
|
|
110
198
|
function buildSubline(input) {
|
|
111
|
-
const callCopy =
|
|
199
|
+
const callCopy = formatCallCopy(input.totalCalls);
|
|
112
200
|
if (input.hideWallet)
|
|
113
|
-
return callCopy
|
|
114
|
-
const parts = [];
|
|
201
|
+
return `${callCopy} on Signals`;
|
|
115
202
|
if (resolveDisplayName(input.realHandle ?? null) !== null) {
|
|
116
|
-
|
|
203
|
+
return `${shortenAddress(input.address)} with ${callCopy}`;
|
|
117
204
|
}
|
|
118
205
|
if (input.twitter) {
|
|
119
|
-
|
|
206
|
+
return `@${input.twitter} with ${callCopy}`;
|
|
207
|
+
}
|
|
208
|
+
return `${callCopy} on Signals`;
|
|
209
|
+
}
|
|
210
|
+
function buildPriceTickCandidate(min, max, step) {
|
|
211
|
+
const alignedMin = Math.floor(min / step) * step;
|
|
212
|
+
const alignedMax = Math.ceil(max / step) * step;
|
|
213
|
+
const count = Math.floor((alignedMax - alignedMin) / step) + 1;
|
|
214
|
+
const ticks = [];
|
|
215
|
+
if (count > MAX_MATERIALIZED_PRICE_TICKS) {
|
|
216
|
+
return {
|
|
217
|
+
min: alignedMin,
|
|
218
|
+
max: alignedMax,
|
|
219
|
+
step,
|
|
220
|
+
count,
|
|
221
|
+
ticks,
|
|
222
|
+
};
|
|
120
223
|
}
|
|
121
|
-
|
|
122
|
-
|
|
224
|
+
for (let tick = alignedMin; tick <= alignedMax + step / 2; tick += step) {
|
|
225
|
+
ticks.push(tick);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
min: alignedMin,
|
|
229
|
+
max: alignedMax,
|
|
230
|
+
step,
|
|
231
|
+
count,
|
|
232
|
+
ticks,
|
|
233
|
+
};
|
|
123
234
|
}
|
|
124
235
|
function getPriceTickStep(span) {
|
|
125
236
|
const target = span / 4;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
1000,
|
|
129
|
-
2500,
|
|
130
|
-
5000,
|
|
131
|
-
10000,
|
|
132
|
-
25000,
|
|
133
|
-
50000,
|
|
134
|
-
100000,
|
|
135
|
-
250000,
|
|
136
|
-
500000,
|
|
137
|
-
1000000,
|
|
138
|
-
];
|
|
139
|
-
return candidates.find((step) => step >= target) ?? 1000000;
|
|
237
|
+
return (PRICE_TICK_STEPS.find((step) => step >= target) ??
|
|
238
|
+
PRICE_TICK_STEPS[PRICE_TICK_STEPS.length - 1]);
|
|
140
239
|
}
|
|
141
240
|
function getExpandedPriceBounds(min, max) {
|
|
142
241
|
const span = max - min;
|
|
143
|
-
const
|
|
242
|
+
const desiredStep = getPriceTickStep(span);
|
|
243
|
+
const candidates = PRICE_TICK_STEPS.map((step) => buildPriceTickCandidate(min, max, step));
|
|
244
|
+
const validCandidates = candidates.filter((candidate) => candidate.ticks.length > 0 &&
|
|
245
|
+
candidate.count >= 4 &&
|
|
246
|
+
candidate.count <= 5);
|
|
247
|
+
const fallbackCandidates = candidates.filter((candidate) => candidate.ticks.length > 0);
|
|
248
|
+
const [selected] = (validCandidates.length > 0
|
|
249
|
+
? validCandidates
|
|
250
|
+
: fallbackCandidates.length > 0
|
|
251
|
+
? fallbackCandidates
|
|
252
|
+
: candidates)
|
|
253
|
+
.slice()
|
|
254
|
+
.sort((a, b) => {
|
|
255
|
+
const aCountPenalty = a.count >= 4 && a.count <= 5
|
|
256
|
+
? 0
|
|
257
|
+
: Math.abs(a.count - 5) * 1000000;
|
|
258
|
+
const bCountPenalty = b.count >= 4 && b.count <= 5
|
|
259
|
+
? 0
|
|
260
|
+
: Math.abs(b.count - 5) * 1000000;
|
|
261
|
+
return (aCountPenalty +
|
|
262
|
+
Math.abs(a.step - desiredStep) -
|
|
263
|
+
(bCountPenalty + Math.abs(b.step - desiredStep)));
|
|
264
|
+
});
|
|
144
265
|
return {
|
|
145
|
-
min:
|
|
146
|
-
max:
|
|
147
|
-
step,
|
|
266
|
+
min: selected.min,
|
|
267
|
+
max: selected.max,
|
|
268
|
+
step: selected.step,
|
|
148
269
|
};
|
|
149
270
|
}
|
|
150
271
|
function getPriceTicks(min, max, step) {
|
|
272
|
+
const count = Math.floor((max - min) / step) + 1;
|
|
151
273
|
const ticks = [];
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
274
|
+
if (count <= MAX_MATERIALIZED_PRICE_TICKS) {
|
|
275
|
+
for (let tick = min; tick <= max + step / 2; tick += step) {
|
|
276
|
+
ticks.push(tick);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (count >= 4 && count <= 5 && ticks.length > 0) {
|
|
280
|
+
return { ticks, step };
|
|
156
281
|
}
|
|
157
|
-
|
|
282
|
+
const fallbackCount = count < 4 ? 4 : 5;
|
|
283
|
+
const fallbackStep = (max - min) / (fallbackCount - 1);
|
|
284
|
+
return {
|
|
285
|
+
ticks: Array.from({ length: fallbackCount }, (_, index) => Math.round(min + fallbackStep * index)),
|
|
286
|
+
step: fallbackStep,
|
|
287
|
+
};
|
|
158
288
|
}
|
|
159
|
-
function formatAxisLabel(value) {
|
|
289
|
+
function formatAxisLabel(value, step) {
|
|
290
|
+
if (step < 100) {
|
|
291
|
+
return `$${Math.round(value).toLocaleString("en-US")}`;
|
|
292
|
+
}
|
|
160
293
|
if (Math.abs(value) >= 1000) {
|
|
294
|
+
if (step >= 100 && step < 1000) {
|
|
295
|
+
return `$${(value / 1000).toFixed(1)}k`;
|
|
296
|
+
}
|
|
161
297
|
return `$${Math.round(value / 1000)}k`;
|
|
162
298
|
}
|
|
163
|
-
return `$${Math.round(value)}`;
|
|
299
|
+
return `$${Math.round(value).toLocaleString("en-US")}`;
|
|
164
300
|
}
|
|
165
301
|
function computePriceBounds(btcHistory, positions) {
|
|
166
302
|
let rawMin = Infinity;
|
|
@@ -232,9 +368,239 @@ function buildHistoryPath(btcHistory, scaleX, scaleY) {
|
|
|
232
368
|
})
|
|
233
369
|
.join(" ");
|
|
234
370
|
}
|
|
371
|
+
function clamp(value, min, max) {
|
|
372
|
+
return Math.min(Math.max(value, min), max);
|
|
373
|
+
}
|
|
374
|
+
function formatTierName(tier) {
|
|
375
|
+
return `${tier[0]}${tier.slice(1).toLowerCase()}`;
|
|
376
|
+
}
|
|
377
|
+
function buildTierChip(tier, themeName, theme) {
|
|
378
|
+
const tierTheme = TIER_CHIP_THEMES[tier][themeName];
|
|
379
|
+
return (0, h_1.h)("div", {
|
|
380
|
+
style: {
|
|
381
|
+
display: "flex",
|
|
382
|
+
alignItems: "center",
|
|
383
|
+
gap: 8,
|
|
384
|
+
padding: "8px 14px",
|
|
385
|
+
borderRadius: 999,
|
|
386
|
+
border: `1px solid ${tierTheme.border}`,
|
|
387
|
+
background: `linear-gradient(135deg, ${tierTheme.from}, ${tierTheme.to})`,
|
|
388
|
+
color: theme.ink,
|
|
389
|
+
fontSize: 18,
|
|
390
|
+
fontWeight: 700,
|
|
391
|
+
lineHeight: 1,
|
|
392
|
+
flexShrink: 0,
|
|
393
|
+
},
|
|
394
|
+
}, (0, h_1.h)("svg", {
|
|
395
|
+
width: 18,
|
|
396
|
+
height: 18,
|
|
397
|
+
viewBox: "0 0 16 16",
|
|
398
|
+
style: { display: "flex", flexShrink: 0 },
|
|
399
|
+
}, (0, h_1.h)("polygon", {
|
|
400
|
+
points: "8 1 15 8 8 15 1 8",
|
|
401
|
+
fill: tierTheme.icon,
|
|
402
|
+
})), (0, h_1.h)("div", { style: { display: "flex" } }, formatTierName(tier)));
|
|
403
|
+
}
|
|
404
|
+
function buildAddressAvatar(address, themeName, theme) {
|
|
405
|
+
const avatar = (0, avatar_1.getAvatarData)(address, themeName);
|
|
406
|
+
return (0, h_1.h)("div", {
|
|
407
|
+
style: {
|
|
408
|
+
width: AVATAR_SIZE,
|
|
409
|
+
height: AVATAR_SIZE,
|
|
410
|
+
display: "flex",
|
|
411
|
+
flexDirection: "column",
|
|
412
|
+
overflow: "hidden",
|
|
413
|
+
borderRadius: 22,
|
|
414
|
+
border: `1px solid ${theme.border}`,
|
|
415
|
+
background: avatar.background,
|
|
416
|
+
flexShrink: 0,
|
|
417
|
+
},
|
|
418
|
+
}, ...avatar.pattern.map((row, y) => (0, h_1.h)("div", {
|
|
419
|
+
style: {
|
|
420
|
+
display: "flex",
|
|
421
|
+
width: "100%",
|
|
422
|
+
height: "20%",
|
|
423
|
+
},
|
|
424
|
+
}, ...row.map((isFilled, x) => (0, h_1.h)("div", {
|
|
425
|
+
style: {
|
|
426
|
+
width: "20%",
|
|
427
|
+
height: "100%",
|
|
428
|
+
backgroundColor: isFilled
|
|
429
|
+
? (y + x) % 2 === 0
|
|
430
|
+
? avatar.primary
|
|
431
|
+
: avatar.secondary
|
|
432
|
+
: "transparent",
|
|
433
|
+
},
|
|
434
|
+
})))));
|
|
435
|
+
}
|
|
436
|
+
function buildGenericAvatar(theme) {
|
|
437
|
+
return (0, h_1.h)("div", {
|
|
438
|
+
style: {
|
|
439
|
+
width: AVATAR_SIZE,
|
|
440
|
+
height: AVATAR_SIZE,
|
|
441
|
+
display: "flex",
|
|
442
|
+
alignItems: "center",
|
|
443
|
+
justifyContent: "center",
|
|
444
|
+
overflow: "hidden",
|
|
445
|
+
borderRadius: 22,
|
|
446
|
+
border: `1px solid ${theme.border}`,
|
|
447
|
+
background: theme.primary,
|
|
448
|
+
flexShrink: 0,
|
|
449
|
+
},
|
|
450
|
+
}, (0, h_1.h)("div", {
|
|
451
|
+
style: {
|
|
452
|
+
width: 58,
|
|
453
|
+
height: 58,
|
|
454
|
+
display: "flex",
|
|
455
|
+
alignItems: "center",
|
|
456
|
+
justifyContent: "center",
|
|
457
|
+
borderRadius: 999,
|
|
458
|
+
background: theme.card,
|
|
459
|
+
},
|
|
460
|
+
}, (0, h_1.h)("img", {
|
|
461
|
+
src: assets_1.SIGNALS_LOGO_URI,
|
|
462
|
+
width: 34,
|
|
463
|
+
height: 32,
|
|
464
|
+
style: { display: "flex" },
|
|
465
|
+
})));
|
|
466
|
+
}
|
|
467
|
+
function buildAvatar(input, theme) {
|
|
468
|
+
if (input.hideWallet) {
|
|
469
|
+
return buildGenericAvatar(theme);
|
|
470
|
+
}
|
|
471
|
+
return buildAddressAvatar(input.address, input.theme, theme);
|
|
472
|
+
}
|
|
473
|
+
function buildBrandWordmark(theme) {
|
|
474
|
+
return (0, h_1.h)("div", {
|
|
475
|
+
style: {
|
|
476
|
+
display: "flex",
|
|
477
|
+
alignItems: "center",
|
|
478
|
+
gap: 10,
|
|
479
|
+
flexShrink: 0,
|
|
480
|
+
},
|
|
481
|
+
}, (0, h_1.h)("img", {
|
|
482
|
+
src: assets_1.SIGNALS_LOGO_URI,
|
|
483
|
+
width: 34,
|
|
484
|
+
height: 32,
|
|
485
|
+
style: { display: "flex" },
|
|
486
|
+
}), (0, h_1.h)("div", {
|
|
487
|
+
style: {
|
|
488
|
+
display: "flex",
|
|
489
|
+
fontSize: 28,
|
|
490
|
+
fontWeight: 700,
|
|
491
|
+
color: theme.primary,
|
|
492
|
+
},
|
|
493
|
+
}, "Signals"));
|
|
494
|
+
}
|
|
495
|
+
function buildHeader(input, theme, mainText, subline) {
|
|
496
|
+
return (0, h_1.h)("div", {
|
|
497
|
+
style: {
|
|
498
|
+
display: "flex",
|
|
499
|
+
alignItems: "center",
|
|
500
|
+
justifyContent: "space-between",
|
|
501
|
+
gap: 24,
|
|
502
|
+
minHeight: AVATAR_SIZE,
|
|
503
|
+
},
|
|
504
|
+
}, (0, h_1.h)("div", {
|
|
505
|
+
style: {
|
|
506
|
+
flex: 1,
|
|
507
|
+
minWidth: 0,
|
|
508
|
+
display: "flex",
|
|
509
|
+
alignItems: "center",
|
|
510
|
+
gap: 18,
|
|
511
|
+
},
|
|
512
|
+
}, buildAvatar(input, theme), (0, h_1.h)("div", {
|
|
513
|
+
style: {
|
|
514
|
+
flex: 1,
|
|
515
|
+
minWidth: 0,
|
|
516
|
+
display: "flex",
|
|
517
|
+
flexDirection: "column",
|
|
518
|
+
},
|
|
519
|
+
}, (0, h_1.h)("div", {
|
|
520
|
+
style: {
|
|
521
|
+
display: "flex",
|
|
522
|
+
alignItems: "center",
|
|
523
|
+
gap: 14,
|
|
524
|
+
minWidth: 0,
|
|
525
|
+
},
|
|
526
|
+
}, (0, h_1.h)("div", {
|
|
527
|
+
style: {
|
|
528
|
+
display: "flex",
|
|
529
|
+
fontSize: 52,
|
|
530
|
+
lineHeight: 1.05,
|
|
531
|
+
fontWeight: 700,
|
|
532
|
+
color: theme.ink,
|
|
533
|
+
maxWidth: 620,
|
|
534
|
+
overflow: "hidden",
|
|
535
|
+
textOverflow: "ellipsis",
|
|
536
|
+
whiteSpace: "nowrap",
|
|
537
|
+
},
|
|
538
|
+
}, mainText), buildTierChip(input.tier, input.theme, theme)), (0, h_1.h)("div", {
|
|
539
|
+
style: {
|
|
540
|
+
display: "flex",
|
|
541
|
+
fontSize: 20,
|
|
542
|
+
fontWeight: 500,
|
|
543
|
+
color: theme.inkMuted,
|
|
544
|
+
marginTop: 8,
|
|
545
|
+
overflow: "hidden",
|
|
546
|
+
textOverflow: "ellipsis",
|
|
547
|
+
whiteSpace: "nowrap",
|
|
548
|
+
},
|
|
549
|
+
}, subline))), buildBrandWordmark(theme));
|
|
550
|
+
}
|
|
551
|
+
function buildStatItem(label, value, valueColor, theme) {
|
|
552
|
+
return (0, h_1.h)("div", {
|
|
553
|
+
style: {
|
|
554
|
+
flex: 1,
|
|
555
|
+
minWidth: 0,
|
|
556
|
+
display: "flex",
|
|
557
|
+
flexDirection: "column",
|
|
558
|
+
},
|
|
559
|
+
}, (0, h_1.h)("div", {
|
|
560
|
+
style: {
|
|
561
|
+
display: "flex",
|
|
562
|
+
fontSize: 14,
|
|
563
|
+
fontWeight: 500,
|
|
564
|
+
color: theme.inkMuted,
|
|
565
|
+
marginBottom: 8,
|
|
566
|
+
},
|
|
567
|
+
}, label), (0, h_1.h)("div", {
|
|
568
|
+
style: {
|
|
569
|
+
display: "flex",
|
|
570
|
+
fontSize: 30,
|
|
571
|
+
lineHeight: 1.1,
|
|
572
|
+
fontWeight: 700,
|
|
573
|
+
color: valueColor,
|
|
574
|
+
},
|
|
575
|
+
}, value));
|
|
576
|
+
}
|
|
577
|
+
function buildStatSeparator(theme) {
|
|
578
|
+
return (0, h_1.h)("div", {
|
|
579
|
+
style: {
|
|
580
|
+
width: 1,
|
|
581
|
+
height: 28,
|
|
582
|
+
display: "flex",
|
|
583
|
+
background: theme.border,
|
|
584
|
+
margin: "0 18px",
|
|
585
|
+
flexShrink: 0,
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
function buildStatRow(input, theme, pnlValue, roiValue, bestMultValue) {
|
|
590
|
+
return (0, h_1.h)("div", {
|
|
591
|
+
style: {
|
|
592
|
+
display: "flex",
|
|
593
|
+
alignItems: "center",
|
|
594
|
+
marginTop: 20,
|
|
595
|
+
padding: "0 4px",
|
|
596
|
+
},
|
|
597
|
+
}, buildStatItem("P&L", pnlValue, input.hideAmounts
|
|
598
|
+
? theme.inkMuted
|
|
599
|
+
: getSignedValueColor(input.totalPnl, theme), theme), buildStatSeparator(theme), buildStatItem("ROI", roiValue, getSignedValueColor(input.roi, theme), theme), buildStatSeparator(theme), buildStatItem("Best call", bestMultValue, theme.primary, theme));
|
|
600
|
+
}
|
|
235
601
|
function buildChartArea(theme, btcHistory, positions) {
|
|
236
602
|
const normalizedPositions = normalizePositions(positions);
|
|
237
|
-
const renderablePositions = normalizedPositions
|
|
603
|
+
const renderablePositions = capRenderablePositions(normalizedPositions);
|
|
238
604
|
const sortedHistory = [...btcHistory].sort((a, b) => a.t - b.t);
|
|
239
605
|
if (btcHistory.length === 0 && renderablePositions.length === 0) {
|
|
240
606
|
return (0, h_1.h)("div", {
|
|
@@ -284,12 +650,40 @@ function buildChartArea(theme, btcHistory, positions) {
|
|
|
284
650
|
return PLOT_TOP + plotHeight / 2;
|
|
285
651
|
}
|
|
286
652
|
return (PLOT_TOP +
|
|
287
|
-
(1 -
|
|
288
|
-
(value - priceBounds.min) / (priceBounds.max - priceBounds.min)) *
|
|
653
|
+
(1 - (value - priceBounds.min) / (priceBounds.max - priceBounds.min)) *
|
|
289
654
|
plotHeight);
|
|
290
655
|
};
|
|
291
656
|
const priceTicks = getPriceTicks(priceBounds.min, priceBounds.max, priceBounds.step);
|
|
292
657
|
const hasOpenPositions = renderablePositions.some((position) => position.status === "OPEN");
|
|
658
|
+
const latestSettlement = renderablePositions
|
|
659
|
+
.filter((position) => (position.status === "WIN" || position.status === "LOSS") &&
|
|
660
|
+
position.settlementPrice != null)
|
|
661
|
+
.sort((a, b) => b.marketEndTimestampMs - a.marketEndTimestampMs)[0];
|
|
662
|
+
const groupSizes = new Map();
|
|
663
|
+
const groupOrdinals = new Map();
|
|
664
|
+
renderablePositions.forEach((position) => {
|
|
665
|
+
const timestamp = position.marketEndTimestampMs;
|
|
666
|
+
const ordinal = groupSizes.get(timestamp) ?? 0;
|
|
667
|
+
groupOrdinals.set(position, ordinal);
|
|
668
|
+
groupSizes.set(timestamp, ordinal + 1);
|
|
669
|
+
});
|
|
670
|
+
const dayWidth = timeMin === timeMax ? 0 : (DAY_MS / (timeMax - timeMin)) * plotWidth;
|
|
671
|
+
const getBandX = (position) => {
|
|
672
|
+
const groupSize = groupSizes.get(position.marketEndTimestampMs) ?? 1;
|
|
673
|
+
const ordinal = groupOrdinals.get(position) ?? 0;
|
|
674
|
+
const center = scaleX(position.marketEndTimestampMs);
|
|
675
|
+
const spreadWidth = groupSize <= 1
|
|
676
|
+
? 0
|
|
677
|
+
: timeMin === timeMax
|
|
678
|
+
? plotWidth / 4
|
|
679
|
+
: Math.max(dayWidth, BAND_WIDTH * (groupSize + 1));
|
|
680
|
+
const offset = groupSize <= 1
|
|
681
|
+
? 0
|
|
682
|
+
: (ordinal - (groupSize - 1) / 2) * (spreadWidth / groupSize);
|
|
683
|
+
const rawX = center + offset - BAND_WIDTH / 2;
|
|
684
|
+
return clamp(rawX, PLOT_LEFT, PLOT_LEFT + plotWidth - BAND_WIDTH);
|
|
685
|
+
};
|
|
686
|
+
const getBandCenterX = (position) => getBandX(position) + BAND_WIDTH / 2;
|
|
293
687
|
const svgChildren = [];
|
|
294
688
|
if (hasOpenPositions) {
|
|
295
689
|
svgChildren.push((0, h_1.h)("defs", null, (0, h_1.h)("pattern", {
|
|
@@ -303,7 +697,7 @@ function buildChartArea(theme, btcHistory, positions) {
|
|
|
303
697
|
strokeWidth: 2,
|
|
304
698
|
}))));
|
|
305
699
|
}
|
|
306
|
-
priceTicks.forEach((tick) => {
|
|
700
|
+
priceTicks.ticks.forEach((tick) => {
|
|
307
701
|
const y = scaleY(tick);
|
|
308
702
|
svgChildren.push((0, h_1.h)("line", {
|
|
309
703
|
x1: PLOT_LEFT,
|
|
@@ -315,10 +709,23 @@ function buildChartArea(theme, btcHistory, positions) {
|
|
|
315
709
|
strokeWidth: 1,
|
|
316
710
|
}));
|
|
317
711
|
});
|
|
712
|
+
if (latestSettlement) {
|
|
713
|
+
const y = scaleY(latestSettlement.settlementPrice);
|
|
714
|
+
svgChildren.push((0, h_1.h)("line", {
|
|
715
|
+
x1: PLOT_LEFT,
|
|
716
|
+
x2: PLOT_LEFT + plotWidth,
|
|
717
|
+
y1: y,
|
|
718
|
+
y2: y,
|
|
719
|
+
stroke: theme.bitcoin,
|
|
720
|
+
strokeDasharray: "4 5",
|
|
721
|
+
strokeWidth: 1.5,
|
|
722
|
+
strokeOpacity: 0.75,
|
|
723
|
+
}));
|
|
724
|
+
}
|
|
318
725
|
renderablePositions.forEach((position) => {
|
|
319
726
|
const topPrice = Math.max(position.lowerPrice, position.upperPrice);
|
|
320
727
|
const bottomPrice = Math.min(position.lowerPrice, position.upperPrice);
|
|
321
|
-
const x =
|
|
728
|
+
const x = getBandX(position);
|
|
322
729
|
const top = scaleY(topPrice);
|
|
323
730
|
const bottom = scaleY(bottomPrice);
|
|
324
731
|
const height = Math.max(bottom - top, 6);
|
|
@@ -330,13 +737,22 @@ function buildChartArea(theme, btcHistory, positions) {
|
|
|
330
737
|
height,
|
|
331
738
|
rx: 6,
|
|
332
739
|
fill: "url(#profile-open-band)",
|
|
740
|
+
opacity: 0.4,
|
|
333
741
|
stroke: theme.bitcoin,
|
|
334
742
|
strokeWidth: 2,
|
|
335
743
|
}));
|
|
336
744
|
return;
|
|
337
745
|
}
|
|
338
|
-
const fill = position.status === "WIN"
|
|
339
|
-
|
|
746
|
+
const fill = position.status === "WIN"
|
|
747
|
+
? theme.primary
|
|
748
|
+
: position.status === "LOSS"
|
|
749
|
+
? theme.negative
|
|
750
|
+
: theme.inkMuted;
|
|
751
|
+
const fillOpacity = position.status === "WIN"
|
|
752
|
+
? 0.45
|
|
753
|
+
: position.status === "LOSS"
|
|
754
|
+
? 0.28
|
|
755
|
+
: 0.3;
|
|
340
756
|
svgChildren.push((0, h_1.h)("rect", {
|
|
341
757
|
x,
|
|
342
758
|
y: top,
|
|
@@ -346,7 +762,7 @@ function buildChartArea(theme, btcHistory, positions) {
|
|
|
346
762
|
fill,
|
|
347
763
|
fillOpacity,
|
|
348
764
|
stroke: fill,
|
|
349
|
-
strokeOpacity: 0.9,
|
|
765
|
+
strokeOpacity: position.status === "CLOSED" ? 0.55 : 0.9,
|
|
350
766
|
}));
|
|
351
767
|
});
|
|
352
768
|
if (sortedHistory.length === 1) {
|
|
@@ -371,14 +787,14 @@ function buildChartArea(theme, btcHistory, positions) {
|
|
|
371
787
|
if ((position.status === "WIN" || position.status === "LOSS") &&
|
|
372
788
|
position.settlementPrice != null) {
|
|
373
789
|
svgChildren.push((0, h_1.h)("circle", {
|
|
374
|
-
cx:
|
|
790
|
+
cx: getBandCenterX(position),
|
|
375
791
|
cy: scaleY(position.settlementPrice),
|
|
376
792
|
r: 4.5,
|
|
377
793
|
fill: position.status === "WIN" ? theme.primary : theme.negative,
|
|
378
794
|
}));
|
|
379
795
|
}
|
|
380
796
|
});
|
|
381
|
-
const labelNodes = priceTicks.map((tick) => (0, h_1.h)("div", {
|
|
797
|
+
const labelNodes = priceTicks.ticks.map((tick) => (0, h_1.h)("div", {
|
|
382
798
|
style: {
|
|
383
799
|
position: "absolute",
|
|
384
800
|
left: 12,
|
|
@@ -390,7 +806,26 @@ function buildChartArea(theme, btcHistory, positions) {
|
|
|
390
806
|
background: theme.card,
|
|
391
807
|
padding: "0 4px",
|
|
392
808
|
},
|
|
393
|
-
}, formatAxisLabel(tick)));
|
|
809
|
+
}, formatAxisLabel(tick, priceTicks.step)));
|
|
810
|
+
const settlementLabel = latestSettlement == null
|
|
811
|
+
? []
|
|
812
|
+
: [
|
|
813
|
+
(0, h_1.h)("div", {
|
|
814
|
+
style: {
|
|
815
|
+
position: "absolute",
|
|
816
|
+
left: PLOT_LEFT + plotWidth - 64,
|
|
817
|
+
top: scaleY(latestSettlement.settlementPrice) - 14,
|
|
818
|
+
display: "flex",
|
|
819
|
+
fontSize: 14,
|
|
820
|
+
fontWeight: 700,
|
|
821
|
+
color: theme.bitcoin,
|
|
822
|
+
background: theme.card,
|
|
823
|
+
border: `1px solid ${theme.bitcoin}`,
|
|
824
|
+
borderRadius: 999,
|
|
825
|
+
padding: "4px 10px",
|
|
826
|
+
},
|
|
827
|
+
}, formatAxisLabel(latestSettlement.settlementPrice, priceTicks.step)),
|
|
828
|
+
];
|
|
394
829
|
return (0, h_1.h)("div", {
|
|
395
830
|
style: {
|
|
396
831
|
width: CHART_WIDTH,
|
|
@@ -398,9 +833,6 @@ function buildChartArea(theme, btcHistory, positions) {
|
|
|
398
833
|
display: "flex",
|
|
399
834
|
position: "relative",
|
|
400
835
|
overflow: "hidden",
|
|
401
|
-
background: theme.neutralBg,
|
|
402
|
-
border: `1px solid ${theme.border}`,
|
|
403
|
-
borderRadius: 20,
|
|
404
836
|
},
|
|
405
837
|
}, (0, h_1.h)("svg", {
|
|
406
838
|
width: CHART_WIDTH,
|
|
@@ -412,43 +844,15 @@ function buildChartArea(theme, btcHistory, positions) {
|
|
|
412
844
|
display: "flex",
|
|
413
845
|
pointerEvents: "none",
|
|
414
846
|
},
|
|
415
|
-
}, ...svgChildren), ...labelNodes);
|
|
416
|
-
}
|
|
417
|
-
function buildStatCard(label, value, valueColor, theme) {
|
|
418
|
-
return (0, h_1.h)("div", {
|
|
419
|
-
style: {
|
|
420
|
-
flex: 1,
|
|
421
|
-
display: "flex",
|
|
422
|
-
flexDirection: "column",
|
|
423
|
-
padding: "16px 18px",
|
|
424
|
-
borderRadius: 18,
|
|
425
|
-
border: `1px solid ${theme.border}`,
|
|
426
|
-
background: theme.neutralBg,
|
|
427
|
-
},
|
|
428
|
-
}, (0, h_1.h)("div", {
|
|
429
|
-
style: {
|
|
430
|
-
display: "flex",
|
|
431
|
-
fontSize: 14,
|
|
432
|
-
fontWeight: 500,
|
|
433
|
-
color: theme.inkMuted,
|
|
434
|
-
marginBottom: 10,
|
|
435
|
-
},
|
|
436
|
-
}, label), (0, h_1.h)("div", {
|
|
437
|
-
style: {
|
|
438
|
-
display: "flex",
|
|
439
|
-
fontSize: 28,
|
|
440
|
-
lineHeight: 1.1,
|
|
441
|
-
fontWeight: 700,
|
|
442
|
-
color: valueColor,
|
|
443
|
-
},
|
|
444
|
-
}, value));
|
|
847
|
+
}, ...svgChildren), ...labelNodes, ...settlementLabel);
|
|
445
848
|
}
|
|
446
849
|
function buildProfileVNode(input) {
|
|
447
850
|
const theme = OG_THEMES[input.theme];
|
|
448
|
-
const accent = TIER_ACCENT_COLORS[input.tier];
|
|
449
851
|
const mainText = buildMainText(input);
|
|
450
852
|
const subline = buildSubline(input);
|
|
451
|
-
const pnlValue = input.hideAmounts
|
|
853
|
+
const pnlValue = input.hideAmounts
|
|
854
|
+
? "••••"
|
|
855
|
+
: formatProfilePnl(input.totalPnl);
|
|
452
856
|
const roiValue = formatProfileRoi(input.roi);
|
|
453
857
|
const bestMultValue = formatBestMult(input.bestMult);
|
|
454
858
|
return (0, h_1.h)("div", {
|
|
@@ -470,79 +874,18 @@ function buildProfileVNode(input) {
|
|
|
470
874
|
border: `1px solid ${theme.border}`,
|
|
471
875
|
borderRadius: 24,
|
|
472
876
|
},
|
|
473
|
-
}, (0, h_1.h)("div", {
|
|
474
|
-
style: {
|
|
475
|
-
display: "flex",
|
|
476
|
-
alignItems: "center",
|
|
477
|
-
justifyContent: "space-between",
|
|
478
|
-
},
|
|
479
|
-
}, (0, h_1.h)("div", {
|
|
480
|
-
style: {
|
|
481
|
-
display: "flex",
|
|
482
|
-
alignItems: "center",
|
|
483
|
-
gap: 10,
|
|
484
|
-
},
|
|
485
|
-
}, (0, h_1.h)("img", {
|
|
486
|
-
src: assets_1.SIGNALS_LOGO_URI,
|
|
487
|
-
width: 34,
|
|
488
|
-
height: 32,
|
|
489
|
-
style: { display: "flex" },
|
|
490
|
-
}), (0, h_1.h)("div", {
|
|
491
|
-
style: {
|
|
492
|
-
display: "flex",
|
|
493
|
-
fontSize: 28,
|
|
494
|
-
fontWeight: 700,
|
|
495
|
-
color: theme.primary,
|
|
496
|
-
},
|
|
497
|
-
}, "Signals")), (0, h_1.h)("div", {
|
|
498
|
-
style: {
|
|
499
|
-
display: "flex",
|
|
500
|
-
fontSize: 18,
|
|
501
|
-
fontWeight: 700,
|
|
502
|
-
letterSpacing: "0.04em",
|
|
503
|
-
textTransform: "uppercase",
|
|
504
|
-
color: accent,
|
|
505
|
-
},
|
|
506
|
-
}, input.tier)), (0, h_1.h)("div", {
|
|
877
|
+
}, buildHeader(input, theme, mainText, subline), (0, h_1.h)("div", {
|
|
507
878
|
style: {
|
|
508
879
|
flex: 1,
|
|
509
880
|
display: "flex",
|
|
510
881
|
flexDirection: "column",
|
|
511
|
-
marginTop:
|
|
512
|
-
},
|
|
513
|
-
}, (0, h_1.h)("div", {
|
|
514
|
-
style: {
|
|
515
|
-
display: "flex",
|
|
516
|
-
flexDirection: "column",
|
|
882
|
+
marginTop: 22,
|
|
517
883
|
},
|
|
518
884
|
}, (0, h_1.h)("div", {
|
|
519
885
|
style: {
|
|
520
886
|
display: "flex",
|
|
521
|
-
fontSize: 52,
|
|
522
|
-
lineHeight: 1.05,
|
|
523
|
-
fontWeight: 700,
|
|
524
|
-
color: theme.ink,
|
|
525
|
-
marginBottom: 10,
|
|
526
|
-
},
|
|
527
|
-
}, mainText), (0, h_1.h)("div", {
|
|
528
|
-
style: {
|
|
529
|
-
display: "flex",
|
|
530
|
-
fontSize: 20,
|
|
531
|
-
fontWeight: 500,
|
|
532
|
-
color: theme.inkMuted,
|
|
533
|
-
},
|
|
534
|
-
}, subline)), (0, h_1.h)("div", {
|
|
535
|
-
style: {
|
|
536
|
-
display: "flex",
|
|
537
|
-
marginTop: 24,
|
|
538
|
-
},
|
|
539
|
-
}, buildChartArea(theme, input.btcHistory, input.positions)), (0, h_1.h)("div", {
|
|
540
|
-
style: {
|
|
541
|
-
display: "flex",
|
|
542
|
-
gap: 16,
|
|
543
|
-
marginTop: 20,
|
|
544
887
|
},
|
|
545
|
-
},
|
|
888
|
+
}, buildChartArea(theme, input.btcHistory, input.positions)), buildStatRow(input, theme, pnlValue, roiValue, bestMultValue), (0, h_1.h)("div", {
|
|
546
889
|
style: {
|
|
547
890
|
display: "flex",
|
|
548
891
|
justifyContent: "flex-end",
|