From bf034d9ada4df156fecfe28669f1ebf426ea4180 Mon Sep 17 00:00:00 2001 From: "Bobby (aider)" Date: Wed, 12 Feb 2025 16:51:05 -0800 Subject: [PATCH] refactor: Move trading journal functionality to separate module --- src/pages/journal/__init__.py | 1 + src/pages/journal/trading_journal_page.py | 308 ++++++++++++++++++++++ src/streamlit_app.py | 304 +-------------------- 3 files changed, 310 insertions(+), 303 deletions(-) create mode 100644 src/pages/journal/__init__.py create mode 100644 src/pages/journal/trading_journal_page.py diff --git a/src/pages/journal/__init__.py b/src/pages/journal/__init__.py new file mode 100644 index 0000000..c045a4e --- /dev/null +++ b/src/pages/journal/__init__.py @@ -0,0 +1 @@ +# Trading journal package diff --git a/src/pages/journal/trading_journal_page.py b/src/pages/journal/trading_journal_page.py new file mode 100644 index 0000000..5d04a9d --- /dev/null +++ b/src/pages/journal/trading_journal_page.py @@ -0,0 +1,308 @@ +import streamlit as st +import plotly.graph_objects as go +from datetime import datetime +import pytz +from trading.journal import ( + create_trades_table, get_open_trades, get_trade_history, + add_trade, update_trade, delete_trade, TradeEntry, + get_open_trades_summary, get_current_prices, generate_position_id, + get_position_summary, get_latest_portfolio_value, update_portfolio_value +) + +def format_datetime(dt): + """Format datetime for display""" + if dt: + return dt.strftime('%Y-%m-%d %H:%M') + return '' + +def plot_trade_history(trades): + """Create a P/L chart using Plotly""" + if not trades: + return None + + # Prepare data + dates = [] + pnl = [] + cumulative_pnl = 0 + + for trade in trades: + if trade['exit_price']: + trade_pnl = (trade['exit_price'] - trade['entry_price']) * trade['shares'] + cumulative_pnl += trade_pnl + dates.append(trade['exit_date']) + pnl.append(cumulative_pnl) + + if not dates: + return None + + # Create figure + fig = go.Figure() + fig.add_trace( + go.Scatter(x=dates, y=pnl, mode='lines+markers', + name='Cumulative P/L', + line=dict(color='blue'), + hovertemplate='Date: %{x}
P/L: $%{y:.2f}') + ) + + fig.update_layout( + title='Cumulative Profit/Loss Over Time', + xaxis_title='Date', + yaxis_title='Cumulative P/L ($)', + hovermode='x unified' + ) + + return fig + +def trading_journal_page(): + st.header("Trading Journal") + + # Tabs for different journal functions + tab1, tab2, tab3, tab4 = st.tabs(["Open Positions", "Add Trade", "Update Trade", "Trade History"]) + + with tab1: + st.subheader("Open Positions") + open_trades = get_open_trades() + open_summary = get_open_trades_summary() + + if open_summary: + # Get current prices + unique_tickers = list(set(summary['ticker'] for summary in open_summary)) + current_prices = get_current_prices(unique_tickers) + + total_portfolio_value = 0 + total_paper_pl = 0 + + for summary in open_summary: + with st.expander(f"{summary['ticker']} Summary"): + ticker = summary['ticker'] + avg_entry = summary['avg_entry_price'] + current_price = current_prices.get(ticker) + total_shares = summary['total_shares'] + position_value = avg_entry * total_shares + + col1, col2 = st.columns(2) + with col1: + st.metric("Total Shares", f"{total_shares:,}") + st.metric("Average Entry", f"${avg_entry:.2f}") + st.metric("Position Value", f"${position_value:.2f}") + + with col2: + if current_price: + current_value = current_price * total_shares + paper_pl = (current_price - avg_entry) * total_shares + pl_percentage = (paper_pl / position_value) * 100 + + st.metric("Current Price", f"${current_price:.2f}") + st.metric("Paper P/L", f"${paper_pl:.2f}", f"{pl_percentage:.2f}%") + + total_portfolio_value += current_value + total_paper_pl += paper_pl + + if total_portfolio_value > 0: + st.markdown("---") + st.subheader("Portfolio Summary") + col1, col2 = st.columns(2) + with col1: + st.metric("Total Portfolio Value", f"${total_portfolio_value:.2f}") + with col2: + st.metric("Total P/L", f"${total_paper_pl:.2f}", + f"{(total_paper_pl / (total_portfolio_value - total_paper_pl)) * 100:.2f}%") + + with tab2: + st.subheader("Add New Trade") + + ticker = st.text_input("Ticker Symbol").upper() + + # Add direction selection + direction = st.selectbox( + "Direction", + ["Buy", "Sell"], + key="trade_direction" + ) + + if ticker: + # Show existing positions for this ticker + existing_positions = get_position_summary(ticker) + if existing_positions: + st.write(f"Existing {ticker} Positions:") + for pos in existing_positions: + st.write(f"Position ID: {pos['position_id']}") + st.write(f"Total Shares: {pos['total_shares']}") + st.write(f"Average Entry: ${pos['avg_entry_price']:.2f}") + + if direction == "Sell": + position_id = st.selectbox( + "Select Position to Exit", + options=[pos['position_id'] for pos in existing_positions], + key="position_select" + ) + else: # Buy + add_to_existing = st.checkbox("Add to existing position") + if add_to_existing: + position_id = st.selectbox( + "Select Position ID", + options=[pos['position_id'] for pos in existing_positions], + key="position_select" + ) + else: + position_id = generate_position_id(ticker) + else: + if direction == "Sell": + st.error("No existing positions found for this ticker") + st.stop() + position_id = generate_position_id(ticker) + + col1, col2 = st.columns(2) + with col1: + shares = st.number_input("Number of Shares", min_value=1, step=1) + if direction == "Buy": + entry_price = st.number_input("Entry Price", min_value=0.01, step=0.01) + else: + entry_price = st.number_input("Exit Price", min_value=0.01, step=0.01) + + with col2: + if direction == "Buy": + target_price = st.number_input("Target Price", min_value=0.01, step=0.01) + stop_loss = st.number_input("Stop Loss", min_value=0.01, step=0.01) + strategy = st.text_input("Strategy") + else: + exit_reason = st.text_area("Exit Reason", key="exit_reason") + + order_type = st.selectbox("Order Type", ["Market", "Limit"], key="add_trade_order_type") + + entry_date = st.date_input("Entry Date") + entry_time = st.time_input("Entry Time") + + if direction == "Buy": + followed_rules = st.checkbox("Followed Trading Rules") + entry_reason = st.text_area("Entry Reason", key="add_trade_reason") + + notes = st.text_area("Notes", key="add_trade_notes") + + if st.button("Add Trade"): + try: + entry_datetime = datetime.combine(entry_date, entry_time) + entry_datetime = pytz.timezone('US/Pacific').localize(entry_datetime) + + trade = TradeEntry( + ticker=ticker, + entry_date=entry_datetime, + shares=shares, + entry_price=entry_price, + target_price=target_price if direction == "Buy" else None, + stop_loss=stop_loss if direction == "Buy" else None, + strategy=strategy if direction == "Buy" else None, + order_type=order_type, + position_id=position_id, + followed_rules=followed_rules if direction == "Buy" else None, + entry_reason=entry_reason if direction == "Buy" else None, + exit_reason=exit_reason if direction == "Sell" else None, + notes=notes, + direction=direction.lower() + ) + + add_trade(trade) + st.success("Trade added successfully!") + st.query_params(rerun=True) + except Exception as e: + st.error(f"Error adding trade: {str(e)}") + + with tab3: + st.subheader("Update Trade") + open_trades = get_open_trades() + + if open_trades: + trade_id = st.selectbox( + "Select Trade to Update", + options=[t['id'] for t in open_trades], + format_func=lambda x: f"{next(t['ticker'] for t in open_trades if t['id'] == x)} - {x}", + key="trade_select" + ) + + trade = next(t for t in open_trades if t['id'] == trade_id) + + col1, col2 = st.columns(2) + with col1: + new_shares = st.number_input("Shares", value=trade['shares']) + new_entry = st.number_input("Entry Price", value=float(trade['entry_price'])) + new_target = st.number_input("Target Price", value=float(trade['target_price'])) + + with col2: + new_stop = st.number_input("Stop Loss", value=float(trade['stop_loss'])) + new_strategy = st.text_input("Strategy", value=trade['strategy']) + new_order_type = st.selectbox("Order Type", ["Market", "Limit"], + index=0 if trade['order_type'] == "Market" else 1, + key="update_trade_order_type") + + # Add date and time fields + entry_date = st.date_input( + "Entry Date", + value=trade['entry_date'].date(), + key="update_entry_date" + ) + entry_time = st.time_input( + "Entry Time", + value=trade['entry_date'].time(), + key="update_entry_time" + ) + + new_notes = st.text_area("Notes", + value=trade['notes'] if trade['notes'] else "", + key="update_trade_notes") + + if st.button("Update Trade"): + try: + # Combine date and time into datetime + entry_datetime = datetime.combine(entry_date, entry_time) + entry_datetime = pytz.timezone('US/Pacific').localize(entry_datetime) + + updates = { + 'entry_date': entry_datetime, + 'shares': new_shares, + 'entry_price': new_entry, + 'target_price': new_target, + 'stop_loss': new_stop, + 'strategy': new_strategy, + 'order_type': new_order_type, + 'notes': new_notes + } + + update_trade(trade_id, updates) + st.success("Trade updated successfully!") + st.query_params(rerun=True) + except Exception as e: + st.error(f"Error updating trade: {str(e)}") + else: + st.info("No open trades to update") + + with tab4: + st.subheader("Trade History") + history = get_trade_history() + + if history: + # Add P/L chart + fig = plot_trade_history(history) + if fig: + st.plotly_chart(fig, use_container_width=True) + + for trade in history: + with st.expander(f"{trade['ticker']} - {format_datetime(trade['entry_date'])}"): + profit_loss = (trade['exit_price'] - trade['entry_price']) * trade['shares'] if trade['exit_price'] else None + + col1, col2 = st.columns(2) + with col1: + st.metric("Entry Price", f"${trade['entry_price']:.2f}") + st.metric("Shares", trade['shares']) + if profit_loss: + st.metric("P/L", f"${profit_loss:.2f}") + + with col2: + if trade['exit_price']: + st.metric("Exit Price", f"${trade['exit_price']:.2f}") + st.metric("Exit Date", format_datetime(trade['exit_date'])) + + st.text(f"Strategy: {trade['strategy']}") + if trade['notes']: + st.text(f"Notes: {trade['notes']}") + else: + st.info("No trade history found") diff --git a/src/streamlit_app.py b/src/streamlit_app.py index 6d6a62d..5509fdd 100644 --- a/src/streamlit_app.py +++ b/src/streamlit_app.py @@ -3,12 +3,7 @@ import pandas as pd from datetime import datetime import pytz from db.db_connection import create_client -from trading.journal import ( - create_trades_table, get_open_trades, get_trade_history, - add_trade, update_trade, delete_trade, TradeEntry, - get_open_trades_summary, get_current_prices, generate_position_id, - get_position_summary, get_latest_portfolio_value, update_portfolio_value -) +from pages.journal.trading_journal_page import trading_journal_page from trading.trading_plan import ( delete_trading_plan, TradingPlan, PlanStatus, Timeframe, MarketFocus, TradeFrequency, @@ -54,303 +49,6 @@ def load_scanner_reports(): # Sort by creation time, newest first return sorted(reports, key=lambda x: x['created'], reverse=True) -def format_datetime(dt): - """Format datetime for display""" - if dt: - return dt.strftime('%Y-%m-%d %H:%M') - return '' - -def plot_trade_history(trades): - """Create a P/L chart using Plotly""" - if not trades: - return None - - # Prepare data - dates = [] - pnl = [] - cumulative_pnl = 0 - - for trade in trades: - if trade['exit_price']: - trade_pnl = (trade['exit_price'] - trade['entry_price']) * trade['shares'] - cumulative_pnl += trade_pnl - dates.append(trade['exit_date']) - pnl.append(cumulative_pnl) - - if not dates: - return None - - # Create figure - fig = go.Figure() - fig.add_trace( - go.Scatter(x=dates, y=pnl, mode='lines+markers', - name='Cumulative P/L', - line=dict(color='blue'), - hovertemplate='Date: %{x}
P/L: $%{y:.2f}') - ) - - fig.update_layout( - title='Cumulative Profit/Loss Over Time', - xaxis_title='Date', - yaxis_title='Cumulative P/L ($)', - hovermode='x unified' - ) - - return fig - -def trading_journal_page(): - st.header("Trading Journal") - - # Tabs for different journal functions - tab1, tab2, tab3, tab4 = st.tabs(["Open Positions", "Add Trade", "Update Trade", "Trade History"]) - - with tab1: - st.subheader("Open Positions") - open_trades = get_open_trades() - open_summary = get_open_trades_summary() - - if open_summary: - # Get current prices - unique_tickers = list(set(summary['ticker'] for summary in open_summary)) - current_prices = get_current_prices(unique_tickers) - - total_portfolio_value = 0 - total_paper_pl = 0 - - for summary in open_summary: - with st.expander(f"{summary['ticker']} Summary"): - ticker = summary['ticker'] - avg_entry = summary['avg_entry_price'] - current_price = current_prices.get(ticker) - total_shares = summary['total_shares'] - position_value = avg_entry * total_shares - - col1, col2 = st.columns(2) - with col1: - st.metric("Total Shares", f"{total_shares:,}") - st.metric("Average Entry", f"${avg_entry:.2f}") - st.metric("Position Value", f"${position_value:.2f}") - - with col2: - if current_price: - current_value = current_price * total_shares - paper_pl = (current_price - avg_entry) * total_shares - pl_percentage = (paper_pl / position_value) * 100 - - st.metric("Current Price", f"${current_price:.2f}") - st.metric("Paper P/L", f"${paper_pl:.2f}", f"{pl_percentage:.2f}%") - - total_portfolio_value += current_value - total_paper_pl += paper_pl - - if total_portfolio_value > 0: - st.markdown("---") - st.subheader("Portfolio Summary") - col1, col2 = st.columns(2) - with col1: - st.metric("Total Portfolio Value", f"${total_portfolio_value:.2f}") - with col2: - st.metric("Total P/L", f"${total_paper_pl:.2f}", - f"{(total_paper_pl / (total_portfolio_value - total_paper_pl)) * 100:.2f}%") - - with tab2: - st.subheader("Add New Trade") - - ticker = st.text_input("Ticker Symbol").upper() - - # Add direction selection - direction = st.selectbox( - "Direction", - ["Buy", "Sell"], - key="trade_direction" - ) - - if ticker: - # Show existing positions for this ticker - existing_positions = get_position_summary(ticker) - if existing_positions: - st.write(f"Existing {ticker} Positions:") - for pos in existing_positions: - st.write(f"Position ID: {pos['position_id']}") - st.write(f"Total Shares: {pos['total_shares']}") - st.write(f"Average Entry: ${pos['avg_entry_price']:.2f}") - - if direction == "Sell": - position_id = st.selectbox( - "Select Position to Exit", - options=[pos['position_id'] for pos in existing_positions], - key="position_select" - ) - else: # Buy - add_to_existing = st.checkbox("Add to existing position") - if add_to_existing: - position_id = st.selectbox( - "Select Position ID", - options=[pos['position_id'] for pos in existing_positions], - key="position_select" - ) - else: - position_id = generate_position_id(ticker) - else: - if direction == "Sell": - st.error("No existing positions found for this ticker") - st.stop() - position_id = generate_position_id(ticker) - - col1, col2 = st.columns(2) - with col1: - shares = st.number_input("Number of Shares", min_value=1, step=1) - if direction == "Buy": - entry_price = st.number_input("Entry Price", min_value=0.01, step=0.01) - else: - entry_price = st.number_input("Exit Price", min_value=0.01, step=0.01) - - with col2: - if direction == "Buy": - target_price = st.number_input("Target Price", min_value=0.01, step=0.01) - stop_loss = st.number_input("Stop Loss", min_value=0.01, step=0.01) - strategy = st.text_input("Strategy") - else: - exit_reason = st.text_area("Exit Reason", key="exit_reason") - - order_type = st.selectbox("Order Type", ["Market", "Limit"], key="add_trade_order_type") - - entry_date = st.date_input("Entry Date") - entry_time = st.time_input("Entry Time") - - if direction == "Buy": - followed_rules = st.checkbox("Followed Trading Rules") - entry_reason = st.text_area("Entry Reason", key="add_trade_reason") - - notes = st.text_area("Notes", key="add_trade_notes") - - if st.button("Add Trade"): - try: - entry_datetime = datetime.combine(entry_date, entry_time) - entry_datetime = pytz.timezone('US/Pacific').localize(entry_datetime) - - trade = TradeEntry( - ticker=ticker, - entry_date=entry_datetime, - shares=shares, - entry_price=entry_price, - target_price=target_price if direction == "Buy" else None, - stop_loss=stop_loss if direction == "Buy" else None, - strategy=strategy if direction == "Buy" else None, - order_type=order_type, - position_id=position_id, - followed_rules=followed_rules if direction == "Buy" else None, - entry_reason=entry_reason if direction == "Buy" else None, - exit_reason=exit_reason if direction == "Sell" else None, - notes=notes, - direction=direction.lower() - ) - - add_trade(trade) - st.success("Trade added successfully!") - st.query_params(rerun=True) - except Exception as e: - st.error(f"Error adding trade: {str(e)}") - - with tab3: - st.subheader("Update Trade") - open_trades = get_open_trades() - - if open_trades: - trade_id = st.selectbox( - "Select Trade to Update", - options=[t['id'] for t in open_trades], - format_func=lambda x: f"{next(t['ticker'] for t in open_trades if t['id'] == x)} - {x}", - key="trade_select" - ) - - trade = next(t for t in open_trades if t['id'] == trade_id) - - col1, col2 = st.columns(2) - with col1: - new_shares = st.number_input("Shares", value=trade['shares']) - new_entry = st.number_input("Entry Price", value=float(trade['entry_price'])) - new_target = st.number_input("Target Price", value=float(trade['target_price'])) - - with col2: - new_stop = st.number_input("Stop Loss", value=float(trade['stop_loss'])) - new_strategy = st.text_input("Strategy", value=trade['strategy']) - new_order_type = st.selectbox("Order Type", ["Market", "Limit"], - index=0 if trade['order_type'] == "Market" else 1, - key="update_trade_order_type") - - # Add date and time fields - entry_date = st.date_input( - "Entry Date", - value=trade['entry_date'].date(), - key="update_entry_date" - ) - entry_time = st.time_input( - "Entry Time", - value=trade['entry_date'].time(), - key="update_entry_time" - ) - - new_notes = st.text_area("Notes", - value=trade['notes'] if trade['notes'] else "", - key="update_trade_notes") - - if st.button("Update Trade"): - try: - # Combine date and time into datetime - entry_datetime = datetime.combine(entry_date, entry_time) - entry_datetime = pytz.timezone('US/Pacific').localize(entry_datetime) - - updates = { - 'entry_date': entry_datetime, - 'shares': new_shares, - 'entry_price': new_entry, - 'target_price': new_target, - 'stop_loss': new_stop, - 'strategy': new_strategy, - 'order_type': new_order_type, - 'notes': new_notes - } - - update_trade(trade_id, updates) - st.success("Trade updated successfully!") - st.query_params(rerun=True) - except Exception as e: - st.error(f"Error updating trade: {str(e)}") - else: - st.info("No open trades to update") - - with tab4: - st.subheader("Trade History") - history = get_trade_history() - - if history: - # Add P/L chart - fig = plot_trade_history(history) - if fig: - st.plotly_chart(fig, use_container_width=True) - - for trade in history: - with st.expander(f"{trade['ticker']} - {format_datetime(trade['entry_date'])}"): - profit_loss = (trade['exit_price'] - trade['entry_price']) * trade['shares'] if trade['exit_price'] else None - - col1, col2 = st.columns(2) - with col1: - st.metric("Entry Price", f"${trade['entry_price']:.2f}") - st.metric("Shares", trade['shares']) - if profit_loss: - st.metric("P/L", f"${profit_loss:.2f}") - - with col2: - if trade['exit_price']: - st.metric("Exit Price", f"${trade['exit_price']:.2f}") - st.metric("Exit Date", format_datetime(trade['exit_date'])) - - st.text(f"Strategy: {trade['strategy']}") - if trade['notes']: - st.text(f"Notes: {trade['notes']}") - else: - st.info("No trade history found") def technical_scanner_page(): st.header("Technical Scanner")