feat: Enhance trading journal with multi-order position tracking and order types
This commit is contained in:
parent
ab5a2a5959
commit
e906c63893
@ -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":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user