neuroalpha 0.5.2 → 0.5.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neuroalpha",
3
- "version": "0.5.2",
3
+ "version": "0.5.3",
4
4
  "description": "НейроАльфа — ИИ-автопилот для рынка акций Мосбиржи: эмбеддинги акций, самообучение правил, коннекторы Т-Банк / БКС / Финам / Алор и боты Telegram / MAX",
5
5
  "author": "Roman A. Perepelkin",
6
6
  "license": "UNLICENSED",
@@ -280,11 +280,15 @@ export function brokerAccountsSummary(uid = 1) {
280
280
  }
281
281
 
282
282
  const candlePrevStmt = db.prepare('SELECT ts, open, close FROM candles WHERE symbol = ? AND interval = 10 AND ts <= ? ORDER BY ts DESC LIMIT 3');
283
+ const executionAuditCache = new Map(); // accountId -> { sig, val }
283
284
 
284
- // Телеметрия качества исполнения и сверка реальных сделок счёта с ориентиром стресс-теста
285
+ // Телеметрия качества исполнения и сверка реальных сделок счёта с ориентиром стресс-теста (с кешированием по срезу истории)
285
286
  function computeExecutionAudit(a) {
286
287
  const ops = Array.isArray(a.lastHistory?.operations) ? a.lastHistory.operations : [];
287
288
  if (!ops.length) return null;
289
+ const sig = `${a.lastHistory?.at || 0}:${ops.length}:${ops[0]?.id || ''}:${ops[ops.length - 1]?.id || ''}`;
290
+ const hit = executionAuditCache.get(a.id);
291
+ if (hit && hit.sig === sig) return hit.val;
288
292
  const sorted = [...ops].sort((x, y) => x.date - y.date);
289
293
  // отсекаем старую историю автоследования (до последнего удержания комиссии автоследования)
290
294
  const lastFollowTs = Math.max(0, ...sorted.filter((o) => o.kind === 'fee' && /автослед/i.test(o.title || '')).map((o) => o.date));
@@ -339,7 +343,7 @@ function computeExecutionAudit(a) {
339
343
  const netRub = round(grossRub - feesRub, 0);
340
344
  const profitFactorGross = lossRub > 0 ? round(winRub / lossRub, 2) : (winRub > 0 ? 3 : null);
341
345
 
342
- return {
346
+ const val = {
343
347
  fromTs: algoOps[0]?.date || null,
344
348
  tradesCount: trades.length,
345
349
  closedCount,
@@ -349,12 +353,14 @@ function computeExecutionAudit(a) {
349
353
  slipTier1Pct,
350
354
  slipTier2Pct,
351
355
  totalCostPerSidePct,
352
- breakEvenTargetPct: 0.255,
356
+ breakEvenTargetPct: 0.229,
353
357
  grossRub,
354
358
  feesRub: round(feesRub, 0),
355
359
  netRub,
356
360
  profitFactorGross,
357
361
  };
362
+ executionAuditCache.set(a.id, { sig, val });
363
+ return val;
358
364
  }
359
365
 
360
366
  function publicAccount(a) {
@@ -866,7 +872,7 @@ async function syncTbank(acc) {
866
872
  // 2) если лимитная заявка висит неисполненной дольше 12 минут (прошла свеча, цена ушла) — автоматически снимаем её и разблокируем рубли!
867
873
  if (acc.mode !== 'readonly' && rawOrders.length > 0) {
868
874
  const seenInstDir = new Set();
869
- const toCancelIds = [];
875
+ const toCancelOrders = [];
870
876
  const keptOrders = [];
871
877
  const sortedByDate = [...rawOrders].sort((a, b) => Date.parse(b.orderDate || 0) - Date.parse(a.orderDate || 0));
872
878
  const nowMs = Date.now();
@@ -874,19 +880,21 @@ async function syncTbank(acc) {
874
880
  const k = `${o.instrumentUid || o.figi || o.ticker}:${o.direction}`;
875
881
  const ageMs = o.orderDate ? (nowMs - Date.parse(o.orderDate)) : 0;
876
882
  if (seenInstDir.has(k) || ageMs > 12 * 60000) {
877
- if (o.orderId) toCancelIds.push(o.orderId);
883
+ if (o.orderId) toCancelOrders.push({ o, reason: ageMs > 12 * 60000 ? '>12 мин' : 'дубль' });
878
884
  } else {
879
885
  seenInstDir.add(k);
880
886
  keptOrders.push(o);
881
887
  }
882
888
  }
883
- if (toCancelIds.length > 0) {
884
- await Promise.all(toCancelIds.map((orderId) => (
889
+ if (toCancelOrders.length > 0) {
890
+ await Promise.all(toCancelOrders.map(({ o }) => (
885
891
  brokerFetch(`${base}/tinkoff.public.invest.api.contract.v1.OrdersService/CancelOrder`, {
886
- method: 'POST', headers, body: JSON.stringify({ accountId, orderId: String(orderId) }), timeoutMs: 8000,
892
+ method: 'POST', headers, body: JSON.stringify({ accountId, orderId: String(o.orderId) }), timeoutMs: 8000,
887
893
  }).catch(() => null)
888
894
  )));
889
- logBrokerEvent(acc, 'order_expired', `авто-снятие ${toCancelIds.length} неисполненных/дублирующих заявок (>12 мин в стакане)`);
895
+ for (const { o, reason } of toCancelOrders) {
896
+ logBrokerEvent(acc, 'order_expired', `${o.ticker || o.figi || 'заявка'}: снята (${reason}) · ${o.orderId}`);
897
+ }
890
898
  rawOrders = keptOrders;
891
899
  }
892
900
  }