Обновить

Комментарии 12

Судя по всему, так как мы имеем дело с человеческой массой, это стандартное распределение

Те кто посередине репостят тех, кто в начале

Вопрос откуда берут посты те самые лидеры мнений, которые при пустой новостной ленте создают тренд. Не проверил, возможно это копипаста из аналитических агенств

Код для копипасты поиска первого блогера в серии

function pickEarlyAuthors(ideas, testMonth, K = 2, EARLY_MAX = 2, WIN_H = 24) {
  const from = monthAdd(testMonth, -12), to = testMonth;
  const bySym = groupBy(ideas, (i) => i.symbol);   // ВСЕ посты, и NEUTRAL
  const stat = {};                                  // author -> [ранних, всего]
  for (const list of Object.values(bySym)) {
    list.sort((a, b) => a.ts - b.ts);
    let lo = 0;
    list.forEach((idea, hi) => {
      if (idea.ts < from || idea.ts >= to || idea.direction === "NEUTRAL") return;
      while (list[lo].ts < idea.ts - WIN_H * 3600e3) lo++;
      let others = 0;
      for (let j = lo; j < hi; j++) if (list[j].author !== idea.author) others++;
      const s = (stat[idea.author] ??= [0, 0]);
      s[0] += others <= EARLY_MAX ? 1 : 0;
      s[1] += 1;
    });
  }
  return Object.entries(stat)
    .filter(([, s]) => s[1] >= 10)
    .sort((a, b) => b[1][0] / b[1][1] - a[1][0] / a[1][1])
    .slice(0, K)
    .map(([author]) => author);
}

Код для копипасты брутфорса входов на любой выборке

import { addExchangeSchema, addSweepSchema, Sweep } from "backtest-kit";
import { readFileSync, writeFileSync } from "fs";
import { singleshot } from "functools-kit";
import { createWriteStream } from "fs";
import { once } from "events";
import ccxt from "ccxt";

// result_reports.jsonl на полной сетке — гигабайты на символ-месяц,
// JSON.stringify всего массива не влезет в кучу: пишем потоком
async function writeLines(path, arr) {
  const ws = createWriteStream(path, {
    flags: "w",
    encoding: "utf8",
    highWaterMark: 1024 * 1024,
  });
  for (let i = 0; i < arr.length; i++) {
    if (!ws.write(JSON.stringify(arr[i]) + "\n")) {
      await once(ws, "drain");
    }
    arr[i] = null; // освобождаем ссылку
  }
  ws.end();
  await once(ws, "finish");
}

const getExchange = singleshot(async () => {
  const exchange = new ccxt.binance({
    options: { defaultType: "spot", adjustForTimeDifference: true, recvWindow: 60000 },
    enableRateLimit: true,
    timeout: 15000,
  });
  await exchange.loadMarkets();
  return exchange;
});

addExchangeSchema({
  exchangeName: "ccxt_cached",
  getCandles: async (symbol, interval, since, limit) => {
    const exchange = await getExchange();
    const candles = await exchange.fetchOHLCV(symbol, interval, since.getTime(), limit);
    return candles.map(([timestamp, open, high, low, close, volume]) => ({
      timestamp, open, high, low, close, volume,
    }));
  },
});

addSweepSchema({
  sweepName: "tv_probe",
  exchangeName: "ccxt_cached",
  reportOrder: "sharpe",
  gridAxes: {
    hardStopPercent: [
      1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5, 5.5, 6, 6.5, 7, 7.5, 8, 8.5, 9, 9.5, 10,
    ],
    trailingTakePercent: [0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5],
    holdMinutes: [
      1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
    ].map((d) => d * 24 * 60),
    profitLockPercent: [1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 5.0],
  },
  callbacks: {
    onProgress(symbol, stage, processed, total) {
      console.log(`symbol=${symbol} stage=${stage} processed=${processed} total=${total}`);
    },
  },
});

const ideas = readFileSync("./assets/tv-ideas.normalized.jsonl", "utf-8")
  .split("\n")
  .filter(Boolean)
  .map((line) => JSON.parse(line));

const result = await Sweep.run({
  symbol: "BTCUSDT",
  sweepName: "tv_probe",
  ideas,
});

const { reports, ...other } = result;

writeFileSync("./result.json", JSON.stringify(other, null, 2));

await writeLines("./result_tracks.jsonl", reports.tracks);
await writeLines("./result_best.jsonl", reports.best);
await writeLines("./result_reports.jsonl", reports.reports);

process.exit(0);

Другими словами, после того как на рынке проливается красное, тот кто первый заорет тот и прав становится The One в оккультном понимании:

А вот в какого бога они верят и кто его мессия, тут наверное надо Арию слушать. Вот этот заоравший первый, он же единственный. Но только по тому что после после резни живым остался

14 суток — это и есть фиксация убытка: не отработала — выходим

Я надеюсь это шутка, ведь на 15й день может быть туземун) С таким риск менеджментом всё сливается на длинной дистанции 100%.

Описание похоже на своего рода инвестирование, причем сброс позиции происходит наихудшим образом на 14 день, если просели на 50% то уже логично пересидеть так как спот, иначе нужно было сбрасывать когда получили посадку на 10%.

Свинг спотом, этот стиль хорошо подходит под торговлю по объемным уровням на 4H. Знаю (не лично) профитного трейдера, он все записи ведет тоже на TV чтобы народ повторял если хочет, там как раз сделки раз в неделю или реже если мало активов будете использовать. Если нужно ссылку на ТВ, напишите в ЛС.

пс. Я не трейдер, просто интересна тема трейдинга

Тут не совсем туземун, критерий закрытия же это не цена, а время

Но если случился туземун сразу надо высовывать

Если делать трейдинг по таймингу, я бы лучше сделал вход в позицию на 2й-3й час Нью Йорк сессии, много раз замечал, что первые часы задают тренд всему дню. Более того, многие трейдеры так и делают, после открытия Америки в первый часы открывают позиции так как волатильность огромная, главное угадать с трендом.

Этот датасет нужен чтобы озадачить ИИ агента: вот пусть крутится и перебирает варианты

import {
  addStrategySchema,
  listenError,
  listenActivePing,
  commitTrailingTakeCost,
  getDate,
  getAveragePrice,
  Log,
  listenIdlePing,
  alignToInterval,
  getClosePrice,
  listenSignal,
  Position,
  commitClosePending,
  getPositionHighestProfitDistancePnlPercentage,
  getPositionHighestPnlPercentage,
  getPositionPnlPercent,
  getCandles,
  getPositionHighestProfitDistancePnlCost,
  getPositionHighestMaxDrawdownPnlCost,
  getPositionPnlCost,
  getPositionHighestPnlCost,
  getPositionHighestProfitMinutes,
} from "backtest-kit";
import { errorData, getErrorMessage, randomString, str } from "functools-kit";
import { readFileSync } from "fs";
import { SignalEntryModel } from "./model/SignalEntry.model";

const PEAK_STALENESS_SINCE_PROFIT = 1.0;
const PEAK_STALENESS_SINCE_MINUTES = 240;

const TRAILING_TAKE = 1.0;
const HARD_STOP = 1.0;

const SIGNALS: SignalEntryModel[] = readFileSync(
  "./assets/entry.jsonl",
  "utf-8",
)
  .split("\n")
  .filter(Boolean)
  .map((line) => JSON.parse(line));

function getActiveSignal(symbol: string, when: Date): SignalEntryModel | null {
  const now = when.getTime();
  const match = SIGNALS.find(
    (s) => {
      if (s.symbol !== symbol) {
        return false;
      }
      const publishedAt = alignToInterval(new Date(s.publishedAt), "1m");
      return publishedAt.getTime() === now;
    }
  );
  return match ?? null;
}

addStrategySchema({
  strategyName: "jan_2026_strategy",
  getSignal: async (symbol, when, currentPrice) => {

    console.log(when)

    const signal = getActiveSignal(symbol, when);

    if (!signal) {
      return null;
    }
    
    const close_1m = await getClosePrice(symbol, "1m");

    if (close_1m < signal.entry.from || close_1m > signal.entry.to) {
      return null;
    }

    return {
      ...Position.moonbag({
        position: signal.direction,
        currentPrice,
        percentStopLoss: HARD_STOP,
      }),
      minuteEstimatedTime: 24 * 60,
      note: signal.note,
    };
  },
});

listenActivePing(async ({ symbol, data }) => {
  const peakProfitDistance = await getPositionHighestProfitDistancePnlPercentage(symbol);
  const currentProfit = await getPositionPnlPercent(symbol);
  if (currentProfit < 0) {
    return;
  }
  if (peakProfitDistance < TRAILING_TAKE) {
    return;
  }
  Log.info("position closed due to the trailing take", {
    symbol,
    data,
  });
  await commitClosePending(symbol, {
    id: "unknown",
    note: str.newline(
      "# Позиция закрыта по trailing take",
    ),
  });
});

listenActivePing(async ({ symbol, data }) => {
  const peakProfitCost = await getPositionHighestPnlPercentage(symbol);
  const peakProfitMinutes = await getPositionHighestProfitMinutes(symbol);
  if (peakProfitCost < PEAK_STALENESS_SINCE_PROFIT) {
    return;
  }
  if (peakProfitMinutes < PEAK_STALENESS_SINCE_MINUTES) {
    return;
  }
  Log.info("position closed due to the peak staleness", {
    symbol,
    data,
  });
  await commitClosePending(symbol, {
    id: "unknown",
    note: str.newline(
      "# Позиция закрыта по peak staleness",
    ),
  });
});

listenError((error) => {
  console.log(error);
  Log.debug("error", {
    error: errorData(error),
    message: getErrorMessage(error),
  });
});

В этой же экосистеме есть среда исполнения, которая функционально чисто позволяет кодить сложные условия выхода, знать бы что

Прибыль может быть не 20, а 12 процентов. Если от них откатилось уже 8, то нужно выходить на +4, так как статистически это разворот

Если от них откатилось уже 8, то нужно выходить на +4, так как статистически это разворот

Или свип ликвидности, ловушка, pullback, после чего тренд продолжится с большей силой

Эффективно определить это по режиму рынка можно только статистически на большой выборке.

Нет, рынок меняется, вы не успеете накопить статистику потому что к этому времени она уже будет невалидна.

Тот же биткоин, статистически по всем циклам топ рынка в этом цикле должен был быть 180к-220к минимум, но мы видим что произошло, это слишком очевидно.

Речь идет об одной позиции. Если для одной позиции не работает статистика, то невозможен риск менеджемент как класс

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации