diff --git a/src/trading/journal.py b/src/trading/journal.py index fc79486..744ee36 100644 --- a/src/trading/journal.py +++ b/src/trading/journal.py @@ -13,12 +13,14 @@ class TradeEntry: target_price: float stop_loss: float strategy: str + order_type: str # New field for Market/Limit 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 + position_id: Optional[str] = None # New field to group related orders @property def expected_profit_loss(self) -> float: @@ -39,6 +41,7 @@ def create_trades_table(): query = """ CREATE TABLE IF NOT EXISTS stock_db.trades ( id UInt32, + position_id String, ticker String, entry_date DateTime, shares UInt32, @@ -46,6 +49,7 @@ def create_trades_table(): target_price Float64, stop_loss Float64, strategy String, + order_type String, followed_rules Nullable(UInt8), entry_reason Nullable(String), exit_price Nullable(Float64), @@ -54,7 +58,7 @@ def create_trades_table(): notes Nullable(String), created_at DateTime DEFAULT now() ) ENGINE = MergeTree() - ORDER BY (id, entry_date) + ORDER BY (position_id, id, entry_date) """ client.execute(query) @@ -62,15 +66,56 @@ def generate_id() -> int: """Generate a unique ID for the trade""" return int(datetime.now().timestamp() * 1000) +def generate_position_id(ticker: str) -> str: + """Generate a unique position ID for grouping related trades""" + timestamp = datetime.now().strftime("%Y%m%d%H%M%S") + return f"{ticker}_{timestamp}" + +def get_position_summary(ticker: str) -> dict: + """Get summary of existing positions for a ticker""" + with create_client() as client: + query = f""" + SELECT + position_id, + sum(shares) as total_shares, + sum(shares * entry_price) / sum(shares) as avg_entry_price, + min(entry_date) as first_entry, + max(entry_date) as last_entry, + count() as num_orders + FROM stock_db.trades + WHERE ticker = '{ticker}' + AND exit_price IS NULL + GROUP BY position_id + ORDER BY first_entry DESC + """ + result = client.execute(query) + columns = ['position_id', 'total_shares', 'avg_entry_price', + 'first_entry', 'last_entry', 'num_orders'] + return [dict(zip(columns, row)) for row in result] + +def get_order_type() -> str: + """Get order type from user""" + while True: + print("\nOrder Type:") + print("1. Market") + print("2. Limit") + choice = input("Select order type (1-2): ") + if choice == "1": + return "Market" + elif choice == "2": + return "Limit" + print("Invalid choice. Please try again.") + def add_trade(trade: TradeEntry): with create_client() as client: query = f""" INSERT INTO stock_db.trades ( - id, ticker, entry_date, shares, entry_price, target_price, stop_loss, - strategy, followed_rules, entry_reason, exit_price, exit_date, + id, position_id, ticker, entry_date, shares, entry_price, target_price, stop_loss, + strategy, order_type, followed_rules, entry_reason, exit_price, exit_date, exit_reason, notes ) VALUES ( {generate_id()}, + '{trade.position_id}', '{trade.ticker}', '{trade.entry_date.strftime('%Y-%m-%d %H:%M:%S')}', {trade.shares}, @@ -78,6 +123,7 @@ def add_trade(trade: TradeEntry): {trade.target_price}, {trade.stop_loss}, '{trade.strategy}', + '{trade.order_type}', {1 if trade.followed_rules else 0}, {f"'{trade.entry_reason}'" if trade.entry_reason else 'NULL'}, {trade.exit_price if trade.exit_price else 'NULL'}, @@ -142,11 +188,43 @@ def journal_menu(): if choice == "1": ticker = input("Enter ticker symbol: ").upper() + + # Show existing positions for this ticker + existing_positions = get_position_summary(ticker) + if existing_positions: + print(f"\nExisting {ticker} Positions:") + for pos in existing_positions: + print(f"\nPosition ID: {pos['position_id']}") + print(f"Total Shares: {pos['total_shares']}") + print(f"Average Entry: ${pos['avg_entry_price']:.2f}") + print(f"First Entry: {pos['first_entry']}") + print(f"Number of Orders: {pos['num_orders']}") + + add_to_existing = input("\nAdd to existing position? (y/n): ").lower() == 'y' + if add_to_existing: + position_id = input("Enter Position ID: ") + else: + position_id = generate_position_id(ticker) + else: + position_id = generate_position_id(ticker) + 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: ") + order_type = get_order_type() + + # If adding to existing position, get target/stop from existing + if existing_positions and add_to_existing: + # Use existing target and stop loss + target_price = float(input("Enter new target price (or press Enter to keep existing): ") or + existing_positions[0]['target_price']) + stop_loss = float(input("Enter new stop loss (or press Enter to keep existing): ") or + existing_positions[0]['stop_loss']) + strategy = input("Enter strategy name (or press Enter to keep existing): ") or existing_positions[0]['strategy'] + else: + 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 @@ -159,14 +237,25 @@ def journal_menu(): target_price=target_price, stop_loss=stop_loss, strategy=strategy, + order_type=order_type, + position_id=position_id, 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}") + + # Show updated position summary + updated_positions = get_position_summary(ticker) + if updated_positions: + pos = updated_positions[0] # Get the most recent position + print(f"\nUpdated Position Summary for {ticker}:") + print(f"Total Shares: {pos['total_shares']}") + print(f"Average Entry: ${pos['avg_entry_price']:.2f}") + print(f"Expected Profit: ${(target_price - pos['avg_entry_price']) * pos['total_shares']:.2f}") + print(f"Maximum Loss: ${(stop_loss - pos['avg_entry_price']) * pos['total_shares']:.2f}") + print("Trade added successfully!") elif choice == "2":