aiden-shared-calculations-unified 1.0.95 → 1.0.97
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/calculations/capitulation/asset-volatility-estimator.js +1 -2
- package/calculations/capitulation/retail-capitulation-risk-forecast.js +1 -2
- package/calculations/core/Insights-total-long-per-stock +56 -0
- package/calculations/core/insights-daily-bought-vs-sold-count.js +74 -0
- package/calculations/core/insights-daily-ownership-delta.js +70 -0
- package/calculations/core/insights-sentimet-per-stock.js +68 -0
- package/calculations/core/insights-total-long-per-sector +73 -0
- package/calculations/core/insights-total-positions-held.js +49 -0
- package/calculations/ghost-book/cost-basis-density.js +1 -2
- package/calculations/ghost-book/liquidity-vacuum.js +4 -4
- package/calculations/ghost-book/retail-gamma-exposure.js +0 -1
- package/calculations/helix/winner-loser-flow.js +1 -1
- package/calculations/predicative-alpha/cognitive-dissonance.js +1 -2
- package/calculations/predicative-alpha/diamond-hand-fracture.js +1 -2
- package/calculations/predicative-alpha/mimetic-latency.js +1 -2
- package/package.json +1 -1
- package/calculations/legacy/activity_by_pnl_status.js +0 -119
- package/calculations/legacy/asset_crowd_flow.js +0 -163
- package/calculations/legacy/capital_deployment_strategy.js +0 -108
- package/calculations/legacy/capital_liquidation_performance.js +0 -139
- package/calculations/legacy/capital_vintage_performance.js +0 -136
- package/calculations/legacy/cash-flow-deployment.js +0 -144
- package/calculations/legacy/cash-flow-liquidation.js +0 -146
- package/calculations/legacy/crowd-cash-flow-proxy.js +0 -128
- package/calculations/legacy/crowd_conviction_score.js +0 -261
- package/calculations/legacy/crowd_sharpe_ratio_proxy.js +0 -137
- package/calculations/legacy/daily_asset_activity.js +0 -128
- package/calculations/legacy/daily_user_activity_tracker.js +0 -182
- package/calculations/legacy/deposit_withdrawal_percentage.js +0 -125
- package/calculations/legacy/diversification_pnl.js +0 -115
- package/calculations/legacy/drawdown_response.js +0 -137
- package/calculations/legacy/dumb-cohort-flow.js +0 -238
- package/calculations/legacy/gain_response.js +0 -137
- package/calculations/legacy/historical_performance_aggregator.js +0 -85
- package/calculations/legacy/in_loss_asset_crowd_flow.js +0 -168
- package/calculations/legacy/in_profit_asset_crowd_flow.js +0 -168
- package/calculations/legacy/negative_expectancy_cohort_flow.js +0 -232
- package/calculations/legacy/new_allocation_percentage.js +0 -98
- package/calculations/legacy/paper_vs_diamond_hands.js +0 -107
- package/calculations/legacy/position_count_pnl.js +0 -120
- package/calculations/legacy/positive_expectancy_cohort_flow.js +0 -232
- package/calculations/legacy/profit_cohort_divergence.js +0 -115
- package/calculations/legacy/profitability_migration.js +0 -104
- package/calculations/legacy/reallocation_increase_percentage.js +0 -104
- package/calculations/legacy/risk_appetite_change.js +0 -97
- package/calculations/legacy/sector_rotation.js +0 -117
- package/calculations/legacy/shark_attack_signal.js +0 -112
- package/calculations/legacy/smart-cohort-flow.js +0 -238
- package/calculations/legacy/smart-dumb-divergence-index.js +0 -143
- package/calculations/legacy/smart_dumb_divergence_index_v2.js +0 -138
- package/calculations/legacy/smart_money_flow.js +0 -198
- package/calculations/legacy/social-predictive-regime-state.js +0 -102
- package/calculations/legacy/social-topic-driver-index.js +0 -147
- package/calculations/legacy/social-topic-predictive-potential.js +0 -461
- package/calculations/legacy/social_flow_correlation.js +0 -112
- package/calculations/legacy/speculator_adjustment_activity.js +0 -103
- package/calculations/legacy/strategy-performance.js +0 -265
- package/calculations/legacy/tsl_effectiveness.js +0 -85
- package/calculations/legacy/user-investment-profile.js +0 -313
- package/calculations/legacy/user_expectancy_score.js +0 -106
- package/calculations/legacy/user_profitability_tracker.js +0 -131
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Meta-calculation (Pass 2) to correlate daily social sentiment with
|
|
3
|
-
* the actual crowd asset flow.
|
|
4
|
-
*
|
|
5
|
-
* --- META REFACTOR (v2) ---
|
|
6
|
-
* This calculation is now stateless. It declares its dependencies and
|
|
7
|
-
* expects them to be passed to its `process` method.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
class SocialFlowCorrelation {
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* (NEW) Statically declare dependencies.
|
|
14
|
-
*/
|
|
15
|
-
static getDependencies() {
|
|
16
|
-
return ['social-sentiment-aggregation', 'asset-crowd-flow'];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
constructor() {
|
|
20
|
-
this.bullishSentimentThreshold = 70.0;
|
|
21
|
-
this.bearishSentimentThreshold = 30.0;
|
|
22
|
-
this.positiveFlowThreshold = 0.005;
|
|
23
|
-
this.negativeFlowThreshold = -0.005;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* REFACTORED PROCESS METHOD
|
|
28
|
-
* @param {string} dateStr The date to run the analysis for (e.g., "2025-10-31").
|
|
29
|
-
* @param {object} dependencies The shared dependencies (db, logger).
|
|
30
|
-
* @param {object} config The computation system configuration.
|
|
31
|
-
* @param {object} fetchedDependencies In-memory results from previous passes.
|
|
32
|
-
* e.g., { 'social-sentiment-aggregation': ..., 'asset-crowd-flow': ... }
|
|
33
|
-
* @returns {Promise<object|null>} The analysis result or null.
|
|
34
|
-
*/
|
|
35
|
-
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
36
|
-
const { logger } = dependencies;
|
|
37
|
-
|
|
38
|
-
// 1. Get dependencies from in-memory cache
|
|
39
|
-
const socialData = fetchedDependencies['social-sentiment-aggregation'];
|
|
40
|
-
const flowData = fetchedDependencies['asset-crowd-flow'];
|
|
41
|
-
|
|
42
|
-
// 2. Handle missing dependencies
|
|
43
|
-
if (!socialData || !flowData) {
|
|
44
|
-
logger.log('WARN', `[SocialFlowCorrelation] Missing computed dependency data for ${dateStr}. Skipping.`);
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// 3. If data exists, perform the correlation
|
|
49
|
-
const sentimentMap = socialData.tickerSentiment || {};
|
|
50
|
-
const correlationResults = {};
|
|
51
|
-
|
|
52
|
-
// Use all tickers from the flow data as the primary loop
|
|
53
|
-
for (const ticker in flowData) {
|
|
54
|
-
if (!flowData[ticker] || typeof flowData[ticker].net_crowd_flow_pct === 'undefined') {
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const flow = flowData[ticker].net_crowd_flow_pct;
|
|
59
|
-
const sentiment = sentimentMap[ticker]?.sentimentRatio;
|
|
60
|
-
|
|
61
|
-
if (typeof sentiment === 'undefined') {
|
|
62
|
-
correlationResults[ticker] = {
|
|
63
|
-
status: 'no_social_sentiment',
|
|
64
|
-
net_crowd_flow_pct: flow
|
|
65
|
-
};
|
|
66
|
-
continue;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// --- The "Jaw-Drop" Logic ---
|
|
70
|
-
if (sentiment >= this.bullishSentimentThreshold && flow <= this.negativeFlowThreshold) {
|
|
71
|
-
correlationResults[ticker] = {
|
|
72
|
-
status: 'Bullish Divergence',
|
|
73
|
-
detail: 'Crowd is publicly bullish but is net-selling the asset.',
|
|
74
|
-
sentiment_ratio: sentiment,
|
|
75
|
-
net_crowd_flow_pct: flow
|
|
76
|
-
};
|
|
77
|
-
} else if (sentiment <= this.bearishSentimentThreshold && flow >= this.positiveFlowThreshold) {
|
|
78
|
-
correlationResults[ticker] = {
|
|
79
|
-
status: 'Bearish Divergence',
|
|
80
|
-
detail: 'Crowd is publicly bearish but is net-buying the asset.',
|
|
81
|
-
sentiment_ratio: sentiment,
|
|
82
|
-
net_crowd_flow_pct: flow
|
|
83
|
-
};
|
|
84
|
-
} else if (sentiment >= this.bullishSentimentThreshold && flow >= this.positiveFlowThreshold) {
|
|
85
|
-
correlationResults[ticker] = {
|
|
86
|
-
status: 'High Conviction Buy',
|
|
87
|
-
sentiment_ratio: sentiment,
|
|
88
|
-
net_crowd_flow_pct: flow
|
|
89
|
-
};
|
|
90
|
-
} else if (sentiment <= this.bearishSentimentThreshold && flow <= this.negativeFlowThreshold) {
|
|
91
|
-
correlationResults[ticker] = {
|
|
92
|
-
status: 'High Conviction Sell',
|
|
93
|
-
sentiment_ratio: sentiment,
|
|
94
|
-
net_crowd_flow_pct: flow
|
|
95
|
-
};
|
|
96
|
-
} else {
|
|
97
|
-
correlationResults[ticker] = {
|
|
98
|
-
status: 'No Clear Signal',
|
|
99
|
-
sentiment_ratio: sentiment,
|
|
100
|
-
net_crowd_flow_pct: flow
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
return correlationResults;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async getResult() { return null; }
|
|
109
|
-
reset() {}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
module.exports = SocialFlowCorrelation;
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Tracks "tinkering" activity from speculators.
|
|
3
|
-
* Instead of just opening/closing, this counts how many users
|
|
4
|
-
* actively *adjusted* the SL, TP, or TSL on existing trades.
|
|
5
|
-
*/
|
|
6
|
-
class SpeculatorAdjustmentActivity {
|
|
7
|
-
constructor() {
|
|
8
|
-
// Use Sets to count unique users
|
|
9
|
-
this.sl_adjusted_users = new Set();
|
|
10
|
-
this.tp_adjusted_users = new Set();
|
|
11
|
-
this.tsl_toggled_users = new Set();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Defines the output schema for this calculation.
|
|
16
|
-
* @returns {object} JSON Schema object
|
|
17
|
-
*/
|
|
18
|
-
static getSchema() {
|
|
19
|
-
return {
|
|
20
|
-
"type": "object",
|
|
21
|
-
"description": "Tracks unique speculators who 'tinkered' with open trades by adjusting SL, TP, or TSL.",
|
|
22
|
-
"properties": {
|
|
23
|
-
"unique_users_adjusted_sl": {
|
|
24
|
-
"type": "number",
|
|
25
|
-
"description": "Count of unique speculators who adjusted a Stop Loss on at least one position."
|
|
26
|
-
},
|
|
27
|
-
"unique_users_adjusted_tp": {
|
|
28
|
-
"type": "number",
|
|
29
|
-
"description": "Count of unique speculators who adjusted a Take Profit on at least one position."
|
|
30
|
-
},
|
|
31
|
-
"unique_users_toggled_tsl": {
|
|
32
|
-
"type": "number",
|
|
33
|
-
"description": "Count of unique speculators who enabled or disabled a Trailing Stop Loss on at least one position."
|
|
34
|
-
}
|
|
35
|
-
},
|
|
36
|
-
"required": ["unique_users_adjusted_sl", "unique_users_adjusted_tp", "unique_users_toggled_tsl"]
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
_getPublicPositionsMap(portfolio) {
|
|
41
|
-
const positions = portfolio?.PublicPositions;
|
|
42
|
-
if (!positions || !Array.isArray(positions)) {
|
|
43
|
-
return new Map();
|
|
44
|
-
}
|
|
45
|
-
// Map<PositionID, PositionObject>
|
|
46
|
-
return new Map(positions.map(p => [p.PositionID, p]));
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
50
|
-
// This calculation is only for speculators
|
|
51
|
-
if (todayPortfolio?.context?.userType !== 'speculator' || !yesterdayPortfolio) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const yPosMap = this._getPublicPositionsMap(yesterdayPortfolio);
|
|
56
|
-
const tPosMap = this._getPublicPositionsMap(todayPortfolio);
|
|
57
|
-
|
|
58
|
-
if (yPosMap.size === 0 || tPosMap.size === 0) {
|
|
59
|
-
return; // No positions to compare
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
for (const [tPosId, tPos] of tPosMap.entries()) {
|
|
63
|
-
// Check if this position existed yesterday
|
|
64
|
-
if (yPosMap.has(tPosId)) {
|
|
65
|
-
const yPos = yPosMap.get(tPosId);
|
|
66
|
-
|
|
67
|
-
// 1. Check for Stop Loss adjustment
|
|
68
|
-
if (tPos.StopLossRate !== yPos.StopLossRate) {
|
|
69
|
-
this.sl_adjusted_users.add(userId);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 2. Check for Take Profit adjustment
|
|
73
|
-
if (tPos.TakeProfitRate !== yPos.TakeProfitRate) {
|
|
74
|
-
this.tp_adjusted_users.add(userId);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// 3. Check if TSL was toggled on or off
|
|
78
|
-
if (tPos.IsTslEnabled !== yPos.IsTslEnabled) {
|
|
79
|
-
this.tsl_toggled_users.add(userId);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
getResult() {
|
|
86
|
-
return {
|
|
87
|
-
// Count of unique users who adjusted at least one trade's SL
|
|
88
|
-
unique_users_adjusted_sl: this.sl_adjusted_users.size,
|
|
89
|
-
// Count of unique users who adjusted at least one trade's TP
|
|
90
|
-
unique_users_adjusted_tp: this.tp_adjusted_users.size,
|
|
91
|
-
// Count of unique users who toggled TSL on or off
|
|
92
|
-
unique_users_toggled_tsl: this.tsl_toggled_users.size
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
reset() {
|
|
97
|
-
this.sl_adjusted_users.clear();
|
|
98
|
-
this.tp_adjusted_users.clear();
|
|
99
|
-
this.tsl_toggled_users.clear();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
module.exports = SpeculatorAdjustmentActivity;
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Backtest (Pass 5) calculation.
|
|
3
|
-
* Runs a full historical simulation of a trading strategy.
|
|
4
|
-
*
|
|
5
|
-
* --- META REFACTOR (v2) ---
|
|
6
|
-
* This calculation ignores the `fetchedDependencies` argument.
|
|
7
|
-
* It runs a full historical backtest up to `dateStr` by reading
|
|
8
|
-
* signal history directly from Firestore. Its dependencies in the
|
|
9
|
-
* manifest are for *scheduling* only (i.e., run this last).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
const { loadAllPriceData } = require('../../utils/price_data_provider');
|
|
13
|
-
const { FieldPath } = require('@google-cloud/firestore');
|
|
14
|
-
|
|
15
|
-
class StrategyPerformance {
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* (NEW) Statically declare dependencies.
|
|
19
|
-
* These are for *ordering only* to ensure this runs in Pass 5.
|
|
20
|
-
* The `process` method does not use them.
|
|
21
|
-
*/
|
|
22
|
-
static getDependencies() {
|
|
23
|
-
return ['smart-dumb-divergence-index', 'profit-cohort-divergence'];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Defines the output schema for this calculation.
|
|
28
|
-
* @returns {object} JSON Schema object
|
|
29
|
-
*/
|
|
30
|
-
static getSchema() {
|
|
31
|
-
return {
|
|
32
|
-
"type": ["object", "null"],
|
|
33
|
-
"description": "Result of a full historical backtest of a trading strategy. Returns null if simulation fails.",
|
|
34
|
-
"properties": {
|
|
35
|
-
"strategyName": {
|
|
36
|
-
"type": "string",
|
|
37
|
-
"description": "The name of the simulated strategy."
|
|
38
|
-
},
|
|
39
|
-
"inceptionDate": {
|
|
40
|
-
"type": "string",
|
|
41
|
-
"description": "The first date a signal was found, marking the start of the backtest."
|
|
42
|
-
},
|
|
43
|
-
"endDate": {
|
|
44
|
-
"type": "string",
|
|
45
|
-
"description": "The final date of the backtest simulation."
|
|
46
|
-
},
|
|
47
|
-
"finalPortfolioValue": {
|
|
48
|
-
"type": "number",
|
|
49
|
-
"description": "The final equity value of the portfolio at the end of the simulation."
|
|
50
|
-
},
|
|
51
|
-
"totalReturnPercent": {
|
|
52
|
-
"type": "number",
|
|
53
|
-
"description": "The total percentage return of the strategy from inception to end date."
|
|
54
|
-
}
|
|
55
|
-
},
|
|
56
|
-
// "required" is not needed at the top level since the whole object can be null.
|
|
57
|
-
// If the object is *not* null, we can imply these fields are required.
|
|
58
|
-
"if": {
|
|
59
|
-
"type": "object"
|
|
60
|
-
},
|
|
61
|
-
"then": {
|
|
62
|
-
"required": ["strategyName", "inceptionDate", "endDate", "finalPortfolioValue", "totalReturnPercent"]
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
constructor() {
|
|
68
|
-
this.INITIAL_CASH = 100000;
|
|
69
|
-
this.TRADE_SIZE_USD = 5000;
|
|
70
|
-
this.strategySignals = {
|
|
71
|
-
'smart-dumb-divergence-index': {
|
|
72
|
-
'Capitulation': 'BUY',
|
|
73
|
-
'Euphoria': 'SELL'
|
|
74
|
-
},
|
|
75
|
-
'profit_cohort_divergence': {
|
|
76
|
-
'Capitulation': 'BUY',
|
|
77
|
-
'Profit Taking': 'SELL'
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
this.priceMap = null;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
async _findSignalInceptionDate(db, collection, computation, category) {
|
|
84
|
-
const snapshot = await db.collection(collection)
|
|
85
|
-
.where(`${category}.${computation}`, '==', true)
|
|
86
|
-
.orderBy(FieldPath.documentId(), 'asc')
|
|
87
|
-
.limit(1)
|
|
88
|
-
.get();
|
|
89
|
-
if (snapshot.empty) return null;
|
|
90
|
-
return snapshot.docs[0].id;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async _fetchAllSignals(db, collection, resultsSub, compsSub, dates) {
|
|
94
|
-
const refs = [];
|
|
95
|
-
const signalMap = new Map();
|
|
96
|
-
for (const date of dates) {
|
|
97
|
-
for (const computation in this.strategySignals) {
|
|
98
|
-
const key = `${date}_${computation}`;
|
|
99
|
-
let category = 'meta';
|
|
100
|
-
// Determine category based on manifest (or simple rule)
|
|
101
|
-
if (computation.includes('cohort') && computation !== 'profit-cohort-divergence') category = 'behavioural';
|
|
102
|
-
if (computation === 'profit-cohort-divergence') category = 'meta';
|
|
103
|
-
|
|
104
|
-
const docRef = db.collection(collection).doc(date)
|
|
105
|
-
.collection(resultsSub).doc(category)
|
|
106
|
-
.collection(compsSub).doc(computation);
|
|
107
|
-
refs.push({ key, ref: docRef });
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
const snapshots = await db.getAll(...refs.map(r => r.ref));
|
|
111
|
-
snapshots.forEach((snap, idx) => {
|
|
112
|
-
if (snap.exists) signalMap.set(refs[idx].key, snap.data());
|
|
113
|
-
});
|
|
114
|
-
return signalMap;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
_findInstrumentId(ticker) {
|
|
118
|
-
// This logic is flawed, as the priceMap doesn't store tickers.
|
|
119
|
-
// A proper implementation would use `loadInstrumentMappings`.
|
|
120
|
-
// For this refactor, we leave the original logic as-is.
|
|
121
|
-
for (const instrumentId in this.priceMap) {
|
|
122
|
-
const priceData = this.priceMap[instrumentId];
|
|
123
|
-
if (priceData && priceData.ticker && priceData.ticker === ticker) {
|
|
124
|
-
return instrumentId;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
// Fallback: assume ticker is ID (will fail for string tickers)
|
|
128
|
-
if (this.priceMap[ticker]) return ticker;
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* REFACTORED PROCESS METHOD
|
|
135
|
-
* @param {string} dateStr - Today's date.
|
|
136
|
-
* @param {object} dependencies - db, logger.
|
|
137
|
-
* @param {object} config - Computation config.
|
|
138
|
-
* @param {object} fetchedDependencies - (UNUSED) In-memory results.
|
|
139
|
-
*/
|
|
140
|
-
async process(dateStr, dependencies, config, fetchedDependencies) {
|
|
141
|
-
const { db, logger, calculationUtils } = dependencies;
|
|
142
|
-
const { resultsCollection, resultsSubcollection, computationsSubcollection } = config;
|
|
143
|
-
|
|
144
|
-
// 1. Load Price Data
|
|
145
|
-
if (!this.priceMap) {
|
|
146
|
-
logger.log('INFO', '[Backtest] Loading all price data for simulation...');
|
|
147
|
-
// Use the utility from dependencies
|
|
148
|
-
this.priceMap = await calculationUtils.loadAllPriceData();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
// 2. Find Backtest Start Date (by finding first-ever signal)
|
|
152
|
-
const inceptionDateStr = await this._findSignalInceptionDate(
|
|
153
|
-
db,
|
|
154
|
-
resultsCollection,
|
|
155
|
-
'smart-dumb-divergence-index',
|
|
156
|
-
'meta' // As defined in manifest
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
if (!inceptionDateStr) {
|
|
160
|
-
logger.log('WARN', '[Backtest] No signal history found. Skipping.');
|
|
161
|
-
return null;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// 3. Build Date Range
|
|
165
|
-
const allDates = [];
|
|
166
|
-
const current = new Date(inceptionDateStr + 'T00:00:00Z');
|
|
167
|
-
const end = new Date(dateStr + 'T00:00:00Z');
|
|
168
|
-
while (current <= end) {
|
|
169
|
-
allDates.push(current.toISOString().slice(0, 10));
|
|
170
|
-
current.setUTCDate(current.getUTCDate() + 1);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (allDates.length < 2) {
|
|
174
|
-
logger.log('WARN', '[Backtest] Not enough history to run simulation.');
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// 4. Fetch ALL signals for ALL dates in one go (Must use DB)
|
|
179
|
-
logger.log('INFO', `[Backtest] Fetching ${allDates.length} days of signal data...`);
|
|
180
|
-
const signalDataMap = await this._fetchAllSignals(
|
|
181
|
-
db, resultsCollection, resultsSubcollection, computationsSubcollection, allDates
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
// 5. --- Run the Simulation Loop ---
|
|
185
|
-
const portfolio = { cash: this.INITIAL_CASH, positions: {} };
|
|
186
|
-
const history = [];
|
|
187
|
-
|
|
188
|
-
for (const date of allDates) {
|
|
189
|
-
// A. Mark-to-Market
|
|
190
|
-
let portfolioValue = portfolio.cash;
|
|
191
|
-
for (const ticker in portfolio.positions) {
|
|
192
|
-
const pos = portfolio.positions[ticker];
|
|
193
|
-
const price = this.priceMap[pos.instrumentId]?.[date];
|
|
194
|
-
|
|
195
|
-
if (price) {
|
|
196
|
-
pos.marketValue = price * pos.shares;
|
|
197
|
-
portfolioValue += pos.marketValue;
|
|
198
|
-
} else {
|
|
199
|
-
portfolioValue += pos.marketValue; // Use last known value if price missing
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
history.push({ date, portfolioValue });
|
|
203
|
-
|
|
204
|
-
// B. Generate trades
|
|
205
|
-
const tradesToMake = {};
|
|
206
|
-
for (const computation in this.strategySignals) {
|
|
207
|
-
const signalData = signalDataMap.get(`${date}_${computation}`);
|
|
208
|
-
if (!signalData) continue;
|
|
209
|
-
|
|
210
|
-
const signalRules = this.strategySignals[computation];
|
|
211
|
-
const assetSignals = signalData.assets || signalData;
|
|
212
|
-
|
|
213
|
-
for (const ticker in assetSignals) {
|
|
214
|
-
const signal = assetSignals[ticker]?.status;
|
|
215
|
-
if (signalRules[signal]) {
|
|
216
|
-
tradesToMake[ticker] = signalRules[signal];
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// C. Execute Trades
|
|
222
|
-
for (const ticker in tradesToMake) {
|
|
223
|
-
const action = tradesToMake[ticker];
|
|
224
|
-
// HACK: Use the (flawed) original logic to find ID
|
|
225
|
-
const instrumentId = this._findInstrumentId(ticker) || ticker;
|
|
226
|
-
|
|
227
|
-
const price = this.priceMap[instrumentId]?.[date];
|
|
228
|
-
if (!price || price <= 0) continue;
|
|
229
|
-
|
|
230
|
-
if (action === 'BUY' && portfolio.cash >= this.TRADE_SIZE_USD) {
|
|
231
|
-
if (!portfolio.positions[ticker]) {
|
|
232
|
-
const shares = this.TRADE_SIZE_USD / price;
|
|
233
|
-
portfolio.cash -= this.TRADE_SIZE_USD;
|
|
234
|
-
portfolio.positions[ticker] = {
|
|
235
|
-
shares: shares,
|
|
236
|
-
instrumentId: instrumentId,
|
|
237
|
-
marketValue: this.TRADE_SIZE_USD
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
} else if (action === 'SELL' && portfolio.positions[ticker]) {
|
|
241
|
-
portfolio.cash += portfolio.positions[ticker].marketValue;
|
|
242
|
-
delete portfolio.positions[ticker];
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
} // --- End Simulation Loop ---
|
|
246
|
-
|
|
247
|
-
const finalValue = history[history.length - 1]?.portfolioValue || this.INITIAL_CASH;
|
|
248
|
-
const totalReturnPct = ((finalValue - this.INITIAL_CASH) / this.INITIAL_CASH) * 100;
|
|
249
|
-
|
|
250
|
-
logger.log('INFO', `[Backtest] Simulation complete. Final Value: ${finalValue}, Return: ${totalReturnPct.toFixed(2)}%`);
|
|
251
|
-
|
|
252
|
-
return {
|
|
253
|
-
strategyName: 'SmartDumbDivergence_v1',
|
|
254
|
-
inceptionDate: inceptionDateStr,
|
|
255
|
-
endDate: dateStr,
|
|
256
|
-
finalPortfolioValue: finalValue,
|
|
257
|
-
totalReturnPercent: totalReturnPct,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
async getResult() { return null; }
|
|
262
|
-
reset() {}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
module.exports = StrategyPerformance;
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Calculation (Pass 2) for TSL effectiveness.
|
|
3
|
-
*
|
|
4
|
-
* This metric answers: "What is the difference in average
|
|
5
|
-
* P&L between speculators who use a Trailing Stop Loss (TSL)
|
|
6
|
-
* versus those who do not?"
|
|
7
|
-
*/
|
|
8
|
-
class TslEffectiveness {
|
|
9
|
-
constructor() {
|
|
10
|
-
this.with_tsl = { pnl_sum: 0, count: 0 };
|
|
11
|
-
this.without_tsl = { pnl_sum: 0, count: 0 };
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Defines the output schema for this calculation.
|
|
16
|
-
* @returns {object} JSON Schema object
|
|
17
|
-
*/
|
|
18
|
-
static getSchema() {
|
|
19
|
-
return {
|
|
20
|
-
"type": "object",
|
|
21
|
-
"description": "Compares the average P&L of speculators who use TSL vs. those who do not.",
|
|
22
|
-
"properties": {
|
|
23
|
-
"with_tsl_avg_pnl": {
|
|
24
|
-
"type": "number",
|
|
25
|
-
"description": "Average P&L for positions with TSL enabled."
|
|
26
|
-
},
|
|
27
|
-
"without_tsl_avg_pnl": {
|
|
28
|
-
"type": "number",
|
|
29
|
-
"description": "Average P&L for positions without TSL enabled."
|
|
30
|
-
},
|
|
31
|
-
"effectiveness_delta": {
|
|
32
|
-
"type": "number",
|
|
33
|
-
"description": "The difference in P&L (With TSL - Without TSL)."
|
|
34
|
-
},
|
|
35
|
-
"with_tsl_count": { "type": "number" },
|
|
36
|
-
"without_tsl_count": { "type": "number" }
|
|
37
|
-
},
|
|
38
|
-
"required": ["with_tsl_avg_pnl", "without_tsl_avg_pnl", "effectiveness_delta", "with_tsl_count", "without_tsl_count"]
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
process(todayPortfolio, yesterdayPortfolio, userId) {
|
|
43
|
-
// This calculation is only for speculators
|
|
44
|
-
if (todayPortfolio?.context?.userType !== 'speculator') {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
const positions = todayPortfolio.PublicPositions;
|
|
49
|
-
if (!positions || !Array.isArray(positions)) {
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
for (const pos of positions) {
|
|
54
|
-
const pnl = pos.NetProfit || 0;
|
|
55
|
-
|
|
56
|
-
if (pos.IsTslEnabled) {
|
|
57
|
-
this.with_tsl.pnl_sum += pnl;
|
|
58
|
-
this.with_tsl.count++;
|
|
59
|
-
} else {
|
|
60
|
-
this.without_tsl.pnl_sum += pnl;
|
|
61
|
-
this.without_tsl.count++;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
getResult() {
|
|
67
|
-
const with_avg = (this.with_tsl.count > 0) ? (this.with_tsl.pnl_sum / this.with_tsl.count) : 0;
|
|
68
|
-
const without_avg = (this.without_tsl.count > 0) ? (this.without_tsl.pnl_sum / this.without_tsl.count) : 0;
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
with_tsl_avg_pnl: with_avg,
|
|
72
|
-
without_tsl_avg_pnl: without_avg,
|
|
73
|
-
effectiveness_delta: with_avg - without_avg,
|
|
74
|
-
with_tsl_count: this.with_tsl.count,
|
|
75
|
-
without_tsl_count: this.without_tsl.count
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
reset() {
|
|
80
|
-
this.with_tsl = { pnl_sum: 0, count: 0 };
|
|
81
|
-
this.without_tsl = { pnl_sum: 0, count: 0 };
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
module.exports = TslEffectiveness;
|