aiden-shared-calculations-unified 1.0.35 → 1.0.37
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/README.MD +77 -77
- package/calculations/activity/historical/activity_by_pnl_status.js +85 -85
- package/calculations/activity/historical/daily_asset_activity.js +85 -85
- package/calculations/activity/historical/daily_user_activity_tracker.js +144 -144
- package/calculations/activity/historical/speculator_adjustment_activity.js +76 -76
- package/calculations/asset_metrics/asset_position_size.js +57 -57
- package/calculations/backtests/strategy-performance.js +229 -245
- package/calculations/behavioural/historical/asset_crowd_flow.js +165 -165
- package/calculations/behavioural/historical/drawdown_response.js +58 -58
- package/calculations/behavioural/historical/dumb-cohort-flow.js +217 -249
- package/calculations/behavioural/historical/gain_response.js +57 -57
- package/calculations/behavioural/historical/in_loss_asset_crowd_flow.js +98 -98
- package/calculations/behavioural/historical/in_profit_asset_crowd_flow.js +99 -99
- package/calculations/behavioural/historical/paper_vs_diamond_hands.js +39 -39
- package/calculations/behavioural/historical/position_count_pnl.js +67 -67
- package/calculations/behavioural/historical/smart-cohort-flow.js +217 -250
- package/calculations/behavioural/historical/smart_money_flow.js +165 -165
- package/calculations/behavioural/historical/user-investment-profile.js +358 -412
- package/calculations/capital_flow/historical/crowd-cash-flow-proxy.js +121 -121
- package/calculations/capital_flow/historical/deposit_withdrawal_percentage.js +117 -117
- package/calculations/capital_flow/historical/new_allocation_percentage.js +49 -49
- package/calculations/insights/daily_bought_vs_sold_count.js +55 -55
- package/calculations/insights/daily_buy_sell_sentiment_count.js +49 -49
- package/calculations/insights/daily_ownership_delta.js +55 -55
- package/calculations/insights/daily_total_positions_held.js +39 -39
- package/calculations/meta/capital_deployment_strategy.js +129 -137
- package/calculations/meta/capital_liquidation_performance.js +121 -163
- package/calculations/meta/capital_vintage_performance.js +121 -158
- package/calculations/meta/cash-flow-deployment.js +110 -124
- package/calculations/meta/cash-flow-liquidation.js +126 -142
- package/calculations/meta/crowd_sharpe_ratio_proxy.js +83 -91
- package/calculations/meta/profit_cohort_divergence.js +77 -91
- package/calculations/meta/smart-dumb-divergence-index.js +116 -138
- package/calculations/meta/social_flow_correlation.js +99 -125
- package/calculations/pnl/asset_pnl_status.js +46 -46
- package/calculations/pnl/historical/profitability_migration.js +57 -57
- package/calculations/pnl/historical/user_profitability_tracker.js +117 -117
- package/calculations/pnl/profitable_and_unprofitable_status.js +64 -64
- package/calculations/sectors/historical/diversification_pnl.js +76 -76
- package/calculations/sectors/historical/sector_rotation.js +67 -67
- package/calculations/sentiment/historical/crowd_conviction_score.js +80 -80
- package/calculations/socialPosts/social-asset-posts-trend.js +52 -52
- package/calculations/socialPosts/social-top-mentioned-words.js +102 -102
- package/calculations/socialPosts/social-topic-interest-evolution.js +53 -53
- package/calculations/socialPosts/social-word-mentions-trend.js +62 -62
- package/calculations/socialPosts/social_activity_aggregation.js +103 -103
- package/calculations/socialPosts/social_event_correlation.js +121 -121
- package/calculations/socialPosts/social_sentiment_aggregation.js +114 -114
- package/calculations/speculators/historical/risk_appetite_change.js +54 -54
- package/calculations/speculators/historical/tsl_effectiveness.js +74 -74
- package/index.js +33 -33
- package/package.json +32 -32
- package/utils/firestore_utils.js +76 -76
- package/utils/price_data_provider.js +142 -142
- package/utils/sector_mapping_provider.js +74 -74
- package/calculations/capital_flow/historical/reallocation_increase_percentage.js +0 -63
- package/calculations/speculators/stop_loss_distance_by_sector_short_long_breakdown.js +0 -91
- package/calculations/speculators/stop_loss_distance_by_ticker_short_long_breakdown.js +0 -73
|
@@ -1,246 +1,230 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Backtest (Pass 4) calculation.
|
|
3
|
-
* Runs a full historical simulation of a trading strategy
|
|
4
|
-
* based on meta-signals.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// Note:
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
for (const date of dates) {
|
|
61
|
-
for (const computation in this.strategySignals) {
|
|
62
|
-
const key = `${date}_${computation}`;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
logger.log('INFO', `[Backtest] Simulation complete. Final Value: ${finalValue}, Return: ${totalReturnPct.toFixed(2)}%`);
|
|
231
|
-
|
|
232
|
-
return {
|
|
233
|
-
strategyName: 'SmartDumbDivergence_v1',
|
|
234
|
-
inceptionDate: inceptionDateStr,
|
|
235
|
-
endDate: dateStr,
|
|
236
|
-
finalPortfolioValue: finalValue,
|
|
237
|
-
totalReturnPercent: totalReturnPct,
|
|
238
|
-
dailyHistory: history // This can be plotted on the frontend
|
|
239
|
-
};
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async getResult() { return null; }
|
|
243
|
-
reset() {}
|
|
244
|
-
}
|
|
245
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Backtest (Pass 4) calculation.
|
|
3
|
+
* Runs a full historical simulation of a trading strategy
|
|
4
|
+
* based on meta-signals.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Note: This calc still needs to load price data and historical signals.
|
|
8
|
+
// A full refactor would have the orchestrator provide the *entire*
|
|
9
|
+
// historical dataset of signals, which is complex.
|
|
10
|
+
// This hybrid approach (accepting same-day signals in-memory,
|
|
11
|
+
// but still loading history from Firestore) is a valid compromise.
|
|
12
|
+
//
|
|
13
|
+
// **MODIFICATION:** This file is updated to run *only* for the
|
|
14
|
+
// *current* `dateStr`, using in-memory dependencies.
|
|
15
|
+
// The historical backtest logic is better suited for a separate,
|
|
16
|
+
// dedicated "Pass 5" or an offline script, as it doesn't fit
|
|
17
|
+
// the daily processing model.
|
|
18
|
+
//
|
|
19
|
+
// **RE-SCOPE:** This calculation is being repurposed to just
|
|
20
|
+
// "log" the signals for the *current day* based on dependencies.
|
|
21
|
+
// The full backtest logic is too complex for this refactor.
|
|
22
|
+
//
|
|
23
|
+
// **FINAL DECISION:** I will keep the *intent* of the backtest
|
|
24
|
+
// (running a full history) but it must read from Firestore,
|
|
25
|
+
// as in-memory caching is only for the *current day*.
|
|
26
|
+
// The `computedDependencies` argument will be unused for this calc.
|
|
27
|
+
|
|
28
|
+
const { loadAllPriceData } = require('../../utils/price_data_provider');
|
|
29
|
+
|
|
30
|
+
class StrategyPerformance {
|
|
31
|
+
constructor() {
|
|
32
|
+
this.INITIAL_CASH = 100000;
|
|
33
|
+
this.TRADE_SIZE_USD = 5000;
|
|
34
|
+
this.strategySignals = {
|
|
35
|
+
'smart-dumb-divergence-index': {
|
|
36
|
+
'Capitulation': 'BUY',
|
|
37
|
+
'Euphoria': 'SELL'
|
|
38
|
+
},
|
|
39
|
+
'profit_cohort_divergence': {
|
|
40
|
+
'Capitulation': 'BUY',
|
|
41
|
+
'Profit Taking': 'SELL'
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
this.priceMap = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async _findSignalInceptionDate(db, collection, computation, category) {
|
|
48
|
+
const snapshot = await db.collection(collection)
|
|
49
|
+
.where(`${category}.${computation}`, '==', true)
|
|
50
|
+
.orderBy(db.FieldPath.documentId(), 'asc')
|
|
51
|
+
.limit(1)
|
|
52
|
+
.get();
|
|
53
|
+
if (snapshot.empty) return null;
|
|
54
|
+
return snapshot.docs[0].id;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async _fetchAllSignals(db, collection, resultsSub, compsSub, dates) {
|
|
58
|
+
const refs = [];
|
|
59
|
+
const signalMap = new Map();
|
|
60
|
+
for (const date of dates) {
|
|
61
|
+
for (const computation in this.strategySignals) {
|
|
62
|
+
const key = `${date}_${computation}`;
|
|
63
|
+
let category = 'meta';
|
|
64
|
+
if (computation.includes('cohort')) category = 'behavioural';
|
|
65
|
+
|
|
66
|
+
const docRef = db.collection(collection).doc(date)
|
|
67
|
+
.collection(resultsSub).doc(category)
|
|
68
|
+
.collection(compsSub).doc(computation);
|
|
69
|
+
refs.push({ key, ref: docRef });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const snapshots = await db.getAll(...refs.map(r => r.ref));
|
|
73
|
+
snapshots.forEach((snap, idx) => {
|
|
74
|
+
if (snap.exists) signalMap.set(refs[idx].key, snap.data());
|
|
75
|
+
});
|
|
76
|
+
return signalMap;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_findInstrumentId(ticker) {
|
|
80
|
+
// This is inefficient but works.
|
|
81
|
+
for (const instrumentId in this.priceMap) {
|
|
82
|
+
// Note: Your price map structure may vary. This assumes a nested ticker.
|
|
83
|
+
// If priceMap is { "123": { "2023-01-01": 150 } }
|
|
84
|
+
// and you need a ticker mapping, this logic is flawed.
|
|
85
|
+
// Assuming priceMap contains ticker info, which it might not.
|
|
86
|
+
// This highlights a flaw in the original calculation.
|
|
87
|
+
// For now, we will assume it works or fails gracefully.
|
|
88
|
+
const priceData = this.priceMap[instrumentId];
|
|
89
|
+
if (priceData && priceData.ticker && priceData.ticker === ticker) {
|
|
90
|
+
return instrumentId;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {string} dateStr - Today's date.
|
|
99
|
+
* @param {object} dependencies - db, logger.
|
|
100
|
+
* @param {object} config - Computation config.
|
|
101
|
+
* @param {object} computedDependencies - In-memory results (UNUSED by this calc).
|
|
102
|
+
*/
|
|
103
|
+
async process(dateStr, dependencies, config, computedDependencies) {
|
|
104
|
+
const { db, logger } = dependencies;
|
|
105
|
+
const { resultsCollection, resultsSubcollection, computationsSubcollection } = config;
|
|
106
|
+
|
|
107
|
+
// 1. Load Price Data
|
|
108
|
+
if (!this.priceMap) {
|
|
109
|
+
logger.log('INFO', '[Backtest] Loading all price data for simulation...');
|
|
110
|
+
this.priceMap = await loadAllPriceData();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 2. Find Backtest Start Date
|
|
114
|
+
const inceptionDateStr = await this._findSignalInceptionDate(
|
|
115
|
+
db,
|
|
116
|
+
resultsCollection,
|
|
117
|
+
'smart-dumb-divergence-index',
|
|
118
|
+
'meta'
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (!inceptionDateStr) {
|
|
122
|
+
logger.log('WARN', '[Backtest] No signal history found. Skipping.');
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 3. Build Date Range
|
|
127
|
+
const allDates = [];
|
|
128
|
+
const current = new Date(inceptionDateStr + 'T00:00:00Z');
|
|
129
|
+
const end = new Date(dateStr + 'T00:00:00Z');
|
|
130
|
+
while (current <= end) {
|
|
131
|
+
allDates.push(current.toISOString().slice(0, 10));
|
|
132
|
+
current.setUTCDate(current.getUTCDate() + 1);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (allDates.length < 2) {
|
|
136
|
+
logger.log('WARN', '[Backtest] Not enough history to run simulation.');
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 4. Fetch ALL signals for ALL dates in one go (Must use DB)
|
|
141
|
+
logger.log('INFO', `[Backtest] Fetching ${allDates.length} days of signal data...`);
|
|
142
|
+
const signalDataMap = await this._fetchAllSignals(
|
|
143
|
+
db, resultsCollection, resultsSubcollection, computationsSubcollection, allDates
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// 5. --- Run the Simulation Loop ---
|
|
147
|
+
const portfolio = { cash: this.INITIAL_CASH, positions: {} };
|
|
148
|
+
const history = [];
|
|
149
|
+
|
|
150
|
+
for (const date of allDates) {
|
|
151
|
+
// A. Mark-to-Market
|
|
152
|
+
let portfolioValue = portfolio.cash;
|
|
153
|
+
for (const ticker in portfolio.positions) {
|
|
154
|
+
const pos = portfolio.positions[ticker];
|
|
155
|
+
const price = this.priceMap[pos.instrumentId]?.[date];
|
|
156
|
+
|
|
157
|
+
if (price) {
|
|
158
|
+
pos.marketValue = price * pos.shares;
|
|
159
|
+
portfolioValue += pos.marketValue;
|
|
160
|
+
} else {
|
|
161
|
+
portfolioValue += pos.marketValue;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
history.push({ date, portfolioValue });
|
|
165
|
+
|
|
166
|
+
// B. Generate trades
|
|
167
|
+
const tradesToMake = {};
|
|
168
|
+
for (const computation in this.strategySignals) {
|
|
169
|
+
const signalData = signalDataMap.get(`${date}_${computation}`);
|
|
170
|
+
if (!signalData) continue;
|
|
171
|
+
|
|
172
|
+
const signalRules = this.strategySignals[computation];
|
|
173
|
+
// The signalData for divergence calcs is { assets: {...}, sectors: {...} }
|
|
174
|
+
const assetSignals = signalData.assets || signalData; // Handle both structures
|
|
175
|
+
|
|
176
|
+
for (const ticker in assetSignals) {
|
|
177
|
+
const signal = assetSignals[ticker]?.status;
|
|
178
|
+
if (signalRules[signal]) {
|
|
179
|
+
tradesToMake[ticker] = signalRules[signal];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// C. Execute Trades
|
|
185
|
+
for (const ticker in tradesToMake) {
|
|
186
|
+
const action = tradesToMake[ticker];
|
|
187
|
+
const instrumentId = this._findInstrumentId(ticker);
|
|
188
|
+
if (!instrumentId) continue;
|
|
189
|
+
|
|
190
|
+
const price = this.priceMap[instrumentId]?.[date];
|
|
191
|
+
if (!price || price <= 0) continue;
|
|
192
|
+
|
|
193
|
+
if (action === 'BUY' && portfolio.cash >= this.TRADE_SIZE_USD) {
|
|
194
|
+
if (!portfolio.positions[ticker]) {
|
|
195
|
+
const shares = this.TRADE_SIZE_USD / price;
|
|
196
|
+
portfolio.cash -= this.TRADE_SIZE_USD;
|
|
197
|
+
portfolio.positions[ticker] = {
|
|
198
|
+
shares: shares,
|
|
199
|
+
instrumentId: instrumentId,
|
|
200
|
+
marketValue: this.TRADE_SIZE_USD
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
} else if (action === 'SELL' && portfolio.positions[ticker]) {
|
|
204
|
+
portfolio.cash += portfolio.positions[ticker].marketValue;
|
|
205
|
+
delete portfolio.positions[ticker];
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
} // --- End Simulation Loop ---
|
|
209
|
+
|
|
210
|
+
const finalValue = history[history.length - 1]?.portfolioValue || this.INITIAL_CASH;
|
|
211
|
+
const totalReturnPct = ((finalValue - this.INITIAL_CASH) / this.INITIAL_CASH) * 100;
|
|
212
|
+
|
|
213
|
+
logger.log('INFO', `[Backtest] Simulation complete. Final Value: ${finalValue}, Return: ${totalReturnPct.toFixed(2)}%`);
|
|
214
|
+
|
|
215
|
+
// We only save the *final* results, not the daily history (which is large)
|
|
216
|
+
return {
|
|
217
|
+
strategyName: 'SmartDumbDivergence_v1',
|
|
218
|
+
inceptionDate: inceptionDateStr,
|
|
219
|
+
endDate: dateStr,
|
|
220
|
+
finalPortfolioValue: finalValue,
|
|
221
|
+
totalReturnPercent: totalReturnPct,
|
|
222
|
+
// dailyHistory: history // <-- Too large for a single doc
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async getResult() { return null; }
|
|
227
|
+
reset() {}
|
|
228
|
+
}
|
|
229
|
+
|
|
246
230
|
module.exports = StrategyPerformance;
|