aiden-shared-calculations-unified 1.0.80 → 1.0.82

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.
@@ -5,11 +5,10 @@
5
5
  * of *unique users* into the 'Winner' (in-profit) and 'Loser'
6
6
  * (in-loss) cohorts?"
7
7
  *
8
- * e.g., "Today, 150 losers capitulated (closed) on AAPL,
9
- * while 25 winners joined (opened)."
10
- *
11
- * This is a 'standard' calc because it must iterate over all
12
- * users to compare their T-1 vs T portfolios.
8
+ * --- MODIFIED ---
9
+ * Removed dependency on 'asset-pnl-status' to fix 1MiB limit.
10
+ * This calculation now determines "winner/loser" status internally
11
+ * by reading the user's portfolio P&L directly.
13
12
  */
14
13
  const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
15
14
 
@@ -18,7 +17,6 @@ class WinnerLoserFlow {
18
17
  constructor() {
19
18
  // We will store { [ticker]: { winners_joined: 0, ... } }
20
19
  this.assetFlows = new Map();
21
- this.cohorts = null; // Caches cohort data
22
20
  this.mappings = null; // Caches mappings
23
21
  }
24
22
 
@@ -76,46 +74,30 @@ class WinnerLoserFlow {
76
74
 
77
75
  /**
78
76
  * Statically declare dependencies.
77
+ * --- MODIFIED ---
78
+ * Removed 'asset-pnl-status' dependency.
79
79
  */
80
80
  static getDependencies() {
81
- return [
82
- 'asset-pnl-status' // from core
83
- ];
84
- }
85
-
86
- /**
87
- * Helper to load cohort data from the dependency (runs once).
88
- */
89
- _loadCohorts(fetchedDependencies) {
90
- if (this.cohorts) return;
91
-
92
- const pnlStatusData = fetchedDependencies['asset-pnl-status'];
93
- if (!pnlStatusData) {
94
- this.cohorts = { winnerMap: new Map(), loserMap: new Map() };
95
- return;
96
- }
97
-
98
- const winnerMap = new Map();
99
- const loserMap = new Map();
100
-
101
- for (const [ticker, data] of Object.entries(pnlStatusData)) {
102
- // 'users_in_profit' and 'users_in_loss' are arrays of user IDs
103
- winnerMap.set(ticker, new Set(data.users_in_profit || []));
104
- loserMap.set(ticker, new Set(data.users_in_loss || []));
105
- }
106
-
107
- this.cohorts = { winnerMap, loserMap };
81
+ return [];
108
82
  }
109
83
 
110
84
  /**
111
- * Helper to get a user's holdings (Instrument IDs)
85
+ * --- MODIFIED ---
86
+ * Helper to get a user's holdings (Instrument IDs) AND their P&L.
87
+ * @returns {Map<InstrumentID, {pnl: number}>}
112
88
  */
113
- _getHoldings(portfolio) {
89
+ _getHoldingsWithPnl(portfolio) {
114
90
  const positions = portfolio?.AggregatedPositions || portfolio?.PublicPositions;
115
91
  if (!positions || !Array.isArray(positions)) {
116
- return new Set();
92
+ return new Map();
93
+ }
94
+ const map = new Map();
95
+ for (const pos of positions) {
96
+ if (pos.InstrumentID) {
97
+ map.set(pos.InstrumentID, { pnl: pos.NetProfit || 0 });
98
+ }
117
99
  }
118
- return new Set(positions.map(p => p.InstrumentID).filter(Boolean));
100
+ return map;
119
101
  }
120
102
 
121
103
  /**
@@ -132,21 +114,19 @@ class WinnerLoserFlow {
132
114
  }
133
115
  }
134
116
 
135
- process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
117
+ process(todayPortfolio, yesterdayPortfolio, userId, context) {
136
118
  if (!todayPortfolio || !yesterdayPortfolio) {
137
119
  return;
138
120
  }
139
121
 
140
- // Load mappings and cohort data on the first user
141
122
  if (!this.mappings) {
142
123
  this.mappings = context.mappings;
143
124
  }
144
- this._loadCohorts(fetchedDependencies);
145
125
 
146
- const yHoldings = this._getHoldings(yesterdayPortfolio); // Set<InstrumentID>
147
- const tHoldings = this._getHoldings(todayPortfolio); // Set<InstrumentID>
126
+ const yHoldings = this._getHoldingsWithPnl(yesterdayPortfolio); // Map<InstID, {pnl}>
127
+ const tHoldings = this._getHoldingsWithPnl(todayPortfolio); // Map<InstID, {pnl}>
148
128
 
149
- const allInstrumentIds = new Set([...yHoldings, ...tHoldings]);
129
+ const allInstrumentIds = new Set([...yHoldings.keys(), ...tHoldings.keys()]);
150
130
  if (allInstrumentIds.size === 0) {
151
131
  return;
152
132
  }
@@ -155,8 +135,12 @@ class WinnerLoserFlow {
155
135
  const ticker = this.mappings.instrumentToTicker[instrumentId];
156
136
  if (!ticker) continue;
157
137
 
158
- const isWinner = this.cohorts.winnerMap.get(ticker)?.has(userId) || false;
159
- const isLoser = this.cohorts.loserMap.get(ticker)?.has(userId) || false;
138
+ // --- MODIFIED ---
139
+ // Determine cohort status from today's portfolio.
140
+ const tPnl = tHoldings.get(instrumentId)?.pnl;
141
+ const isWinner = tPnl > 0;
142
+ const isLoser = tPnl < 0;
143
+ // --- END MODIFIED ---
160
144
 
161
145
  // We only care about users who are *currently* in a cohort
162
146
  if (!isWinner && !isLoser) {
@@ -199,7 +183,6 @@ class WinnerLoserFlow {
199
183
 
200
184
  reset() {
201
185
  this.assetFlows.clear();
202
- this.cohorts = null;
203
186
  this.mappings = null;
204
187
  }
205
188
  }
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 3) for "In Loss" cohort asset flow.
3
3
  *
4
- * This metric calculates the "Net Crowd Flow Percentage" for each asset,
5
- * but *only* for the cohort of users who are currently *at a loss*
6
- * on that specific asset.
7
- *
8
- * This helps identify if losers are capitulating or doubling down.
9
- *
10
- * This calculation *depends* on 'asset_pnl_status' to identify the cohort.
4
+ * --- MODIFIED ---
5
+ * Removed dependency on 'asset_pnl_status' to fix 1MiB limit.
6
+ * This calculation now determines "in loss" status internally
7
+ * by reading the *yesterday's* portfolio P&L directly.
11
8
  */
12
9
  const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
13
10
 
@@ -15,7 +12,8 @@ class InLossAssetCrowdFlow {
15
12
  constructor() {
16
13
  this.assetData = new Map();
17
14
  this.mappings = null;
18
- this.inLossCohorts = null; // Map<ticker, Set<userId>>
15
+ // No longer need this:
16
+ // this.inLossCohorts = null;
19
17
  }
20
18
 
21
19
  /**
@@ -57,9 +55,11 @@ class InLossAssetCrowdFlow {
57
55
 
58
56
  /**
59
57
  * Statically declare dependencies.
58
+ * --- MODIFIED ---
59
+ * Removed 'asset_pnl-status'
60
60
  */
61
61
  static getDependencies() {
62
- return ['asset_pnl_status'];
62
+ return [];
63
63
  }
64
64
 
65
65
  _getPortfolioPositions(portfolio) {
@@ -77,31 +77,8 @@ class InLossAssetCrowdFlow {
77
77
  }
78
78
  }
79
79
 
80
- /**
81
- * Helper to get the cohort data from the dependency.
82
- * --- MODIFIED ---
83
- * Reads `data.users_in_loss` as a string array, not an object array.
84
- */
85
- _getInLossCohorts(fetchedDependencies) {
86
- if (this.inLossCohorts) {
87
- return this.inLossCohorts;
88
- }
89
-
90
- const pnlStatusData = fetchedDependencies['asset_pnl_status'];
91
- if (!pnlStatusData) {
92
- return new Map();
93
- }
94
-
95
- // Re-structure the data for efficient lookup
96
- // Map<ticker, Set<userId>>
97
- this.inLossCohorts = new Map();
98
- for (const [ticker, data] of Object.entries(pnlStatusData)) {
99
- // `data.users_in_loss` is now a string[], so no .map() is needed.
100
- const userSet = new Set(data.users_in_loss || []); // <-- MODIFIED
101
- this.inLossCohorts.set(ticker, userSet);
102
- }
103
- return this.inLossCohorts;
104
- }
80
+ // --- MODIFIED ---
81
+ // Removed _getInLossCohorts helper
105
82
 
106
83
  process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
107
84
  if (!todayPortfolio || !yesterdayPortfolio) {
@@ -109,15 +86,9 @@ class InLossAssetCrowdFlow {
109
86
  }
110
87
 
111
88
  if (!this.mappings) {
112
- // Context contains the mappings loaded in Pass 1
113
89
  this.mappings = context.mappings;
114
90
  }
115
91
 
116
- const cohorts = this._getInLossCohorts(fetchedDependencies);
117
- if (cohorts.size === 0) {
118
- return; // No dependency data
119
- }
120
-
121
92
  const yPos = this._getPortfolioPositions(yesterdayPortfolio);
122
93
  const tPos = this._getPortfolioPositions(todayPortfolio);
123
94
 
@@ -129,22 +100,22 @@ class InLossAssetCrowdFlow {
129
100
  for (const instrumentId of allInstrumentIds) {
130
101
  if (!instrumentId) continue;
131
102
 
132
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
133
- const cohort = cohorts.get(ticker);
103
+ const yP = yPosMap.get(instrumentId);
104
+ const tP = tPosMap.get(instrumentId);
134
105
 
135
- // This user is not in the "in loss" cohort for this asset, skip.
136
- if (!cohort || !cohort.has(userId)) {
137
- continue;
106
+ // --- MODIFIED ---
107
+ // Check P&L from YESTERDAY's position to define cohort
108
+ const yPnl = yP?.NetProfit || 0;
109
+ if (yPnl >= 0) {
110
+ continue; // User was not in loss yesterday, skip.
138
111
  }
112
+ // --- END MODIFIED ---
139
113
 
140
114
  // User *is* in the cohort, process their data
141
115
  this._initAsset(instrumentId);
142
116
  const asset = this.assetData.get(instrumentId);
143
117
  asset.cohort.add(userId); // Track cohort size
144
118
 
145
- const yP = yPosMap.get(instrumentId);
146
- const tP = tPosMap.get(instrumentId);
147
-
148
119
  const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
149
120
  const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
150
121
 
@@ -191,7 +162,6 @@ class InLossAssetCrowdFlow {
191
162
  reset() {
192
163
  this.assetData.clear();
193
164
  this.mappings = null;
194
- this.inLossCohorts = null;
195
165
  }
196
166
  }
197
167
 
@@ -1,13 +1,10 @@
1
1
  /**
2
2
  * @fileoverview Calculation (Pass 3) for "In Profit" cohort asset flow.
3
3
  *
4
- * This metric calculates the "Net Crowd Flow Percentage" for each asset,
5
- * but *only* for the cohort of users who are currently *in profit*
6
- * on that specific asset.
7
- *
8
- * This helps identify if winners are taking profit or adding to positions.
9
- *
10
- * This calculation *depends* on 'asset_pnl_status' to identify the cohort.
4
+ * --- MODIFIED ---
5
+ * Removed dependency on 'asset_pnl_status' to fix 1MiB limit.
6
+ * This calculation now determines "in profit" status internally
7
+ * by reading the *yesterday's* portfolio P&L directly.
11
8
  */
12
9
  const { loadInstrumentMappings } = require('../../utils/sector_mapping_provider');
13
10
 
@@ -15,7 +12,8 @@ class InProfitAssetCrowdFlow {
15
12
  constructor() {
16
13
  this.assetData = new Map();
17
14
  this.mappings = null;
18
- this.inProfitCohorts = null; // Map<ticker, Set<userId>>
15
+ // No longer need this:
16
+ // this.inProfitCohorts = null;
19
17
  }
20
18
 
21
19
  /**
@@ -57,9 +55,11 @@ class InProfitAssetCrowdFlow {
57
55
 
58
56
  /**
59
57
  * Statically declare dependencies.
58
+ * --- MODIFIED ---
59
+ * Removed 'asset_pnl-status'
60
60
  */
61
61
  static getDependencies() {
62
- return ['asset_pnl_status'];
62
+ return [];
63
63
  }
64
64
 
65
65
  _getPortfolioPositions(portfolio) {
@@ -77,31 +77,8 @@ class InProfitAssetCrowdFlow {
77
77
  }
78
78
  }
79
79
 
80
- /**
81
- * Helper to get the cohort data from the dependency.
82
- * --- MODIFIED ---
83
- * Reads `data.users_in_profit` as a string array, not an object array.
84
- */
85
- _getInProfitCohorts(fetchedDependencies) {
86
- if (this.inProfitCohorts) {
87
- return this.inProfitCohorts;
88
- }
89
-
90
- const pnlStatusData = fetchedDependencies['asset_pnl_status'];
91
- if (!pnlStatusData) {
92
- return new Map();
93
- }
94
-
95
- // Re-structure the data for efficient lookup
96
- // Map<ticker, Set<userId>>
97
- this.inProfitCohorts = new Map();
98
- for (const [ticker, data] of Object.entries(pnlStatusData)) {
99
- // `data.users_in_profit` is now a string[], so no .map() is needed.
100
- const userSet = new Set(data.users_in_profit || []); // <-- MODIFIED
101
- this.inProfitCohorts.set(ticker, userSet);
102
- }
103
- return this.inProfitCohorts;
104
- }
80
+ // --- MODIFIED ---
81
+ // Removed _getInProfitCohorts helper
105
82
 
106
83
  process(todayPortfolio, yesterdayPortfolio, userId, context, todayInsights, yesterdayInsights, fetchedDependencies) {
107
84
  if (!todayPortfolio || !yesterdayPortfolio) {
@@ -109,15 +86,9 @@ class InProfitAssetCrowdFlow {
109
86
  }
110
87
 
111
88
  if (!this.mappings) {
112
- // Context contains the mappings loaded in Pass 1
113
89
  this.mappings = context.mappings;
114
90
  }
115
91
 
116
- const cohorts = this._getInProfitCohorts(fetchedDependencies);
117
- if (cohorts.size === 0) {
118
- return; // No dependency data
119
- }
120
-
121
92
  const yPos = this._getPortfolioPositions(yesterdayPortfolio);
122
93
  const tPos = this._getPortfolioPositions(todayPortfolio);
123
94
 
@@ -129,22 +100,22 @@ class InProfitAssetCrowdFlow {
129
100
  for (const instrumentId of allInstrumentIds) {
130
101
  if (!instrumentId) continue;
131
102
 
132
- const ticker = this.mappings.instrumentToTicker[instrumentId] || `id_${instrumentId}`;
133
- const cohort = cohorts.get(ticker);
103
+ const yP = yPosMap.get(instrumentId);
104
+ const tP = tPosMap.get(instrumentId);
134
105
 
135
- // This user is not in the "in profit" cohort for this asset, skip.
136
- if (!cohort || !cohort.has(userId)) {
137
- continue;
106
+ // --- MODIFIED ---
107
+ // Check P&L from YESTERDAY's position to define cohort
108
+ const yPnl = yP?.NetProfit || 0;
109
+ if (yPnl <= 0) {
110
+ continue; // User was not in profit yesterday, skip.
138
111
  }
139
-
112
+ // --- END MODIFIED ---
113
+
140
114
  // User *is* in the cohort, process their data
141
115
  this._initAsset(instrumentId);
142
116
  const asset = this.assetData.get(instrumentId);
143
117
  asset.cohort.add(userId); // Track cohort size
144
118
 
145
- const yP = yPosMap.get(instrumentId);
146
- const tP = tPosMap.get(instrumentId);
147
-
148
119
  const yInvested = yP?.InvestedAmount || yP?.Amount || 0;
149
120
  const tInvested = tP?.InvestedAmount || tP?.Amount || 0;
150
121
 
@@ -191,7 +162,6 @@ class InProfitAssetCrowdFlow {
191
162
  reset() {
192
163
  this.assetData.clear();
193
164
  this.mappings = null;
194
- this.inProfitCohorts = null;
195
165
  }
196
166
  }
197
167
 
@@ -0,0 +1,123 @@
1
+ # node-graphviz
2
+
3
+ A JS + WASM module for compiling graphs written in DOT to images, using GraphViz in Node.js.
4
+
5
+ No annoying native build system or native dependencies that need to be compiled.
6
+
7
+ ## Installation
8
+
9
+ ```
10
+ npm install node-graphviz --save
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ See [The DOT Language](https://graphviz.gitlab.io/_pages/doc/info/lang.html) for more information about DOT, and [GraphViz Pocket Reference](https://graphs.grevian.org/example) for some examples.
16
+
17
+ ```js
18
+ const fs = require('fs');
19
+ const { graphviz } = require('node-graphviz');
20
+
21
+ // Define a graph using DOT notation
22
+ const graph = `
23
+ digraph {
24
+ a -> b;
25
+ b -> c;
26
+ c -> d;
27
+ d -> a;
28
+ }
29
+ `;
30
+
31
+ // Compile the graph to SVG using the `circo` layout algorithm
32
+ graphviz.circo(graph, 'svg').then((svg) => {
33
+ // Write the SVG to file
34
+ fs.writeFileSync('graph.svg', svg);
35
+ });
36
+ ```
37
+
38
+ Running the above produces the following SVG:
39
+
40
+ ![SVG Image showing compiled graph](./tests/output.svg)
41
+
42
+ ## API
43
+
44
+ The module exports the following API:
45
+
46
+ ```ts
47
+ declare type Format = 'svg' | 'dot' | 'json' | 'dot_json' | 'xdot_json';
48
+
49
+ declare type Engine = 'circo' | 'dot' | 'fdp' | 'neato' | 'osage' | 'patchwork' | 'twopi';
50
+
51
+ export declare const graphviz = {
52
+ layout(dotSource: string, outputFormat?: Format, layoutEngine?: Engine): Promise<string>;
53
+ circo(dotSource: string, outputFormat?: Format): Promise<string>;
54
+ dot(dotSource: string, outputFormat?: Format): Promise<string>;
55
+ fdp(dotSource: string, outputFormat?: Format): Promise<string>;
56
+ neato(dotSource: string, outputFormat?: Format): Promise<string>;
57
+ osage(dotSource: string, outputFormat?: Format): Promise<string>;
58
+ patchwork(dotSource: string, outputFormat?: Format): Promise<string>;
59
+ twopi(dotSource: string, outputFormat?: Format): Promise<string>;
60
+ };
61
+ ```
62
+
63
+ ### graphviz.layout(_dotSource_[, _outputFormat_][, _layoutengine_])
64
+
65
+ Performs layout for the supplied `dotSource`.
66
+
67
+ Where:
68
+
69
+ - `outputFormat` is one of the following (see [Output Formats](https://graphviz.gitlab.io/_pages/doc/info/output.html) for details):
70
+ - `dot`
71
+ - `dot_json`
72
+ - `json`
73
+ - `svg` (default)
74
+ - `xdot_json`
75
+ - `layoutEngine` is one of the following (see [Layout documentation](https://www.graphviz.org/documentation/) for details):
76
+ - `circo`
77
+ - `dot` (default)
78
+ - `fdp`
79
+ - `neato`
80
+ - `osage`
81
+ - `patchwork`
82
+ - `twopi`
83
+
84
+ ### graphviz.circo(_dotSource_[, _outputFormat_])
85
+
86
+ Convenience function that performs **circo** layout, is equivalent to `layout(dotSource, outputFormat, 'circo')`.
87
+
88
+ ### graphviz.dot(_dotSource_[, _outputFormat_])
89
+
90
+ Convenience function that performs **dot** layout, is equivalent to `layout(dotSource, outputFormat, 'dot')`.
91
+
92
+ ### graphviz.fdp(_dotSource_[, _outputFormat_])
93
+
94
+ Convenience function that performs **circo** layout, is equivalent to `layout(dotSource, outputFormat, 'fdp')`.
95
+
96
+ ### graphviz.neato(_dotSource_[, _outputFormat_])
97
+
98
+ Convenience function that performs **neato** layout, is equivalent to `layout(dotSource, outputFormat, 'neato')`.
99
+
100
+ ### graphviz.osage(_dotSource_[, _outputFormat_])
101
+
102
+ Convenience function that performs **osage** layout, is equivalent to `layout(dotSource, outputFormat, 'osage')`.
103
+
104
+ ### graphviz.patchwork(_dotSource_[, _outputFormat_])
105
+
106
+ Convenience function that performs **patchwork** layout, is equivalent to `layout(dotSource, outputFormat, 'patchwork')`.
107
+
108
+ ### graphviz.twopi(_dotSource_[, _outputFormat_])
109
+
110
+ Convenience function that performs **twopi** layout, is equivalent to `layout(dotSource, outputFormat, 'twopi')`.
111
+
112
+ ## Credits
113
+
114
+ This module is based on [hpcc-systems/hpcc-js-wasm](https://github.com/hpcc-systems/hpcc-js-wasm), which is designed for use in a browser, not Node.js. The following changes were made to support Node and simplify the module to include only GraphViz:
115
+
116
+ - Rewrote WASM binary location and fetching to read from the filesystem
117
+ - Added the compiled [WASM binary](https://unpkg.com/browse/@hpcc-js/wasm@0.3.14/dist/) to the source
118
+ - Reduced the JS code by half to include only GraphViz, removed Expat and other unrelated code
119
+ - Removed build system and TypeScript, in favor of a single source file (based on the compiled dist file from [hpcc-systems/hpcc-js-wasm](https://github.com/hpcc-systems/hpcc-js-wasm))
120
+
121
+ ## Licence
122
+
123
+ [MIT](LICENSE)
@@ -0,0 +1,33 @@
1
+ declare type Format = "svg" | "dot" | "json" | "dot_json" | "xdot_json";
2
+
3
+ declare type Engine = "circo" | "dot" | "fdp" | "neato" | "osage" | "patchwork" | "twopi";
4
+
5
+ interface Image {
6
+ path: string;
7
+ width: string;
8
+ height: string;
9
+ }
10
+
11
+ interface File {
12
+ path: string;
13
+ data: string;
14
+ }
15
+
16
+ interface Ext {
17
+ images?: Image[];
18
+ files?: File[];
19
+ }
20
+
21
+ export declare const graphviz: {
22
+ layout(dotSource: string, outputFormat?: Format, layoutEngine?: Engine, ext?: Ext | undefined): Promise<string>;
23
+ circo(dotSource: string, outputFormat?: Format, ext?: Ext | undefined): Promise<string>;
24
+ dot(dotSource: string, outputFormat?: Format, ext?: Ext | undefined): Promise<string>;
25
+ fdp(dotSource: string, outputFormat?: Format, ext?: Ext | undefined): Promise<string>;
26
+ neato(dotSource: string, outputFormat?: Format, ext?: Ext | undefined): Promise<string>;
27
+ osage(dotSource: string, outputFormat?: Format, ext?: Ext | undefined): Promise<string>;
28
+ patchwork(dotSource: string, outputFormat?: Format, ext?: Ext | undefined): Promise<string>;
29
+ twopi(dotSource: string, outputFormat?: Format, ext?: Ext | undefined): Promise<string>;
30
+ };
31
+
32
+ export {};
33
+