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
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
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:
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
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.
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
Journal immediately after closing a trade
Emotions and market context are freshest right after the trade. Don’t wait until end-of-day.
Use consistent tags across trades
This makes filtering more effective. Create a personal tag taxonomy and stick to it.
Focus on 'why' not 'what'
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