feat: Add trading journal feature with database tracking
This commit is contained in:
parent
937e52f2f7
commit
590c6fd2b7
116
src/main.py
116
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:
|
||||
|
||||
99
src/trading/journal.py
Normal file
99
src/trading/journal.py
Normal file
@ -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()
|
||||
Loading…
Reference in New Issue
Block a user