From b7d3b8bc6dcc2107f5debbb799b12099ade00afb Mon Sep 17 00:00:00 2001 From: "Bobby Abellana (aider)" Date: Thu, 6 Feb 2025 21:10:59 -0800 Subject: [PATCH] feat: Add trading app with position calculator and portfolio management --- src/trading/main.py | 87 ++++++++++++++++++++++++++++++ src/trading/portfolio.py | 44 +++++++++++++++ src/trading/position_calculator.py | 44 +++++++++++++++ 3 files changed, 175 insertions(+) create mode 100644 src/trading/main.py create mode 100644 src/trading/portfolio.py create mode 100644 src/trading/position_calculator.py diff --git a/src/trading/main.py b/src/trading/main.py new file mode 100644 index 0000000..5923cf5 --- /dev/null +++ b/src/trading/main.py @@ -0,0 +1,87 @@ +from datetime import datetime +from position_calculator import PositionCalculator +from portfolio import Portfolio, Position + +def main(): + # Initialize portfolio and position calculator + portfolio = Portfolio() + calculator = PositionCalculator(account_size=100000) # $100k account + + while True: + print("\nTrading Management System") + print("1. Calculate Position Size") + print("2. Add Position") + print("3. View Portfolio") + print("4. Remove Position") + print("5. Exit") + + choice = input("\nSelect an option (1-5): ") + + if choice == "1": + try: + entry_price = float(input("Enter entry price: ")) + stop_loss = float(input("Enter stop loss price: ")) + + position = calculator.calculate_position_size(entry_price, stop_loss) + + print("\nPosition Details:") + print(f"Shares: {position['shares']}") + print(f"Position Value: ${position['position_value']:.2f}") + print(f"Risk Amount: ${position['risk_amount']:.2f}") + print(f"Risk Per Share: ${position['risk_per_share']:.2f}") + + except ValueError as e: + print(f"Error: {e}") + + elif choice == "2": + try: + symbol = input("Enter symbol: ") + entry_price = float(input("Enter entry price: ")) + shares = int(input("Enter number of shares: ")) + stop_loss = float(input("Enter stop loss: ")) + target_price = float(input("Enter target price: ")) + + position = Position( + symbol=symbol, + entry_date=datetime.now(), + entry_price=entry_price, + shares=shares, + stop_loss=stop_loss, + target_price=target_price + ) + + portfolio.add_position(position) + print(f"\nAdded position: {symbol}") + + except ValueError: + print("Invalid input. Please try again.") + + elif choice == "3": + positions = portfolio.get_position_summary() + if not positions: + print("\nNo positions in portfolio") + else: + print("\nCurrent Portfolio:") + for pos in positions: + print(f"\nSymbol: {pos['symbol']}") + print(f"Entry Date: {pos['entry_date']}") + print(f"Entry Price: ${pos['entry_price']:.2f}") + print(f"Shares: {pos['shares']}") + print(f"Current Value: ${pos['current_value']:.2f}") + print(f"Stop Loss: ${pos['stop_loss']:.2f}") + print(f"Target: ${pos['target_price']:.2f}") + + elif choice == "4": + symbol = input("Enter symbol to remove: ") + portfolio.remove_position(symbol) + print(f"\nRemoved position: {symbol}") + + elif choice == "5": + print("\nExiting Trading Management System") + break + + else: + print("\nInvalid choice. Please try again.") + +if __name__ == "__main__": + main() diff --git a/src/trading/portfolio.py b/src/trading/portfolio.py new file mode 100644 index 0000000..c301650 --- /dev/null +++ b/src/trading/portfolio.py @@ -0,0 +1,44 @@ +from typing import List, Dict +from dataclasses import dataclass +from datetime import datetime + +@dataclass +class Position: + symbol: str + entry_date: datetime + entry_price: float + shares: int + stop_loss: float + target_price: float + + @property + def current_value(self) -> float: + # TODO: Implement real-time price fetching + return self.shares * self.entry_price + +class Portfolio: + def __init__(self): + self.positions: List[Position] = [] + + def add_position(self, position: Position): + """Add a new position to the portfolio""" + self.positions.append(position) + + def remove_position(self, symbol: str): + """Remove a position from the portfolio by symbol""" + self.positions = [p for p in self.positions if p.symbol != symbol] + + def get_position_summary(self) -> List[Dict]: + """Get summary of all positions""" + return [ + { + "symbol": p.symbol, + "entry_date": p.entry_date, + "entry_price": p.entry_price, + "shares": p.shares, + "current_value": p.current_value, + "stop_loss": p.stop_loss, + "target_price": p.target_price + } + for p in self.positions + ] diff --git a/src/trading/position_calculator.py b/src/trading/position_calculator.py new file mode 100644 index 0000000..563cf42 --- /dev/null +++ b/src/trading/position_calculator.py @@ -0,0 +1,44 @@ +class PositionCalculator: + def __init__(self, account_size: float, risk_percentage: float = 1.0): + """ + Initialize position calculator with account size and risk percentage + + Args: + account_size (float): Total trading account value + risk_percentage (float): Maximum risk per trade as percentage (default 1%) + """ + self.account_size = account_size + self.risk_percentage = risk_percentage / 100.0 # Convert to decimal + + def calculate_position_size(self, entry_price: float, stop_loss: float) -> dict: + """ + Calculate position size based on risk parameters + + Args: + entry_price (float): Planned entry price + stop_loss (float): Stop loss price + + Returns: + dict: Position details including shares and dollar amounts + """ + # Calculate risk amount in dollars + risk_amount = self.account_size * self.risk_percentage + + # Calculate per-share risk + risk_per_share = abs(entry_price - stop_loss) + + if risk_per_share == 0: + raise ValueError("Entry price cannot equal stop loss price") + + # Calculate number of shares + shares = int(risk_amount / risk_per_share) + + # Calculate total position value + position_value = shares * entry_price + + return { + "shares": shares, + "position_value": position_value, + "risk_amount": risk_amount, + "risk_per_share": risk_per_share + }