feat: Enhance trading journal with multi-order position tracking and order types

This commit is contained in:
Bobby (aider) 2025-02-10 09:38:16 -08:00
parent ab5a2a5959
commit e906c63893

View File

@ -13,12 +13,14 @@ class TradeEntry:
target_price: float target_price: float
stop_loss: float stop_loss: float
strategy: str strategy: str
order_type: str # New field for Market/Limit
followed_rules: Optional[bool] = None followed_rules: Optional[bool] = None
entry_reason: Optional[str] = None entry_reason: Optional[str] = None
exit_price: Optional[float] = None exit_price: Optional[float] = None
exit_date: Optional[datetime] = None exit_date: Optional[datetime] = None
exit_reason: Optional[str] = None exit_reason: Optional[str] = None
notes: Optional[str] = None notes: Optional[str] = None
position_id: Optional[str] = None # New field to group related orders
@property @property
def expected_profit_loss(self) -> float: def expected_profit_loss(self) -> float:
@ -39,6 +41,7 @@ def create_trades_table():
query = """ query = """
CREATE TABLE IF NOT EXISTS stock_db.trades ( CREATE TABLE IF NOT EXISTS stock_db.trades (
id UInt32, id UInt32,
position_id String,
ticker String, ticker String,
entry_date DateTime, entry_date DateTime,
shares UInt32, shares UInt32,
@ -46,6 +49,7 @@ def create_trades_table():
target_price Float64, target_price Float64,
stop_loss Float64, stop_loss Float64,
strategy String, strategy String,
order_type String,
followed_rules Nullable(UInt8), followed_rules Nullable(UInt8),
entry_reason Nullable(String), entry_reason Nullable(String),
exit_price Nullable(Float64), exit_price Nullable(Float64),
@ -54,7 +58,7 @@ def create_trades_table():
notes Nullable(String), notes Nullable(String),
created_at DateTime DEFAULT now() created_at DateTime DEFAULT now()
) ENGINE = MergeTree() ) ENGINE = MergeTree()
ORDER BY (id, entry_date) ORDER BY (position_id, id, entry_date)
""" """
client.execute(query) client.execute(query)
@ -62,15 +66,56 @@ def generate_id() -> int:
"""Generate a unique ID for the trade""" """Generate a unique ID for the trade"""
return int(datetime.now().timestamp() * 1000) 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): def add_trade(trade: TradeEntry):
with create_client() as client: with create_client() as client:
query = f""" query = f"""
INSERT INTO stock_db.trades ( INSERT INTO stock_db.trades (
id, ticker, entry_date, shares, entry_price, target_price, stop_loss, id, position_id, ticker, entry_date, shares, entry_price, target_price, stop_loss,
strategy, followed_rules, entry_reason, exit_price, exit_date, strategy, order_type, followed_rules, entry_reason, exit_price, exit_date,
exit_reason, notes exit_reason, notes
) VALUES ( ) VALUES (
{generate_id()}, {generate_id()},
'{trade.position_id}',
'{trade.ticker}', '{trade.ticker}',
'{trade.entry_date.strftime('%Y-%m-%d %H:%M:%S')}', '{trade.entry_date.strftime('%Y-%m-%d %H:%M:%S')}',
{trade.shares}, {trade.shares},
@ -78,6 +123,7 @@ def add_trade(trade: TradeEntry):
{trade.target_price}, {trade.target_price},
{trade.stop_loss}, {trade.stop_loss},
'{trade.strategy}', '{trade.strategy}',
'{trade.order_type}',
{1 if trade.followed_rules else 0}, {1 if trade.followed_rules else 0},
{f"'{trade.entry_reason}'" if trade.entry_reason else 'NULL'}, {f"'{trade.entry_reason}'" if trade.entry_reason else 'NULL'},
{trade.exit_price if trade.exit_price else 'NULL'}, {trade.exit_price if trade.exit_price else 'NULL'},
@ -142,11 +188,43 @@ def journal_menu():
if choice == "1": if choice == "1":
ticker = input("Enter ticker symbol: ").upper() 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: ")) shares = int(input("Enter number of shares: "))
entry_price = float(input("Enter entry price: ")) entry_price = float(input("Enter entry price: "))
target_price = float(input("Enter target price: ")) order_type = get_order_type()
stop_loss = float(input("Enter stop loss: "))
strategy = input("Enter strategy name: ") # 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' followed_rules = input("Did you follow your rules? (y/n): ").lower() == 'y'
entry_reason = input("Enter entry reason (optional): ") or None entry_reason = input("Enter entry reason (optional): ") or None
notes = input("Additional notes (optional): ") or None notes = input("Additional notes (optional): ") or None
@ -159,14 +237,25 @@ def journal_menu():
target_price=target_price, target_price=target_price,
stop_loss=stop_loss, stop_loss=stop_loss,
strategy=strategy, strategy=strategy,
order_type=order_type,
position_id=position_id,
followed_rules=followed_rules, followed_rules=followed_rules,
entry_reason=entry_reason, entry_reason=entry_reason,
notes=notes notes=notes
) )
add_trade(trade) 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!") print("Trade added successfully!")
elif choice == "2": elif choice == "2":