feat: Add FIFO sell order handling in trade journal
This commit is contained in:
parent
952dfcd266
commit
8ea233ff7c
@ -7,6 +7,98 @@ from db.db_connection import create_client
|
||||
from trading.position_calculator import PositionCalculator
|
||||
from utils.data_utils import get_user_input
|
||||
|
||||
def handle_sell_order(ticker: str, shares_to_sell: int, exit_price: float, exit_date: datetime,
|
||||
order_type: str, followed_rules: bool, exit_reason: str, notes: Optional[str] = None) -> bool:
|
||||
"""
|
||||
Handle sell order using FIFO logic
|
||||
|
||||
Args:
|
||||
ticker (str): Stock ticker
|
||||
shares_to_sell (int): Number of shares to sell
|
||||
exit_price (float): Exit price per share
|
||||
exit_date (datetime): Exit date and time
|
||||
order_type (str): Order type (Market/Limit)
|
||||
followed_rules (bool): Whether trading rules were followed
|
||||
exit_reason (str): Reason for exit
|
||||
notes (Optional[str]): Additional notes
|
||||
|
||||
Returns:
|
||||
bool: True if sell order was processed successfully
|
||||
"""
|
||||
with create_client() as client:
|
||||
# Get open positions for this ticker ordered by entry date (FIFO)
|
||||
query = f"""
|
||||
SELECT id, shares, entry_price, position_id
|
||||
FROM stock_db.trades
|
||||
WHERE ticker = '{ticker}'
|
||||
AND exit_price IS NULL
|
||||
ORDER BY entry_date ASC
|
||||
"""
|
||||
result = client.query(query).result_rows
|
||||
|
||||
if not result:
|
||||
print(f"No open positions found for {ticker}")
|
||||
return False
|
||||
|
||||
remaining_shares = shares_to_sell
|
||||
positions = [dict(zip(['id', 'shares', 'entry_price', 'position_id'], row)) for row in result]
|
||||
total_available_shares = sum(pos['shares'] for pos in positions)
|
||||
|
||||
if shares_to_sell > total_available_shares:
|
||||
print(f"Error: Attempting to sell {shares_to_sell} shares but only {total_available_shares} available")
|
||||
return False
|
||||
|
||||
for position in positions:
|
||||
if remaining_shares <= 0:
|
||||
break
|
||||
|
||||
shares_from_position = min(remaining_shares, position['shares'])
|
||||
|
||||
if shares_from_position == position['shares']:
|
||||
# Close entire position
|
||||
update_trade_exit(
|
||||
trade_id=position['id'],
|
||||
exit_price=exit_price,
|
||||
exit_date=exit_date,
|
||||
followed_rules=followed_rules,
|
||||
exit_reason=exit_reason,
|
||||
notes=notes
|
||||
)
|
||||
else:
|
||||
# Split position: update original position with remaining shares
|
||||
# and create a new closed position for the sold shares
|
||||
new_position_shares = position['shares'] - shares_from_position
|
||||
|
||||
# Update original position with reduced shares
|
||||
client.command(f"""
|
||||
ALTER TABLE stock_db.trades
|
||||
UPDATE shares = {new_position_shares}
|
||||
WHERE id = {position['id']}
|
||||
""")
|
||||
|
||||
# Create new record for the sold portion
|
||||
trade = TradeEntry(
|
||||
ticker=ticker,
|
||||
entry_date=datetime.now(), # Use original entry date?
|
||||
shares=shares_from_position,
|
||||
entry_price=position['entry_price'],
|
||||
target_price=0, # Not relevant for closed portion
|
||||
stop_loss=0, # Not relevant for closed portion
|
||||
strategy="FIFO_SPLIT",
|
||||
order_type=order_type,
|
||||
position_id=position['position_id'],
|
||||
followed_rules=followed_rules,
|
||||
exit_price=exit_price,
|
||||
exit_date=exit_date,
|
||||
exit_reason=exit_reason,
|
||||
notes=notes
|
||||
)
|
||||
add_trade(trade)
|
||||
|
||||
remaining_shares -= shares_from_position
|
||||
|
||||
return True
|
||||
|
||||
def create_portfolio_table():
|
||||
with create_client() as client:
|
||||
query = """
|
||||
@ -343,6 +435,55 @@ def journal_menu():
|
||||
continue
|
||||
ticker = ticker.upper()
|
||||
|
||||
# Ask if this is a buy or sell order
|
||||
print("\nOrder Direction:")
|
||||
print("1. Buy")
|
||||
print("2. Sell")
|
||||
direction = get_user_input("Select direction (1-2):", str)
|
||||
if direction is None:
|
||||
continue
|
||||
|
||||
if direction not in ["1", "2"]:
|
||||
print("Invalid direction")
|
||||
continue
|
||||
|
||||
shares = get_user_input("Enter number of shares:", int)
|
||||
if shares is None:
|
||||
continue
|
||||
|
||||
if direction == "2": # Sell order
|
||||
exit_price = get_user_input("Enter exit price:", float)
|
||||
if exit_price is None:
|
||||
continue
|
||||
|
||||
exit_date = get_datetime_input("Enter exit date and time", default=datetime.now())
|
||||
if exit_date is None:
|
||||
continue
|
||||
|
||||
order_type = get_order_type()
|
||||
if order_type is None:
|
||||
continue
|
||||
|
||||
followed_rules = get_user_input("Did you follow your rules? (y/n):", bool)
|
||||
if followed_rules is None:
|
||||
continue
|
||||
|
||||
exit_reason = input("Enter exit reason: ")
|
||||
notes = input("Additional notes (optional): ") or None
|
||||
|
||||
if handle_sell_order(
|
||||
ticker=ticker,
|
||||
shares_to_sell=shares,
|
||||
exit_price=exit_price,
|
||||
exit_date=exit_date,
|
||||
order_type=order_type,
|
||||
followed_rules=followed_rules,
|
||||
exit_reason=exit_reason,
|
||||
notes=notes
|
||||
):
|
||||
print("Sell order processed successfully!")
|
||||
continue
|
||||
|
||||
# Show existing positions for this ticker
|
||||
existing_positions = get_position_summary(ticker)
|
||||
if existing_positions:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user