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 trading.position_calculator import PositionCalculator
|
||||||
from utils.data_utils import get_user_input
|
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():
|
def create_portfolio_table():
|
||||||
with create_client() as client:
|
with create_client() as client:
|
||||||
query = """
|
query = """
|
||||||
@ -343,6 +435,55 @@ def journal_menu():
|
|||||||
continue
|
continue
|
||||||
ticker = ticker.upper()
|
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
|
# Show existing positions for this ticker
|
||||||
existing_positions = get_position_summary(ticker)
|
existing_positions = get_position_summary(ticker)
|
||||||
if existing_positions:
|
if existing_positions:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user