@drico2008/fincli 0.1.3 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +909 -684
  3. package/fincli/__init__.py +3 -3
  4. package/fincli/app/agents/__init__.py +5 -0
  5. package/fincli/app/agents/registry.py +76 -0
  6. package/fincli/app/analysis/ai_prompts.py +23 -16
  7. package/fincli/app/analysis/analyzer.py +107 -100
  8. package/fincli/app/analysis/assistant_context.py +187 -160
  9. package/fincli/app/analysis/backtest.py +179 -0
  10. package/fincli/app/analysis/gameplay_plan.py +79 -0
  11. package/fincli/app/analysis/indicators.py +1 -1
  12. package/fincli/app/analysis/market_structure.py +1 -1
  13. package/fincli/app/analysis/multi_timeframe.py +180 -0
  14. package/fincli/app/analysis/trading_methods.py +144 -0
  15. package/fincli/app/cli/commands.py +105 -77
  16. package/fincli/app/cli/router.py +2143 -1121
  17. package/fincli/app/connectors/__init__.py +5 -0
  18. package/fincli/app/connectors/catalog.py +148 -0
  19. package/fincli/app/connectors/news_connectors.py +412 -0
  20. package/fincli/app/modules/alerts.py +80 -0
  21. package/fincli/app/modules/economic_calendar.py +374 -1
  22. package/fincli/app/modules/reports.py +151 -0
  23. package/fincli/app/modules/scanner.py +111 -93
  24. package/fincli/app/modules/session_history.py +113 -0
  25. package/fincli/app/modules/transactions.py +84 -84
  26. package/fincli/app/modules/user_profile.py +84 -0
  27. package/fincli/app/plugins/loader.py +72 -0
  28. package/fincli/app/providers/ai/anthropic_provider.py +8 -7
  29. package/fincli/app/providers/ai/gemini_provider.py +8 -7
  30. package/fincli/app/providers/ai/groq_provider.py +8 -7
  31. package/fincli/app/providers/ai/huggingface_provider.py +8 -7
  32. package/fincli/app/providers/ai/manager.py +60 -60
  33. package/fincli/app/providers/ai/openai_provider.py +8 -7
  34. package/fincli/app/providers/ai/openrouter_provider.py +8 -7
  35. package/fincli/app/providers/ai/together_provider.py +8 -7
  36. package/fincli/app/providers/market/alphavantage_provider.py +194 -0
  37. package/fincli/app/providers/market/base.py +98 -77
  38. package/fincli/app/providers/market/custom_provider.py +186 -169
  39. package/fincli/app/providers/market/manager.py +85 -2
  40. package/fincli/app/providers/market/news_provider.py +4 -4
  41. package/fincli/app/providers/market/symbols.py +143 -0
  42. package/fincli/app/providers/market/twelvedata_provider.py +167 -167
  43. package/fincli/app/providers/market/yfinance_provider.py +1 -1
  44. package/fincli/app/research/__init__.py +7 -0
  45. package/fincli/app/research/engine.py +75 -0
  46. package/fincli/app/research/formatter.py +22 -0
  47. package/fincli/app/research/models.py +18 -0
  48. package/fincli/app/research/prompt_builder.py +47 -0
  49. package/fincli/app/services/macro_data.py +50 -0
  50. package/fincli/app/services/market_data.py +203 -203
  51. package/fincli/app/services/news_aggregator.py +90 -0
  52. package/fincli/app/services/web_research.py +267 -0
  53. package/fincli/app/storage/cache.py +2 -2
  54. package/fincli/app/storage/config.py +122 -88
  55. package/fincli/app/storage/database.py +201 -85
  56. package/fincli/app/storage/secrets.py +12 -3
  57. package/fincli/app/tui/components.py +68 -50
  58. package/fincli/app/tui/layout.py +270 -258
  59. package/fincli/app/tui/market_provider_selector.py +6 -1
  60. package/fincli/app/tui/model_selector.py +11 -3
  61. package/fincli/app/tui/theme.py +134 -74
  62. package/fincli/app/utils/formatting.py +125 -12
  63. package/npm/bin/fincli.js +9 -2
  64. package/package.json +23 -23
  65. package/pyproject.toml +35 -35
@@ -1,3 +1,3 @@
1
- """FinCLI package."""
2
-
3
- __version__ = "0.1.3"
1
+ """FinCLI package."""
2
+
3
+ __version__ = "0.2.2"
@@ -0,0 +1,5 @@
1
+ """AI agent framework registry."""
2
+
3
+ from fincli.app.agents.registry import Agent, AgentRegistry
4
+
5
+ __all__ = ["Agent", "AgentRegistry"]
@@ -0,0 +1,76 @@
1
+ """Curated financial agent registry for FinCLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class Agent:
10
+ slug: str
11
+ name: str
12
+ category: str
13
+ framework: str
14
+ role: str
15
+
16
+
17
+ class AgentRegistry:
18
+ """37-agent catalog across trader, investor, economic, and geopolitics frameworks."""
19
+
20
+ def __init__(self, agents: tuple[Agent, ...] | None = None) -> None:
21
+ self._agents = agents or AGENTS
22
+
23
+ def all(self) -> tuple[Agent, ...]:
24
+ return self._agents
25
+
26
+ def categories(self) -> tuple[str, ...]:
27
+ return tuple(sorted({agent.category for agent in self._agents}))
28
+
29
+ def get(self, slug: str) -> Agent | None:
30
+ normalized = slug.strip().lower()
31
+ return next((agent for agent in self._agents if agent.slug == normalized), None)
32
+
33
+ def by_category(self, category: str) -> list[Agent]:
34
+ normalized = category.strip().lower()
35
+ return [agent for agent in self._agents if agent.category == normalized]
36
+
37
+
38
+ AGENTS: tuple[Agent, ...] = (
39
+ Agent("buffett", "Warren Buffett", "investor", "quality value", "Business quality, moat, cashflow durability."),
40
+ Agent("graham", "Benjamin Graham", "investor", "deep value", "Margin of safety and balance-sheet conservatism."),
41
+ Agent("lynch", "Peter Lynch", "investor", "growth at reasonable price", "Story, earnings growth, and valuation discipline."),
42
+ Agent("munger", "Charlie Munger", "investor", "mental models", "Incentives, durability, and avoiding stupidity."),
43
+ Agent("klarman", "Seth Klarman", "investor", "risk-first value", "Downside protection and asymmetric payoff."),
44
+ Agent("marks", "Howard Marks", "investor", "cycle risk", "Market cycle, credit risk, and second-level thinking."),
45
+ Agent("fisher", "Philip Fisher", "investor", "scuttlebutt growth", "Qualitative growth and management quality."),
46
+ Agent("dalio", "Ray Dalio", "economic", "macro regime", "Debt cycle, liquidity, rates, and diversification."),
47
+ Agent("soros", "George Soros", "trader", "reflexivity", "Crowded positioning and feedback loops."),
48
+ Agent("livermore", "Jesse Livermore", "trader", "price action", "Trend following, pivots, and discipline."),
49
+ Agent("wyckoff", "Richard Wyckoff", "trader", "accumulation distribution", "Volume, composite operator, and phase analysis."),
50
+ Agent("minervini", "Mark Minervini", "trader", "momentum risk", "Relative strength, volatility contraction, and tight risk."),
51
+ Agent("oneil", "William O'Neil", "trader", "CAN SLIM", "Earnings momentum, leadership, and breakout quality."),
52
+ Agent("tudor", "Paul Tudor Jones", "trader", "macro trading", "Asymmetric trades, trend, and risk control."),
53
+ Agent("druckenmiller", "Stanley Druckenmiller", "trader", "concentrated macro", "Liquidity, policy, and high-conviction asymmetry."),
54
+ Agent("seykota", "Ed Seykota", "trader", "systematic trend", "Trend rules, stops, and emotional control."),
55
+ Agent("volatility", "Volatility Analyst", "trader", "volatility regime", "ATR, options pressure, VIX, and realized volatility."),
56
+ Agent("liquidity", "Liquidity Analyst", "trader", "market microstructure", "Liquidity zones, stops, gaps, and execution risk."),
57
+ Agent("snr", "Support Resistance Analyst", "trader", "SNR and pivots", "Pivot highs/lows, breakouts, rejection, and volume."),
58
+ Agent("volume", "Volume Analyst", "trader", "volume confirmation", "Participation, abnormal volume, and failed moves."),
59
+ Agent("risk", "Risk Manager", "trader", "position risk", "Invalidation, SL/TP, RR, and exposure control."),
60
+ Agent("fed", "Federal Reserve Analyst", "economic", "US monetary policy", "Rates, inflation, labor, and dollar liquidity."),
61
+ Agent("ecb", "ECB Analyst", "economic", "Europe macro", "Euro area rates, inflation, PMI, and credit conditions."),
62
+ Agent("boj", "BOJ Analyst", "economic", "Japan macro", "Yield control, JPY, inflation, and carry trades."),
63
+ Agent("bi", "Bank Indonesia Analyst", "economic", "Indonesia macro", "BI rate, IDR, inflation, and capital flows."),
64
+ Agent("fred", "FRED Macro Analyst", "economic", "macro indicators", "US time series and regime confirmation."),
65
+ Agent("imf", "IMF Analyst", "economic", "global macro", "Growth, debt, current account, and country risk."),
66
+ Agent("worldbank", "World Bank Analyst", "economic", "development macro", "Long-term country indicators and structural trend."),
67
+ Agent("commodity", "Commodity Macro Analyst", "economic", "commodity cycle", "Energy, metals, weather, and supply chain."),
68
+ Agent("credit", "Credit Analyst", "economic", "credit spreads", "Default risk, spreads, and funding stress."),
69
+ Agent("geopolitics", "Geopolitical Strategist", "geopolitics", "event risk", "Conflict, sanctions, elections, and risk premium."),
70
+ Agent("energygeo", "Energy Geopolitics Analyst", "geopolitics", "energy security", "Oil, gas, shipping routes, and supply shocks."),
71
+ Agent("china", "China Policy Analyst", "geopolitics", "China policy", "Credit impulse, regulation, property, and geopolitics."),
72
+ Agent("supplychain", "Supply Chain Analyst", "geopolitics", "trade routes", "Shipping, logistics, and operational disruptions."),
73
+ Agent("fxgeo", "FX Geopolitics Analyst", "geopolitics", "currency risk", "Sovereign risk, reserves, policy, and flows."),
74
+ Agent("sentiment", "Sentiment Analyst", "trader", "news sentiment", "Market tone, crowding, and narrative shifts."),
75
+ Agent("judge", "FinCLI Judge", "trader", "evidence arbitration", "Weighs bull, bear, and caution cases into final scenario."),
76
+ )
@@ -1,19 +1,20 @@
1
- """Prompt templates for AI market analysis."""
2
-
3
- MARKET_ANALYSIS_PROMPT = """
4
- You are FinCLI's market analysis assistant.
5
-
6
- Rules:
7
- - Analyze only from the provided OHLCV, indicators, market structure, and news/fundamental context.
1
+ """Prompt templates for AI market analysis."""
2
+
3
+ MARKET_ANALYSIS_PROMPT = """
4
+ You are FinCLI's market analysis assistant.
5
+
6
+ Rules:
7
+ - Analyze only from the provided OHLCV, indicators, market structure, and news/fundamental context.
8
8
  - Treat the provided Signal Assessment as a rule-based candidate signal, not a guaranteed trade instruction.
9
- - Do not invent prices, news, fundamentals, or certainty.
10
- - If data is missing, state that data quality is insufficient.
11
- - Use probabilistic scenario language, not guaranteed entry signals.
12
- - If discussing buy/sell, phrase it as candidate bias with confirmation and invalidation conditions.
13
- - Do not promise profit.
14
- - Keep the output structured and concise.
15
- - Add a short non-financial-advice disclaimer.
16
-
9
+ - Treat the provided User Gameplay Profile as a risk constraint for SL/TP sizing and scenario wording.
10
+ - Do not invent prices, news, fundamentals, or certainty.
11
+ - If data is missing, state that data quality is insufficient.
12
+ - Use probabilistic scenario language, not guaranteed entry signals.
13
+ - If discussing buy/sell, phrase it as candidate bias with confirmation and invalidation conditions.
14
+ - Do not promise profit.
15
+ - Keep the output structured and concise.
16
+ - Add a short non-financial-advice disclaimer.
17
+
17
18
  Required output:
18
19
  Instrument:
19
20
  Timeframe:
@@ -24,10 +25,16 @@ Key Levels:
24
25
  Technical Indicators:
25
26
  Market Structure:
26
27
  Signal Assessment:
28
+ Signal:
29
+ SL:
30
+ TP1:
31
+ TP2:
32
+ TP3:
33
+ Reason:
27
34
  News/Fundamental Context:
28
35
  Bullish Scenario:
29
36
  Bearish Scenario:
30
37
  Risk Notes:
31
38
  Conclusion:
32
39
  Disclaimer:
33
- """
40
+ """
@@ -1,20 +1,21 @@
1
- """Analysis prompt orchestration."""
2
-
3
- from __future__ import annotations
4
-
5
- from fincli.app.analysis.ai_prompts import MARKET_ANALYSIS_PROMPT
6
- from fincli.app.analysis.indicators import TechnicalSummary
1
+ """Analysis prompt orchestration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from fincli.app.analysis.ai_prompts import MARKET_ANALYSIS_PROMPT
6
+ from fincli.app.analysis.indicators import TechnicalSummary
7
7
  from fincli.app.analysis.market_structure import MarketStructureSummary
8
8
  from fincli.app.analysis.technical_debate import format_debate, run_technical_debate
9
9
  from fincli.app.analysis.technical_signal import format_signal
10
+ from fincli.app.analysis.trading_methods import analyze_trading_methods, format_trading_methods_context
10
11
  from fincli.app.providers.market.base import Candle
11
-
12
-
13
- def market_analysis_prompt() -> str:
14
- """Return the configured market analysis prompt template."""
15
- return MARKET_ANALYSIS_PROMPT.strip()
16
-
17
-
12
+
13
+
14
+ def market_analysis_prompt() -> str:
15
+ """Return the configured market analysis prompt template."""
16
+ return MARKET_ANALYSIS_PROMPT.strip()
17
+
18
+
18
19
  def build_market_analysis_prompt(
19
20
  symbol: str,
20
21
  timeframe: str,
@@ -22,98 +23,104 @@ def build_market_analysis_prompt(
22
23
  technical: TechnicalSummary,
23
24
  structure: MarketStructureSummary | None = None,
24
25
  news_context: str = "No news/fundamental context provided.",
26
+ user_gameplay_context: str = "User Gameplay Profile: not configured.",
27
+ trading_methods_context: str = "",
25
28
  ) -> str:
26
- """Build a structured AI prompt from market data and computed indicators."""
27
- recent = candles[-10:]
28
- ohlcv_lines = [
29
- (
30
- f"- {candle.timestamp.isoformat(timespec='seconds')}: "
31
- f"O={candle.open:.4f} H={candle.high:.4f} L={candle.low:.4f} "
32
- f"C={candle.close:.4f} V={candle.volume:.0f}"
33
- )
34
- for candle in recent
35
- ]
36
- indicator_lines = [
37
- f"Latest Close: {_fmt(technical.latest_close)}",
38
- f"Trend Bias: {technical.trend_bias}",
39
- f"SMA 5: {_fmt(technical.sma_fast)}",
40
- f"SMA 20: {_fmt(technical.sma_slow)}",
41
- f"EMA 12: {_fmt(technical.ema_fast)}",
42
- f"RSI 14: {_fmt(technical.rsi)}",
43
- f"MACD: {_fmt(technical.macd)}",
44
- f"MACD Signal: {_fmt(technical.macd_signal)}",
45
- f"Bollinger Upper: {_fmt(technical.bollinger_upper)}",
46
- f"Bollinger Lower: {_fmt(technical.bollinger_lower)}",
47
- f"ATR 14: {_fmt(technical.atr)}",
48
- f"Support: {_fmt(technical.support)}",
49
- f"Resistance: {_fmt(technical.resistance)}",
50
- ]
51
- debate = run_technical_debate(technical, structure, candles) if structure is not None else None
52
- return (
53
- f"{market_analysis_prompt()}\n\n"
54
- f"Instrument: {symbol}\n"
55
- f"Timeframe: {timeframe}\n"
56
- f"Data Quality: {len(candles)} candles available from provider.\n\n"
57
- "Recent OHLCV:\n"
58
- f"{chr(10).join(ohlcv_lines)}\n\n"
59
- "Computed Indicators:\n"
60
- f"{chr(10).join(indicator_lines)}\n\n"
61
- "Market Structure:\n"
62
- f"{_format_structure(structure)}\n\n"
29
+ """Build a structured AI prompt from market data and computed indicators."""
30
+ recent = candles[-10:]
31
+ ohlcv_lines = [
32
+ (
33
+ f"- {candle.timestamp.isoformat(timespec='seconds')}: "
34
+ f"O={candle.open:.4f} H={candle.high:.4f} L={candle.low:.4f} "
35
+ f"C={candle.close:.4f} V={candle.volume:.0f}"
36
+ )
37
+ for candle in recent
38
+ ]
39
+ indicator_lines = [
40
+ f"Latest Close: {_fmt(technical.latest_close)}",
41
+ f"Trend Bias: {technical.trend_bias}",
42
+ f"SMA 5: {_fmt(technical.sma_fast)}",
43
+ f"SMA 20: {_fmt(technical.sma_slow)}",
44
+ f"EMA 12: {_fmt(technical.ema_fast)}",
45
+ f"RSI 14: {_fmt(technical.rsi)}",
46
+ f"MACD: {_fmt(technical.macd)}",
47
+ f"MACD Signal: {_fmt(technical.macd_signal)}",
48
+ f"Bollinger Upper: {_fmt(technical.bollinger_upper)}",
49
+ f"Bollinger Lower: {_fmt(technical.bollinger_lower)}",
50
+ f"ATR 14: {_fmt(technical.atr)}",
51
+ f"Support: {_fmt(technical.support)}",
52
+ f"Resistance: {_fmt(technical.resistance)}",
53
+ ]
54
+ debate = run_technical_debate(technical, structure, candles) if structure is not None else None
55
+ return (
56
+ f"{market_analysis_prompt()}\n\n"
57
+ f"Instrument: {symbol}\n"
58
+ f"Timeframe: {timeframe}\n"
59
+ f"Data Quality: {len(candles)} candles available from provider.\n\n"
60
+ "Recent OHLCV:\n"
61
+ f"{chr(10).join(ohlcv_lines)}\n\n"
62
+ "Computed Indicators:\n"
63
+ f"{chr(10).join(indicator_lines)}\n\n"
64
+ "Market Structure:\n"
65
+ f"{_format_structure(structure)}\n\n"
63
66
  "Signal Assessment:\n"
64
67
  f"{format_signal(debate.judge_signal) if debate is not None else 'No signal assessment available.'}\n\n"
65
68
  "Technical Debate:\n"
66
69
  f"{format_debate(debate) if debate is not None else 'No technical debate available.'}\n\n"
70
+ "Trading Method Context:\n"
71
+ f"{trading_methods_context or format_trading_methods_context(analyze_trading_methods(candles))}\n\n"
72
+ "User Gameplay Context:\n"
73
+ f"{user_gameplay_context}\n\n"
67
74
  "News/Fundamental Context:\n"
68
75
  f"{news_context}\n"
69
76
  )
70
-
71
-
72
- def build_technical_ai_summary(symbol: str, timeframe: str, candles: list[Candle]) -> str:
73
- """Build a concise technical summary intended as AI assistant context."""
74
- from fincli.app.analysis.indicators import summarize_technical_indicators
75
- from fincli.app.analysis.market_structure import analyze_market_structure
76
-
77
- technical = summarize_technical_indicators(candles)
78
- structure = analyze_market_structure(candles)
79
- debate = run_technical_debate(technical, structure, candles)
80
- signal = debate.judge_signal
81
- return (
82
- "AI Assistance Summary:\n"
83
- f"Instrument: {symbol}\n"
84
- f"Timeframe: {timeframe}\n"
85
- f"Data Quality: {len(candles)} candles\n"
86
- f"Latest Close: {_fmt(technical.latest_close)}\n"
87
- f"Trend Bias: {technical.trend_bias}\n"
88
- f"RSI 14: {_fmt(technical.rsi)}\n"
89
- f"MACD/Signal: {_fmt(technical.macd)} / {_fmt(technical.macd_signal)}\n"
90
- f"Support/Resistance: {_fmt(technical.support)} / {_fmt(technical.resistance)}\n"
91
- f"ATR 14: {_fmt(technical.atr)}\n"
92
- f"Market Structure: {structure.trend}; {structure.latest_pattern}\n"
93
- f"Signal: {signal.label} | Score {signal.score} | Confidence {signal.confidence}\n"
94
- f"Signal Reasoning: {'; '.join(signal.reasons[:3])}\n"
95
- f"Debate Judge: {signal.label}; {'; '.join(debate.judge_reasoning[:2])}\n"
96
- f"Risk Notes: volatility={_fmt(technical.atr)}, liquidity={structure.liquidity_area or 'N/A'}, risk_zone={structure.risk_zone or 'N/A'}\n"
97
- "Use this as context for scenario analysis. This is informational, not financial advice."
98
- )
99
-
100
-
101
- def _fmt(value: float | None) -> str:
102
- if value is None:
103
- return "N/A"
104
- return f"{value:.4f}"
105
-
106
-
107
- def _format_structure(structure: MarketStructureSummary | None) -> str:
108
- if structure is None:
109
- return "No market structure context provided."
110
- return (
111
- f"Trend: {structure.trend}\n"
112
- f"Latest Pattern: {structure.latest_pattern}\n"
113
- f"Break of Structure: {structure.break_of_structure}\n"
114
- f"Change of Character: {structure.change_of_character}\n"
115
- f"Support: {_fmt(structure.support)}\n"
116
- f"Resistance: {_fmt(structure.resistance)}\n"
117
- f"Liquidity Area: {structure.liquidity_area or 'N/A'}\n"
118
- f"Risk Zone: {structure.risk_zone or 'N/A'}"
119
- )
77
+
78
+
79
+ def build_technical_ai_summary(symbol: str, timeframe: str, candles: list[Candle]) -> str:
80
+ """Build a concise technical summary intended as AI assistant context."""
81
+ from fincli.app.analysis.indicators import summarize_technical_indicators
82
+ from fincli.app.analysis.market_structure import analyze_market_structure
83
+
84
+ technical = summarize_technical_indicators(candles)
85
+ structure = analyze_market_structure(candles)
86
+ debate = run_technical_debate(technical, structure, candles)
87
+ signal = debate.judge_signal
88
+ return (
89
+ "AI Assistance Summary:\n"
90
+ f"Instrument: {symbol}\n"
91
+ f"Timeframe: {timeframe}\n"
92
+ f"Data Quality: {len(candles)} candles\n"
93
+ f"Latest Close: {_fmt(technical.latest_close)}\n"
94
+ f"Trend Bias: {technical.trend_bias}\n"
95
+ f"RSI 14: {_fmt(technical.rsi)}\n"
96
+ f"MACD/Signal: {_fmt(technical.macd)} / {_fmt(technical.macd_signal)}\n"
97
+ f"Support/Resistance: {_fmt(technical.support)} / {_fmt(technical.resistance)}\n"
98
+ f"ATR 14: {_fmt(technical.atr)}\n"
99
+ f"Market Structure: {structure.trend}; {structure.latest_pattern}\n"
100
+ f"Signal: {signal.label} | Score {signal.score} | Confidence {signal.confidence}\n"
101
+ f"Signal Reasoning: {'; '.join(signal.reasons[:3])}\n"
102
+ f"Debate Judge: {signal.label}; {'; '.join(debate.judge_reasoning[:2])}\n"
103
+ f"Risk Notes: volatility={_fmt(technical.atr)}, liquidity={structure.liquidity_area or 'N/A'}, risk_zone={structure.risk_zone or 'N/A'}\n"
104
+ "Use this as context for scenario analysis. This is informational, not financial advice."
105
+ )
106
+
107
+
108
+ def _fmt(value: float | None) -> str:
109
+ if value is None:
110
+ return "N/A"
111
+ return f"{value:.4f}"
112
+
113
+
114
+ def _format_structure(structure: MarketStructureSummary | None) -> str:
115
+ if structure is None:
116
+ return "No market structure context provided."
117
+ return (
118
+ f"Trend: {structure.trend}\n"
119
+ f"Latest Pattern: {structure.latest_pattern}\n"
120
+ f"Break of Structure: {structure.break_of_structure}\n"
121
+ f"Change of Character: {structure.change_of_character}\n"
122
+ f"Support: {_fmt(structure.support)}\n"
123
+ f"Resistance: {_fmt(structure.resistance)}\n"
124
+ f"Liquidity Area: {structure.liquidity_area or 'N/A'}\n"
125
+ f"Risk Zone: {structure.risk_zone or 'N/A'}"
126
+ )