Polish exit timeline and always show S/R levels.
Keep support/resistance visible even near trade lines, tighten the how-this-exits spine so steps connect, unify step styling, and add space above the panel.
This commit is contained in:
@@ -354,8 +354,8 @@ export function CandlestickChart({
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
// Left-side role labels only; prices live on the right axis.
|
||||
// Priority when levels crowd: Now > Stop > Gate > Entry > S/R.
|
||||
// Exterior left-side role labels only; prices live on the right axis.
|
||||
// Priority when levels crowd: Now > Stop > Gate > Entry.
|
||||
// Best practice: hide Entry when it sits on top of Now — at market, Now
|
||||
// *is* the fill; Entry only matters once price has drifted from the scan.
|
||||
type LineLabel = {
|
||||
@@ -385,27 +385,20 @@ export function CandlestickChart({
|
||||
&& (entryDriftPx >= 12 || entryDriftR >= 0.25),
|
||||
);
|
||||
|
||||
const tradeYs: number[] = [nowY];
|
||||
if (tradeSetup) {
|
||||
tradeYs.push(yScale(tradeSetup.stop_loss), yScale(tradeSetup.target));
|
||||
if (showEntry) tradeYs.push(yScale(tradeSetup.entry_price));
|
||||
}
|
||||
|
||||
// In-plot S/R labels (drawn after candles so they stay readable). Not
|
||||
// exterior margin labels — they ride the line itself, left-aligned.
|
||||
type InPlotLabel = { y: number; text: string; color: string; bg: string };
|
||||
const inPlotLabels: InPlotLabel[] = [];
|
||||
|
||||
// Nearest support/resistance only (band if it came from a zone).
|
||||
// Drop S/R entirely when it sits on a trade line — the trade label wins.
|
||||
// Always draw them — labels live in-plot on the line, so proximity to
|
||||
// Now/Stop no longer forces a hide (that was only to free the left gutter).
|
||||
markers.forEach((m) => {
|
||||
const isSupport = m.role === 'support';
|
||||
// Support stays cyan; resistance is a soft coral (warmer than neutral ink,
|
||||
// cooler/softer than the stop so the two never read as the same thing).
|
||||
const color = isSupport ? '#2f9db2' : '#e89b8c';
|
||||
const yMid = yScale(m.price);
|
||||
const collidesTrade = tradeYs.some((ty) => Math.abs(ty - yMid) < LABEL_MIN_GAP_PX);
|
||||
if (collidesTrade) return;
|
||||
|
||||
if (m.high > m.low) {
|
||||
const yTop = yScale(m.high);
|
||||
|
||||
@@ -16,35 +16,34 @@ export function ExitPlanPanel({ plan, direction }: { plan: ExitPlan; direction:
|
||||
const steps = buildSteps(plan, isLong);
|
||||
|
||||
return (
|
||||
<div className="mt-5 rounded-xl border border-white/[0.07] bg-white/[0.02] p-4">
|
||||
<div className="mt-8 rounded-xl border border-white/[0.07] bg-white/[0.02] p-4">
|
||||
<div className="flex flex-wrap items-baseline gap-x-2 gap-y-1">
|
||||
<span className="num text-[10px] uppercase tracking-[0.16em] text-gray-500">how this exits</span>
|
||||
<span className="text-[11.5px] font-medium text-gray-300">{plan.headline}</span>
|
||||
</div>
|
||||
|
||||
<ol className="relative mt-4 space-y-0">
|
||||
<ol className="relative mt-4">
|
||||
{steps.map((step, i) => {
|
||||
const isLast = i === steps.length - 1;
|
||||
return (
|
||||
<li key={step.title} className="relative flex gap-3 pb-4 last:pb-0">
|
||||
{/* Spine */}
|
||||
<div className="flex w-5 shrink-0 flex-col items-center">
|
||||
<li key={step.title} className="relative flex gap-3">
|
||||
{/* Spine column: continuous connector from this node into the next */}
|
||||
<div className="relative flex w-5 shrink-0 flex-col items-center self-stretch">
|
||||
<span
|
||||
className={`num mt-0.5 flex h-5 w-5 items-center justify-center rounded-full border text-[10px] font-semibold ${
|
||||
step.tone === 'muted'
|
||||
? 'border-white/[0.12] text-gray-500'
|
||||
: 'border-blue-400/35 bg-blue-400/10 text-blue-200'
|
||||
}`}
|
||||
className="num relative z-10 mt-0.5 flex h-5 w-5 items-center justify-center rounded-full border border-blue-400/35 bg-blue-400/10 text-[10px] font-semibold text-blue-200"
|
||||
aria-hidden
|
||||
>
|
||||
{step.icon ?? i + 1}
|
||||
{i + 1}
|
||||
</span>
|
||||
{!isLast && (
|
||||
<span className="mt-1 w-px flex-1 min-h-[12px] bg-white/[0.08]" aria-hidden />
|
||||
<span
|
||||
className="absolute left-1/2 top-5 bottom-0 w-px -translate-x-1/2 bg-blue-400/25"
|
||||
aria-hidden
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="min-w-0 flex-1 pt-0.5">
|
||||
<div className={`min-w-0 flex-1 pt-0.5 ${isLast ? 'pb-0' : 'pb-5'}`}>
|
||||
<p className="text-[12px] font-medium text-gray-200">{step.title}</p>
|
||||
{step.primary && (
|
||||
<p className="num mt-0.5 text-[13px] font-semibold tracking-tight text-gray-100">
|
||||
@@ -73,9 +72,6 @@ interface ExitStep {
|
||||
title: string;
|
||||
primary?: string;
|
||||
detail?: string;
|
||||
/** Optional glyph instead of a step number (e.g. clock for time stop). */
|
||||
icon?: string;
|
||||
tone?: 'default' | 'muted';
|
||||
}
|
||||
|
||||
function buildSteps(plan: ExitPlan, isLong: boolean): ExitStep[] {
|
||||
@@ -114,11 +110,12 @@ function buildSteps(plan: ExitPlan, isLong: boolean): ExitStep[] {
|
||||
});
|
||||
}
|
||||
|
||||
// Same visual weight as the other steps — time stop is a real exit rule,
|
||||
// not a footnote.
|
||||
steps.push({
|
||||
title: 'Time stop',
|
||||
primary: `${plan.maxHoldDays} trading days`,
|
||||
detail: 'Flat if still open after the max hold.',
|
||||
tone: 'muted',
|
||||
});
|
||||
|
||||
return steps;
|
||||
|
||||
Reference in New Issue
Block a user