@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.
Files changed (67) hide show
  1. package/DESIGN.md +492 -151
  2. package/_vendor/claude-skills-finance/SKILL.md +192 -0
  3. package/_vendor/claude-skills-finance/assets/dcf_analysis_template.md +184 -0
  4. package/_vendor/claude-skills-finance/assets/expected_output.json +161 -0
  5. package/_vendor/claude-skills-finance/assets/forecast_report_template.md +177 -0
  6. package/_vendor/claude-skills-finance/assets/sample_financial_data.json +219 -0
  7. package/_vendor/claude-skills-finance/assets/variance_report_template.md +122 -0
  8. package/_vendor/claude-skills-finance/references/financial-ratios-guide.md +396 -0
  9. package/_vendor/claude-skills-finance/references/forecasting-best-practices.md +294 -0
  10. package/_vendor/claude-skills-finance/references/valuation-methodology.md +255 -0
  11. package/_vendor/claude-skills-finance/scripts/budget_variance_analyzer.py +406 -0
  12. package/_vendor/claude-skills-finance/scripts/dcf_valuation.py +449 -0
  13. package/_vendor/claude-skills-finance/scripts/forecast_builder.py +494 -0
  14. package/_vendor/claude-skills-finance/scripts/ratio_calculator.py +432 -0
  15. package/index.ts +332 -14
  16. package/openclaw.plugin.json +2 -2
  17. package/package.json +1 -1
  18. package/references/cn-market-specifics.md +165 -0
  19. package/references/crypto-analysis.md +635 -0
  20. package/references/financial-ratios-cn.md +452 -0
  21. package/references/hk-market-specifics.md +166 -0
  22. package/references/macro-cycle-cn.md +409 -0
  23. package/references/valuation-cn.md +427 -0
  24. package/skills/README.md +294 -0
  25. package/skills/a-concept-cycle/skill.md +200 -0
  26. package/skills/a-convertible-arb/skill.md +294 -0
  27. package/skills/a-dividend-king/skill.md +187 -0
  28. package/skills/a-earnings-season/skill.md +221 -0
  29. package/skills/a-index-timer/skill.md +192 -0
  30. package/skills/a-ipo-new/skill.md +297 -0
  31. package/skills/a-northbound-decoder/skill.md +185 -0
  32. package/skills/a-quant-board/skill.md +286 -0
  33. package/skills/a-share/skill.md +347 -0
  34. package/skills/a-share-radar/skill.md +185 -0
  35. package/skills/cross-asset/skill.md +202 -0
  36. package/skills/crypto/skill.md +269 -0
  37. package/skills/crypto-altseason/skill.md +208 -0
  38. package/skills/crypto-btc-cycle/skill.md +231 -0
  39. package/skills/crypto-defi-yield/skill.md +181 -0
  40. package/skills/crypto-funding-arb/skill.md +158 -0
  41. package/skills/crypto-stablecoin-flow/skill.md +149 -0
  42. package/skills/data-query/skill.md +124 -30
  43. package/skills/derivatives/skill.md +188 -35
  44. package/skills/etf-fund/skill.md +216 -0
  45. package/skills/factor-screen/skill.md +186 -0
  46. package/skills/hk-china-internet/skill.md +190 -0
  47. package/skills/hk-dividend-harvest/skill.md +192 -0
  48. package/skills/hk-hsi-pulse/skill.md +154 -0
  49. package/skills/hk-southbound-alpha/skill.md +163 -0
  50. package/skills/hk-stock/skill.md +295 -0
  51. package/skills/macro/skill.md +244 -53
  52. package/skills/risk-monitor/skill.md +171 -0
  53. package/skills/us-dividend/skill.md +162 -0
  54. package/skills/us-earnings/skill.md +149 -0
  55. package/skills/us-equity/skill.md +235 -0
  56. package/skills/us-etf/skill.md +261 -0
  57. package/skills/us-sector-rotation/skill.md +223 -0
  58. package/src/config.ts +4 -5
  59. package/src/datahub-client.test.ts +4 -7
  60. package/src/datahub-client.ts +6 -1
  61. package/src/register-tools.ts +720 -0
  62. package/src/tool-helpers.ts +89 -0
  63. package/test/e2e/l3-gateway-bootstrap.live.test.ts +339 -0
  64. package/test/e2e/l4-skill-tool-chain.live.test.ts +465 -0
  65. package/skills/crypto-defi/skill.md +0 -69
  66. package/skills/equity/skill.md +0 -64
  67. 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()