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.
- package/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/agents/euler.js +261 -0
- package/dist/agents/fisher.js +348 -0
- package/dist/agents/gauss.js +177 -0
- package/dist/agents/governor.js +201 -0
- package/dist/agents/newton.js +207 -0
- package/dist/agents/turing.js +307 -0
- package/dist/cli/banner.js +136 -0
- package/dist/cli/configCommand.js +125 -0
- package/dist/cli/interactive.js +115 -0
- package/dist/cli/outputsCommand.js +191 -0
- package/dist/cli/resumeCommand.js +78 -0
- package/dist/cli/runCommand.js +91 -0
- package/dist/config/loader.js +154 -0
- package/dist/config/setup.js +179 -0
- package/dist/connectors/academic.js +307 -0
- package/dist/connectors/eodhd.js +90 -0
- package/dist/connectors/firecrawl.js +94 -0
- package/dist/connectors/fmp.js +115 -0
- package/dist/connectors/fred.js +82 -0
- package/dist/connectors/index.js +24 -0
- package/dist/connectors/websearch.js +117 -0
- package/dist/index.js +72 -0
- package/dist/latex/generator.js +413 -0
- package/dist/python/runner.js +141 -0
- package/dist/utils/llm.js +73 -0
- package/dist/utils/logger.js +83 -0
- package/dist/utils/project.js +100 -0
- package/package.json +63 -0
- package/python/analysis/garch_template.py +131 -0
- package/python/clients/eodhd_client.py +78 -0
- package/python/clients/fmp_client.py +64 -0
- package/python/clients/fred_client.py +57 -0
- package/python/clients/macro_clients.py +81 -0
- package/python/requirements.txt +23 -0
|
@@ -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()
|