@pencil-agent/nano-pencil 2.0.0-beta.8 → 2.0.0

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 (241) hide show
  1. package/README.md +267 -267
  2. package/dist/build-meta.json +3 -3
  3. package/dist/core/export-html/AGENT.md +11 -11
  4. package/dist/core/export-html/template.css +971 -971
  5. package/dist/core/export-html/template.html +54 -54
  6. package/dist/core/extensions-host/index.d.ts +1 -1
  7. package/dist/core/extensions-host/loader.js +1 -1
  8. package/dist/core/extensions-host/runner.d.ts +1 -0
  9. package/dist/core/extensions-host/runner.js +2 -2
  10. package/dist/core/extensions-host/types.d.ts +17 -22
  11. package/dist/core/lib/ai/src/types.d.ts +12 -2
  12. package/dist/core/persona/persona-manager.js +5 -2
  13. package/dist/core/runtime/agent-session.js +3 -3
  14. package/dist/core/runtime/extension-core-bindings.d.ts +1 -0
  15. package/dist/core/runtime/extension-core-bindings.js +2 -2
  16. package/dist/extensions/builtin/AGENT.md +115 -115
  17. package/dist/extensions/builtin/browser/AGENT.md +17 -17
  18. package/dist/extensions/builtin/browser/agent-workspace/agent_helpers.py +12 -12
  19. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/amazon/product-search.md +198 -198
  20. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/archive-org/scraping.md +341 -341
  21. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv/scraping.md +311 -311
  22. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/arxiv-bulk/scraping.md +333 -333
  23. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/atlas/overview.md +70 -70
  24. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/booking-com/scraping.md +578 -578
  25. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/capterra/scraping.md +440 -440
  26. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/centilebrain/generate-estimates.md +110 -110
  27. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coingecko/scraping.md +325 -325
  28. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coinmarketcap/scraping.md +463 -463
  29. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/coursera/scraping.md +360 -360
  30. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/craigslist/scraping.md +390 -390
  31. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/crossref/scraping.md +568 -568
  32. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/dev-to/scraping.md +323 -323
  33. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/duckduckgo/scraping.md +349 -349
  34. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/ebay/scraping.md +435 -435
  35. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/etsy/scraping.md +506 -506
  36. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/eventbrite/scraping.md +363 -363
  37. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/expedia/automation.md +168 -168
  38. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/groups.md +236 -236
  39. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/facebook/pages.md +295 -295
  40. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/framer/editor.md +108 -108
  41. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/fred/scraping.md +493 -493
  42. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/g2/scraping.md +580 -580
  43. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/genius/scraping.md +511 -511
  44. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/repo-actions.md +65 -65
  45. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/github/scraping.md +184 -184
  46. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/glassdoor/scraping.md +543 -543
  47. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gmail/compose.md +122 -122
  48. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/goodreads/scraping.md +461 -461
  49. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/gutenberg/scraping.md +383 -383
  50. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/hackernews/scraping.md +243 -243
  51. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/howlongtobeat/scraping.md +473 -473
  52. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/imdb/scraping.md +271 -271
  53. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/itch-io/scraping.md +436 -436
  54. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/job-boards/indeed-glassdoor.md +1021 -1021
  55. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/letterboxd/scraping.md +349 -349
  56. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/linkedin/invitation-manager.md +109 -109
  57. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/loom/folder-enumeration.md +170 -170
  58. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/macrotrends/scraping.md +537 -537
  59. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/article-hydration.md +120 -120
  60. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/medium/scraping.md +414 -414
  61. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/metacritic/scraping.md +477 -477
  62. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/musicbrainz/scraping.md +478 -478
  63. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/nasa/scraping.md +339 -339
  64. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/news-aggregation/multi-source.md +205 -205
  65. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/open-library/scraping.md +472 -472
  66. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openalex/scraping.md +470 -470
  67. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/openstreetmap/scraping.md +490 -490
  68. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/package-registries/npm-pypi.md +478 -478
  69. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/polymarket/scraping.md +234 -234
  70. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/producthunt/scraping.md +307 -307
  71. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/pubmed/scraping.md +421 -421
  72. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/quora/scraping.md +364 -364
  73. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rawg/scraping.md +352 -352
  74. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/reddit/scraping.md +124 -124
  75. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/rest-countries/scraping.md +233 -233
  76. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/sec-edgar/scraping.md +361 -361
  77. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/README.md +36 -36
  78. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/embedded-apps.md +72 -72
  79. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/knowledge-base.md +109 -109
  80. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/shopify-admin/polaris-inputs.md +137 -137
  81. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/soundcloud/scraping.md +362 -362
  82. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/spotify/scraping.md +339 -339
  83. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/stackoverflow/scraping.md +435 -435
  84. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/steam/scraping.md +575 -575
  85. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/substack/scraping.md +338 -338
  86. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/thetechgeeks/pricing.md +52 -52
  87. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tiktok/upload.md +107 -107
  88. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/tradingview/scraping.md +309 -309
  89. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trello/boards-and-lists.md +88 -88
  90. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/trustpilot/scraping.md +375 -375
  91. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/walmart/scraping.md +444 -444
  92. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wayback-machine/scraping.md +306 -306
  93. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/weather/scraping.md +398 -398
  94. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/wellfound/scraping.md +596 -596
  95. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/world-bank/scraping.md +356 -356
  96. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/xiaohongshu/scraping.md +84 -84
  97. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/youtube/scraping.md +418 -418
  98. package/dist/extensions/builtin/browser/agent-workspace/domain-skills/zillow/scraping.md +433 -433
  99. package/dist/extensions/builtin/browser/browser.md +73 -73
  100. package/dist/extensions/builtin/browser/install.md +142 -142
  101. package/dist/extensions/builtin/browser/interaction-skills/connection.md +48 -48
  102. package/dist/extensions/builtin/browser/interaction-skills/cookies.md +3 -3
  103. package/dist/extensions/builtin/browser/interaction-skills/cross-origin-iframes.md +3 -3
  104. package/dist/extensions/builtin/browser/interaction-skills/dialogs.md +64 -64
  105. package/dist/extensions/builtin/browser/interaction-skills/downloads.md +3 -3
  106. package/dist/extensions/builtin/browser/interaction-skills/drag-and-drop.md +3 -3
  107. package/dist/extensions/builtin/browser/interaction-skills/dropdowns.md +3 -3
  108. package/dist/extensions/builtin/browser/interaction-skills/iframes.md +3 -3
  109. package/dist/extensions/builtin/browser/interaction-skills/network-requests.md +3 -3
  110. package/dist/extensions/builtin/browser/interaction-skills/print-as-pdf.md +3 -3
  111. package/dist/extensions/builtin/browser/interaction-skills/profile-sync.md +90 -90
  112. package/dist/extensions/builtin/browser/interaction-skills/screenshots.md +17 -17
  113. package/dist/extensions/builtin/browser/interaction-skills/scrolling.md +3 -3
  114. package/dist/extensions/builtin/browser/interaction-skills/shadow-dom.md +3 -3
  115. package/dist/extensions/builtin/browser/interaction-skills/tabs.md +69 -69
  116. package/dist/extensions/builtin/browser/interaction-skills/uploads.md +1 -1
  117. package/dist/extensions/builtin/browser/interaction-skills/viewport.md +3 -3
  118. package/dist/extensions/builtin/browser/src/browser_harness/AGENT.md +15 -15
  119. package/dist/extensions/builtin/browser/src/browser_harness/__init__.py +8 -8
  120. package/dist/extensions/builtin/browser/src/browser_harness/_ipc.py +90 -90
  121. package/dist/extensions/builtin/browser/src/browser_harness/admin.py +722 -722
  122. package/dist/extensions/builtin/browser/src/browser_harness/daemon.py +328 -328
  123. package/dist/extensions/builtin/browser/src/browser_harness/helpers.py +396 -396
  124. package/dist/extensions/builtin/browser/src/browser_harness/run.py +103 -103
  125. package/dist/extensions/builtin/discipline/skills/brainstorming/SKILL.md +33 -33
  126. package/dist/extensions/builtin/discipline/skills/executing-plans/SKILL.md +25 -25
  127. package/dist/extensions/builtin/discipline/skills/finishing-development-branch/SKILL.md +25 -25
  128. package/dist/extensions/builtin/discipline/skills/receiving-code-review/SKILL.md +22 -22
  129. package/dist/extensions/builtin/discipline/skills/requesting-code-review/SKILL.md +31 -31
  130. package/dist/extensions/builtin/discipline/skills/systematic-debugging/SKILL.md +28 -28
  131. package/dist/extensions/builtin/discipline/skills/test-driven-development/SKILL.md +32 -32
  132. package/dist/extensions/builtin/discipline/skills/using-git-worktrees/SKILL.md +25 -25
  133. package/dist/extensions/builtin/discipline/skills/verification-before-completion/SKILL.md +27 -27
  134. package/dist/extensions/builtin/discipline/skills/writing-plans/SKILL.md +26 -26
  135. package/dist/extensions/builtin/goal/README.md +67 -67
  136. package/dist/extensions/builtin/goal/goal-controller.d.ts +39 -10
  137. package/dist/extensions/builtin/goal/goal-controller.js +1 -1
  138. package/dist/extensions/builtin/goal/goal-format.js +1 -1
  139. package/dist/extensions/builtin/goal/goal-prompts.d.ts +2 -0
  140. package/dist/extensions/builtin/goal/goal-prompts.js +5 -4
  141. package/dist/extensions/builtin/goal/goal-store.js +1 -1
  142. package/dist/extensions/builtin/goal/index.d.ts +1 -1
  143. package/dist/extensions/builtin/goal/index.js +10 -7
  144. package/dist/extensions/builtin/grub/README.md +112 -112
  145. package/dist/extensions/builtin/link-world/agent-workspace/README.md +16 -16
  146. package/dist/extensions/builtin/link-world/index.js +6 -6
  147. package/dist/extensions/builtin/link-world/internet-search/internet-search.md +65 -65
  148. package/dist/extensions/builtin/link-world/link-world-agent.md +82 -82
  149. package/dist/extensions/builtin/link-world/linkworld.md +313 -313
  150. package/dist/extensions/builtin/link-world/{network-routing.md → network-routing/network-routing.md} +67 -67
  151. package/dist/extensions/builtin/loop/README.md +92 -92
  152. package/dist/extensions/builtin/mcp/figma-design.md +68 -68
  153. package/dist/extensions/builtin/mcp/mcp-management.md +85 -85
  154. package/dist/extensions/builtin/plan/index.js +1 -1
  155. package/dist/extensions/builtin/recap/AGENT.md +15 -15
  156. package/dist/extensions/builtin/sal/README.md +72 -72
  157. package/dist/extensions/builtin/security-audit/README.md +289 -289
  158. package/dist/extensions/builtin/task/task-store.d.ts +4 -0
  159. package/dist/extensions/builtin/task/task-store.js +1 -1
  160. package/dist/extensions/builtin/team/AGENT.md +112 -112
  161. package/dist/extensions/builtin/team/TESTING.md +299 -299
  162. package/dist/extensions/builtin/token-save/README.md +56 -56
  163. package/dist/extensions/optional/AGENT.md +10 -10
  164. package/dist/index.d.ts +5 -30
  165. package/dist/index.js +1 -1
  166. package/dist/models.d.ts +7 -0
  167. package/dist/models.js +1 -0
  168. package/dist/modes/interactive/components/footer.js +1 -1
  169. package/dist/modes/interactive/components/task-status-panel.d.ts +36 -0
  170. package/dist/modes/interactive/components/task-status-panel.js +1 -0
  171. package/dist/modes/interactive/controllers/stream-render-controller.d.ts +7 -0
  172. package/dist/modes/interactive/controllers/stream-render-controller.js +2 -2
  173. package/dist/modes/interactive/interactive-mode.js +40 -40
  174. package/dist/modes/interactive/state/interactive-state.d.ts +2 -0
  175. package/dist/modes/interactive/state/interactive-state.js +1 -1
  176. package/dist/modes/interactive/theme/dark.json +85 -85
  177. package/dist/modes/interactive/theme/light.json +84 -84
  178. package/dist/modes/interactive/theme/theme-schema.json +335 -335
  179. package/dist/modes/interactive/theme/warm.json +81 -81
  180. package/dist/node_modules/@pencil-agent/ai/dist/cli.js +0 -0
  181. package/dist/node_modules/@pencil-agent/ai/dist/models.generated.js +1 -1
  182. package/dist/node_modules/@pencil-agent/ai/dist/providers/anthropic.js +2 -2
  183. package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-completions.js +5 -5
  184. package/dist/node_modules/@pencil-agent/ai/dist/providers/openai-responses.js +1 -1
  185. package/dist/node_modules/@pencil-agent/ai/dist/stream.js +1 -1
  186. package/dist/packages/protocol/src/commands.d.ts +33 -0
  187. package/dist/packages/protocol/src/flags.d.ts +20 -0
  188. package/dist/packages/protocol/src/hooks.d.ts +17 -0
  189. package/dist/packages/protocol/src/hooks.js +0 -0
  190. package/dist/packages/{extension-sdk → protocol}/src/index.d.ts +7 -4
  191. package/dist/packages/protocol/src/index.js +1 -0
  192. package/dist/packages/{extension-sdk → protocol}/src/lifecycle.d.ts +15 -27
  193. package/dist/packages/protocol/src/lifecycle.js +0 -0
  194. package/dist/packages/{extension-sdk → protocol}/src/tools.d.ts +1 -1
  195. package/dist/packages/protocol/src/tools.js +0 -0
  196. package/dist/public-config.d.ts +12 -0
  197. package/dist/public-config.js +1 -0
  198. package/dist/runtime.d.ts +9 -0
  199. package/dist/runtime.js +1 -0
  200. package/dist/session-compaction.d.ts +7 -0
  201. package/dist/session-compaction.js +1 -0
  202. package/dist/session.d.ts +7 -0
  203. package/dist/session.js +1 -0
  204. package/dist/skills.d.ts +7 -0
  205. package/dist/skills.js +1 -0
  206. package/dist/tools.d.ts +7 -0
  207. package/dist/tools.js +1 -0
  208. package/docs/ACP/345/215/217/350/256/256/351/233/206/346/210/220/345/274/200/345/217/221/346/226/207/346/241/243.md +851 -0
  209. package/docs/SDK-TESTING.md +364 -0
  210. package/docs/codex-goal-command-impl.md +1055 -1055
  211. package/docs/codex-goal-vs-grub.md +500 -500
  212. package/docs/custom-provider.md +27 -27
  213. package/docs/extensions.md +27 -27
  214. package/docs/keybindings.md +27 -27
  215. package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/200/273/347/273/223.md" +250 -250
  216. package/docs/loop /351/207/215/346/236/204/345/256/214/346/210/220/346/212/245/345/221/212.md" +122 -122
  217. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210.md" +1222 -1222
  218. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/256/236/347/216/260/346/212/245/345/221/212.md" +158 -158
  219. package/docs/loop /351/207/215/346/236/204/346/226/271/346/241/210/345/257/271/346/257/224/345/210/206/346/236/220.md" +128 -128
  220. package/docs/loop /351/207/215/346/236/204/350/256/241/345/210/222.md" +320 -320
  221. package/docs/loop-usage-examples.md +214 -214
  222. package/docs/mem-core/346/212/200/346/234/257/346/226/207/346/241/243.md +593 -0
  223. package/docs/models.md +27 -27
  224. package/docs/packages.md +27 -27
  225. package/docs/pi-design-philosophy.md +457 -457
  226. package/docs/planmode.md +1987 -1987
  227. package/docs/prompt-templates.md +27 -27
  228. package/docs/providers.md +27 -27
  229. package/docs/sdk.md +27 -27
  230. package/docs/skills.md +27 -27
  231. package/docs/startup-performance-optimization.md +301 -0
  232. package/docs/themes.md +27 -27
  233. package/docs/tui.md +27 -27
  234. package/docs//350/256/244/347/237/245/345/234/260/345/233/276.md +47 -0
  235. package/package.json +190 -162
  236. package/dist/packages/extension-sdk/src/index.js +0 -1
  237. package/docs/cc-agent-design.md +0 -1297
  238. package/docs/cc-tui-design.md +0 -1333
  239. package/docs//345/257/271/346/240/207Claude-Code.md +0 -1775
  240. /package/dist/packages/{extension-sdk/src/lifecycle.js → protocol/src/commands.js} +0 -0
  241. /package/dist/packages/{extension-sdk/src/tools.js → protocol/src/flags.js} +0 -0
@@ -1,537 +1,537 @@
1
- # Macrotrends — Data Extraction
2
-
3
- `https://www.macrotrends.net` — long-term historical financial and economic charts. Three access patterns depending on page type; all work with plain `http_get`, no browser required.
4
-
5
- All results validated against live site on 2026-04-18.
6
-
7
- ## Do this first: pick your access pattern
8
-
9
- | Goal | Pattern | Latency | Variable |
10
- |------|---------|---------|----------|
11
- | Stock OHLCV price history | Direct iframe PHP | ~190ms | `dataDaily` |
12
- | Stock market cap (daily) | Direct iframe PHP | ~200ms | `chartData` |
13
- | Stock fundamentals (PE, revenue, margins) | Direct iframe PHP | ~140ms | `chartData` |
14
- | S&P 500 / composite index charts | `chart_iframe_comp.php` | ~90ms | `originalData` |
15
- | Economic indicators (rates, yields, CPI) | `/economic-data/` JSON API | ~150ms | `data[]` array |
16
- | Gold, commodity prices | Either path (both work) | ~150ms | `data[]` or `originalData` |
17
-
18
- **Never use the browser for Macrotrends read-only tasks.** All endpoints are accessible via `http_get` with the default `Mozilla/5.0` UA. For pages that occasionally 403, switch to a Chrome UA (see gotchas).
19
-
20
- ---
21
-
22
- ## Pattern 1: Stock price history (OHLCV)
23
-
24
- Construct the iframe URL directly — no need to fetch the main page first.
25
-
26
- ```python
27
- import json, re
28
- from helpers import http_get
29
-
30
- def get_stock_ohlcv(ticker: str, years_back: int = None) -> list[dict]:
31
- """
32
- Returns daily OHLCV records for any US stock.
33
-
34
- ticker: uppercase ticker symbol, e.g. 'AAPL', 'MSFT', 'TSLA', 'NVDA'
35
- years_back: number of years of history (1=~250 records, 15=~3772 records).
36
- Omit (None) to get ALL available history (AAPL goes back to 1980).
37
- """
38
- url = f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/stock_price_history.php?t={ticker}"
39
- if years_back:
40
- url += f"&yb={years_back}"
41
-
42
- html = http_get(url)
43
- m = re.search(r'var\s+dataDaily\s*=\s*\[', html)
44
- if not m:
45
- raise ValueError(f"No dataDaily found for ticker {ticker!r}")
46
-
47
- si = html.index('[', m.start())
48
- bc = 0
49
- for j, ch in enumerate(html[si:], si):
50
- if ch == '[': bc += 1
51
- elif ch == ']':
52
- bc -= 1
53
- if bc == 0: ei = j; break
54
- return json.loads(html[si:ei+1])
55
-
56
- # Usage
57
- records = get_stock_ohlcv('AAPL', years_back=15)
58
- # [{'d': '2011-04-18', 'o': '9.771', 'h': '9.9547', 'l': '9.593', 'c': '9.9433', 'v': '18.275'}, ...]
59
-
60
- latest = records[-1]
61
- # {'d': '2026-04-17', 'o': '266.96', 'h': '272.3', 'l': '266.72', 'c': '270.23',
62
- # 'v': '55.211', 'ma50': '260.554', 'ma200': '251.828'}
63
-
64
- print(f"{latest['d']}: close=${latest['c']} vol={latest['v']}M shares")
65
- ```
66
-
67
- ### dataDaily field reference
68
-
69
- | Field | Meaning | Type |
70
- |-------|---------|------|
71
- | `d` | Date (YYYY-MM-DD) | str |
72
- | `o` | Open price (adjusted for splits) | str/float |
73
- | `h` | High | str/float |
74
- | `l` | Low | str/float |
75
- | `c` | Close | str/float |
76
- | `v` | Volume in **millions of shares** | str/float |
77
- | `ma50` | 50-day moving average | str/float (appears on recent records only) |
78
- | `ma200` | 200-day moving average | str/float (appears on recent records only) |
79
-
80
- **Note:** All price values are strings — cast with `float()`. Volume is millions: `55.211` = 55.2M shares traded.
81
-
82
- ### Confirmed tickers (2026-04-18)
83
-
84
- All tested with direct iframe URL, no page fetch needed:
85
-
86
- ```python
87
- # All work: AAPL, MSFT, TSLA, NVDA, GOOGL, AMZN, META, NFLX, etc.
88
- # 3772 records for yb=15 (goes back to 2011-04-18)
89
- # AAPL full history: 11428 records back to 1980-12-12
90
- ```
91
-
92
- ---
93
-
94
- ## Pattern 2: Stock fundamentals (PE ratio, revenue, market cap, margins)
95
-
96
- Different PHP files depending on metric. Construct directly.
97
-
98
- ### Market cap (daily, in billions USD)
99
-
100
- ```python
101
- import json, re
102
- from helpers import http_get
103
-
104
- def get_market_cap(ticker: str, years_back: int = 15) -> list[dict]:
105
- url = f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/market_cap.php?t={ticker}&yb={years_back}"
106
- html = http_get(url)
107
- m = re.search(r'var\s+chartData\s*=\s*\[', html)
108
- si = html.index('[', m.start())
109
- bc = 0
110
- for j, ch in enumerate(html[si:], si):
111
- if ch == '[': bc += 1
112
- elif ch == ']':
113
- bc -= 1
114
- if bc == 0: ei = j; break
115
- return json.loads(html[si:ei+1])
116
-
117
- data = get_market_cap('AAPL')
118
- # [{'date': '2026-04-15', 'v1': 3929.35}, {'date': '2026-04-16', 'v1': 3884.67}, ...]
119
- # v1 = market cap in billions USD
120
- ```
121
-
122
- ### PE ratio, revenue, current ratio (quarterly/annual fundamentals)
123
-
124
- ```python
125
- import json, re
126
- from helpers import http_get
127
-
128
- def get_fundamental(ticker: str, metric_type: str, statement: str,
129
- freq: str = 'Q', years_back: int = 15) -> list[dict]:
130
- """
131
- freq: 'Q' = quarterly, 'A' = annual
132
- """
133
- url = (
134
- f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/"
135
- f"fundamental_iframe.php?t={ticker}&type={metric_type}&statement={statement}"
136
- f"&freq={freq}&sub=&yb={years_back}"
137
- )
138
- html = http_get(url)
139
- m = re.search(r'var\s+chartData\s*=\s*\[', html)
140
- si = html.index('[', m.start())
141
- bc = 0
142
- for j, ch in enumerate(html[si:], si):
143
- if ch == '[': bc += 1
144
- elif ch == ']':
145
- bc -= 1
146
- if bc == 0: ei = j; break
147
- return json.loads(html[si:ei+1])
148
-
149
- # PE ratio
150
- pe = get_fundamental('AAPL', 'pe-ratio', 'price-ratios')
151
- # [{'date': '2025-09-30', 'v1': 254.146, 'v2': 7.46, 'v3': 34.07}, ...]
152
- # v1 = stock price, v2 = quarterly EPS, v3 = PE ratio
153
-
154
- # Revenue
155
- rev = get_fundamental('AAPL', 'revenue', 'income-statement')
156
- # [{'date': '2025-12-31', 'v1': 435.617, 'v2': 143.756, 'v3': 15.65}, ...]
157
- # v1 = TTM revenue ($B), v2 = quarterly revenue ($B), v3 = YoY growth %
158
-
159
- # Total assets
160
- assets = get_fundamental('AAPL', 'total-assets', 'balance-sheet')
161
-
162
- # Current ratio
163
- ratio = get_fundamental('AAPL', 'current-ratio', 'ratios')
164
- ```
165
-
166
- ### Profit margins
167
-
168
- ```python
169
- def get_profit_margins(ticker: str, years_back: int = 15) -> list[dict]:
170
- url = (
171
- f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/"
172
- f"fundamental_metric.php?t={ticker}&chart=profit-margin&sub=&yb={years_back}"
173
- )
174
- html = http_get(url)
175
- m = re.search(r'var\s+chartData\s*=\s*\[', html)
176
- si = html.index('[', m.start())
177
- bc = 0
178
- for j, ch in enumerate(html[si:], si):
179
- if ch == '[': bc += 1
180
- elif ch == ']':
181
- bc -= 1
182
- if bc == 0: ei = j; break
183
- return json.loads(html[si:ei+1])
184
-
185
- margins = get_profit_margins('AAPL')
186
- # [{'date': '2025-12-31', 'v1': 47.33, 'v2': 32.38, 'v3': 27.04}, ...]
187
- # v1 = gross margin %, v2 = operating margin %, v3 = net margin %
188
- ```
189
-
190
- ### Dividend yield
191
-
192
- ```python
193
- def get_dividend_yield(ticker: str, years_back: int = 15) -> list[dict]:
194
- url = f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/dividend_yield.php?t={ticker}&yb={years_back}"
195
- html = http_get(url)
196
- m = re.search(r'var\s+chartData\s*=\s*\[', html)
197
- si = html.index('[', m.start())
198
- bc = 0
199
- for j, ch in enumerate(html[si:], si):
200
- if ch == '[': bc += 1
201
- elif ch == ']':
202
- bc -= 1
203
- if bc == 0: ei = j; break
204
- return json.loads(html[si:ei+1])
205
-
206
- dy = get_dividend_yield('AAPL')
207
- # [{'date': '2026-04-17', 'c': 270.23, 'ttm_d': 1.03848, 'ttm_dy': 0.3843}, ...]
208
- # c = stock price, ttm_d = TTM dividend ($), ttm_dy = TTM yield (%)
209
- ```
210
-
211
- ### Stock metric URL reference
212
-
213
- | Metric | PHP file | Extra params |
214
- |--------|----------|-------------|
215
- | Stock price OHLCV | `stock_price_history.php` | — |
216
- | Market cap (daily) | `market_cap.php` | — |
217
- | Dividend yield | `dividend_yield.php` | — |
218
- | Stock splits (price history) | `stock_splits.php` | — |
219
- | PE ratio | `fundamental_iframe.php` | `type=pe-ratio&statement=price-ratios` |
220
- | Revenue | `fundamental_iframe.php` | `type=revenue&statement=income-statement` |
221
- | Total assets | `fundamental_iframe.php` | `type=total-assets&statement=balance-sheet` |
222
- | Current ratio | `fundamental_iframe.php` | `type=current-ratio&statement=ratios` |
223
- | Profit margins | `fundamental_metric.php` | `chart=profit-margin` |
224
-
225
- Base URL prefix: `https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/`
226
-
227
- All take `?t={TICKER}&yb={N}` (or `&sub=&yb={N}` for the fundamental ones).
228
-
229
- ---
230
-
231
- ## Pattern 3: Index and composite charts (S&P 500, Shiller PE, etc.)
232
-
233
- These pages embed chart data via `chart_iframe_comp.php`. The variable is `originalData`.
234
-
235
- ```python
236
- import json, re
237
- from helpers import http_get
238
-
239
- def extract_index_chart(page_id: int, url_slug: str) -> list[dict]:
240
- """
241
- page_id: the numeric ID from the page URL, e.g. 2577
242
- url_slug: last segment of the page URL, e.g. 'sp500-pe-ratio-price-to-earnings-chart'
243
- """
244
- url = f"https://www.macrotrends.net/assets/php/chart_iframe_comp.php?id={page_id}&url={url_slug}"
245
- html = http_get(url)
246
- m = re.search(r'var\s+originalData\s*=\s*\[', html)
247
- if not m:
248
- raise ValueError("originalData not found — this page may use a different pattern")
249
- si = html.index('[', m.start())
250
- bc = 0
251
- for j, ch in enumerate(html[si:], si):
252
- if ch == '[': bc += 1
253
- elif ch == ']':
254
- bc -= 1
255
- if bc == 0: ei = j; break
256
- return json.loads(html[si:ei+1])
257
-
258
- # S&P 500 PE ratio (1180 monthly records, 1927-2026)
259
- pe_data = extract_index_chart(2577, 'sp500-pe-ratio-price-to-earnings-chart')
260
- # [{'date': '1927-12-01', 'close': '15.9099'}, ..., {'date': '2026-03-01', 'close': '27.8925'}]
261
- # 'close' is the PE ratio value
262
-
263
- # Gold prices (1336 monthly records, 1915-2026)
264
- gold_data = extract_index_chart(1333, 'historical-gold-prices-100-year-chart')
265
- # [{'id': 'GOLDAMGBD228NLBM', 'date': '1915-01-01', 'close': '629.36', 'close1': '19.250'}, ...]
266
- # 'close' = inflation-adjusted price, 'close1' = nominal USD price
267
-
268
- print(f"Latest S&P PE: {pe_data[-1]}") # {'date': '2026-03-01', 'close': '27.8925'}
269
- print(f"Latest gold: {gold_data[-1]}") # {'id': ..., 'date': '2026-04-01', 'close': '5177.19', 'close1': '5177.190'}
270
- ```
271
-
272
- ### Detecting which pattern a page uses
273
-
274
- ```python
275
- def get_page_pattern(page_url: str) -> str:
276
- html = http_get(page_url)
277
- if 'chart_iframe_comp.php' in html:
278
- return 'index_chart' # use extract_index_chart()
279
- elif 'generateChart' in html and 'highchartsURL' in html:
280
- return 'economic_api' # use get_economic_data()
281
- elif '/production/stocks/desktop/PRODUCTION/' in html:
282
- return 'stock_iframe' # use get_stock_ohlcv() etc.
283
- return 'unknown'
284
- ```
285
-
286
- ### To get the ID and slug from a page
287
-
288
- ```python
289
- import re
290
- from helpers import http_get
291
-
292
- page_url = "https://www.macrotrends.net/2577/sp500-pe-ratio-price-to-earnings-chart"
293
- html = http_get(page_url)
294
-
295
- # Option A: parse from the iframe src in the HTML
296
- m = re.search(r'chart_iframe_comp\.php\?id=(\d+)&url=([^"&]+)', html)
297
- if m:
298
- page_id, url_slug = int(m.group(1)), m.group(2)
299
-
300
- # Option B: derive from the page URL (works when slug matches)
301
- import urllib.parse
302
- parts = page_url.rstrip('/').split('/')
303
- page_id = int(parts[-2]) # 2577
304
- url_slug = parts[-1] # 'sp500-pe-ratio-price-to-earnings-chart'
305
- ```
306
-
307
- ---
308
-
309
- ## Pattern 4: Economic indicator API
310
-
311
- Pages that use `generateChart()` in their JS load data from `/economic-data/{pageID}/{freq}`.
312
- This endpoint requires a `Referer` header matching the page URL.
313
-
314
- ```python
315
- import json, datetime, gzip, urllib.request
316
- from helpers import http_get
317
-
318
- def get_economic_data(page_id: int, referer_url: str, freq: str = 'D') -> dict:
319
- """
320
- page_id: numeric ID from the page URL (e.g. 2015 for Fed Funds Rate)
321
- referer_url: the full page URL — required as Referer header
322
- freq: 'D' = daily, 'M' = monthly (not all support both)
323
-
324
- Returns {'data': [[ts_ms, value], ...], 'metadata': {...}}
325
- """
326
- url = f"https://www.macrotrends.net/economic-data/{page_id}/{freq}"
327
- headers = {
328
- "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
329
- "Accept": "application/json, */*",
330
- "Accept-Encoding": "gzip",
331
- "Referer": referer_url,
332
- }
333
- with urllib.request.urlopen(urllib.request.Request(url, headers=headers), timeout=20) as r:
334
- raw = r.read()
335
- if r.headers.get("Content-Encoding") == "gzip":
336
- raw = gzip.decompress(raw)
337
- result = json.loads(raw)
338
- if result is None:
339
- raise ValueError(f"pageID={page_id} does not support freq={freq!r}")
340
- return result
341
-
342
- # Fed Funds Rate (daily, 25319 records)
343
- ffr = get_economic_data(2015, "https://www.macrotrends.net/2015/fed-funds-rate-historical-chart", freq='D')
344
- print(ffr['metadata']['name']) # 'Fed Funds Interest Rate'
345
- print(ffr['metadata']['label']) # '%'
346
-
347
- # Convert timestamps to dates
348
- for ts_ms, value in ffr['data'][-3:]:
349
- dt = datetime.datetime.fromtimestamp(ts_ms / 1000, datetime.UTC)
350
- print(f"{dt.strftime('%Y-%m-%d')}: {value}%")
351
- # 2026-04-13: 3.64%
352
- # 2026-04-14: 3.64%
353
- # 2026-04-15: 3.64%
354
-
355
- # 10-Year Treasury yield (daily, 16074 records)
356
- t10 = get_economic_data(2016, "https://www.macrotrends.net/2016/10-year-treasury-bond-rate-yield-chart", freq='D')
357
- # Last: 2026-04-15: 4.29%
358
-
359
- # Gold prices (monthly, 1336 records, 1915-present) — template=5
360
- gold = get_economic_data(1333, "https://www.macrotrends.net/1333/historical-gold-prices-100-year-chart", freq='M')
361
- # metadata: {'name': 'Gold Prices', 'currency': '$', 'label': ''}
362
-
363
- # US Unemployment Rate (monthly, 938 records)
364
- unemp = get_economic_data(1316, "https://www.macrotrends.net/1316/us-national-unemployment-rate", freq='M')
365
- # metadata: {'name': 'U.S. Unemployment Rate', 'label': '%'}
366
-
367
- # Debt-to-GDP ratio (monthly, 712 records)
368
- debt_gdp = get_economic_data(1381, "https://www.macrotrends.net/1381/debt-to-gdp-ratio-historical-chart", freq='M')
369
- ```
370
-
371
- ### metadata fields
372
-
373
- ```python
374
- {
375
- 'name': 'Fed Funds Interest Rate', # chart title
376
- 'tableHeaderName': 'Fed Funds Interest Rate',
377
- 'currency': '', # '$' for dollar-denominated series
378
- 'label': '%', # units label
379
- 'chartType': 'line',
380
- 'mobileChartType': 'line',
381
- 'lineWidth': 2,
382
- 'positiveColor': '#2caffe',
383
- 'negativeColor': '',
384
- 'decimals': '',
385
- 'chartScale': 'linear',
386
- 'seriesUnits': ''
387
- }
388
- ```
389
-
390
- ### Available frequency codes
391
-
392
- | Code | Meaning | Notes |
393
- |------|---------|-------|
394
- | `D` | Daily | Most series support this |
395
- | `M` | Monthly | Returns `null` if not available |
396
- | `Q` | Quarterly | Usually `null` — use `M` instead |
397
- | `A` | Annual | Usually `null` — use `M` instead |
398
- | `DEFAULT` | Default (usually monthly) | Same data as `M` for most series |
399
- | `INDEXMONTHLY` | Monthly index close | Some commodity/index series |
400
- | `INDEXDAILY` | Daily index | Some series |
401
- | `DAILYEXCHANGERATE` | Daily FX rate | Currency pairs |
402
- | `10YD` | 10-year daily | Specialized series |
403
-
404
- Try `D` first, fall back to `M` if you get `null`.
405
-
406
- ### Known economic page IDs
407
-
408
- | ID | URL slug | Description |
409
- |----|----------|-------------|
410
- | 1316 | us-national-unemployment-rate | U.S. Unemployment Rate (monthly, back to 1948) |
411
- | 1333 | historical-gold-prices-100-year-chart | Gold Prices (monthly, back to 1915) |
412
- | 1381 | debt-to-gdp-ratio-historical-chart | U.S. Debt to GDP Ratio |
413
- | 2015 | fed-funds-rate-historical-chart | Fed Funds Interest Rate (daily, back to 1954) |
414
- | 2016 | 10-year-treasury-bond-rate-yield-chart | 10-Year Treasury Yield (daily, back to 1962) |
415
- | 2577 | sp500-pe-ratio-price-to-earnings-chart | S&P 500 PE Ratio (uses `chart_iframe_comp.php`) |
416
-
417
- ---
418
-
419
- ## Generic extraction helper
420
-
421
- One function that handles all three embedded-JS patterns:
422
-
423
- ```python
424
- import json, re
425
- from helpers import http_get
426
-
427
- def extract_chart_var(html: str, var_name: str) -> list:
428
- """Extract a JS array variable from Macrotrends iframe HTML."""
429
- m = re.search(rf'var\s+{re.escape(var_name)}\s*=\s*\[', html)
430
- if not m:
431
- return []
432
- si = html.index('[', m.start())
433
- bc = 0
434
- for j, ch in enumerate(html[si:], si):
435
- if ch == '[': bc += 1
436
- elif ch == ']':
437
- bc -= 1
438
- if bc == 0:
439
- return json.loads(html[si:j+1])
440
- return []
441
-
442
- # Works for dataDaily, chartData, or originalData:
443
- html = http_get("https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/stock_price_history.php?t=AAPL&yb=15")
444
- daily = extract_chart_var(html, 'dataDaily')
445
-
446
- html2 = http_get("https://www.macrotrends.net/assets/php/chart_iframe_comp.php?id=2577&url=sp500-pe-ratio-price-to-earnings-chart")
447
- pe_data = extract_chart_var(html2, 'originalData')
448
- ```
449
-
450
- ---
451
-
452
- ## URL construction guide
453
-
454
- ### Stock pages
455
-
456
- ```python
457
- STOCK_BASE = "https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/"
458
-
459
- # Price history OHLCV
460
- f"{STOCK_BASE}stock_price_history.php?t={ticker}" # all history
461
- f"{STOCK_BASE}stock_price_history.php?t={ticker}&yb={years}" # last N years
462
-
463
- # Market cap
464
- f"{STOCK_BASE}market_cap.php?t={ticker}&yb={years}"
465
-
466
- # Fundamentals
467
- f"{STOCK_BASE}fundamental_iframe.php?t={ticker}&type={type}&statement={stmt}&freq={freq}&sub=&yb={years}"
468
- # type/statement combos: pe-ratio/price-ratios, revenue/income-statement,
469
- # total-assets/balance-sheet, current-ratio/ratios
470
-
471
- # Metrics
472
- f"{STOCK_BASE}fundamental_metric.php?t={ticker}&chart={metric}&sub=&yb={years}"
473
- # metrics: profit-margin
474
-
475
- # Dividend yield
476
- f"{STOCK_BASE}dividend_yield.php?t={ticker}&yb={years}"
477
- ```
478
-
479
- ### Economic / index pages
480
-
481
- ```python
482
- # From numeric ID + URL slug (read from page source or page URL)
483
- f"https://www.macrotrends.net/assets/php/chart_iframe_comp.php?id={id}&url={slug}"
484
-
485
- # Economic indicator JSON API (requires Referer header)
486
- f"https://www.macrotrends.net/economic-data/{page_id}/{freq}"
487
- ```
488
-
489
- ---
490
-
491
- ## Rate limits and anti-bot
492
-
493
- - **No rate limiting observed** at any tested volume. 10 rapid requests to the same stock iframe completed in 1.8s with no throttling, CAPTCHA, or 429 errors.
494
- - **Default UA works** (`Mozilla/5.0`) for most endpoints. The iframe PHP files never 403'd.
495
- - **Chrome UA needed** for some main HTML pages (not data endpoints): use when fetching `/stocks/charts/...` or `/2015/...` wrapper pages if you get 403. Switch to:
496
- ```python
497
- headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}
498
- ```
499
- - **Referer required** for `/economic-data/{id}/{freq}` — send the page URL as `Referer`. Without it, the request is allowed but you get a 403 on some pages.
500
- - **No cookies, sessions, or auth tokens** needed for any endpoint.
501
-
502
- ---
503
-
504
- ## Gotchas
505
-
506
- **Main page URL ≠ data page:** Some URLs redirect to different content. `/1316/us-national-debt-by-year` redirects to `/1316/us-national-unemployment-rate`. Always check the final URL with `r.url` if the returned data looks wrong. Use the final URL as the Referer.
507
-
508
- **yb parameter controls history depth:**
509
- - `yb=1` → ~250 records (last year)
510
- - `yb=15` → ~3772 records (last 15 years)
511
- - omit → full history (AAPL: 11428 records to 1980; default for most queries)
512
-
513
- **Two iframe patterns for economic pages:** Pages at `macrotrends.net/NNNN/slug` use either `chart_iframe_comp.php` (→ `originalData`) or `generateChart` + `/economic-data/` API. Check the main page HTML to detect which:
514
- ```python
515
- if 'chart_iframe_comp.php' in html: # use extract_index_chart()
516
- elif 'highchartsURL' in html: # use get_economic_data()
517
- ```
518
-
519
- **Gold data has two price columns:**
520
- ```python
521
- {'id': 'GOLDAMGBD228NLBM', 'date': '2026-04-01', 'close': '5177.19', 'close1': '5177.190'}
522
- # 'close' = inflation-adjusted price (base year adjusts over time)
523
- # 'close1' = nominal USD price (the raw market price)
524
- ```
525
-
526
- **Economic API frequency codes:** Only `D` and `M` consistently return data across most series. `A` and `Q` return `null` for most economic indicators. Always try `D` first.
527
-
528
- **chartData fields vary by metric:**
529
- - `market_cap.php` → `{'date', 'v1'}` (v1 = market cap in $B)
530
- - `fundamental_iframe.php` type=pe-ratio → `{'date', 'v1', 'v2', 'v3'}` (stock price, EPS, PE)
531
- - `fundamental_iframe.php` type=revenue → `{'date', 'v1', 'v2', 'v3'}` (TTM revenue, quarterly revenue, YoY%)
532
- - `fundamental_metric.php` chart=profit-margin → `{'date', 'v1', 'v2', 'v3'}` (gross%, operating%, net%)
533
- - `dividend_yield.php` → `{'date', 'c', 'ttm_d', 'ttm_dy'}` (price, dividend, yield%)
534
-
535
- **Bracket matching required for large arrays:** The `var dataDaily = [...]` in stock iframes is ~450KB with 3772 OHLCV records. The `re.DOTALL` greedy approach works but is slow; bracket-counting (`bc` pattern above) is O(n) and fast.
536
-
537
- **No public API for ticker lookup:** To find the company slug for a URL, check the search endpoint: `https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/ticker_search_list.php?v=YYYYMMDD` — but the stock price iframe only needs the ticker symbol (`?t=AAPL`), not the slug.
1
+ # Macrotrends — Data Extraction
2
+
3
+ `https://www.macrotrends.net` — long-term historical financial and economic charts. Three access patterns depending on page type; all work with plain `http_get`, no browser required.
4
+
5
+ All results validated against live site on 2026-04-18.
6
+
7
+ ## Do this first: pick your access pattern
8
+
9
+ | Goal | Pattern | Latency | Variable |
10
+ |------|---------|---------|----------|
11
+ | Stock OHLCV price history | Direct iframe PHP | ~190ms | `dataDaily` |
12
+ | Stock market cap (daily) | Direct iframe PHP | ~200ms | `chartData` |
13
+ | Stock fundamentals (PE, revenue, margins) | Direct iframe PHP | ~140ms | `chartData` |
14
+ | S&P 500 / composite index charts | `chart_iframe_comp.php` | ~90ms | `originalData` |
15
+ | Economic indicators (rates, yields, CPI) | `/economic-data/` JSON API | ~150ms | `data[]` array |
16
+ | Gold, commodity prices | Either path (both work) | ~150ms | `data[]` or `originalData` |
17
+
18
+ **Never use the browser for Macrotrends read-only tasks.** All endpoints are accessible via `http_get` with the default `Mozilla/5.0` UA. For pages that occasionally 403, switch to a Chrome UA (see gotchas).
19
+
20
+ ---
21
+
22
+ ## Pattern 1: Stock price history (OHLCV)
23
+
24
+ Construct the iframe URL directly — no need to fetch the main page first.
25
+
26
+ ```python
27
+ import json, re
28
+ from helpers import http_get
29
+
30
+ def get_stock_ohlcv(ticker: str, years_back: int = None) -> list[dict]:
31
+ """
32
+ Returns daily OHLCV records for any US stock.
33
+
34
+ ticker: uppercase ticker symbol, e.g. 'AAPL', 'MSFT', 'TSLA', 'NVDA'
35
+ years_back: number of years of history (1=~250 records, 15=~3772 records).
36
+ Omit (None) to get ALL available history (AAPL goes back to 1980).
37
+ """
38
+ url = f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/stock_price_history.php?t={ticker}"
39
+ if years_back:
40
+ url += f"&yb={years_back}"
41
+
42
+ html = http_get(url)
43
+ m = re.search(r'var\s+dataDaily\s*=\s*\[', html)
44
+ if not m:
45
+ raise ValueError(f"No dataDaily found for ticker {ticker!r}")
46
+
47
+ si = html.index('[', m.start())
48
+ bc = 0
49
+ for j, ch in enumerate(html[si:], si):
50
+ if ch == '[': bc += 1
51
+ elif ch == ']':
52
+ bc -= 1
53
+ if bc == 0: ei = j; break
54
+ return json.loads(html[si:ei+1])
55
+
56
+ # Usage
57
+ records = get_stock_ohlcv('AAPL', years_back=15)
58
+ # [{'d': '2011-04-18', 'o': '9.771', 'h': '9.9547', 'l': '9.593', 'c': '9.9433', 'v': '18.275'}, ...]
59
+
60
+ latest = records[-1]
61
+ # {'d': '2026-04-17', 'o': '266.96', 'h': '272.3', 'l': '266.72', 'c': '270.23',
62
+ # 'v': '55.211', 'ma50': '260.554', 'ma200': '251.828'}
63
+
64
+ print(f"{latest['d']}: close=${latest['c']} vol={latest['v']}M shares")
65
+ ```
66
+
67
+ ### dataDaily field reference
68
+
69
+ | Field | Meaning | Type |
70
+ |-------|---------|------|
71
+ | `d` | Date (YYYY-MM-DD) | str |
72
+ | `o` | Open price (adjusted for splits) | str/float |
73
+ | `h` | High | str/float |
74
+ | `l` | Low | str/float |
75
+ | `c` | Close | str/float |
76
+ | `v` | Volume in **millions of shares** | str/float |
77
+ | `ma50` | 50-day moving average | str/float (appears on recent records only) |
78
+ | `ma200` | 200-day moving average | str/float (appears on recent records only) |
79
+
80
+ **Note:** All price values are strings — cast with `float()`. Volume is millions: `55.211` = 55.2M shares traded.
81
+
82
+ ### Confirmed tickers (2026-04-18)
83
+
84
+ All tested with direct iframe URL, no page fetch needed:
85
+
86
+ ```python
87
+ # All work: AAPL, MSFT, TSLA, NVDA, GOOGL, AMZN, META, NFLX, etc.
88
+ # 3772 records for yb=15 (goes back to 2011-04-18)
89
+ # AAPL full history: 11428 records back to 1980-12-12
90
+ ```
91
+
92
+ ---
93
+
94
+ ## Pattern 2: Stock fundamentals (PE ratio, revenue, market cap, margins)
95
+
96
+ Different PHP files depending on metric. Construct directly.
97
+
98
+ ### Market cap (daily, in billions USD)
99
+
100
+ ```python
101
+ import json, re
102
+ from helpers import http_get
103
+
104
+ def get_market_cap(ticker: str, years_back: int = 15) -> list[dict]:
105
+ url = f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/market_cap.php?t={ticker}&yb={years_back}"
106
+ html = http_get(url)
107
+ m = re.search(r'var\s+chartData\s*=\s*\[', html)
108
+ si = html.index('[', m.start())
109
+ bc = 0
110
+ for j, ch in enumerate(html[si:], si):
111
+ if ch == '[': bc += 1
112
+ elif ch == ']':
113
+ bc -= 1
114
+ if bc == 0: ei = j; break
115
+ return json.loads(html[si:ei+1])
116
+
117
+ data = get_market_cap('AAPL')
118
+ # [{'date': '2026-04-15', 'v1': 3929.35}, {'date': '2026-04-16', 'v1': 3884.67}, ...]
119
+ # v1 = market cap in billions USD
120
+ ```
121
+
122
+ ### PE ratio, revenue, current ratio (quarterly/annual fundamentals)
123
+
124
+ ```python
125
+ import json, re
126
+ from helpers import http_get
127
+
128
+ def get_fundamental(ticker: str, metric_type: str, statement: str,
129
+ freq: str = 'Q', years_back: int = 15) -> list[dict]:
130
+ """
131
+ freq: 'Q' = quarterly, 'A' = annual
132
+ """
133
+ url = (
134
+ f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/"
135
+ f"fundamental_iframe.php?t={ticker}&type={metric_type}&statement={statement}"
136
+ f"&freq={freq}&sub=&yb={years_back}"
137
+ )
138
+ html = http_get(url)
139
+ m = re.search(r'var\s+chartData\s*=\s*\[', html)
140
+ si = html.index('[', m.start())
141
+ bc = 0
142
+ for j, ch in enumerate(html[si:], si):
143
+ if ch == '[': bc += 1
144
+ elif ch == ']':
145
+ bc -= 1
146
+ if bc == 0: ei = j; break
147
+ return json.loads(html[si:ei+1])
148
+
149
+ # PE ratio
150
+ pe = get_fundamental('AAPL', 'pe-ratio', 'price-ratios')
151
+ # [{'date': '2025-09-30', 'v1': 254.146, 'v2': 7.46, 'v3': 34.07}, ...]
152
+ # v1 = stock price, v2 = quarterly EPS, v3 = PE ratio
153
+
154
+ # Revenue
155
+ rev = get_fundamental('AAPL', 'revenue', 'income-statement')
156
+ # [{'date': '2025-12-31', 'v1': 435.617, 'v2': 143.756, 'v3': 15.65}, ...]
157
+ # v1 = TTM revenue ($B), v2 = quarterly revenue ($B), v3 = YoY growth %
158
+
159
+ # Total assets
160
+ assets = get_fundamental('AAPL', 'total-assets', 'balance-sheet')
161
+
162
+ # Current ratio
163
+ ratio = get_fundamental('AAPL', 'current-ratio', 'ratios')
164
+ ```
165
+
166
+ ### Profit margins
167
+
168
+ ```python
169
+ def get_profit_margins(ticker: str, years_back: int = 15) -> list[dict]:
170
+ url = (
171
+ f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/"
172
+ f"fundamental_metric.php?t={ticker}&chart=profit-margin&sub=&yb={years_back}"
173
+ )
174
+ html = http_get(url)
175
+ m = re.search(r'var\s+chartData\s*=\s*\[', html)
176
+ si = html.index('[', m.start())
177
+ bc = 0
178
+ for j, ch in enumerate(html[si:], si):
179
+ if ch == '[': bc += 1
180
+ elif ch == ']':
181
+ bc -= 1
182
+ if bc == 0: ei = j; break
183
+ return json.loads(html[si:ei+1])
184
+
185
+ margins = get_profit_margins('AAPL')
186
+ # [{'date': '2025-12-31', 'v1': 47.33, 'v2': 32.38, 'v3': 27.04}, ...]
187
+ # v1 = gross margin %, v2 = operating margin %, v3 = net margin %
188
+ ```
189
+
190
+ ### Dividend yield
191
+
192
+ ```python
193
+ def get_dividend_yield(ticker: str, years_back: int = 15) -> list[dict]:
194
+ url = f"https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/dividend_yield.php?t={ticker}&yb={years_back}"
195
+ html = http_get(url)
196
+ m = re.search(r'var\s+chartData\s*=\s*\[', html)
197
+ si = html.index('[', m.start())
198
+ bc = 0
199
+ for j, ch in enumerate(html[si:], si):
200
+ if ch == '[': bc += 1
201
+ elif ch == ']':
202
+ bc -= 1
203
+ if bc == 0: ei = j; break
204
+ return json.loads(html[si:ei+1])
205
+
206
+ dy = get_dividend_yield('AAPL')
207
+ # [{'date': '2026-04-17', 'c': 270.23, 'ttm_d': 1.03848, 'ttm_dy': 0.3843}, ...]
208
+ # c = stock price, ttm_d = TTM dividend ($), ttm_dy = TTM yield (%)
209
+ ```
210
+
211
+ ### Stock metric URL reference
212
+
213
+ | Metric | PHP file | Extra params |
214
+ |--------|----------|-------------|
215
+ | Stock price OHLCV | `stock_price_history.php` | — |
216
+ | Market cap (daily) | `market_cap.php` | — |
217
+ | Dividend yield | `dividend_yield.php` | — |
218
+ | Stock splits (price history) | `stock_splits.php` | — |
219
+ | PE ratio | `fundamental_iframe.php` | `type=pe-ratio&statement=price-ratios` |
220
+ | Revenue | `fundamental_iframe.php` | `type=revenue&statement=income-statement` |
221
+ | Total assets | `fundamental_iframe.php` | `type=total-assets&statement=balance-sheet` |
222
+ | Current ratio | `fundamental_iframe.php` | `type=current-ratio&statement=ratios` |
223
+ | Profit margins | `fundamental_metric.php` | `chart=profit-margin` |
224
+
225
+ Base URL prefix: `https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/`
226
+
227
+ All take `?t={TICKER}&yb={N}` (or `&sub=&yb={N}` for the fundamental ones).
228
+
229
+ ---
230
+
231
+ ## Pattern 3: Index and composite charts (S&P 500, Shiller PE, etc.)
232
+
233
+ These pages embed chart data via `chart_iframe_comp.php`. The variable is `originalData`.
234
+
235
+ ```python
236
+ import json, re
237
+ from helpers import http_get
238
+
239
+ def extract_index_chart(page_id: int, url_slug: str) -> list[dict]:
240
+ """
241
+ page_id: the numeric ID from the page URL, e.g. 2577
242
+ url_slug: last segment of the page URL, e.g. 'sp500-pe-ratio-price-to-earnings-chart'
243
+ """
244
+ url = f"https://www.macrotrends.net/assets/php/chart_iframe_comp.php?id={page_id}&url={url_slug}"
245
+ html = http_get(url)
246
+ m = re.search(r'var\s+originalData\s*=\s*\[', html)
247
+ if not m:
248
+ raise ValueError("originalData not found — this page may use a different pattern")
249
+ si = html.index('[', m.start())
250
+ bc = 0
251
+ for j, ch in enumerate(html[si:], si):
252
+ if ch == '[': bc += 1
253
+ elif ch == ']':
254
+ bc -= 1
255
+ if bc == 0: ei = j; break
256
+ return json.loads(html[si:ei+1])
257
+
258
+ # S&P 500 PE ratio (1180 monthly records, 1927-2026)
259
+ pe_data = extract_index_chart(2577, 'sp500-pe-ratio-price-to-earnings-chart')
260
+ # [{'date': '1927-12-01', 'close': '15.9099'}, ..., {'date': '2026-03-01', 'close': '27.8925'}]
261
+ # 'close' is the PE ratio value
262
+
263
+ # Gold prices (1336 monthly records, 1915-2026)
264
+ gold_data = extract_index_chart(1333, 'historical-gold-prices-100-year-chart')
265
+ # [{'id': 'GOLDAMGBD228NLBM', 'date': '1915-01-01', 'close': '629.36', 'close1': '19.250'}, ...]
266
+ # 'close' = inflation-adjusted price, 'close1' = nominal USD price
267
+
268
+ print(f"Latest S&P PE: {pe_data[-1]}") # {'date': '2026-03-01', 'close': '27.8925'}
269
+ print(f"Latest gold: {gold_data[-1]}") # {'id': ..., 'date': '2026-04-01', 'close': '5177.19', 'close1': '5177.190'}
270
+ ```
271
+
272
+ ### Detecting which pattern a page uses
273
+
274
+ ```python
275
+ def get_page_pattern(page_url: str) -> str:
276
+ html = http_get(page_url)
277
+ if 'chart_iframe_comp.php' in html:
278
+ return 'index_chart' # use extract_index_chart()
279
+ elif 'generateChart' in html and 'highchartsURL' in html:
280
+ return 'economic_api' # use get_economic_data()
281
+ elif '/production/stocks/desktop/PRODUCTION/' in html:
282
+ return 'stock_iframe' # use get_stock_ohlcv() etc.
283
+ return 'unknown'
284
+ ```
285
+
286
+ ### To get the ID and slug from a page
287
+
288
+ ```python
289
+ import re
290
+ from helpers import http_get
291
+
292
+ page_url = "https://www.macrotrends.net/2577/sp500-pe-ratio-price-to-earnings-chart"
293
+ html = http_get(page_url)
294
+
295
+ # Option A: parse from the iframe src in the HTML
296
+ m = re.search(r'chart_iframe_comp\.php\?id=(\d+)&url=([^"&]+)', html)
297
+ if m:
298
+ page_id, url_slug = int(m.group(1)), m.group(2)
299
+
300
+ # Option B: derive from the page URL (works when slug matches)
301
+ import urllib.parse
302
+ parts = page_url.rstrip('/').split('/')
303
+ page_id = int(parts[-2]) # 2577
304
+ url_slug = parts[-1] # 'sp500-pe-ratio-price-to-earnings-chart'
305
+ ```
306
+
307
+ ---
308
+
309
+ ## Pattern 4: Economic indicator API
310
+
311
+ Pages that use `generateChart()` in their JS load data from `/economic-data/{pageID}/{freq}`.
312
+ This endpoint requires a `Referer` header matching the page URL.
313
+
314
+ ```python
315
+ import json, datetime, gzip, urllib.request
316
+ from helpers import http_get
317
+
318
+ def get_economic_data(page_id: int, referer_url: str, freq: str = 'D') -> dict:
319
+ """
320
+ page_id: numeric ID from the page URL (e.g. 2015 for Fed Funds Rate)
321
+ referer_url: the full page URL — required as Referer header
322
+ freq: 'D' = daily, 'M' = monthly (not all support both)
323
+
324
+ Returns {'data': [[ts_ms, value], ...], 'metadata': {...}}
325
+ """
326
+ url = f"https://www.macrotrends.net/economic-data/{page_id}/{freq}"
327
+ headers = {
328
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
329
+ "Accept": "application/json, */*",
330
+ "Accept-Encoding": "gzip",
331
+ "Referer": referer_url,
332
+ }
333
+ with urllib.request.urlopen(urllib.request.Request(url, headers=headers), timeout=20) as r:
334
+ raw = r.read()
335
+ if r.headers.get("Content-Encoding") == "gzip":
336
+ raw = gzip.decompress(raw)
337
+ result = json.loads(raw)
338
+ if result is None:
339
+ raise ValueError(f"pageID={page_id} does not support freq={freq!r}")
340
+ return result
341
+
342
+ # Fed Funds Rate (daily, 25319 records)
343
+ ffr = get_economic_data(2015, "https://www.macrotrends.net/2015/fed-funds-rate-historical-chart", freq='D')
344
+ print(ffr['metadata']['name']) # 'Fed Funds Interest Rate'
345
+ print(ffr['metadata']['label']) # '%'
346
+
347
+ # Convert timestamps to dates
348
+ for ts_ms, value in ffr['data'][-3:]:
349
+ dt = datetime.datetime.fromtimestamp(ts_ms / 1000, datetime.UTC)
350
+ print(f"{dt.strftime('%Y-%m-%d')}: {value}%")
351
+ # 2026-04-13: 3.64%
352
+ # 2026-04-14: 3.64%
353
+ # 2026-04-15: 3.64%
354
+
355
+ # 10-Year Treasury yield (daily, 16074 records)
356
+ t10 = get_economic_data(2016, "https://www.macrotrends.net/2016/10-year-treasury-bond-rate-yield-chart", freq='D')
357
+ # Last: 2026-04-15: 4.29%
358
+
359
+ # Gold prices (monthly, 1336 records, 1915-present) — template=5
360
+ gold = get_economic_data(1333, "https://www.macrotrends.net/1333/historical-gold-prices-100-year-chart", freq='M')
361
+ # metadata: {'name': 'Gold Prices', 'currency': '$', 'label': ''}
362
+
363
+ # US Unemployment Rate (monthly, 938 records)
364
+ unemp = get_economic_data(1316, "https://www.macrotrends.net/1316/us-national-unemployment-rate", freq='M')
365
+ # metadata: {'name': 'U.S. Unemployment Rate', 'label': '%'}
366
+
367
+ # Debt-to-GDP ratio (monthly, 712 records)
368
+ debt_gdp = get_economic_data(1381, "https://www.macrotrends.net/1381/debt-to-gdp-ratio-historical-chart", freq='M')
369
+ ```
370
+
371
+ ### metadata fields
372
+
373
+ ```python
374
+ {
375
+ 'name': 'Fed Funds Interest Rate', # chart title
376
+ 'tableHeaderName': 'Fed Funds Interest Rate',
377
+ 'currency': '', # '$' for dollar-denominated series
378
+ 'label': '%', # units label
379
+ 'chartType': 'line',
380
+ 'mobileChartType': 'line',
381
+ 'lineWidth': 2,
382
+ 'positiveColor': '#2caffe',
383
+ 'negativeColor': '',
384
+ 'decimals': '',
385
+ 'chartScale': 'linear',
386
+ 'seriesUnits': ''
387
+ }
388
+ ```
389
+
390
+ ### Available frequency codes
391
+
392
+ | Code | Meaning | Notes |
393
+ |------|---------|-------|
394
+ | `D` | Daily | Most series support this |
395
+ | `M` | Monthly | Returns `null` if not available |
396
+ | `Q` | Quarterly | Usually `null` — use `M` instead |
397
+ | `A` | Annual | Usually `null` — use `M` instead |
398
+ | `DEFAULT` | Default (usually monthly) | Same data as `M` for most series |
399
+ | `INDEXMONTHLY` | Monthly index close | Some commodity/index series |
400
+ | `INDEXDAILY` | Daily index | Some series |
401
+ | `DAILYEXCHANGERATE` | Daily FX rate | Currency pairs |
402
+ | `10YD` | 10-year daily | Specialized series |
403
+
404
+ Try `D` first, fall back to `M` if you get `null`.
405
+
406
+ ### Known economic page IDs
407
+
408
+ | ID | URL slug | Description |
409
+ |----|----------|-------------|
410
+ | 1316 | us-national-unemployment-rate | U.S. Unemployment Rate (monthly, back to 1948) |
411
+ | 1333 | historical-gold-prices-100-year-chart | Gold Prices (monthly, back to 1915) |
412
+ | 1381 | debt-to-gdp-ratio-historical-chart | U.S. Debt to GDP Ratio |
413
+ | 2015 | fed-funds-rate-historical-chart | Fed Funds Interest Rate (daily, back to 1954) |
414
+ | 2016 | 10-year-treasury-bond-rate-yield-chart | 10-Year Treasury Yield (daily, back to 1962) |
415
+ | 2577 | sp500-pe-ratio-price-to-earnings-chart | S&P 500 PE Ratio (uses `chart_iframe_comp.php`) |
416
+
417
+ ---
418
+
419
+ ## Generic extraction helper
420
+
421
+ One function that handles all three embedded-JS patterns:
422
+
423
+ ```python
424
+ import json, re
425
+ from helpers import http_get
426
+
427
+ def extract_chart_var(html: str, var_name: str) -> list:
428
+ """Extract a JS array variable from Macrotrends iframe HTML."""
429
+ m = re.search(rf'var\s+{re.escape(var_name)}\s*=\s*\[', html)
430
+ if not m:
431
+ return []
432
+ si = html.index('[', m.start())
433
+ bc = 0
434
+ for j, ch in enumerate(html[si:], si):
435
+ if ch == '[': bc += 1
436
+ elif ch == ']':
437
+ bc -= 1
438
+ if bc == 0:
439
+ return json.loads(html[si:j+1])
440
+ return []
441
+
442
+ # Works for dataDaily, chartData, or originalData:
443
+ html = http_get("https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/stock_price_history.php?t=AAPL&yb=15")
444
+ daily = extract_chart_var(html, 'dataDaily')
445
+
446
+ html2 = http_get("https://www.macrotrends.net/assets/php/chart_iframe_comp.php?id=2577&url=sp500-pe-ratio-price-to-earnings-chart")
447
+ pe_data = extract_chart_var(html2, 'originalData')
448
+ ```
449
+
450
+ ---
451
+
452
+ ## URL construction guide
453
+
454
+ ### Stock pages
455
+
456
+ ```python
457
+ STOCK_BASE = "https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/"
458
+
459
+ # Price history OHLCV
460
+ f"{STOCK_BASE}stock_price_history.php?t={ticker}" # all history
461
+ f"{STOCK_BASE}stock_price_history.php?t={ticker}&yb={years}" # last N years
462
+
463
+ # Market cap
464
+ f"{STOCK_BASE}market_cap.php?t={ticker}&yb={years}"
465
+
466
+ # Fundamentals
467
+ f"{STOCK_BASE}fundamental_iframe.php?t={ticker}&type={type}&statement={stmt}&freq={freq}&sub=&yb={years}"
468
+ # type/statement combos: pe-ratio/price-ratios, revenue/income-statement,
469
+ # total-assets/balance-sheet, current-ratio/ratios
470
+
471
+ # Metrics
472
+ f"{STOCK_BASE}fundamental_metric.php?t={ticker}&chart={metric}&sub=&yb={years}"
473
+ # metrics: profit-margin
474
+
475
+ # Dividend yield
476
+ f"{STOCK_BASE}dividend_yield.php?t={ticker}&yb={years}"
477
+ ```
478
+
479
+ ### Economic / index pages
480
+
481
+ ```python
482
+ # From numeric ID + URL slug (read from page source or page URL)
483
+ f"https://www.macrotrends.net/assets/php/chart_iframe_comp.php?id={id}&url={slug}"
484
+
485
+ # Economic indicator JSON API (requires Referer header)
486
+ f"https://www.macrotrends.net/economic-data/{page_id}/{freq}"
487
+ ```
488
+
489
+ ---
490
+
491
+ ## Rate limits and anti-bot
492
+
493
+ - **No rate limiting observed** at any tested volume. 10 rapid requests to the same stock iframe completed in 1.8s with no throttling, CAPTCHA, or 429 errors.
494
+ - **Default UA works** (`Mozilla/5.0`) for most endpoints. The iframe PHP files never 403'd.
495
+ - **Chrome UA needed** for some main HTML pages (not data endpoints): use when fetching `/stocks/charts/...` or `/2015/...` wrapper pages if you get 403. Switch to:
496
+ ```python
497
+ headers = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"}
498
+ ```
499
+ - **Referer required** for `/economic-data/{id}/{freq}` — send the page URL as `Referer`. Without it, the request is allowed but you get a 403 on some pages.
500
+ - **No cookies, sessions, or auth tokens** needed for any endpoint.
501
+
502
+ ---
503
+
504
+ ## Gotchas
505
+
506
+ **Main page URL ≠ data page:** Some URLs redirect to different content. `/1316/us-national-debt-by-year` redirects to `/1316/us-national-unemployment-rate`. Always check the final URL with `r.url` if the returned data looks wrong. Use the final URL as the Referer.
507
+
508
+ **yb parameter controls history depth:**
509
+ - `yb=1` → ~250 records (last year)
510
+ - `yb=15` → ~3772 records (last 15 years)
511
+ - omit → full history (AAPL: 11428 records to 1980; default for most queries)
512
+
513
+ **Two iframe patterns for economic pages:** Pages at `macrotrends.net/NNNN/slug` use either `chart_iframe_comp.php` (→ `originalData`) or `generateChart` + `/economic-data/` API. Check the main page HTML to detect which:
514
+ ```python
515
+ if 'chart_iframe_comp.php' in html: # use extract_index_chart()
516
+ elif 'highchartsURL' in html: # use get_economic_data()
517
+ ```
518
+
519
+ **Gold data has two price columns:**
520
+ ```python
521
+ {'id': 'GOLDAMGBD228NLBM', 'date': '2026-04-01', 'close': '5177.19', 'close1': '5177.190'}
522
+ # 'close' = inflation-adjusted price (base year adjusts over time)
523
+ # 'close1' = nominal USD price (the raw market price)
524
+ ```
525
+
526
+ **Economic API frequency codes:** Only `D` and `M` consistently return data across most series. `A` and `Q` return `null` for most economic indicators. Always try `D` first.
527
+
528
+ **chartData fields vary by metric:**
529
+ - `market_cap.php` → `{'date', 'v1'}` (v1 = market cap in $B)
530
+ - `fundamental_iframe.php` type=pe-ratio → `{'date', 'v1', 'v2', 'v3'}` (stock price, EPS, PE)
531
+ - `fundamental_iframe.php` type=revenue → `{'date', 'v1', 'v2', 'v3'}` (TTM revenue, quarterly revenue, YoY%)
532
+ - `fundamental_metric.php` chart=profit-margin → `{'date', 'v1', 'v2', 'v3'}` (gross%, operating%, net%)
533
+ - `dividend_yield.php` → `{'date', 'c', 'ttm_d', 'ttm_dy'}` (price, dividend, yield%)
534
+
535
+ **Bracket matching required for large arrays:** The `var dataDaily = [...]` in stock iframes is ~450KB with 3772 OHLCV records. The `re.DOTALL` greedy approach works but is slow; bracket-counting (`bc` pattern above) is O(n) and fast.
536
+
537
+ **No public API for ticker lookup:** To find the company slug for a URL, check the search endpoint: `https://www.macrotrends.net/production/stocks/desktop/PRODUCTION/ticker_search_list.php?v=YYYYMMDD` — but the stock price iframe only needs the ticker symbol (`?t=AAPL`), not the slug.