autoscholar-cli 1.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.
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.runPython = runPython;
37
+ exports.checkPythonDeps = checkPythonDeps;
38
+ const child_process_1 = require("child_process");
39
+ const fs = __importStar(require("fs"));
40
+ const path = __importStar(require("path"));
41
+ const os = __importStar(require("os"));
42
+ function findPython() {
43
+ const candidates = ['python3', 'python'];
44
+ for (const cmd of candidates) {
45
+ try {
46
+ const { execSync } = require('child_process');
47
+ const version = execSync(`${cmd} --version 2>&1`, { encoding: 'utf-8' });
48
+ if (version.includes('Python 3')) {
49
+ return cmd;
50
+ }
51
+ }
52
+ catch { }
53
+ }
54
+ return 'python3';
55
+ }
56
+ async function runPython(code, workingDir, timeoutMs = 120000) {
57
+ const pythonBin = findPython();
58
+ const tmpDir = path.join(os.tmpdir(), 'autoscholar-py');
59
+ if (!fs.existsSync(tmpDir))
60
+ fs.mkdirSync(tmpDir, { recursive: true });
61
+ const tmpFile = path.join(tmpDir, `script_${Date.now()}.py`);
62
+ fs.writeFileSync(tmpFile, code, 'utf-8');
63
+ return new Promise((resolve) => {
64
+ const proc = (0, child_process_1.execFile)(pythonBin, [tmpFile], {
65
+ cwd: workingDir,
66
+ timeout: timeoutMs,
67
+ maxBuffer: 50 * 1024 * 1024,
68
+ env: {
69
+ ...process.env,
70
+ MPLBACKEND: 'Agg',
71
+ PYTHONDONTWRITEBYTECODE: '1',
72
+ PYTHONUNBUFFERED: '1',
73
+ },
74
+ }, (error, stdout, stderr) => {
75
+ try {
76
+ fs.unlinkSync(tmpFile);
77
+ }
78
+ catch { }
79
+ if (error) {
80
+ resolve({
81
+ success: false,
82
+ output: stdout || '',
83
+ error: stderr || error.message || 'Unknown Python error',
84
+ exitCode: error.code ? parseInt(String(error.code)) : 1,
85
+ });
86
+ }
87
+ else {
88
+ resolve({
89
+ success: true,
90
+ output: stdout || '',
91
+ error: stderr || '',
92
+ exitCode: 0,
93
+ });
94
+ }
95
+ });
96
+ });
97
+ }
98
+ async function checkPythonDeps() {
99
+ const pythonBin = findPython();
100
+ const required = [
101
+ 'pandas', 'numpy', 'scipy', 'matplotlib', 'seaborn',
102
+ 'statsmodels', 'sklearn',
103
+ ];
104
+ const optional = [
105
+ 'arch', 'linearmodels', 'xgboost', 'lightgbm',
106
+ ];
107
+ const missing = [];
108
+ const installed = [];
109
+ const checkCode = `
110
+ import json
111
+ result = {}
112
+ for pkg in ${JSON.stringify([...required, ...optional])}:
113
+ try:
114
+ __import__(pkg)
115
+ result[pkg] = True
116
+ except ImportError:
117
+ result[pkg] = False
118
+ print(json.dumps(result))
119
+ `;
120
+ const result = await runPython(checkCode, process.cwd(), 15000);
121
+ if (result.success) {
122
+ try {
123
+ const pkgs = JSON.parse(result.output.trim());
124
+ for (const [pkg, available] of Object.entries(pkgs)) {
125
+ if (available) {
126
+ installed.push(pkg);
127
+ }
128
+ else if (required.includes(pkg)) {
129
+ missing.push(pkg);
130
+ }
131
+ }
132
+ }
133
+ catch { }
134
+ }
135
+ return {
136
+ available: missing.length === 0,
137
+ python: pythonBin,
138
+ missing,
139
+ installed,
140
+ };
141
+ }
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getAnthropicClient = getAnthropicClient;
7
+ exports.callLLM = callLLM;
8
+ exports.callLLMWithTools = callLLMWithTools;
9
+ exports.parseJsonResponse = parseJsonResponse;
10
+ const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
11
+ let client = null;
12
+ function getAnthropicClient(config) {
13
+ if (!client) {
14
+ client = new sdk_1.default({
15
+ apiKey: config.ANTHROPIC_API_KEY,
16
+ });
17
+ }
18
+ return client;
19
+ }
20
+ async function callLLM(config, system, userMessage, options) {
21
+ const anthropic = getAnthropicClient(config);
22
+ const response = await anthropic.messages.create({
23
+ model: options?.model || 'claude-sonnet-4-20250514',
24
+ max_tokens: options?.maxTokens || 8192,
25
+ temperature: options?.temperature ?? 0.3,
26
+ system,
27
+ messages: [{ role: 'user', content: userMessage }],
28
+ });
29
+ const textBlock = response.content.find((b) => b.type === 'text');
30
+ return textBlock ? textBlock.text : '';
31
+ }
32
+ async function callLLMWithTools(config, system, messages, tools, options) {
33
+ const anthropic = getAnthropicClient(config);
34
+ const response = await anthropic.messages.create({
35
+ model: options?.model || 'claude-sonnet-4-20250514',
36
+ max_tokens: options?.maxTokens || 8192,
37
+ temperature: options?.temperature ?? 0.3,
38
+ system,
39
+ messages,
40
+ tools,
41
+ });
42
+ return response;
43
+ }
44
+ function parseJsonResponse(text) {
45
+ try {
46
+ return JSON.parse(text);
47
+ }
48
+ catch { }
49
+ const codeFenceMatch = text.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
50
+ if (codeFenceMatch) {
51
+ try {
52
+ return JSON.parse(codeFenceMatch[1]);
53
+ }
54
+ catch { }
55
+ }
56
+ const braceStart = text.indexOf('{');
57
+ const braceEnd = text.lastIndexOf('}');
58
+ if (braceStart !== -1 && braceEnd > braceStart) {
59
+ try {
60
+ return JSON.parse(text.substring(braceStart, braceEnd + 1));
61
+ }
62
+ catch { }
63
+ }
64
+ const bracketStart = text.indexOf('[');
65
+ const bracketEnd = text.lastIndexOf(']');
66
+ if (bracketStart !== -1 && bracketEnd > bracketStart) {
67
+ try {
68
+ return JSON.parse(text.substring(bracketStart, bracketEnd + 1));
69
+ }
70
+ catch { }
71
+ }
72
+ return null;
73
+ }
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.Logger = void 0;
37
+ exports.formatDuration = formatDuration;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ class Logger {
41
+ constructor(projectDir) {
42
+ this.stream = null;
43
+ const logsDir = path.join(projectDir, 'logs');
44
+ if (!fs.existsSync(logsDir)) {
45
+ fs.mkdirSync(logsDir, { recursive: true });
46
+ }
47
+ this.logPath = path.join(logsDir, `run_${new Date().toISOString().replace(/[:.]/g, '-')}.log`);
48
+ this.stream = fs.createWriteStream(this.logPath, { flags: 'a' });
49
+ }
50
+ log(level, message) {
51
+ const timestamp = new Date().toISOString();
52
+ const line = `[${timestamp}] [${level}] ${message}\n`;
53
+ this.stream?.write(line);
54
+ }
55
+ info(message) {
56
+ this.log('INFO', message);
57
+ }
58
+ warn(message) {
59
+ this.log('WARN', message);
60
+ }
61
+ error(message) {
62
+ this.log('ERROR', message);
63
+ }
64
+ debug(message) {
65
+ this.log('DEBUG', message);
66
+ }
67
+ close() {
68
+ this.stream?.end();
69
+ }
70
+ getPath() {
71
+ return this.logPath;
72
+ }
73
+ }
74
+ exports.Logger = Logger;
75
+ function formatDuration(ms) {
76
+ if (ms < 1000)
77
+ return `${ms}ms`;
78
+ if (ms < 60000)
79
+ return `${(ms / 1000).toFixed(1)}s`;
80
+ const minutes = Math.floor(ms / 60000);
81
+ const seconds = Math.round((ms % 60000) / 1000);
82
+ return `${minutes}m ${seconds}s`;
83
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.generateProjectId = generateProjectId;
37
+ exports.createProject = createProject;
38
+ exports.updateProject = updateProject;
39
+ exports.getProjectDir = getProjectDir;
40
+ exports.saveProjectFile = saveProjectFile;
41
+ exports.saveProjectBuffer = saveProjectBuffer;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const loader_1 = require("../config/loader");
45
+ function generateProjectId() {
46
+ const now = new Date();
47
+ const date = now.toISOString().slice(0, 10).replace(/-/g, '');
48
+ const rand = Math.random().toString(36).substring(2, 8);
49
+ return `AS-${date}-${rand}`;
50
+ }
51
+ function createProject(topic, assets, method, hf) {
52
+ const id = generateProjectId();
53
+ const dir = path.join((0, loader_1.getProjectsDir)(), id);
54
+ fs.mkdirSync(path.join(dir, 'data'), { recursive: true });
55
+ fs.mkdirSync(path.join(dir, 'code'), { recursive: true });
56
+ fs.mkdirSync(path.join(dir, 'output', 'figures'), { recursive: true });
57
+ fs.mkdirSync(path.join(dir, 'logs'), { recursive: true });
58
+ const meta = {
59
+ id,
60
+ topic,
61
+ assets,
62
+ method,
63
+ status: 'planning',
64
+ createdAt: new Date().toISOString(),
65
+ updatedAt: new Date().toISOString(),
66
+ highFrequency: hf,
67
+ };
68
+ fs.writeFileSync(path.join(dir, 'meta.json'), JSON.stringify(meta, null, 2));
69
+ return { meta, dir };
70
+ }
71
+ function updateProject(projectId, updates) {
72
+ const dir = path.join((0, loader_1.getProjectsDir)(), projectId);
73
+ const metaPath = path.join(dir, 'meta.json');
74
+ if (!fs.existsSync(metaPath))
75
+ return;
76
+ const meta = JSON.parse(fs.readFileSync(metaPath, 'utf-8'));
77
+ const updated = { ...meta, ...updates, updatedAt: new Date().toISOString() };
78
+ fs.writeFileSync(metaPath, JSON.stringify(updated, null, 2));
79
+ }
80
+ function getProjectDir(projectId) {
81
+ return path.join((0, loader_1.getProjectsDir)(), projectId);
82
+ }
83
+ function saveProjectFile(projectId, subPath, content) {
84
+ const filePath = path.join((0, loader_1.getProjectsDir)(), projectId, subPath);
85
+ const dir = path.dirname(filePath);
86
+ if (!fs.existsSync(dir)) {
87
+ fs.mkdirSync(dir, { recursive: true });
88
+ }
89
+ fs.writeFileSync(filePath, content, 'utf-8');
90
+ return filePath;
91
+ }
92
+ function saveProjectBuffer(projectId, subPath, content) {
93
+ const filePath = path.join((0, loader_1.getProjectsDir)(), projectId, subPath);
94
+ const dir = path.dirname(filePath);
95
+ if (!fs.existsSync(dir)) {
96
+ fs.mkdirSync(dir, { recursive: true });
97
+ }
98
+ fs.writeFileSync(filePath, content);
99
+ return filePath;
100
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "autoscholar-cli",
3
+ "version": "1.0.0",
4
+ "description": "Autonomous scientific research agent — a quant researcher + economist + data scientist living inside your terminal",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "autoscholar": "dist/index.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "ts-node src/index.ts",
12
+ "start": "node dist/index.js",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "keywords": [
16
+ "research",
17
+ "academic",
18
+ "econometrics",
19
+ "finance",
20
+ "ai",
21
+ "autonomous",
22
+ "cli",
23
+ "paper-generation",
24
+ "garch",
25
+ "literature-review"
26
+ ],
27
+ "author": "AutoScholar",
28
+ "license": "MIT",
29
+ "dependencies": {
30
+ "@anthropic-ai/sdk": "^0.37.0",
31
+ "axios": "^1.7.0",
32
+ "boxen": "^5.1.2",
33
+ "chalk": "^4.1.2",
34
+ "cli-table3": "^0.6.5",
35
+ "commander": "^12.1.0",
36
+ "dotenv": "^16.4.5",
37
+ "figlet": "^1.7.0",
38
+ "gradient-string": "^2.0.2",
39
+ "inquirer": "^8.2.6",
40
+ "marked": "^12.0.0",
41
+ "marked-terminal": "^7.0.0",
42
+ "nanoid": "^3.3.7",
43
+ "ora": "^5.4.1",
44
+ "xml2js": "^0.6.2"
45
+ },
46
+ "devDependencies": {
47
+ "@types/figlet": "^1.5.8",
48
+ "@types/inquirer": "^8.2.10",
49
+ "@types/node": "^20.14.0",
50
+ "@types/xml2js": "^0.4.14",
51
+ "typescript": "^5.5.0"
52
+ },
53
+ "engines": {
54
+ "node": ">=18.0.0"
55
+ },
56
+ "files": [
57
+ "dist/**/*.js",
58
+ "python",
59
+ "templates",
60
+ "README.md",
61
+ "LICENSE"
62
+ ]
63
+ }
@@ -0,0 +1,131 @@
1
+ """
2
+ GARCH / DCC-GARCH Analysis Template
3
+ Used by Fisher agent for volatility modeling
4
+ """
5
+ import matplotlib
6
+ matplotlib.use('Agg')
7
+ import pandas as pd
8
+ import numpy as np
9
+ import matplotlib.pyplot as plt
10
+ import seaborn as sns
11
+ import json
12
+ import os
13
+ import warnings
14
+ warnings.filterwarnings('ignore')
15
+
16
+ sns.set_theme(style='whitegrid', font_scale=1.2)
17
+
18
+
19
+ def run_garch_analysis(data_path, output_dir, target_col=None, date_col='date'):
20
+ """
21
+ Run GARCH(1,1) analysis on a time series.
22
+
23
+ Args:
24
+ data_path: Path to CSV file
25
+ output_dir: Directory to save figures
26
+ target_col: Column to analyze (auto-detected if None)
27
+ date_col: Date column name
28
+ """
29
+ figures_dir = os.path.join(output_dir, 'figures')
30
+ os.makedirs(figures_dir, exist_ok=True)
31
+
32
+ df = pd.read_csv(data_path, parse_dates=[date_col] if date_col else False)
33
+ numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()
34
+
35
+ if target_col is None:
36
+ # Auto-detect: prefer 'close', 'adjusted_close', 'value'
37
+ for candidate in ['close', 'adjusted_close', 'adj_close', 'value', 'price']:
38
+ if candidate in [c.lower() for c in df.columns]:
39
+ target_col = [c for c in df.columns if c.lower() == candidate][0]
40
+ break
41
+ if target_col is None and numeric_cols:
42
+ target_col = numeric_cols[0]
43
+
44
+ if target_col is None:
45
+ return {"error": "No suitable column found"}
46
+
47
+ # Calculate returns
48
+ df['returns'] = df[target_col].pct_change().dropna()
49
+ df = df.dropna(subset=['returns'])
50
+ returns = df['returns'] * 100 # Scale to percentage
51
+
52
+ results = {"models": [], "figures": [], "diagnostics": []}
53
+
54
+ try:
55
+ from arch import arch_model
56
+
57
+ # Fit GARCH(1,1)
58
+ model = arch_model(returns, vol='Garch', p=1, q=1, dist='normal')
59
+ res = model.fit(disp='off')
60
+
61
+ results["models"].append({
62
+ "name": "GARCH(1,1)",
63
+ "type": "volatility",
64
+ "coefficients": {
65
+ "omega": float(res.params.get('omega', 0)),
66
+ "alpha[1]": float(res.params.get('alpha[1]', 0)),
67
+ "beta[1]": float(res.params.get('beta[1]', 0)),
68
+ },
69
+ "standardErrors": {k: float(v) for k, v in res.std_err.items()},
70
+ "pValues": {k: float(v) for k, v in res.pvalues.items()},
71
+ "diagnostics": [
72
+ f"Log-likelihood: {res.loglikelihood:.2f}",
73
+ f"AIC: {res.aic:.2f}",
74
+ f"BIC: {res.bic:.2f}",
75
+ ],
76
+ "significant": True,
77
+ })
78
+
79
+ # Conditional volatility
80
+ cond_vol = res.conditional_volatility
81
+
82
+ # Figure 1: Returns and conditional volatility
83
+ fig, axes = plt.subplots(2, 1, figsize=(14, 10), sharex=True)
84
+ axes[0].plot(range(len(returns)), returns, linewidth=0.5, alpha=0.8)
85
+ axes[0].set_title(f'Returns of {target_col}')
86
+ axes[0].set_ylabel('Returns (%)')
87
+ axes[1].plot(range(len(cond_vol)), cond_vol, color='red', linewidth=0.8)
88
+ axes[1].set_title('GARCH(1,1) Conditional Volatility')
89
+ axes[1].set_ylabel('Volatility (%)')
90
+ axes[1].set_xlabel('Observation')
91
+ plt.tight_layout()
92
+ plt.savefig(os.path.join(figures_dir, 'garch_volatility.png'), dpi=300, bbox_inches='tight')
93
+ plt.close()
94
+ results["figures"].append({"filename": "garch_volatility.png", "caption": "Returns and GARCH(1,1) conditional volatility"})
95
+
96
+ # Figure 2: News impact curve
97
+ fig, ax = plt.subplots(figsize=(10, 6))
98
+ shocks = np.linspace(-3, 3, 100)
99
+ omega = res.params.get('omega', 0.01)
100
+ alpha = res.params.get('alpha[1]', 0.1)
101
+ beta = res.params.get('beta[1]', 0.85)
102
+ avg_vol = cond_vol.mean()
103
+ impact = omega + alpha * (shocks * avg_vol) ** 2 + beta * avg_vol ** 2
104
+ ax.plot(shocks, np.sqrt(impact), linewidth=2)
105
+ ax.set_title('News Impact Curve')
106
+ ax.set_xlabel('Standardized Shock')
107
+ ax.set_ylabel('Next Period Volatility')
108
+ ax.axvline(x=0, color='gray', linestyle='--', alpha=0.5)
109
+ plt.tight_layout()
110
+ plt.savefig(os.path.join(figures_dir, 'news_impact.png'), dpi=300, bbox_inches='tight')
111
+ plt.close()
112
+ results["figures"].append({"filename": "news_impact.png", "caption": "GARCH news impact curve showing asymmetric volatility response"})
113
+
114
+ results["diagnostics"].extend([
115
+ f"Persistence (alpha+beta): {(alpha + beta):.4f}",
116
+ f"Half-life: {np.log(2) / np.log(alpha + beta):.1f} periods" if alpha + beta < 1 else "Non-stationary",
117
+ f"Unconditional variance: {omega / (1 - alpha - beta):.4f}" if alpha + beta < 1 else "Infinite",
118
+ ])
119
+
120
+ except ImportError:
121
+ results["diagnostics"].append("arch package not available — skipping GARCH estimation")
122
+
123
+ return results
124
+
125
+
126
+ if __name__ == "__main__":
127
+ import sys
128
+ data_path = sys.argv[1] if len(sys.argv) > 1 else "data.csv"
129
+ output_dir = sys.argv[2] if len(sys.argv) > 2 else "output"
130
+ result = run_garch_analysis(data_path, output_dir)
131
+ print(json.dumps(result, indent=2))
@@ -0,0 +1,78 @@
1
+ """
2
+ EODHD (End of Day Historical Data) Python Client
3
+ Pre-loaded in AutoScholar Python execution environment
4
+ """
5
+ import os
6
+ import requests
7
+ import pandas as pd
8
+ from datetime import datetime
9
+
10
+ class EODHDClient:
11
+ BASE_URL = "https://eodhd.com/api"
12
+
13
+ def __init__(self, api_key=None):
14
+ self.api_key = api_key or os.environ.get("EODHD_API_KEY", "")
15
+
16
+ def _normalize(self, ticker):
17
+ if "." not in ticker:
18
+ return f"{ticker}.US"
19
+ return ticker
20
+
21
+ def get_eod_df(self, ticker, from_date=None, to_date=None, period="d"):
22
+ """Get end-of-day historical data as DataFrame"""
23
+ normalized = self._normalize(ticker)
24
+ params = {"api_token": self.api_key, "fmt": "json", "period": period}
25
+ if from_date: params["from"] = from_date
26
+ if to_date: params["to"] = to_date
27
+ resp = requests.get(f"{self.BASE_URL}/eod/{normalized}", params=params, timeout=30)
28
+ resp.raise_for_status()
29
+ df = pd.DataFrame(resp.json())
30
+ if len(df) > 0:
31
+ df["date"] = pd.to_datetime(df["date"])
32
+ return df
33
+
34
+ def get_intraday_df(self, ticker, interval="5m", from_date=None, to_date=None):
35
+ """Get intraday data as DataFrame"""
36
+ normalized = self._normalize(ticker)
37
+ params = {"api_token": self.api_key, "fmt": "json", "interval": interval}
38
+ if from_date:
39
+ params["from"] = int(datetime.strptime(from_date, "%Y-%m-%d").timestamp())
40
+ if to_date:
41
+ params["to"] = int(datetime.strptime(to_date, "%Y-%m-%d").timestamp())
42
+ resp = requests.get(f"{self.BASE_URL}/intraday/{normalized}", params=params, timeout=30)
43
+ resp.raise_for_status()
44
+ df = pd.DataFrame(resp.json())
45
+ if len(df) > 0 and "datetime" in df.columns:
46
+ df["datetime"] = pd.to_datetime(df["datetime"])
47
+ return df
48
+
49
+ def get_options_df(self, ticker):
50
+ """Get options chain as DataFrame"""
51
+ normalized = self._normalize(ticker)
52
+ resp = requests.get(f"{self.BASE_URL}/options/{normalized}",
53
+ params={"api_token": self.api_key, "fmt": "json"}, timeout=30)
54
+ resp.raise_for_status()
55
+ data = resp.json()
56
+ if isinstance(data, dict) and "data" in data:
57
+ rows = []
58
+ for exp in data["data"]:
59
+ exp_date = exp.get("expirationDate", "")
60
+ for opt_type in ["CALL", "PUT"]:
61
+ for opt in exp.get("options", {}).get(opt_type, []):
62
+ opt["expirationDate"] = exp_date
63
+ opt["optionType"] = opt_type
64
+ rows.append(opt)
65
+ return pd.DataFrame(rows)
66
+ return pd.DataFrame()
67
+
68
+ def get_live_quote(self, ticker):
69
+ """Get real-time quote"""
70
+ normalized = self._normalize(ticker)
71
+ resp = requests.get(f"{self.BASE_URL}/real-time/{normalized}",
72
+ params={"api_token": self.api_key, "fmt": "json"}, timeout=10)
73
+ resp.raise_for_status()
74
+ return resp.json()
75
+
76
+
77
+ # Pre-instantiate
78
+ eodhd = EODHDClient()