@openfinclaw/findoo-datahub-plugin 2026.3.2 → 2026.3.10
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.
- package/DESIGN.md +492 -151
- package/_vendor/claude-skills-finance/SKILL.md +192 -0
- package/_vendor/claude-skills-finance/assets/dcf_analysis_template.md +184 -0
- package/_vendor/claude-skills-finance/assets/expected_output.json +161 -0
- package/_vendor/claude-skills-finance/assets/forecast_report_template.md +177 -0
- package/_vendor/claude-skills-finance/assets/sample_financial_data.json +219 -0
- package/_vendor/claude-skills-finance/assets/variance_report_template.md +122 -0
- package/_vendor/claude-skills-finance/references/financial-ratios-guide.md +396 -0
- package/_vendor/claude-skills-finance/references/forecasting-best-practices.md +294 -0
- package/_vendor/claude-skills-finance/references/valuation-methodology.md +255 -0
- package/_vendor/claude-skills-finance/scripts/budget_variance_analyzer.py +406 -0
- package/_vendor/claude-skills-finance/scripts/dcf_valuation.py +449 -0
- package/_vendor/claude-skills-finance/scripts/forecast_builder.py +494 -0
- package/_vendor/claude-skills-finance/scripts/ratio_calculator.py +432 -0
- package/index.ts +332 -14
- package/openclaw.plugin.json +2 -2
- package/package.json +1 -1
- package/references/cn-market-specifics.md +165 -0
- package/references/crypto-analysis.md +635 -0
- package/references/financial-ratios-cn.md +452 -0
- package/references/hk-market-specifics.md +166 -0
- package/references/macro-cycle-cn.md +409 -0
- package/references/valuation-cn.md +427 -0
- package/skills/README.md +294 -0
- package/skills/a-concept-cycle/skill.md +200 -0
- package/skills/a-convertible-arb/skill.md +294 -0
- package/skills/a-dividend-king/skill.md +187 -0
- package/skills/a-earnings-season/skill.md +221 -0
- package/skills/a-index-timer/skill.md +192 -0
- package/skills/a-ipo-new/skill.md +297 -0
- package/skills/a-northbound-decoder/skill.md +185 -0
- package/skills/a-quant-board/skill.md +286 -0
- package/skills/a-share/skill.md +347 -0
- package/skills/a-share-radar/skill.md +185 -0
- package/skills/cross-asset/skill.md +202 -0
- package/skills/crypto/skill.md +269 -0
- package/skills/crypto-altseason/skill.md +208 -0
- package/skills/crypto-btc-cycle/skill.md +231 -0
- package/skills/crypto-defi-yield/skill.md +181 -0
- package/skills/crypto-funding-arb/skill.md +158 -0
- package/skills/crypto-stablecoin-flow/skill.md +149 -0
- package/skills/data-query/skill.md +124 -30
- package/skills/derivatives/skill.md +188 -35
- package/skills/etf-fund/skill.md +216 -0
- package/skills/factor-screen/skill.md +186 -0
- package/skills/hk-china-internet/skill.md +190 -0
- package/skills/hk-dividend-harvest/skill.md +192 -0
- package/skills/hk-hsi-pulse/skill.md +154 -0
- package/skills/hk-southbound-alpha/skill.md +163 -0
- package/skills/hk-stock/skill.md +295 -0
- package/skills/macro/skill.md +244 -53
- package/skills/risk-monitor/skill.md +171 -0
- package/skills/us-dividend/skill.md +162 -0
- package/skills/us-earnings/skill.md +149 -0
- package/skills/us-equity/skill.md +235 -0
- package/skills/us-etf/skill.md +261 -0
- package/skills/us-sector-rotation/skill.md +223 -0
- package/src/config.ts +4 -5
- package/src/datahub-client.test.ts +4 -7
- package/src/datahub-client.ts +6 -1
- package/src/register-tools.ts +720 -0
- package/src/tool-helpers.ts +89 -0
- package/test/e2e/l3-gateway-bootstrap.live.test.ts +339 -0
- package/test/e2e/l4-skill-tool-chain.live.test.ts +465 -0
- package/skills/crypto-defi/skill.md +0 -69
- package/skills/equity/skill.md +0 -64
- package/skills/market-radar/skill.md +0 -47
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Financial Ratio Calculator
|
|
4
|
+
|
|
5
|
+
Calculates and interprets financial ratios across 5 categories:
|
|
6
|
+
profitability, liquidity, leverage, efficiency, and valuation.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python ratio_calculator.py financial_data.json
|
|
10
|
+
python ratio_calculator.py financial_data.json --format json
|
|
11
|
+
python ratio_calculator.py financial_data.json --category profitability
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import argparse
|
|
15
|
+
import json
|
|
16
|
+
import sys
|
|
17
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def safe_divide(numerator: float, denominator: float, default: float = 0.0) -> float:
|
|
21
|
+
"""Safely divide two numbers, returning default if denominator is zero."""
|
|
22
|
+
if denominator == 0 or denominator is None:
|
|
23
|
+
return default
|
|
24
|
+
return numerator / denominator
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class FinancialRatioCalculator:
|
|
28
|
+
"""Calculate and interpret financial ratios from statement data."""
|
|
29
|
+
|
|
30
|
+
# Industry benchmark ranges: (low, typical, high)
|
|
31
|
+
BENCHMARKS: Dict[str, Tuple[float, float, float]] = {
|
|
32
|
+
"roe": (0.08, 0.15, 0.25),
|
|
33
|
+
"roa": (0.03, 0.06, 0.12),
|
|
34
|
+
"gross_margin": (0.25, 0.40, 0.60),
|
|
35
|
+
"operating_margin": (0.05, 0.15, 0.25),
|
|
36
|
+
"net_margin": (0.03, 0.10, 0.20),
|
|
37
|
+
"current_ratio": (1.0, 1.5, 3.0),
|
|
38
|
+
"quick_ratio": (0.8, 1.0, 2.0),
|
|
39
|
+
"cash_ratio": (0.2, 0.5, 1.0),
|
|
40
|
+
"debt_to_equity": (0.3, 0.8, 2.0),
|
|
41
|
+
"interest_coverage": (2.0, 5.0, 10.0),
|
|
42
|
+
"dscr": (1.0, 1.5, 2.5),
|
|
43
|
+
"asset_turnover": (0.5, 1.0, 2.0),
|
|
44
|
+
"inventory_turnover": (4.0, 8.0, 12.0),
|
|
45
|
+
"receivables_turnover": (6.0, 10.0, 15.0),
|
|
46
|
+
"dso": (30.0, 45.0, 60.0),
|
|
47
|
+
"pe_ratio": (10.0, 20.0, 35.0),
|
|
48
|
+
"pb_ratio": (1.0, 2.5, 5.0),
|
|
49
|
+
"ps_ratio": (1.0, 3.0, 8.0),
|
|
50
|
+
"ev_ebitda": (6.0, 12.0, 20.0),
|
|
51
|
+
"peg_ratio": (0.5, 1.0, 2.0),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def __init__(self, data: Dict[str, Any]) -> None:
|
|
55
|
+
"""Initialize with financial statement data."""
|
|
56
|
+
self.income = data.get("income_statement", {})
|
|
57
|
+
self.balance = data.get("balance_sheet", {})
|
|
58
|
+
self.cash_flow = data.get("cash_flow", {})
|
|
59
|
+
self.market = data.get("market_data", {})
|
|
60
|
+
self.results: Dict[str, Dict[str, Any]] = {}
|
|
61
|
+
|
|
62
|
+
def calculate_profitability(self) -> Dict[str, Any]:
|
|
63
|
+
"""Calculate profitability ratios."""
|
|
64
|
+
revenue = self.income.get("revenue", 0)
|
|
65
|
+
cogs = self.income.get("cost_of_goods_sold", 0)
|
|
66
|
+
operating_income = self.income.get("operating_income", 0)
|
|
67
|
+
net_income = self.income.get("net_income", 0)
|
|
68
|
+
total_equity = self.balance.get("total_equity", 0)
|
|
69
|
+
total_assets = self.balance.get("total_assets", 0)
|
|
70
|
+
|
|
71
|
+
gross_profit = revenue - cogs
|
|
72
|
+
|
|
73
|
+
ratios = {
|
|
74
|
+
"roe": {
|
|
75
|
+
"value": safe_divide(net_income, total_equity),
|
|
76
|
+
"formula": "Net Income / Total Equity",
|
|
77
|
+
"name": "Return on Equity",
|
|
78
|
+
},
|
|
79
|
+
"roa": {
|
|
80
|
+
"value": safe_divide(net_income, total_assets),
|
|
81
|
+
"formula": "Net Income / Total Assets",
|
|
82
|
+
"name": "Return on Assets",
|
|
83
|
+
},
|
|
84
|
+
"gross_margin": {
|
|
85
|
+
"value": safe_divide(gross_profit, revenue),
|
|
86
|
+
"formula": "(Revenue - COGS) / Revenue",
|
|
87
|
+
"name": "Gross Margin",
|
|
88
|
+
},
|
|
89
|
+
"operating_margin": {
|
|
90
|
+
"value": safe_divide(operating_income, revenue),
|
|
91
|
+
"formula": "Operating Income / Revenue",
|
|
92
|
+
"name": "Operating Margin",
|
|
93
|
+
},
|
|
94
|
+
"net_margin": {
|
|
95
|
+
"value": safe_divide(net_income, revenue),
|
|
96
|
+
"formula": "Net Income / Revenue",
|
|
97
|
+
"name": "Net Margin",
|
|
98
|
+
},
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for key, ratio in ratios.items():
|
|
102
|
+
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
103
|
+
|
|
104
|
+
self.results["profitability"] = ratios
|
|
105
|
+
return ratios
|
|
106
|
+
|
|
107
|
+
def calculate_liquidity(self) -> Dict[str, Any]:
|
|
108
|
+
"""Calculate liquidity ratios."""
|
|
109
|
+
current_assets = self.balance.get("current_assets", 0)
|
|
110
|
+
current_liabilities = self.balance.get("current_liabilities", 0)
|
|
111
|
+
inventory = self.balance.get("inventory", 0)
|
|
112
|
+
cash = self.balance.get("cash_and_equivalents", 0)
|
|
113
|
+
|
|
114
|
+
ratios = {
|
|
115
|
+
"current_ratio": {
|
|
116
|
+
"value": safe_divide(current_assets, current_liabilities),
|
|
117
|
+
"formula": "Current Assets / Current Liabilities",
|
|
118
|
+
"name": "Current Ratio",
|
|
119
|
+
},
|
|
120
|
+
"quick_ratio": {
|
|
121
|
+
"value": safe_divide(
|
|
122
|
+
current_assets - inventory, current_liabilities
|
|
123
|
+
),
|
|
124
|
+
"formula": "(Current Assets - Inventory) / Current Liabilities",
|
|
125
|
+
"name": "Quick Ratio",
|
|
126
|
+
},
|
|
127
|
+
"cash_ratio": {
|
|
128
|
+
"value": safe_divide(cash, current_liabilities),
|
|
129
|
+
"formula": "Cash & Equivalents / Current Liabilities",
|
|
130
|
+
"name": "Cash Ratio",
|
|
131
|
+
},
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for key, ratio in ratios.items():
|
|
135
|
+
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
136
|
+
|
|
137
|
+
self.results["liquidity"] = ratios
|
|
138
|
+
return ratios
|
|
139
|
+
|
|
140
|
+
def calculate_leverage(self) -> Dict[str, Any]:
|
|
141
|
+
"""Calculate leverage ratios."""
|
|
142
|
+
total_debt = self.balance.get("total_debt", 0)
|
|
143
|
+
total_equity = self.balance.get("total_equity", 0)
|
|
144
|
+
operating_income = self.income.get("operating_income", 0)
|
|
145
|
+
interest_expense = self.income.get("interest_expense", 0)
|
|
146
|
+
operating_cash_flow = self.cash_flow.get("operating_cash_flow", 0)
|
|
147
|
+
total_debt_service = self.cash_flow.get(
|
|
148
|
+
"total_debt_service", interest_expense
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
ratios = {
|
|
152
|
+
"debt_to_equity": {
|
|
153
|
+
"value": safe_divide(total_debt, total_equity),
|
|
154
|
+
"formula": "Total Debt / Total Equity",
|
|
155
|
+
"name": "Debt-to-Equity Ratio",
|
|
156
|
+
},
|
|
157
|
+
"interest_coverage": {
|
|
158
|
+
"value": safe_divide(operating_income, interest_expense),
|
|
159
|
+
"formula": "Operating Income / Interest Expense",
|
|
160
|
+
"name": "Interest Coverage Ratio",
|
|
161
|
+
},
|
|
162
|
+
"dscr": {
|
|
163
|
+
"value": safe_divide(operating_cash_flow, total_debt_service),
|
|
164
|
+
"formula": "Operating Cash Flow / Total Debt Service",
|
|
165
|
+
"name": "Debt Service Coverage Ratio",
|
|
166
|
+
},
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
for key, ratio in ratios.items():
|
|
170
|
+
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
171
|
+
|
|
172
|
+
self.results["leverage"] = ratios
|
|
173
|
+
return ratios
|
|
174
|
+
|
|
175
|
+
def calculate_efficiency(self) -> Dict[str, Any]:
|
|
176
|
+
"""Calculate efficiency ratios."""
|
|
177
|
+
revenue = self.income.get("revenue", 0)
|
|
178
|
+
cogs = self.income.get("cost_of_goods_sold", 0)
|
|
179
|
+
total_assets = self.balance.get("total_assets", 0)
|
|
180
|
+
inventory = self.balance.get("inventory", 0)
|
|
181
|
+
accounts_receivable = self.balance.get("accounts_receivable", 0)
|
|
182
|
+
|
|
183
|
+
receivables_turnover_val = safe_divide(revenue, accounts_receivable)
|
|
184
|
+
|
|
185
|
+
ratios = {
|
|
186
|
+
"asset_turnover": {
|
|
187
|
+
"value": safe_divide(revenue, total_assets),
|
|
188
|
+
"formula": "Revenue / Total Assets",
|
|
189
|
+
"name": "Asset Turnover",
|
|
190
|
+
},
|
|
191
|
+
"inventory_turnover": {
|
|
192
|
+
"value": safe_divide(cogs, inventory),
|
|
193
|
+
"formula": "COGS / Inventory",
|
|
194
|
+
"name": "Inventory Turnover",
|
|
195
|
+
},
|
|
196
|
+
"receivables_turnover": {
|
|
197
|
+
"value": receivables_turnover_val,
|
|
198
|
+
"formula": "Revenue / Accounts Receivable",
|
|
199
|
+
"name": "Receivables Turnover",
|
|
200
|
+
},
|
|
201
|
+
"dso": {
|
|
202
|
+
"value": safe_divide(365, receivables_turnover_val)
|
|
203
|
+
if receivables_turnover_val > 0
|
|
204
|
+
else 0.0,
|
|
205
|
+
"formula": "365 / Receivables Turnover",
|
|
206
|
+
"name": "Days Sales Outstanding",
|
|
207
|
+
},
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for key, ratio in ratios.items():
|
|
211
|
+
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
212
|
+
|
|
213
|
+
self.results["efficiency"] = ratios
|
|
214
|
+
return ratios
|
|
215
|
+
|
|
216
|
+
def calculate_valuation(self) -> Dict[str, Any]:
|
|
217
|
+
"""Calculate valuation ratios (requires market data)."""
|
|
218
|
+
market_cap = self.market.get("market_cap", 0)
|
|
219
|
+
share_price = self.market.get("share_price", 0)
|
|
220
|
+
shares_outstanding = self.market.get("shares_outstanding", 0)
|
|
221
|
+
earnings_growth_rate = self.market.get("earnings_growth_rate", 0)
|
|
222
|
+
|
|
223
|
+
net_income = self.income.get("net_income", 0)
|
|
224
|
+
revenue = self.income.get("revenue", 0)
|
|
225
|
+
total_equity = self.balance.get("total_equity", 0)
|
|
226
|
+
total_debt = self.balance.get("total_debt", 0)
|
|
227
|
+
cash = self.balance.get("cash_and_equivalents", 0)
|
|
228
|
+
ebitda = self.income.get("ebitda", 0)
|
|
229
|
+
|
|
230
|
+
if market_cap == 0 and share_price > 0 and shares_outstanding > 0:
|
|
231
|
+
market_cap = share_price * shares_outstanding
|
|
232
|
+
|
|
233
|
+
eps = safe_divide(net_income, shares_outstanding)
|
|
234
|
+
book_value_per_share = safe_divide(total_equity, shares_outstanding)
|
|
235
|
+
enterprise_value = market_cap + total_debt - cash
|
|
236
|
+
pe = safe_divide(share_price, eps)
|
|
237
|
+
|
|
238
|
+
ratios = {
|
|
239
|
+
"pe_ratio": {
|
|
240
|
+
"value": pe,
|
|
241
|
+
"formula": "Share Price / Earnings Per Share",
|
|
242
|
+
"name": "Price-to-Earnings Ratio",
|
|
243
|
+
},
|
|
244
|
+
"pb_ratio": {
|
|
245
|
+
"value": safe_divide(share_price, book_value_per_share),
|
|
246
|
+
"formula": "Share Price / Book Value Per Share",
|
|
247
|
+
"name": "Price-to-Book Ratio",
|
|
248
|
+
},
|
|
249
|
+
"ps_ratio": {
|
|
250
|
+
"value": safe_divide(
|
|
251
|
+
market_cap, revenue
|
|
252
|
+
),
|
|
253
|
+
"formula": "Market Cap / Revenue",
|
|
254
|
+
"name": "Price-to-Sales Ratio",
|
|
255
|
+
},
|
|
256
|
+
"ev_ebitda": {
|
|
257
|
+
"value": safe_divide(enterprise_value, ebitda),
|
|
258
|
+
"formula": "Enterprise Value / EBITDA",
|
|
259
|
+
"name": "EV/EBITDA",
|
|
260
|
+
},
|
|
261
|
+
"peg_ratio": {
|
|
262
|
+
"value": safe_divide(pe, earnings_growth_rate * 100)
|
|
263
|
+
if earnings_growth_rate > 0
|
|
264
|
+
else 0.0,
|
|
265
|
+
"formula": "P/E Ratio / Earnings Growth Rate (%)",
|
|
266
|
+
"name": "PEG Ratio",
|
|
267
|
+
},
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
for key, ratio in ratios.items():
|
|
271
|
+
ratio["interpretation"] = self.interpret_ratio(key, ratio["value"])
|
|
272
|
+
|
|
273
|
+
self.results["valuation"] = ratios
|
|
274
|
+
return ratios
|
|
275
|
+
|
|
276
|
+
def calculate_all(self) -> Dict[str, Dict[str, Any]]:
|
|
277
|
+
"""Calculate all ratio categories."""
|
|
278
|
+
self.calculate_profitability()
|
|
279
|
+
self.calculate_liquidity()
|
|
280
|
+
self.calculate_leverage()
|
|
281
|
+
self.calculate_efficiency()
|
|
282
|
+
self.calculate_valuation()
|
|
283
|
+
return self.results
|
|
284
|
+
|
|
285
|
+
def interpret_ratio(self, ratio_key: str, value: float) -> str:
|
|
286
|
+
"""Interpret a ratio value against benchmarks."""
|
|
287
|
+
if value == 0.0:
|
|
288
|
+
return "Insufficient data to calculate"
|
|
289
|
+
|
|
290
|
+
benchmarks = self.BENCHMARKS.get(ratio_key)
|
|
291
|
+
if not benchmarks:
|
|
292
|
+
return "No benchmark available"
|
|
293
|
+
|
|
294
|
+
low, typical, high = benchmarks
|
|
295
|
+
|
|
296
|
+
# DSO is inverse - lower is better
|
|
297
|
+
if ratio_key == "dso":
|
|
298
|
+
if value <= low:
|
|
299
|
+
return "Excellent - collections well above average"
|
|
300
|
+
elif value <= typical:
|
|
301
|
+
return "Good - collections within normal range"
|
|
302
|
+
elif value <= high:
|
|
303
|
+
return "Acceptable - monitor collection trends"
|
|
304
|
+
else:
|
|
305
|
+
return "Concern - collections significantly slower than peers"
|
|
306
|
+
|
|
307
|
+
# Debt-to-equity - lower generally better (but context matters)
|
|
308
|
+
if ratio_key == "debt_to_equity":
|
|
309
|
+
if value <= low:
|
|
310
|
+
return "Conservative leverage - strong equity position"
|
|
311
|
+
elif value <= typical:
|
|
312
|
+
return "Moderate leverage - well balanced"
|
|
313
|
+
elif value <= high:
|
|
314
|
+
return "Elevated leverage - monitor debt levels"
|
|
315
|
+
else:
|
|
316
|
+
return "High leverage - potential financial risk"
|
|
317
|
+
|
|
318
|
+
# Standard interpretation (higher is better for most ratios)
|
|
319
|
+
if value < low:
|
|
320
|
+
return "Below average - needs improvement"
|
|
321
|
+
elif value <= typical:
|
|
322
|
+
return "Acceptable - within normal range"
|
|
323
|
+
elif value <= high:
|
|
324
|
+
return "Good - above average performance"
|
|
325
|
+
else:
|
|
326
|
+
return "Excellent - significantly above peers"
|
|
327
|
+
|
|
328
|
+
@staticmethod
|
|
329
|
+
def format_ratio(value: float, is_percentage: bool = False) -> str:
|
|
330
|
+
"""Format a ratio value for display."""
|
|
331
|
+
if is_percentage:
|
|
332
|
+
return f"{value * 100:.1f}%"
|
|
333
|
+
return f"{value:.2f}"
|
|
334
|
+
|
|
335
|
+
def format_text(self, category: Optional[str] = None) -> str:
|
|
336
|
+
"""Format results as human-readable text."""
|
|
337
|
+
lines: List[str] = []
|
|
338
|
+
lines.append("=" * 70)
|
|
339
|
+
lines.append("FINANCIAL RATIO ANALYSIS")
|
|
340
|
+
lines.append("=" * 70)
|
|
341
|
+
|
|
342
|
+
categories = (
|
|
343
|
+
{category: self.results[category]}
|
|
344
|
+
if category and category in self.results
|
|
345
|
+
else self.results
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
percentage_ratios = {
|
|
349
|
+
"roe", "roa", "gross_margin", "operating_margin", "net_margin"
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for cat_name, ratios in categories.items():
|
|
353
|
+
lines.append(f"\n--- {cat_name.upper()} ---")
|
|
354
|
+
for key, ratio in ratios.items():
|
|
355
|
+
is_pct = key in percentage_ratios
|
|
356
|
+
formatted = self.format_ratio(ratio["value"], is_pct)
|
|
357
|
+
lines.append(f" {ratio['name']}: {formatted}")
|
|
358
|
+
lines.append(f" Formula: {ratio['formula']}")
|
|
359
|
+
lines.append(f" Assessment: {ratio['interpretation']}")
|
|
360
|
+
|
|
361
|
+
lines.append("\n" + "=" * 70)
|
|
362
|
+
return "\n".join(lines)
|
|
363
|
+
|
|
364
|
+
def to_json(self, category: Optional[str] = None) -> Dict[str, Any]:
|
|
365
|
+
"""Return results as JSON-serializable dict."""
|
|
366
|
+
if category and category in self.results:
|
|
367
|
+
return {"category": category, "ratios": self.results[category]}
|
|
368
|
+
return {"categories": self.results}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def main() -> None:
|
|
372
|
+
"""Main entry point."""
|
|
373
|
+
parser = argparse.ArgumentParser(
|
|
374
|
+
description="Calculate and interpret financial ratios"
|
|
375
|
+
)
|
|
376
|
+
parser.add_argument(
|
|
377
|
+
"input_file",
|
|
378
|
+
help="Path to JSON file with financial statement data",
|
|
379
|
+
)
|
|
380
|
+
parser.add_argument(
|
|
381
|
+
"--format",
|
|
382
|
+
choices=["text", "json"],
|
|
383
|
+
default="text",
|
|
384
|
+
help="Output format (default: text)",
|
|
385
|
+
)
|
|
386
|
+
parser.add_argument(
|
|
387
|
+
"--category",
|
|
388
|
+
choices=[
|
|
389
|
+
"profitability",
|
|
390
|
+
"liquidity",
|
|
391
|
+
"leverage",
|
|
392
|
+
"efficiency",
|
|
393
|
+
"valuation",
|
|
394
|
+
],
|
|
395
|
+
default=None,
|
|
396
|
+
help="Calculate only a specific ratio category",
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
args = parser.parse_args()
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
with open(args.input_file, "r") as f:
|
|
403
|
+
data = json.load(f)
|
|
404
|
+
except FileNotFoundError:
|
|
405
|
+
print(f"Error: File '{args.input_file}' not found.", file=sys.stderr)
|
|
406
|
+
sys.exit(1)
|
|
407
|
+
except json.JSONDecodeError as e:
|
|
408
|
+
print(f"Error: Invalid JSON in '{args.input_file}': {e}", file=sys.stderr)
|
|
409
|
+
sys.exit(1)
|
|
410
|
+
|
|
411
|
+
calculator = FinancialRatioCalculator(data)
|
|
412
|
+
|
|
413
|
+
if args.category:
|
|
414
|
+
method_map = {
|
|
415
|
+
"profitability": calculator.calculate_profitability,
|
|
416
|
+
"liquidity": calculator.calculate_liquidity,
|
|
417
|
+
"leverage": calculator.calculate_leverage,
|
|
418
|
+
"efficiency": calculator.calculate_efficiency,
|
|
419
|
+
"valuation": calculator.calculate_valuation,
|
|
420
|
+
}
|
|
421
|
+
method_map[args.category]()
|
|
422
|
+
else:
|
|
423
|
+
calculator.calculate_all()
|
|
424
|
+
|
|
425
|
+
if args.format == "json":
|
|
426
|
+
print(json.dumps(calculator.to_json(args.category), indent=2))
|
|
427
|
+
else:
|
|
428
|
+
print(calculator.format_text(args.category))
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
if __name__ == "__main__":
|
|
432
|
+
main()
|