hedgequantx 1.1.1 → 1.2.31
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 +128 -136
- package/bin/cli.js +28 -2076
- package/package.json +3 -3
- package/src/app.js +550 -0
- package/src/config/index.js +16 -2
- package/src/config/propfirms.js +324 -12
- package/src/pages/accounts.js +115 -0
- package/src/pages/algo.js +538 -0
- package/src/pages/index.js +13 -2
- package/src/pages/orders.js +114 -0
- package/src/pages/positions.js +115 -0
- package/src/pages/stats.js +212 -3
- package/src/pages/user.js +92 -0
- package/src/security/encryption.js +168 -0
- package/src/security/index.js +61 -0
- package/src/security/rateLimit.js +155 -0
- package/src/security/validation.js +253 -0
- package/src/services/hqx-server.js +34 -17
- package/src/services/index.js +2 -1
- package/src/services/projectx.js +383 -35
- package/src/services/session.js +150 -38
- package/src/ui/index.js +4 -1
- package/src/ui/menu.js +154 -0
- package/src/services/local-storage.js +0 -309
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Local Storage Service
|
|
3
|
-
* Stores user data locally on their machine
|
|
4
|
-
* - Saved connections (PropFirm credentials)
|
|
5
|
-
* - Session history
|
|
6
|
-
* - User preferences
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const crypto = require('crypto');
|
|
12
|
-
const os = require('os');
|
|
13
|
-
|
|
14
|
-
// Storage directory in user's home folder
|
|
15
|
-
const STORAGE_DIR = path.join(os.homedir(), '.hedgequantx');
|
|
16
|
-
const CONNECTIONS_FILE = path.join(STORAGE_DIR, 'connections.enc');
|
|
17
|
-
const SETTINGS_FILE = path.join(STORAGE_DIR, 'settings.json');
|
|
18
|
-
const HISTORY_FILE = path.join(STORAGE_DIR, 'history.json');
|
|
19
|
-
|
|
20
|
-
// Encryption key derived from machine ID
|
|
21
|
-
const getEncryptionKey = () => {
|
|
22
|
-
const machineId = `${os.hostname()}-${os.platform()}-${os.userInfo().username}`;
|
|
23
|
-
return crypto.createHash('sha256').update(machineId).digest();
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
class LocalStorageService {
|
|
27
|
-
constructor() {
|
|
28
|
-
this._ensureStorageDir();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Ensure storage directory exists
|
|
33
|
-
*/
|
|
34
|
-
_ensureStorageDir() {
|
|
35
|
-
if (!fs.existsSync(STORAGE_DIR)) {
|
|
36
|
-
fs.mkdirSync(STORAGE_DIR, { recursive: true, mode: 0o700 });
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Encrypt data
|
|
42
|
-
*/
|
|
43
|
-
_encrypt(data) {
|
|
44
|
-
const key = getEncryptionKey();
|
|
45
|
-
const iv = crypto.randomBytes(16);
|
|
46
|
-
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
|
|
47
|
-
let encrypted = cipher.update(JSON.stringify(data), 'utf8', 'hex');
|
|
48
|
-
encrypted += cipher.final('hex');
|
|
49
|
-
return iv.toString('hex') + ':' + encrypted;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Decrypt data
|
|
54
|
-
*/
|
|
55
|
-
_decrypt(encryptedData) {
|
|
56
|
-
try {
|
|
57
|
-
const key = getEncryptionKey();
|
|
58
|
-
const [ivHex, encrypted] = encryptedData.split(':');
|
|
59
|
-
const iv = Buffer.from(ivHex, 'hex');
|
|
60
|
-
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
|
|
61
|
-
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
62
|
-
decrypted += decipher.final('utf8');
|
|
63
|
-
return JSON.parse(decrypted);
|
|
64
|
-
} catch (error) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// ==================== CONNECTIONS ====================
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Save a PropFirm connection
|
|
73
|
-
*/
|
|
74
|
-
saveConnection(connection) {
|
|
75
|
-
const connections = this.getConnections();
|
|
76
|
-
|
|
77
|
-
// Check if connection already exists
|
|
78
|
-
const existingIndex = connections.findIndex(
|
|
79
|
-
c => c.propfirm === connection.propfirm && c.username === connection.username
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const connectionData = {
|
|
83
|
-
id: connection.id || crypto.randomUUID(),
|
|
84
|
-
propfirm: connection.propfirm,
|
|
85
|
-
propfirmName: connection.propfirmName,
|
|
86
|
-
username: connection.username,
|
|
87
|
-
password: connection.password, // Encrypted in file
|
|
88
|
-
lastUsed: Date.now(),
|
|
89
|
-
createdAt: connection.createdAt || Date.now()
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
if (existingIndex >= 0) {
|
|
93
|
-
connections[existingIndex] = connectionData;
|
|
94
|
-
} else {
|
|
95
|
-
connections.push(connectionData);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Save encrypted
|
|
99
|
-
const encrypted = this._encrypt(connections);
|
|
100
|
-
fs.writeFileSync(CONNECTIONS_FILE, encrypted, { mode: 0o600 });
|
|
101
|
-
|
|
102
|
-
return connectionData;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Get all saved connections
|
|
107
|
-
*/
|
|
108
|
-
getConnections() {
|
|
109
|
-
try {
|
|
110
|
-
if (!fs.existsSync(CONNECTIONS_FILE)) {
|
|
111
|
-
return [];
|
|
112
|
-
}
|
|
113
|
-
const encrypted = fs.readFileSync(CONNECTIONS_FILE, 'utf8');
|
|
114
|
-
return this._decrypt(encrypted) || [];
|
|
115
|
-
} catch (error) {
|
|
116
|
-
return [];
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Get connection by ID
|
|
122
|
-
*/
|
|
123
|
-
getConnection(id) {
|
|
124
|
-
const connections = this.getConnections();
|
|
125
|
-
return connections.find(c => c.id === id);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Delete a connection
|
|
130
|
-
*/
|
|
131
|
-
deleteConnection(id) {
|
|
132
|
-
const connections = this.getConnections();
|
|
133
|
-
const filtered = connections.filter(c => c.id !== id);
|
|
134
|
-
|
|
135
|
-
if (filtered.length === connections.length) {
|
|
136
|
-
return false; // Not found
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const encrypted = this._encrypt(filtered);
|
|
140
|
-
fs.writeFileSync(CONNECTIONS_FILE, encrypted, { mode: 0o600 });
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Update last used timestamp
|
|
146
|
-
*/
|
|
147
|
-
updateConnectionLastUsed(id) {
|
|
148
|
-
const connections = this.getConnections();
|
|
149
|
-
const connection = connections.find(c => c.id === id);
|
|
150
|
-
|
|
151
|
-
if (connection) {
|
|
152
|
-
connection.lastUsed = Date.now();
|
|
153
|
-
const encrypted = this._encrypt(connections);
|
|
154
|
-
fs.writeFileSync(CONNECTIONS_FILE, encrypted, { mode: 0o600 });
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// ==================== SETTINGS ====================
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Save user settings
|
|
162
|
-
*/
|
|
163
|
-
saveSettings(settings) {
|
|
164
|
-
const currentSettings = this.getSettings();
|
|
165
|
-
const merged = { ...currentSettings, ...settings };
|
|
166
|
-
fs.writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2), { mode: 0o600 });
|
|
167
|
-
return merged;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Get user settings
|
|
172
|
-
*/
|
|
173
|
-
getSettings() {
|
|
174
|
-
try {
|
|
175
|
-
if (!fs.existsSync(SETTINGS_FILE)) {
|
|
176
|
-
return this._getDefaultSettings();
|
|
177
|
-
}
|
|
178
|
-
const data = fs.readFileSync(SETTINGS_FILE, 'utf8');
|
|
179
|
-
return { ...this._getDefaultSettings(), ...JSON.parse(data) };
|
|
180
|
-
} catch (error) {
|
|
181
|
-
return this._getDefaultSettings();
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Default settings
|
|
187
|
-
*/
|
|
188
|
-
_getDefaultSettings() {
|
|
189
|
-
return {
|
|
190
|
-
defaultContracts: 1,
|
|
191
|
-
defaultDailyTarget: 500,
|
|
192
|
-
defaultMaxRisk: 250,
|
|
193
|
-
autoConnect: false,
|
|
194
|
-
theme: 'dark',
|
|
195
|
-
notifications: true,
|
|
196
|
-
analyticsEnabled: true // User can opt-out
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ==================== HISTORY ====================
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Add to trading history
|
|
204
|
-
*/
|
|
205
|
-
addToHistory(entry) {
|
|
206
|
-
const history = this.getHistory();
|
|
207
|
-
|
|
208
|
-
history.push({
|
|
209
|
-
id: crypto.randomUUID(),
|
|
210
|
-
timestamp: Date.now(),
|
|
211
|
-
...entry
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
// Keep last 1000 entries
|
|
215
|
-
const trimmed = history.slice(-1000);
|
|
216
|
-
fs.writeFileSync(HISTORY_FILE, JSON.stringify(trimmed, null, 2), { mode: 0o600 });
|
|
217
|
-
|
|
218
|
-
return entry;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get trading history
|
|
223
|
-
*/
|
|
224
|
-
getHistory(limit = 100) {
|
|
225
|
-
try {
|
|
226
|
-
if (!fs.existsSync(HISTORY_FILE)) {
|
|
227
|
-
return [];
|
|
228
|
-
}
|
|
229
|
-
const data = fs.readFileSync(HISTORY_FILE, 'utf8');
|
|
230
|
-
const history = JSON.parse(data);
|
|
231
|
-
return history.slice(-limit);
|
|
232
|
-
} catch (error) {
|
|
233
|
-
return [];
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Get stats from history
|
|
239
|
-
*/
|
|
240
|
-
getLocalStats() {
|
|
241
|
-
const history = this.getHistory(1000);
|
|
242
|
-
const trades = history.filter(h => h.type === 'trade');
|
|
243
|
-
|
|
244
|
-
const totalTrades = trades.length;
|
|
245
|
-
const wins = trades.filter(t => t.pnl > 0).length;
|
|
246
|
-
const losses = trades.filter(t => t.pnl < 0).length;
|
|
247
|
-
const totalPnl = trades.reduce((sum, t) => sum + (t.pnl || 0), 0);
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
totalTrades,
|
|
251
|
-
wins,
|
|
252
|
-
losses,
|
|
253
|
-
winRate: totalTrades > 0 ? ((wins / totalTrades) * 100).toFixed(1) : 0,
|
|
254
|
-
totalPnl: totalPnl.toFixed(2),
|
|
255
|
-
avgPnl: totalTrades > 0 ? (totalPnl / totalTrades).toFixed(2) : 0
|
|
256
|
-
};
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Clear all history
|
|
261
|
-
*/
|
|
262
|
-
clearHistory() {
|
|
263
|
-
if (fs.existsSync(HISTORY_FILE)) {
|
|
264
|
-
fs.unlinkSync(HISTORY_FILE);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ==================== UTILITIES ====================
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Get storage path info
|
|
272
|
-
*/
|
|
273
|
-
getStoragePath() {
|
|
274
|
-
return STORAGE_DIR;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Check if storage exists
|
|
279
|
-
*/
|
|
280
|
-
hasStoredData() {
|
|
281
|
-
return fs.existsSync(CONNECTIONS_FILE) || fs.existsSync(HISTORY_FILE);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* Export all data (for backup)
|
|
286
|
-
*/
|
|
287
|
-
exportData() {
|
|
288
|
-
return {
|
|
289
|
-
connections: this.getConnections(),
|
|
290
|
-
settings: this.getSettings(),
|
|
291
|
-
history: this.getHistory(1000),
|
|
292
|
-
exportedAt: Date.now()
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Clear all data
|
|
298
|
-
*/
|
|
299
|
-
clearAll() {
|
|
300
|
-
if (fs.existsSync(CONNECTIONS_FILE)) fs.unlinkSync(CONNECTIONS_FILE);
|
|
301
|
-
if (fs.existsSync(SETTINGS_FILE)) fs.unlinkSync(SETTINGS_FILE);
|
|
302
|
-
if (fs.existsSync(HISTORY_FILE)) fs.unlinkSync(HISTORY_FILE);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Singleton
|
|
307
|
-
const localStorage = new LocalStorageService();
|
|
308
|
-
|
|
309
|
-
module.exports = { LocalStorageService, localStorage };
|