diff --git a/src/main.py b/src/main.py index 4001bde..9c1b1b2 100644 --- a/src/main.py +++ b/src/main.py @@ -2,6 +2,9 @@ import warnings from urllib3.exceptions import NotOpenSSLWarning warnings.filterwarnings('ignore', category=NotOpenSSLWarning) +from trading.journal import (create_trades_table, TradeEntry, add_trade, + update_trade_exit, get_open_trades, get_trade_history) + import datetime from screener.data_fetcher import validate_date_range, fetch_financial_data, get_stocks_in_time_range from screener.t_sunnyband import run_sunny_scanner @@ -30,14 +33,118 @@ def get_scanner_parameters(): portfolio_size = get_float_input("Enter portfolio size ($) or 0 to skip position sizing: ") return min_price, max_price, min_volume, portfolio_size +def journal_menu(): + create_trades_table() # Ensure table exists + + while True: + print("\nTrading Journal") + print("1. Add New Trade") + print("2. Update Existing Trade") + print("3. View Open Trades") + print("4. View Trade History") + print("5. Return to Main Menu") + + choice = input("\nSelect an option (1-5): ") + + if choice == "1": + ticker = input("Enter ticker symbol: ").upper() + shares = int(input("Enter number of shares: ")) + entry_price = float(input("Enter entry price: ")) + target_price = float(input("Enter target price: ")) + stop_loss = float(input("Enter stop loss: ")) + strategy = input("Enter strategy name: ") + followed_rules = input("Did you follow your rules? (y/n): ").lower() == 'y' + entry_reason = input("Enter entry reason (optional): ") or None + notes = input("Additional notes (optional): ") or None + + trade = TradeEntry( + ticker=ticker, + entry_date=datetime.datetime.now(), + shares=shares, + entry_price=entry_price, + target_price=target_price, + stop_loss=stop_loss, + strategy=strategy, + followed_rules=followed_rules, + entry_reason=entry_reason, + notes=notes + ) + + add_trade(trade) + print(f"\nExpected Profit: ${trade.expected_profit_loss:.2f}") + print(f"Maximum Loss: ${trade.max_loss:.2f}") + print("Trade added successfully!") + + elif choice == "2": + open_trades = get_open_trades() + if not open_trades: + print("No open trades to update.") + continue + + print("\nOpen Trades:") + for trade in open_trades: + print(f"{trade['id']}: {trade['ticker']} - Entered at ${trade['entry_price']}") + + trade_id = int(input("\nEnter trade ID to update: ")) + exit_price = float(input("Enter exit price: ")) + followed_rules = input("Did you follow your rules? (y/n): ").lower() == 'y' + exit_reason = input("Enter exit reason: ") + notes = input("Additional notes (optional): ") or None + + update_trade_exit( + trade_id=trade_id, + exit_price=exit_price, + exit_date=datetime.datetime.now(), + followed_rules=followed_rules, + exit_reason=exit_reason, + notes=notes + ) + print("Trade updated successfully!") + + elif choice == "3": + open_trades = get_open_trades() + if not open_trades: + print("No open trades found.") + else: + print("\nOpen Trades:") + for trade in open_trades: + print(f"\nTicker: {trade['ticker']}") + print(f"Entry Date: {trade['entry_date']}") + print(f"Shares: {trade['shares']}") + print(f"Entry Price: ${trade['entry_price']}") + print(f"Target: ${trade['target_price']}") + print(f"Stop Loss: ${trade['stop_loss']}") + print(f"Strategy: {trade['strategy']}") + + elif choice == "4": + history = get_trade_history() + if not history: + print("No trade history found.") + else: + print("\nTrade History:") + for trade in history: + profit_loss = (trade['exit_price'] - trade['entry_price']) * trade['shares'] + print(f"\nTicker: {trade['ticker']}") + print(f"Entry: ${trade['entry_price']} on {trade['entry_date']}") + print(f"Exit: ${trade['exit_price']} on {trade['exit_date']}") + print(f"P/L: ${profit_loss:.2f}") + print(f"Strategy: {trade['strategy']}") + if trade['notes']: + print(f"Notes: {trade['notes']}") + print("-" * 40) + + elif choice == "5": + break + def main(): print("\nStock Analysis System") print("1. Run CANSLIM Screener") print("2. Run Technical Scanners (SunnyBands/ATR-EMA)") - print("3. Launch Trading System") - print("4. Exit") + print("3. Launch Trading System") + print("4. Trading Journal") + print("5. Exit") - choice = input("\nSelect an option (1-4): ") + choice = input("\nSelect an option (1-5): ") if choice == "1": # 1️⃣ Ask user for start and end date @@ -117,6 +224,9 @@ def main(): trading_main() elif choice == "4": + journal_menu() + + elif choice == "5": print("Exiting...") return else: diff --git a/src/trading/journal.py b/src/trading/journal.py new file mode 100644 index 0000000..f5370c8 --- /dev/null +++ b/src/trading/journal.py @@ -0,0 +1,99 @@ +from datetime import datetime +from dataclasses import dataclass +from typing import Optional +from src.db.db_connection import create_client +from src.trading.position_calculator import PositionCalculator + +@dataclass +class TradeEntry: + ticker: str + entry_date: datetime + shares: int + entry_price: float + target_price: float + stop_loss: float + strategy: str + followed_rules: Optional[bool] = None + entry_reason: Optional[str] = None + exit_price: Optional[float] = None + exit_date: Optional[datetime] = None + exit_reason: Optional[str] = None + notes: Optional[str] = None + + @property + def expected_profit_loss(self) -> float: + return (self.target_price - self.entry_price) * self.shares + + @property + def max_loss(self) -> float: + return (self.stop_loss - self.entry_price) * self.shares + + @property + def actual_profit_loss(self) -> Optional[float]: + if self.exit_price: + return (self.exit_price - self.entry_price) * self.shares + return None + +def create_trades_table(): + with create_client() as client: + client.execute(""" + CREATE TABLE IF NOT EXISTS trades ( + id SERIAL PRIMARY KEY, + ticker VARCHAR(10) NOT NULL, + entry_date TIMESTAMP NOT NULL, + shares INTEGER NOT NULL, + entry_price DECIMAL(10,2) NOT NULL, + target_price DECIMAL(10,2) NOT NULL, + stop_loss DECIMAL(10,2) NOT NULL, + strategy VARCHAR(50) NOT NULL, + followed_rules BOOLEAN, + entry_reason TEXT, + exit_price DECIMAL(10,2), + exit_date TIMESTAMP, + exit_reason TEXT, + notes TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + """) + +def add_trade(trade: TradeEntry): + with create_client() as client: + client.execute(""" + INSERT INTO trades ( + ticker, entry_date, shares, entry_price, target_price, stop_loss, + strategy, followed_rules, entry_reason, exit_price, exit_date, + exit_reason, notes + ) VALUES ( + %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s + ) + """, ( + trade.ticker, trade.entry_date, trade.shares, trade.entry_price, + trade.target_price, trade.stop_loss, trade.strategy, trade.followed_rules, + trade.entry_reason, trade.exit_price, trade.exit_date, trade.exit_reason, + trade.notes + )) + +def update_trade_exit(trade_id: int, exit_price: float, exit_date: datetime, + followed_rules: bool, exit_reason: str, notes: Optional[str] = None): + with create_client() as client: + client.execute(""" + UPDATE trades + SET exit_price = %s, exit_date = %s, followed_rules = %s, + exit_reason = %s, notes = CASE WHEN %s IS NULL THEN notes ELSE %s END + WHERE id = %s + """, (exit_price, exit_date, followed_rules, exit_reason, notes, notes, trade_id)) + +def get_open_trades(): + with create_client() as client: + client.execute("SELECT * FROM trades WHERE exit_price IS NULL ORDER BY entry_date DESC") + return client.fetchall() + +def get_trade_history(limit: int = 50): + with create_client() as client: + client.execute(""" + SELECT * FROM trades + WHERE exit_price IS NOT NULL + ORDER BY exit_date DESC + LIMIT %s + """, (limit,)) + return client.fetchall()