Skip to content
SlashClone
Made by@qtrader

Attribution analysis

@qtrader/attribution-analysis

Explain why a portfolio beat or lagged its benchmark. Brinson-Fachler allocation, selection, and interaction effects by sector and period, Carino-linked across periods so they add up exactly, plus a plain-English summary. Works offline from a CSV. Not financial advice.

SkillTradingv1.0.0 (latest)Tested
Activity
New: be the first to try it
  1. 1{
  2. 2 "name": "attribution-analysis",
  3. 3 "owner": "qtrader",
  4. 4 "version": "1.0.0",
  5. 5 "type": "skill",
  6. 6 "description": "Explain why a portfolio beat or lagged its benchmark. Brinson-Fachler allocation, selection, and interaction effects by sector and period, Carino-linked across periods so they add up exactly, plus a plain-English summary. Works offline from a CSV. Not financial advice.",
  7. 7 "category": "trading",
  8. 8 "license": "MIT",
  9. 9 "compat": [
  10. 10 "claude-code"
  11. 11 ],
  12. 12 "permissions": {
  13. 13 "shell": [
  14. 14 "node scripts/attribution.mjs",
  15. 15 "node scripts/selftest.mjs"
  16. 16 ],
  17. 17 "files": {
  18. 18 "read": [
  19. 19 "./data/**"
  20. 20 ],
  21. 21 "write": [
  22. 22 "./out/**"
  23. 23 ]
  24. 24 }
  25. 25 },
  26. 26 "tests": {
  27. 27 "cases": [
  28. 28 {
  29. 29 "name": "matches the hand calculation in the README",
  30. 30 "inputs": {},
  31. 31 "checks": [
  32. 32 {
  33. 33 "type": "command_exits_zero",
  34. 34 "command": "node scripts/selftest.mjs"
  35. 35 }
  36. 36 ]
  37. 37 },
  38. 38 {
  39. 39 "name": "writes linked attribution for the two-period example",
  40. 40 "inputs": {},
  41. 41 "checks": [
  42. 42 {
  43. 43 "type": "command_exits_zero",
  44. 44 "command": "node scripts/attribution.mjs examples/holdings.csv --out out/attribution"
  45. 45 },
  46. 46 {
  47. 47 "type": "output_matches_schema",
  48. 48 "path": "out/attribution.json",
  49. 49 "schema": {
  50. 50 "type": "object",
  51. 51 "required": [
  52. 52 "periods",
  53. 53 "linked",
  54. 54 "summary",
  55. 55 "warnings"
  56. 56 ],
  57. 57 "properties": {
  58. 58 "periods": {
  59. 59 "type": "array",
  60. 60 "minItems": 2,
  61. 61 "maxItems": 2
  62. 62 },
  63. 63 "linked": {
  64. 64 "type": "object",
  65. 65 "required": [
  66. 66 "method",
  67. 67 "port_return",
  68. 68 "bench_return",
  69. 69 "active_return",
  70. 70 "sectors",
  71. 71 "total"
  72. 72 ],
  73. 73 "properties": {
  74. 74 "method": {
  75. 75 "const": "carino"
  76. 76 },
  77. 77 "sectors": {
  78. 78 "type": "array",
  79. 79 "minItems": 3,
  80. 80 "maxItems": 3
  81. 81 }
  82. 82 }
  83. 83 },
  84. 84 "warnings": {
  85. 85 "type": "array",
  86. 86 "maxItems": 0
  87. 87 }
  88. 88 }
  89. 89 }
  90. 90 },
  91. 91 {
  92. 92 "type": "file_contains",
  93. 93 "path": "out/attribution.md",
  94. 94 "text": "| **Total** | +0.3870% | -0.1910% | +0.5022% | +0.6982% |"
  95. 95 },
  96. 96 {
  97. 97 "type": "file_contains",
  98. 98 "path": "out/attribution.md",
  99. 99 "text": "so it beat by 70 bps"
  100. 100 }
  101. 101 ]
  102. 102 }
  103. 103 ]
  104. 104 }