|
|
""" |
|
|
Position analysis commands for the Folio CLI. |
|
|
|
|
|
This module provides commands for analyzing specific position groups. |
|
|
""" |
|
|
|
|
|
from typing import Any |
|
|
|
|
|
from src.focli.formatters import ( |
|
|
display_position_details, |
|
|
display_position_risk_analysis, |
|
|
display_position_simulation, |
|
|
) |
|
|
from src.focli.utils import find_position_group, parse_args |
|
|
from src.folio.simulator import generate_spy_changes, simulate_position_with_spy_changes |
|
|
|
|
|
|
|
|
def position_command(args: list[str], state: dict[str, Any], console): |
|
|
"""Analyze a specific position group. |
|
|
|
|
|
Args: |
|
|
args: Command arguments |
|
|
state: Application state |
|
|
console: Rich console for output |
|
|
""" |
|
|
|
|
|
if not state.get("portfolio_groups"): |
|
|
console.print("[bold red]Error:[/bold red] No portfolio loaded.") |
|
|
console.print("Use 'portfolio load <path>' to load a portfolio.") |
|
|
return |
|
|
|
|
|
|
|
|
if not args: |
|
|
console.print("[bold yellow]Usage:[/bold yellow] position <ticker> [options]") |
|
|
console.print("Type 'help position' for more information.") |
|
|
return |
|
|
|
|
|
|
|
|
ticker = args[0].upper() |
|
|
|
|
|
|
|
|
if len(args) > 1 and args[1] in ["details", "risk", "simulate"]: |
|
|
subcommand = args[1] |
|
|
position_args = args[2:] |
|
|
|
|
|
if subcommand == "details": |
|
|
position_details(ticker, position_args, state, console) |
|
|
elif subcommand == "risk": |
|
|
position_risk(ticker, position_args, state, console) |
|
|
elif subcommand == "simulate": |
|
|
position_simulate(ticker, position_args, state, console) |
|
|
else: |
|
|
|
|
|
position_args = args[1:] |
|
|
position_details(ticker, position_args, state, console) |
|
|
|
|
|
|
|
|
def position_details(ticker: str, args: list[str], state: dict[str, Any], console): |
|
|
"""Show detailed information about a position. |
|
|
|
|
|
Args: |
|
|
ticker: Ticker symbol |
|
|
args: Command arguments |
|
|
state: Application state |
|
|
console: Rich console for output |
|
|
""" |
|
|
|
|
|
arg_specs = { |
|
|
"detailed": { |
|
|
"type": bool, |
|
|
"default": True, |
|
|
"help": "Show detailed information", |
|
|
"aliases": ["-d", "--detailed", "--no-detailed"], |
|
|
} |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
parsed_args = parse_args(args, arg_specs) |
|
|
detailed = parsed_args["detailed"] |
|
|
|
|
|
|
|
|
group = find_position_group(ticker, state["portfolio_groups"]) |
|
|
|
|
|
if not group: |
|
|
console.print(f"[bold red]Position not found:[/bold red] {ticker}") |
|
|
return |
|
|
|
|
|
|
|
|
display_position_details(group, detailed, console) |
|
|
|
|
|
|
|
|
state["last_position"] = group |
|
|
|
|
|
except ValueError as e: |
|
|
console.print(f"[bold red]Error:[/bold red] {e!s}") |
|
|
except Exception as e: |
|
|
console.print(f"[bold red]Error analyzing position:[/bold red] {e!s}") |
|
|
import traceback |
|
|
|
|
|
console.print(traceback.format_exc()) |
|
|
|
|
|
|
|
|
def position_risk(ticker: str, args: list[str], state: dict[str, Any], console): |
|
|
"""Show risk analysis for a position. |
|
|
|
|
|
Args: |
|
|
ticker: Ticker symbol |
|
|
args: Command arguments |
|
|
state: Application state |
|
|
console: Rich console for output |
|
|
""" |
|
|
|
|
|
arg_specs = { |
|
|
"detailed": { |
|
|
"type": bool, |
|
|
"default": False, |
|
|
"help": "Show detailed information", |
|
|
"aliases": ["-d", "--detailed"], |
|
|
} |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
parsed_args = parse_args(args, arg_specs) |
|
|
detailed = parsed_args["detailed"] |
|
|
|
|
|
|
|
|
group = find_position_group(ticker, state["portfolio_groups"]) |
|
|
|
|
|
if not group: |
|
|
console.print(f"[bold red]Position not found:[/bold red] {ticker}") |
|
|
return |
|
|
|
|
|
|
|
|
display_position_risk_analysis(group, detailed, console) |
|
|
|
|
|
|
|
|
state["last_position"] = group |
|
|
|
|
|
except ValueError as e: |
|
|
console.print(f"[bold red]Error:[/bold red] {e!s}") |
|
|
except Exception as e: |
|
|
console.print(f"[bold red]Error analyzing position risk:[/bold red] {e!s}") |
|
|
import traceback |
|
|
|
|
|
console.print(traceback.format_exc()) |
|
|
|
|
|
|
|
|
def position_simulate(ticker: str, args: list[str], state: dict[str, Any], console): |
|
|
"""Simulate a position with SPY changes. |
|
|
|
|
|
Args: |
|
|
ticker: Ticker symbol |
|
|
args: Command arguments |
|
|
state: Application state |
|
|
console: Rich console for output |
|
|
""" |
|
|
|
|
|
arg_specs = { |
|
|
"range": { |
|
|
"type": float, |
|
|
"default": 20.0, |
|
|
"help": "SPY change range in percent", |
|
|
"aliases": ["-r", "--range"], |
|
|
}, |
|
|
"steps": { |
|
|
"type": int, |
|
|
"default": 13, |
|
|
"help": "Number of steps in the simulation", |
|
|
"aliases": ["-s", "--steps"], |
|
|
}, |
|
|
} |
|
|
|
|
|
try: |
|
|
|
|
|
parsed_args = parse_args(args, arg_specs) |
|
|
range_pct = parsed_args["range"] |
|
|
steps = parsed_args["steps"] |
|
|
|
|
|
|
|
|
group = find_position_group(ticker, state["portfolio_groups"]) |
|
|
|
|
|
if not group: |
|
|
console.print(f"[bold red]Position not found:[/bold red] {ticker}") |
|
|
return |
|
|
|
|
|
|
|
|
spy_changes = generate_spy_changes(range_pct, steps) |
|
|
|
|
|
|
|
|
console.print( |
|
|
f"[bold]Simulating {ticker} with SPY range ±{range_pct}% and {steps} steps...[/bold]" |
|
|
) |
|
|
|
|
|
|
|
|
results = simulate_position_with_spy_changes(group, spy_changes) |
|
|
|
|
|
|
|
|
if "position_simulations" not in state: |
|
|
state["position_simulations"] = {} |
|
|
state["position_simulations"][ticker] = results |
|
|
state["last_position"] = group |
|
|
|
|
|
|
|
|
display_position_simulation(results, console) |
|
|
|
|
|
except ValueError as e: |
|
|
console.print(f"[bold red]Error:[/bold red] {e!s}") |
|
|
except Exception as e: |
|
|
console.print(f"[bold red]Error simulating position:[/bold red] {e!s}") |
|
|
import traceback |
|
|
|
|
|
console.print(traceback.format_exc()) |
|
|
|