Skip to main content
The Journal is where you add context to your trades, identify patterns, and develop disciplined trading habits. Every annotation helps you learn from both wins and losses.

Why Journal Your Trades?

Trading journals are proven to improve performance by:
  • Identifying emotional patterns (FOMO, revenge trading)
  • Documenting successful setups for replication
  • Learning from mistakes before they become habits
  • Building consistency through daily reflection
Deriverse automatically celebrates your first journal entry with confetti to encourage habit formation.

Adding Annotations

Click any trade card to open the annotation modal:
src/components/features/Journal.tsx
const handleSaveAnnotation = async (data: TradeAnnotation) => {
    if (!selectedTrade) return;

    try {
        // Count existing annotations with content
        const existingCount = Object.values(annotations).filter(a => a.notes && a.notes.trim().length > 0).length;
        const isFirstAnnotation = existingCount === 0;

        let saved: TradeAnnotation;

        if (network === 'mock') {
            // Save to localStorage
            saveToLocalStorage(selectedTrade.id, data.notes);
            saved = { ...data, updatedAt: new Date() };
        } else {
            // Save to Supabase
            const analyzingWalletAddress = analyzingWallet || undefined;
            saved = await annotationService.saveAnnotation(data, analyzingWalletAddress);
        }

        setAnnotations(prev => ({
            ...prev,
            [selectedTrade.id]: saved
        }));

        if (isFirstAnnotation) {
            // Celebration for the first journal entry!
            setTimeout(() => {
                const duration = 5 * 1000;
                const animationEnd = Date.now() + duration;
                const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 100 };

                const interval: any = setInterval(function () {
                    const timeLeft = animationEnd - Date.now();

                    if (timeLeft <= 0) {
                        return clearInterval(interval);
                    }

                    const particleCount = 50 * (timeLeft / duration);
                    confetti({ ...defaults, particleCount, origin: { x: Math.random() * (0.3 - 0.1) + 0.1, y: Math.random() - 0.2 } });
                    confetti({ ...defaults, particleCount, origin: { x: Math.random() * (0.9 - 0.7) + 0.7, y: Math.random() - 0.2 } });
                }, 250);

                toast.success('Your first journal entry! Keep it up for 21 days!', {
                    duration: 5000,
                    icon: '🚀'
                });
            }, 300);
        } else {
            toast.success('Annotation saved');
        }
        setIsModalOpen(false);
        setSelectedTrade(null);
    } catch (err) {
        console.error('Failed to save annotation:', err);
        toast.error('Failed to save annotation to cloud');
    }
};
Annotation fields:
  • Notes: Free-form text for trade context and thoughts
  • Tags: Categorize trades (e.g., “Good Setup”, “FOMO”, “Revenge”)
  • Lessons Learned: What you’ll do differently next time
Annotation Modal

Using Tags Effectively

Tags help you filter and analyze specific trade types: Setup Quality
  • Good Setup - Trade met all criteria
  • Partial Setup - Missing one or two criteria
  • Poor Setup - Impulsive or low-probability entry
Emotional State
  • FOMO - Fear of missing out drove the trade
  • Revenge - Trying to recover losses
  • Patience - Waited for the right opportunity
  • Discipline - Followed the trading plan
Market Conditions
  • High Vol - Volatile market conditions
  • Choppy - Sideways, unclear direction
  • Trending - Clear directional move

Filtering by Tags

src/components/features/Journal.tsx
// Filtering logic
const filteredTrades = trades.filter(trade => {
    if (selectedTags.length === 0) return true;
    const annotation = annotations[trade.id];
    if (!annotation) return false;
    return selectedTags.every(tag => annotation.tags.includes(tag));
});

const toggleTagFilter = (tag: string) => {
    setSelectedTags(prev =>
        prev.includes(tag)
            ? prev.filter(t => t !== tag)
            : [...prev, tag]
    );
};
Click the Filter Tags button to select one or multiple tags. Only trades with ALL selected tags will display.
No ‘#FOMO’ or ‘#Revenge’ trades found? That’s a sign of disciplined trading!

The 21-Day Streak System

Building habits requires consistency. Deriverse tracks your journaling streak over 21 days:
src/lib/tradeFilters.ts
export function calculateJournalStreak(trades: Trade[], annotations: Record<string, any>): boolean[] {
    const streak: boolean[] = [];
    const now = new Date();

    // Look back 21 days, starting from Today (index 0)
    for (let i = 0; i < 21; i++) {
        const targetDay = startOfDay(subDays(now, i));

        // Find trades on this day
        const dayTrades = trades.filter(t => isSameDay(t.closedAt, targetDay));

        // Check if any of these trades have an annotation
        const isAnnotated = dayTrades.some(t => annotations[t.id]);

        streak.push(isAnnotated);
    }

    return streak;
}

Streak Visualization

21-Day Streak Card
The Journal Streak Card shows:
  • Active Days: 🔥 Fire emoji (animated)
  • Inactive Days: 🔥 Fire emoji (gray)
  • Progress: “12/21 DAYS” badge
src/components/features/JournalStreakCard.tsx
export default function JournalStreakCard({ trades, annotations }: JournalStreakCardProps) {
  const stats = useMemo(() => {
    const streak = calculateJournalStreak(trades, annotations);
    const activeDays = streak.filter(Boolean).length;
    return { streak, activeDays };
  }, [trades, annotations]);

  return (
    <CardWithCornerShine padding="lg" minHeight="min-h-[160px]">
      <div className="flex flex-col h-full justify-between relative z-10">
        <div>
          <div className="flex items-center justify-between">
            <div className="flex items-center">
              <h3 className="text-white/40 text-sm font-mono uppercase tracking-wider">Journal Streak</h3>
              <InfoTooltip infoKey="tradeStreak" />
            </div>
            <span className="text-purple-400 text-[10px] font-mono border border-purple-500/20 px-2 py-0.5 bg-purple-500/5">
              {stats.activeDays}/21 DAYS
            </span>
          </div>
          <p className="text-white/60 text-xs mt-1">
            Journal for 21 days and It will become a Habit.
          </p>
        </div>
        <div className="flex items-center gap-1.5 overflow-x-auto pb-2 scrollbar-hide mt-4">
          {stats.streak.map((isActive, index) => (
            <div key={index} className="relative w-7 h-7 flex-shrink-0">
              <Image
                src={isActive ? '/assets/fire-active.gif' : '/assets/fire-inactive.png'}
                alt={isActive ? 'Active Streak' : 'Inactive Streak'}
                fill
                className="object-contain"
                unoptimized
              />
            </div>
          ))}
        </div>
      </div>
    </CardWithCornerShine>
  );
}
How it works: A day is marked active if you trade AND journal at least one trade on that day. Days with trades but no annotations remain inactive.

Pagination & Export

The journal displays 25 trades per page with navigation controls:
src/components/features/Journal.tsx
const ITEMS_PER_PAGE = 25;

// Pagination
const totalPages = Math.ceil(filteredTrades.length / ITEMS_PER_PAGE);
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
const endIndex = startIndex + ITEMS_PER_PAGE;
const paginatedTrades = filteredTrades.slice(startIndex, endIndex);

const goToPage = (page: number) => {
    setCurrentPage(Math.max(1, Math.min(page, totalPages)));
};

Export Journal

Click the Export button (book icon) to download all annotations:
src/lib/annotationStorage.ts
export function downloadAnnotations(trades: Trade[]) {
    const annotations = loadAnnotations();
    const annotatedTrades = trades
        .filter(t => annotations[t.id])
        .map(t => ({
            date: format(t.closedAt, 'yyyy-MM-dd HH:mm'),
            symbol: t.symbol,
            side: t.side,
            pnl: t.pnl,
            annotation: annotations[t.id].note,
        }));

    const csv = [
        'Date,Symbol,Side,PnL,Notes',
        ...annotatedTrades.map(t => 
            `${t.date},${t.symbol},${t.side},${t.pnl},"${t.annotation}"`
        )
    ].join('\n');

    const blob = new Blob([csv], { type: 'text/csv' });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = `trade-journal-${format(new Date(), 'yyyy-MM-dd')}.csv`;
    a.click();
}
Exported CSV includes: Date, Symbol, Side, PnL, and Notes.

Storage: Mock Mode vs. Devnet

Mock Mode (localStorage)

Annotations are stored locally in your browser:
if (network === 'mock') {
    saveToLocalStorage(selectedTrade.id, data.notes);
    saved = { ...data, updatedAt: new Date() };
}
Local storage is cleared if you clear browser data. Connect your wallet and save to Supabase for persistent cloud storage.

Devnet Mode (Supabase)

Annotations are saved to the cloud and tied to your wallet address:
else {
    const analyzingWalletAddress = analyzingWallet || undefined;
    saved = await annotationService.saveAnnotation(data, analyzingWalletAddress);
}
Your annotations automatically migrate to the cloud when you first connect your wallet in Devnet mode.

Best Practices

Emotions and market context are freshest right after the trade. Don’t wait until end-of-day.
This makes filtering more effective. Create a personal tag taxonomy and stick to it.
Don’t just describe the trade. Explain your reasoning, emotional state, and market conditions.
Filter by tags like #FOMO or #Revenge weekly to identify patterns before they become habits.

Next Steps

Analytics Dashboard

See how your journaling affects performance metrics

Data Modes

Understand mock mode vs. devnet mode for journal storage

Build docs developers (and LLMs) love