bulltrackers-module 1.0.766 → 1.0.769
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/functions/computation-system-v2/UserPortfolioMetrics.js +50 -0
- package/functions/computation-system-v2/computations/BehavioralAnomaly.js +559 -227
- package/functions/computation-system-v2/computations/GlobalAumPerAsset30D.js +103 -0
- package/functions/computation-system-v2/computations/NewSectorExposure.js +82 -35
- package/functions/computation-system-v2/computations/NewSocialPost.js +52 -24
- package/functions/computation-system-v2/computations/PIDailyAssetAUM.js +134 -0
- package/functions/computation-system-v2/computations/PiFeatureVectors.js +227 -0
- package/functions/computation-system-v2/computations/PiRecommender.js +359 -0
- package/functions/computation-system-v2/computations/PopularInvestorProfileMetrics.js +354 -641
- package/functions/computation-system-v2/computations/SignedInUserList.js +51 -0
- package/functions/computation-system-v2/computations/SignedInUserMirrorHistory.js +138 -0
- package/functions/computation-system-v2/computations/SignedInUserPIProfileMetrics.js +106 -0
- package/functions/computation-system-v2/computations/SignedInUserProfileMetrics.js +324 -0
- package/functions/computation-system-v2/config/bulltrackers.config.js +40 -126
- package/functions/computation-system-v2/core-api.js +17 -9
- package/functions/computation-system-v2/data_schema_reference.MD +108 -0
- package/functions/computation-system-v2/devtools/builder/builder.js +362 -0
- package/functions/computation-system-v2/devtools/builder/examples/user-metrics.yaml +26 -0
- package/functions/computation-system-v2/devtools/index.js +36 -0
- package/functions/computation-system-v2/devtools/shared/MockDataFactory.js +235 -0
- package/functions/computation-system-v2/devtools/shared/SchemaTemplates.js +475 -0
- package/functions/computation-system-v2/devtools/shared/SystemIntrospector.js +517 -0
- package/functions/computation-system-v2/devtools/shared/index.js +16 -0
- package/functions/computation-system-v2/devtools/simulation/DAGAnalyzer.js +243 -0
- package/functions/computation-system-v2/devtools/simulation/MockDataFetcher.js +306 -0
- package/functions/computation-system-v2/devtools/simulation/MockStorageManager.js +336 -0
- package/functions/computation-system-v2/devtools/simulation/SimulationEngine.js +525 -0
- package/functions/computation-system-v2/devtools/simulation/SimulationServer.js +581 -0
- package/functions/computation-system-v2/devtools/simulation/index.js +17 -0
- package/functions/computation-system-v2/devtools/simulation/simulate.js +324 -0
- package/functions/computation-system-v2/devtools/vscode-computation/package.json +90 -0
- package/functions/computation-system-v2/devtools/vscode-computation/snippets/computation.json +128 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/extension.ts +401 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/codeActions.ts +152 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/completions.ts +207 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/diagnostics.ts +205 -0
- package/functions/computation-system-v2/devtools/vscode-computation/src/providers/hover.ts +205 -0
- package/functions/computation-system-v2/devtools/vscode-computation/tsconfig.json +22 -0
- package/functions/computation-system-v2/docs/HowToCreateComputations.MD +602 -0
- package/functions/computation-system-v2/framework/core/Manifest.js +9 -16
- package/functions/computation-system-v2/framework/core/RunAnalyzer.js +2 -1
- package/functions/computation-system-v2/framework/data/DataFetcher.js +330 -126
- package/functions/computation-system-v2/framework/data/MaterializedViewManager.js +84 -0
- package/functions/computation-system-v2/framework/data/QueryBuilder.js +38 -38
- package/functions/computation-system-v2/framework/execution/Orchestrator.js +226 -153
- package/functions/computation-system-v2/framework/scheduling/ScheduleValidator.js +17 -19
- package/functions/computation-system-v2/framework/storage/StateRepository.js +32 -2
- package/functions/computation-system-v2/framework/storage/StorageManager.js +111 -83
- package/functions/computation-system-v2/framework/testing/ComputationTester.js +161 -66
- package/functions/computation-system-v2/handlers/dispatcher.js +57 -29
- package/functions/computation-system-v2/legacy/PiAssetRecommender.js.old +115 -0
- package/functions/computation-system-v2/legacy/PiSimilarityMatrix.js +104 -0
- package/functions/computation-system-v2/legacy/PiSimilarityVector.js +71 -0
- package/functions/computation-system-v2/scripts/debug_aggregation.js +25 -0
- package/functions/computation-system-v2/scripts/test-computation-dag.js +109 -0
- package/functions/computation-system-v2/scripts/test-invalidation-scenarios.js +234 -0
- package/functions/task-engine/helpers/data_storage_helpers.js +6 -6
- package/package.json +1 -1
- package/functions/computation-system-v2/computations/PopularInvestorRiskAssessment.js +0 -176
- package/functions/computation-system-v2/computations/PopularInvestorRiskMetrics.js +0 -294
- package/functions/computation-system-v2/computations/UserPortfolioSummary.js +0 -172
- package/functions/computation-system-v2/scripts/migrate-sectors.js +0 -73
- package/functions/computation-system-v2/test/analyze-results.js +0 -238
- package/functions/computation-system-v2/test/other/test-dependency-cascade.js +0 -150
- package/functions/computation-system-v2/test/other/test-dispatcher.js +0 -317
- package/functions/computation-system-v2/test/other/test-framework.js +0 -500
- package/functions/computation-system-v2/test/other/test-real-execution.js +0 -166
- package/functions/computation-system-v2/test/other/test-real-integration.js +0 -194
- package/functions/computation-system-v2/test/other/test-refactor-e2e.js +0 -131
- package/functions/computation-system-v2/test/other/test-results.json +0 -31
- package/functions/computation-system-v2/test/other/test-risk-metrics-computation.js +0 -329
- package/functions/computation-system-v2/test/other/test-scheduler.js +0 -204
- package/functions/computation-system-v2/test/other/test-storage.js +0 -449
- package/functions/computation-system-v2/test/run-pipeline-test.js +0 -554
- package/functions/computation-system-v2/test/test-full-pipeline.js +0 -227
- package/functions/computation-system-v2/test/test-worker-pool.js +0 -266
package/functions/computation-system-v2/devtools/vscode-computation/src/providers/completions.ts
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Completion Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides intelligent autocomplete for:
|
|
5
|
+
* - Table names in requires blocks
|
|
6
|
+
* - Field names for tables
|
|
7
|
+
* - Rule module/function access
|
|
8
|
+
* - Computation dependencies
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as vscode from 'vscode';
|
|
12
|
+
|
|
13
|
+
export class ComputationCompletionProvider implements vscode.CompletionItemProvider {
|
|
14
|
+
private knowledge: SystemKnowledge | null;
|
|
15
|
+
|
|
16
|
+
constructor(knowledge: SystemKnowledge | null) {
|
|
17
|
+
this.knowledge = knowledge;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
provideCompletionItems(
|
|
21
|
+
document: vscode.TextDocument,
|
|
22
|
+
position: vscode.Position,
|
|
23
|
+
token: vscode.CancellationToken,
|
|
24
|
+
context: vscode.CompletionContext
|
|
25
|
+
): vscode.CompletionItem[] | vscode.CompletionList {
|
|
26
|
+
const line = document.lineAt(position).text;
|
|
27
|
+
const textBefore = line.substring(0, position.character);
|
|
28
|
+
|
|
29
|
+
// Detect context
|
|
30
|
+
if (this.isInRequiresBlock(document, position)) {
|
|
31
|
+
if (this.isStartingQuote(textBefore)) {
|
|
32
|
+
return this.getTableCompletions();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (this.isAccessingRules(textBefore)) {
|
|
37
|
+
return this.getRuleCompletions(textBefore);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (this.isAccessingData(textBefore)) {
|
|
41
|
+
return this.getTableCompletions();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (this.isInFieldsArray(document, position)) {
|
|
45
|
+
return this.getFieldCompletions(document, position);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (this.isInTypeValue(textBefore)) {
|
|
49
|
+
return this.getTypeCompletions();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (this.isInCategoryValue(textBefore)) {
|
|
53
|
+
return this.getCategoryCompletions();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// =========================================================================
|
|
60
|
+
// COMPLETION GENERATORS
|
|
61
|
+
// =========================================================================
|
|
62
|
+
|
|
63
|
+
private getTableCompletions(): vscode.CompletionItem[] {
|
|
64
|
+
if (!this.knowledge) return [];
|
|
65
|
+
|
|
66
|
+
return this.knowledge.tables.map(table => {
|
|
67
|
+
const item = new vscode.CompletionItem(table, vscode.CompletionItemKind.Field);
|
|
68
|
+
const meta = this.knowledge?.tableMetadata[table];
|
|
69
|
+
item.detail = meta ? `Entity: ${meta.entityField}, Date: ${meta.dateField}` : 'Table';
|
|
70
|
+
item.documentation = new vscode.MarkdownString(`**${table}**\n\nBigQuery table for computation input.`);
|
|
71
|
+
item.insertText = table;
|
|
72
|
+
return item;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private getRuleCompletions(textBefore: string): vscode.CompletionItem[] {
|
|
77
|
+
if (!this.knowledge) return [];
|
|
78
|
+
|
|
79
|
+
// Check if accessing rules.MODULE.
|
|
80
|
+
const moduleMatch = textBefore.match(/rules\.(\w+)\.$/);
|
|
81
|
+
if (moduleMatch) {
|
|
82
|
+
const module = moduleMatch[1];
|
|
83
|
+
const functions = this.knowledge.ruleFunctions[module] || [];
|
|
84
|
+
return functions.map(fn => {
|
|
85
|
+
const item = new vscode.CompletionItem(fn, vscode.CompletionItemKind.Method);
|
|
86
|
+
item.detail = `${module}.${fn}()`;
|
|
87
|
+
item.insertText = new vscode.SnippetString(`${fn}(\${1})`);
|
|
88
|
+
return item;
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Accessing rules.
|
|
93
|
+
if (textBefore.endsWith('rules.')) {
|
|
94
|
+
return this.knowledge.ruleModules.map(mod => {
|
|
95
|
+
const item = new vscode.CompletionItem(mod, vscode.CompletionItemKind.Module);
|
|
96
|
+
item.detail = 'Rule module';
|
|
97
|
+
const funcs = this.knowledge?.ruleFunctions[mod] || [];
|
|
98
|
+
item.documentation = new vscode.MarkdownString(`**${mod}**\n\nFunctions: ${funcs.join(', ')}`);
|
|
99
|
+
return item;
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private getFieldCompletions(document: vscode.TextDocument, position: vscode.Position): vscode.CompletionItem[] {
|
|
107
|
+
if (!this.knowledge) return [];
|
|
108
|
+
|
|
109
|
+
// Try to determine which table we're in
|
|
110
|
+
const text = document.getText(new vscode.Range(new vscode.Position(0, 0), position));
|
|
111
|
+
|
|
112
|
+
// Find the nearest table name in the context
|
|
113
|
+
const tableMatches = [...text.matchAll(/'(\w+_\w+)':\s*\{/g)];
|
|
114
|
+
if (tableMatches.length === 0) return [];
|
|
115
|
+
|
|
116
|
+
const lastTable = tableMatches[tableMatches.length - 1][1];
|
|
117
|
+
|
|
118
|
+
// Return common fields
|
|
119
|
+
const commonFields = ['user_id', 'date', 'pi_id', 'created_at'];
|
|
120
|
+
const tableSpecificFields: Record<string, string[]> = {
|
|
121
|
+
portfolio_snapshots: ['portfolio_data', 'cash', 'total_value', 'platform'],
|
|
122
|
+
trade_history_snapshots: ['trade_history', 'open_trades_count', 'closed_trades_count'],
|
|
123
|
+
pi_rankings: ['rank', 'percentile', 'rankings_data'],
|
|
124
|
+
social_post_snapshots: ['post_count', 'engagement_data'],
|
|
125
|
+
pi_master_list: ['verified_date', 'tier', 'country']
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
const fields = [...commonFields, ...(tableSpecificFields[lastTable] || [])];
|
|
129
|
+
|
|
130
|
+
return fields.map(field => {
|
|
131
|
+
const item = new vscode.CompletionItem(field, vscode.CompletionItemKind.Property);
|
|
132
|
+
item.detail = lastTable;
|
|
133
|
+
item.insertText = `'${field}'`;
|
|
134
|
+
return item;
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private getTypeCompletions(): vscode.CompletionItem[] {
|
|
139
|
+
return ['per-entity', 'global'].map(type => {
|
|
140
|
+
const item = new vscode.CompletionItem(type, vscode.CompletionItemKind.Value);
|
|
141
|
+
item.detail = type === 'per-entity'
|
|
142
|
+
? 'Processes one entity at a time'
|
|
143
|
+
: 'Processes all data at once';
|
|
144
|
+
return item;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private getCategoryCompletions(): vscode.CompletionItem[] {
|
|
149
|
+
return ['signed_in_user', 'popular_investor', 'global', 'instrument'].map(cat => {
|
|
150
|
+
const item = new vscode.CompletionItem(cat, vscode.CompletionItemKind.Value);
|
|
151
|
+
item.detail = 'Computation category';
|
|
152
|
+
return item;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// =========================================================================
|
|
157
|
+
// CONTEXT DETECTION
|
|
158
|
+
// =========================================================================
|
|
159
|
+
|
|
160
|
+
private isInRequiresBlock(document: vscode.TextDocument, position: vscode.Position): boolean {
|
|
161
|
+
const text = document.getText(new vscode.Range(new vscode.Position(0, 0), position));
|
|
162
|
+
const lastRequires = text.lastIndexOf('requires:');
|
|
163
|
+
if (lastRequires === -1) return false;
|
|
164
|
+
|
|
165
|
+
// Count braces after 'requires:'
|
|
166
|
+
const afterRequires = text.substring(lastRequires);
|
|
167
|
+
const openBraces = (afterRequires.match(/\{/g) || []).length;
|
|
168
|
+
const closeBraces = (afterRequires.match(/\}/g) || []).length;
|
|
169
|
+
|
|
170
|
+
return openBraces > closeBraces;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private isStartingQuote(textBefore: string): boolean {
|
|
174
|
+
return /[{,]\s*['"]?$/.test(textBefore);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private isAccessingRules(textBefore: string): boolean {
|
|
178
|
+
return /rules\.(\w+\.)?$/.test(textBefore);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private isAccessingData(textBefore: string): boolean {
|
|
182
|
+
return /data\.\[?['"]?$/.test(textBefore);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private isInFieldsArray(document: vscode.TextDocument, position: vscode.Position): boolean {
|
|
186
|
+
const line = document.lineAt(position).text;
|
|
187
|
+
return line.includes('fields:') && line.includes('[');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private isInTypeValue(textBefore: string): boolean {
|
|
191
|
+
return /type:\s*['"]?$/.test(textBefore);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private isInCategoryValue(textBefore: string): boolean {
|
|
195
|
+
return /category:\s*['"]?$/.test(textBefore);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
interface SystemKnowledge {
|
|
200
|
+
tables: string[];
|
|
201
|
+
tableMetadata: Record<string, any>;
|
|
202
|
+
ruleModules: string[];
|
|
203
|
+
ruleFunctions: Record<string, string[]>;
|
|
204
|
+
maxLookback: number;
|
|
205
|
+
validTypes: string[];
|
|
206
|
+
validCategories: string[];
|
|
207
|
+
}
|
package/functions/computation-system-v2/devtools/vscode-computation/src/providers/diagnostics.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Diagnostics Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides real-time validation:
|
|
5
|
+
* - Unknown table names
|
|
6
|
+
* - Lookback exceeds maximum
|
|
7
|
+
* - Missing required config fields
|
|
8
|
+
* - Invalid type/category values
|
|
9
|
+
* - Unused dependencies
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as vscode from 'vscode';
|
|
13
|
+
|
|
14
|
+
export class ComputationDiagnosticsProvider implements vscode.Disposable {
|
|
15
|
+
private diagnosticCollection: vscode.DiagnosticCollection;
|
|
16
|
+
private knowledge: SystemKnowledge | null;
|
|
17
|
+
|
|
18
|
+
constructor(knowledge: SystemKnowledge | null) {
|
|
19
|
+
this.knowledge = knowledge;
|
|
20
|
+
this.diagnosticCollection = vscode.languages.createDiagnosticCollection('computation');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
dispose() {
|
|
24
|
+
this.diagnosticCollection.dispose();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
validateDocument(document: vscode.TextDocument) {
|
|
28
|
+
const diagnostics: vscode.Diagnostic[] = [];
|
|
29
|
+
const text = document.getText();
|
|
30
|
+
|
|
31
|
+
// Only validate computation files
|
|
32
|
+
if (!this.isComputationFile(text)) {
|
|
33
|
+
this.diagnosticCollection.set(document.uri, []);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Extract config block
|
|
38
|
+
const configMatch = text.match(/getConfig\s*\(\s*\)\s*\{[\s\S]*?return\s*\{([\s\S]*?)\};/);
|
|
39
|
+
if (!configMatch) {
|
|
40
|
+
diagnostics.push(this.createDiagnostic(
|
|
41
|
+
document,
|
|
42
|
+
'Missing getConfig() method',
|
|
43
|
+
0,
|
|
44
|
+
vscode.DiagnosticSeverity.Error
|
|
45
|
+
));
|
|
46
|
+
} else {
|
|
47
|
+
const configContent = configMatch[1];
|
|
48
|
+
const configStart = document.positionAt(text.indexOf(configMatch[0]));
|
|
49
|
+
|
|
50
|
+
// Validate required fields
|
|
51
|
+
this.validateRequiredFields(document, configContent, configStart, diagnostics);
|
|
52
|
+
|
|
53
|
+
// Validate requires block
|
|
54
|
+
this.validateRequiresBlock(document, text, diagnostics);
|
|
55
|
+
|
|
56
|
+
// Validate type and category
|
|
57
|
+
this.validateEnumFields(document, text, diagnostics);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Validate process method
|
|
61
|
+
if (!text.includes('async process(')) {
|
|
62
|
+
diagnostics.push(this.createDiagnostic(
|
|
63
|
+
document,
|
|
64
|
+
'Missing process() method',
|
|
65
|
+
0,
|
|
66
|
+
vscode.DiagnosticSeverity.Error
|
|
67
|
+
));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.diagnosticCollection.set(document.uri, diagnostics);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private validateRequiredFields(
|
|
74
|
+
document: vscode.TextDocument,
|
|
75
|
+
configContent: string,
|
|
76
|
+
configStart: vscode.Position,
|
|
77
|
+
diagnostics: vscode.Diagnostic[]
|
|
78
|
+
) {
|
|
79
|
+
const requiredFields = ['name', 'type', 'category'];
|
|
80
|
+
|
|
81
|
+
for (const field of requiredFields) {
|
|
82
|
+
const regex = new RegExp(`${field}\\s*:`);
|
|
83
|
+
if (!regex.test(configContent)) {
|
|
84
|
+
diagnostics.push(new vscode.Diagnostic(
|
|
85
|
+
new vscode.Range(configStart, configStart.translate(1)),
|
|
86
|
+
`Missing required config field: ${field}`,
|
|
87
|
+
vscode.DiagnosticSeverity.Error
|
|
88
|
+
));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private validateRequiresBlock(
|
|
94
|
+
document: vscode.TextDocument,
|
|
95
|
+
text: string,
|
|
96
|
+
diagnostics: vscode.Diagnostic[]
|
|
97
|
+
) {
|
|
98
|
+
if (!this.knowledge) return;
|
|
99
|
+
|
|
100
|
+
// Find all table references in requires
|
|
101
|
+
const tableMatches = [...text.matchAll(/'(\w+_?\w*)':\s*\{[^}]*lookback/g)];
|
|
102
|
+
|
|
103
|
+
for (const match of tableMatches) {
|
|
104
|
+
const tableName = match[1];
|
|
105
|
+
const position = document.positionAt(text.indexOf(match[0]));
|
|
106
|
+
|
|
107
|
+
// Check if table exists
|
|
108
|
+
if (!this.knowledge.tables.includes(tableName)) {
|
|
109
|
+
const range = new vscode.Range(
|
|
110
|
+
position,
|
|
111
|
+
position.translate(0, tableName.length + 2)
|
|
112
|
+
);
|
|
113
|
+
diagnostics.push(new vscode.Diagnostic(
|
|
114
|
+
range,
|
|
115
|
+
`Unknown table: ${tableName}`,
|
|
116
|
+
vscode.DiagnosticSeverity.Warning
|
|
117
|
+
));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Check lookback values
|
|
122
|
+
const lookbackMatches = [...text.matchAll(/lookback:\s*(\d+)/g)];
|
|
123
|
+
for (const match of lookbackMatches) {
|
|
124
|
+
const lookback = parseInt(match[1]);
|
|
125
|
+
const position = document.positionAt(text.indexOf(match[0]));
|
|
126
|
+
|
|
127
|
+
if (lookback > this.knowledge.maxLookback) {
|
|
128
|
+
const range = new vscode.Range(
|
|
129
|
+
position,
|
|
130
|
+
position.translate(0, match[0].length)
|
|
131
|
+
);
|
|
132
|
+
diagnostics.push(new vscode.Diagnostic(
|
|
133
|
+
range,
|
|
134
|
+
`Lookback ${lookback} exceeds maximum of ${this.knowledge.maxLookback}`,
|
|
135
|
+
vscode.DiagnosticSeverity.Error
|
|
136
|
+
));
|
|
137
|
+
} else if (lookback > this.knowledge.maxLookback * 0.7) {
|
|
138
|
+
const range = new vscode.Range(
|
|
139
|
+
position,
|
|
140
|
+
position.translate(0, match[0].length)
|
|
141
|
+
);
|
|
142
|
+
diagnostics.push(new vscode.Diagnostic(
|
|
143
|
+
range,
|
|
144
|
+
`Lookback ${lookback} is high (${Math.round(lookback / this.knowledge.maxLookback * 100)}% of max)`,
|
|
145
|
+
vscode.DiagnosticSeverity.Warning
|
|
146
|
+
));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private validateEnumFields(
|
|
152
|
+
document: vscode.TextDocument,
|
|
153
|
+
text: string,
|
|
154
|
+
diagnostics: vscode.Diagnostic[]
|
|
155
|
+
) {
|
|
156
|
+
if (!this.knowledge) return;
|
|
157
|
+
|
|
158
|
+
// Validate type
|
|
159
|
+
const typeMatch = text.match(/type:\s*['"](\w+)['"]/);
|
|
160
|
+
if (typeMatch && !this.knowledge.validTypes.includes(typeMatch[1])) {
|
|
161
|
+
const position = document.positionAt(text.indexOf(typeMatch[0]));
|
|
162
|
+
diagnostics.push(new vscode.Diagnostic(
|
|
163
|
+
new vscode.Range(position, position.translate(0, typeMatch[0].length)),
|
|
164
|
+
`Invalid type: ${typeMatch[1]}. Must be: ${this.knowledge.validTypes.join(', ')}`,
|
|
165
|
+
vscode.DiagnosticSeverity.Error
|
|
166
|
+
));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Validate category
|
|
170
|
+
const categoryMatch = text.match(/category:\s*['"](\w+)['"]/);
|
|
171
|
+
if (categoryMatch && !this.knowledge.validCategories.includes(categoryMatch[1])) {
|
|
172
|
+
const position = document.positionAt(text.indexOf(categoryMatch[0]));
|
|
173
|
+
diagnostics.push(new vscode.Diagnostic(
|
|
174
|
+
new vscode.Range(position, position.translate(0, categoryMatch[0].length)),
|
|
175
|
+
`Invalid category: ${categoryMatch[1]}. Must be: ${this.knowledge.validCategories.join(', ')}`,
|
|
176
|
+
vscode.DiagnosticSeverity.Error
|
|
177
|
+
));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private isComputationFile(text: string): boolean {
|
|
182
|
+
return text.includes('extends Computation') ||
|
|
183
|
+
(text.includes('getConfig') && text.includes('process'));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private createDiagnostic(
|
|
187
|
+
document: vscode.TextDocument,
|
|
188
|
+
message: string,
|
|
189
|
+
line: number,
|
|
190
|
+
severity: vscode.DiagnosticSeverity
|
|
191
|
+
): vscode.Diagnostic {
|
|
192
|
+
const lineText = document.lineAt(line);
|
|
193
|
+
return new vscode.Diagnostic(lineText.range, message, severity);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface SystemKnowledge {
|
|
198
|
+
tables: string[];
|
|
199
|
+
tableMetadata: Record<string, any>;
|
|
200
|
+
ruleModules: string[];
|
|
201
|
+
ruleFunctions: Record<string, string[]>;
|
|
202
|
+
maxLookback: number;
|
|
203
|
+
validTypes: string[];
|
|
204
|
+
validCategories: string[];
|
|
205
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Hover Provider
|
|
3
|
+
*
|
|
4
|
+
* Provides hover documentation for:
|
|
5
|
+
* - Table names (shows fields, date field, entity field)
|
|
6
|
+
* - Rule functions (shows signature, description)
|
|
7
|
+
* - Config fields (shows valid values, defaults)
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as vscode from 'vscode';
|
|
11
|
+
|
|
12
|
+
export class ComputationHoverProvider implements vscode.HoverProvider {
|
|
13
|
+
private knowledge: SystemKnowledge | null;
|
|
14
|
+
|
|
15
|
+
constructor(knowledge: SystemKnowledge | null) {
|
|
16
|
+
this.knowledge = knowledge;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
provideHover(
|
|
20
|
+
document: vscode.TextDocument,
|
|
21
|
+
position: vscode.Position,
|
|
22
|
+
token: vscode.CancellationToken
|
|
23
|
+
): vscode.Hover | null {
|
|
24
|
+
const wordRange = document.getWordRangeAtPosition(position, /[\w_]+/);
|
|
25
|
+
if (!wordRange) return null;
|
|
26
|
+
|
|
27
|
+
const word = document.getText(wordRange);
|
|
28
|
+
const line = document.lineAt(position).text;
|
|
29
|
+
|
|
30
|
+
// Check if hovering over a table name
|
|
31
|
+
if (this.knowledge?.tables.includes(word)) {
|
|
32
|
+
return this.getTableHover(word);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check if hovering over a rule function
|
|
36
|
+
const ruleMatch = line.match(/rules\.(\w+)\.(\w+)/);
|
|
37
|
+
if (ruleMatch && ruleMatch[2] === word) {
|
|
38
|
+
return this.getRuleFunctionHover(ruleMatch[1], ruleMatch[2]);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check if hovering over config field
|
|
42
|
+
if (this.isConfigField(word, line)) {
|
|
43
|
+
return this.getConfigFieldHover(word);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private getTableHover(tableName: string): vscode.Hover {
|
|
50
|
+
const meta = this.knowledge?.tableMetadata[tableName];
|
|
51
|
+
|
|
52
|
+
const md = new vscode.MarkdownString();
|
|
53
|
+
md.appendMarkdown(`### 📊 ${tableName}\n\n`);
|
|
54
|
+
|
|
55
|
+
if (meta) {
|
|
56
|
+
md.appendMarkdown(`**Entity Field:** \`${meta.entityField || 'unknown'}\`\n\n`);
|
|
57
|
+
md.appendMarkdown(`**Date Field:** \`${meta.dateField || 'none'}\`\n\n`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Add common fields for known tables
|
|
61
|
+
const fieldInfo = this.getTableFieldInfo(tableName);
|
|
62
|
+
if (fieldInfo) {
|
|
63
|
+
md.appendMarkdown(`**Key Fields:**\n`);
|
|
64
|
+
for (const [field, desc] of Object.entries(fieldInfo)) {
|
|
65
|
+
md.appendMarkdown(`- \`${field}\`: ${desc}\n`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return new vscode.Hover(md);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private getRuleFunctionHover(module: string, func: string): vscode.Hover | null {
|
|
73
|
+
const functionDocs: Record<string, Record<string, string>> = {
|
|
74
|
+
portfolio: {
|
|
75
|
+
extractPositions: `Extract positions from portfolio_data JSON.
|
|
76
|
+
\`\`\`javascript
|
|
77
|
+
const positions = rules.portfolio.extractPositions(portfolioData);
|
|
78
|
+
// Returns: { instrumentId: position }[]
|
|
79
|
+
\`\`\``,
|
|
80
|
+
extractMirrors: `Extract mirror positions from portfolio_data.
|
|
81
|
+
\`\`\`javascript
|
|
82
|
+
const mirrors = rules.portfolio.extractMirrors(portfolioData);
|
|
83
|
+
// Returns: { username, allocation, netProfit }[]
|
|
84
|
+
\`\`\``,
|
|
85
|
+
getInvested: `Calculate total invested amount.
|
|
86
|
+
\`\`\`javascript
|
|
87
|
+
const invested = rules.portfolio.getInvested(positions);
|
|
88
|
+
// Returns: number
|
|
89
|
+
\`\`\``,
|
|
90
|
+
getNetProfit: `Calculate net profit from positions.
|
|
91
|
+
\`\`\`javascript
|
|
92
|
+
const profit = rules.portfolio.getNetProfit(positions);
|
|
93
|
+
// Returns: number
|
|
94
|
+
\`\`\``
|
|
95
|
+
},
|
|
96
|
+
metrics: {
|
|
97
|
+
calculateSharpeRatio: `Calculate Sharpe ratio from daily returns.
|
|
98
|
+
\`\`\`javascript
|
|
99
|
+
const sharpe = rules.metrics.calculateSharpeRatio(returns);
|
|
100
|
+
// Returns: number (-3 to 3 typical)
|
|
101
|
+
\`\`\``,
|
|
102
|
+
calculateVolatility: `Calculate annualized volatility.
|
|
103
|
+
\`\`\`javascript
|
|
104
|
+
const vol = rules.metrics.calculateVolatility(returns);
|
|
105
|
+
// Returns: number (percentage)
|
|
106
|
+
\`\`\``,
|
|
107
|
+
calculateDrawdown: `Calculate maximum drawdown.
|
|
108
|
+
\`\`\`javascript
|
|
109
|
+
const dd = rules.metrics.calculateDrawdown(equityCurve);
|
|
110
|
+
// Returns: { maxDrawdown, currentDrawdown }
|
|
111
|
+
\`\`\``
|
|
112
|
+
},
|
|
113
|
+
rankings: {
|
|
114
|
+
getRank: `Get ranking position.
|
|
115
|
+
\`\`\`javascript
|
|
116
|
+
const rank = rules.rankings.getRank(data, metric);
|
|
117
|
+
// Returns: number (1 = best)
|
|
118
|
+
\`\`\``,
|
|
119
|
+
getPercentile: `Get percentile position.
|
|
120
|
+
\`\`\`javascript
|
|
121
|
+
const pct = rules.rankings.getPercentile(data, metric);
|
|
122
|
+
// Returns: number (0-100)
|
|
123
|
+
\`\`\``
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const doc = functionDocs[module]?.[func];
|
|
128
|
+
if (!doc) return null;
|
|
129
|
+
|
|
130
|
+
const md = new vscode.MarkdownString();
|
|
131
|
+
md.appendMarkdown(`### ${module}.${func}\n\n`);
|
|
132
|
+
md.appendMarkdown(doc);
|
|
133
|
+
md.isTrusted = true;
|
|
134
|
+
|
|
135
|
+
return new vscode.Hover(md);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private getConfigFieldHover(field: string): vscode.Hover | null {
|
|
139
|
+
const configDocs: Record<string, string> = {
|
|
140
|
+
name: `**name** (required)\n\nUnique identifier for the computation. Should match the class name.`,
|
|
141
|
+
type: `**type** (required)\n\nExecution mode:\n- \`per-entity\`: Process one entity at a time\n- \`global\`: Process all data at once`,
|
|
142
|
+
category: `**category** (required)\n\nEntity category:\n- \`signed_in_user\`: User-specific computations\n- \`popular_investor\`: PI-specific computations\n- \`global\`: Cross-entity computations`,
|
|
143
|
+
isHistorical: `**isHistorical** (optional)\n\n\`true\` = needs lookback data for comparison\n\`false\` = only needs current date (default)`,
|
|
144
|
+
lookback: `**lookback** (optional)\n\nNumber of historical days to fetch.\n- Max: **90 days**\n- Warning threshold: **63 days** (70%)`,
|
|
145
|
+
mandatory: `**mandatory** (optional)\n\n\`true\` = computation fails if data missing\n\`false\` = continues with empty data (default)`,
|
|
146
|
+
requires: `**requires** (important)\n\nTable/dependency requirements for this computation.\n\`\`\`javascript
|
|
147
|
+
requires: {
|
|
148
|
+
'table_name': { lookback: 0, mandatory: true }
|
|
149
|
+
}
|
|
150
|
+
\`\`\``,
|
|
151
|
+
dependencies: `**dependencies** (optional)\n\nOther computations that must run first.\n\`\`\`javascript
|
|
152
|
+
dependencies: ['ComputationA', 'ComputationB']
|
|
153
|
+
\`\`\``
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const doc = configDocs[field];
|
|
157
|
+
if (!doc) return null;
|
|
158
|
+
|
|
159
|
+
const md = new vscode.MarkdownString(doc);
|
|
160
|
+
md.isTrusted = true;
|
|
161
|
+
return new vscode.Hover(md);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private getTableFieldInfo(tableName: string): Record<string, string> | null {
|
|
165
|
+
const fieldDocs: Record<string, Record<string, string>> = {
|
|
166
|
+
portfolio_snapshots: {
|
|
167
|
+
user_id: 'Entity identifier',
|
|
168
|
+
date: 'Snapshot date',
|
|
169
|
+
portfolio_data: 'JSON with positions, mirrors, metrics',
|
|
170
|
+
cash: 'Available cash balance',
|
|
171
|
+
total_value: 'Total portfolio value'
|
|
172
|
+
},
|
|
173
|
+
pi_rankings: {
|
|
174
|
+
pi_id: 'Popular Investor ID',
|
|
175
|
+
date: 'Ranking date',
|
|
176
|
+
rank: 'Overall rank position',
|
|
177
|
+
rankings_data: 'JSON with detailed rankings'
|
|
178
|
+
},
|
|
179
|
+
trade_history_snapshots: {
|
|
180
|
+
user_id: 'Entity identifier',
|
|
181
|
+
trade_history: 'JSON array of trades',
|
|
182
|
+
open_trades_count: 'Number of open positions',
|
|
183
|
+
closed_trades_count: 'Number of closed positions'
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
return fieldDocs[tableName] || null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
private isConfigField(word: string, line: string): boolean {
|
|
191
|
+
const configFields = ['name', 'type', 'category', 'isHistorical', 'lookback',
|
|
192
|
+
'mandatory', 'requires', 'dependencies', 'storage'];
|
|
193
|
+
return configFields.includes(word) && line.includes(':');
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface SystemKnowledge {
|
|
198
|
+
tables: string[];
|
|
199
|
+
tableMetadata: Record<string, any>;
|
|
200
|
+
ruleModules: string[];
|
|
201
|
+
ruleFunctions: Record<string, string[]>;
|
|
202
|
+
maxLookback: number;
|
|
203
|
+
validTypes: string[];
|
|
204
|
+
validCategories: string[];
|
|
205
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"module": "commonjs",
|
|
4
|
+
"target": "ES2020",
|
|
5
|
+
"outDir": "out",
|
|
6
|
+
"lib": [
|
|
7
|
+
"ES2020"
|
|
8
|
+
],
|
|
9
|
+
"sourceMap": true,
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"strict": true,
|
|
12
|
+
"esModuleInterop": true,
|
|
13
|
+
"skipLibCheck": true,
|
|
14
|
+
"forceConsistentCasingInFileNames": true
|
|
15
|
+
},
|
|
16
|
+
"include": [
|
|
17
|
+
"src/**/*"
|
|
18
|
+
],
|
|
19
|
+
"exclude": [
|
|
20
|
+
"node_modules"
|
|
21
|
+
]
|
|
22
|
+
}
|