// GENERATED BY STOCHASTLY - do not edit by hand, re-export.
// kernel     : ema_cross
// parameters : {"atr_mult": 0.75, "ema_fast": 13, "ema_slow": 21, "rr": 3}
// direction  : L
// fidelity   : 1.0 over 7 engine trades (direction L), compared TRADE FOR TRADE — entry bar, exit bar, side and gross R, no tolerance — against the engine's own trade table. Precision 1.0 (0 trade(s) this file books and the engine does not). IDENTICAL on every path measured. Measured on synthetic price paths, not on your own data. Worst of 4 synthetic path(s) that produced engine activity, out of 4 tried.
//
// C++ is an ISO standard (ISO/IEC 14882), not a product: there is no vendor to name and no
// trademark to acknowledge here. This file depends on the standard library only - no third
// party, no framework, nothing to install. Inventing a mention would be inventing a tie.
//
// EXECUTION, exactly as the engine ran it (`trades()` below replays it trade for trade):
//   - a signal at the close of bar i opens at the OPEN of bar i+1;
//   - stop  = 0.75 x ATR(14) of the signal bar, checked on each bar's low/high (stop FIRST, then target), booked as -1 R;
//   - target = 2.25 x ATR (rr = 3), booked as +3 R;
//   - no session rule was wired: nothing forces a close, ever;
//   - direction L: the engine simulates BOTH sides and drops the other side's trades afterwards, so a phantom position on the forbidden side blocks new entries while it lives. `trades()` does the same;
//   - Execution-node settings this kernel IGNORES (hard-coded in the engine, so your backtest ignored them too): trail_k (this kernel has no trailing-stop input); exit_mode (this kernel uses its fixed exit policy); sl_variant (this kernel has no stop-variant input);
//   - sizing is one unit per trade and the R is GROSS: your broker's cost applies instead of the engine's cost model.
// `signal()` returns +1 (long), -1 (short) or 0, one value per bar - the entries the
// engine would have TAKEN (direction applied).
//
// It reads NO future bar: every operator looks at the current bar or an older one.
// Windows that are not filled yet yield NaN, and a NaN never triggers anything.
//
// NOT EXPORTED : nothing — every node wired in this graph is in this file.

#include <algorithm>
#include <cmath>
#include <cstddef>
#include <limits>
#include <vector>

namespace stochastly {

typedef std::vector<double> V;
inline double na() { return std::numeric_limits<double>::quiet_NaN(); }

// A series, or a scalar broadcast over one. The implicit constructor from double is what lets a
// constant be written plainly in the generated expressions: sub(close, 2.0).
struct S {
  V v; bool scalar;
  S(double x) : v(1, x), scalar(true) {}
  S(V d) : v(d), scalar(false) {}
  std::size_t len() const { return scalar ? 0 : v.size(); }
  double at(std::size_t i) const { return scalar ? v[0] : (i < v.size() ? v[i] : na()); }
};

template <class F> inline S zip(const S& a, const S& b, F f) {
  std::size_t n = std::max(a.len(), b.len());
  if (n == 0) n = 1;
  V o(n);
  for (std::size_t i = 0; i < n; ++i) o[i] = f(a.at(i), b.at(i));
  return S(o);
}
template <class F> inline S map1(const S& a, F f) {
  std::size_t n = a.len();
  if (n == 0) n = 1;
  V o(n);
  for (std::size_t i = 0; i < n; ++i) o[i] = f(a.at(i));
  return S(o);
}
template <class F> inline S roll(const S& x, int n, F f) {
  std::size_t m = x.len();
  V o(m, na());
  if (n <= 0 || m == 0 || (std::size_t)n > m) return S(o);
  for (std::size_t i = (std::size_t)n - 1; i < m; ++i) {
    V w(x.v.begin() + (i - (std::size_t)n + 1), x.v.begin() + i + 1);
    o[i] = f(w);
  }
  return S(o);
}

inline double mean_(const V& w) { double s = 0.0; for (std::size_t i = 0; i < w.size(); ++i) s += w[i]; return s / (double)w.size(); }
inline S sma(const S& x, int n) { return roll(x, n, [](const V& w) { return mean_(w); }); }
inline S rstd(const S& x, int n) {
  return roll(x, n, [](const V& w) {
    double m = mean_(w), s = 0.0;
    for (std::size_t i = 0; i < w.size(); ++i) { double e = w[i] - m; s += e * e; }
    return std::sqrt(s / (double)w.size());
  });
}
// NaN-propagating, like numpy's max/min over a window: one missing value makes the window unknown.
inline S rmax(const S& x, int n) {
  return roll(x, n, [](const V& w) { double m = w[0];
    for (std::size_t i = 0; i < w.size(); ++i) { if (std::isnan(w[i])) return na(); if (w[i] > m) m = w[i]; } return m; });
}
inline S rmin(const S& x, int n) {
  return roll(x, n, [](const V& w) { double m = w[0];
    for (std::size_t i = 0; i < w.size(); ++i) { if (std::isnan(w[i])) return na(); if (w[i] < m) m = w[i]; } return m; });
}
inline S ema(const S& x, int n) {
  std::size_t m = x.len();
  V o(m, na());
  if (m == 0) return S(o);
  double k = 2.0 / ((double)n + 1.0), acc = x.at(0);
  o[0] = acc;
  for (std::size_t i = 1; i < m; ++i) { acc = x.at(i) * k + acc * (1.0 - k); o[i] = acc; }
  return S(o);
}
inline S delta(const S& x, int n) {
  std::size_t m = x.len();
  V o(m, na());
  if (n <= 0) return S(o);
  for (std::size_t i = (std::size_t)n; i < m; ++i) o[i] = x.at(i) - x.at(i - (std::size_t)n);
  return S(o);
}
inline S shift(const S& x, int k) {
  if (k <= 0) return x;
  std::size_t m = x.len();
  V o(m, na());
  for (std::size_t i = (std::size_t)k; i < m; ++i) o[i] = x.at(i - (std::size_t)k);
  return S(o);
}
inline S zscore(const S& x, int n) {
  S m = sma(x, n), s = rstd(x, n);
  std::size_t L = x.len();
  if (L == 0) L = 1;
  V o(L, 0.0);
  for (std::size_t i = 0; i < L; ++i) { double sd = s.at(i); o[i] = (sd > 0.0) ? (x.at(i) - m.at(i)) / sd : 0.0; }
  return S(o);
}
// Wilder's RSI exactly as the engine computes it: 50 on the first n bars, seeded on the plain
// average of the changes of bars 1..n, then (avg*(n-1)+x)/n; an average loss under 1e-12 reads 100.
inline S rsi(const S& c, int n) {
  std::size_t m = c.len();
  V o(m, 50.0);
  if (n <= 0 || m < (std::size_t)n + 1) return S(o);
  V up(m, 0.0), dn(m, 0.0);
  for (std::size_t i = 1; i < m; ++i) {
    double d = c.at(i) - c.at(i - 1);
    up[i] = d > 0.0 ? d : 0.0;
    dn[i] = d < 0.0 ? -d : 0.0;
  }
  double ag = 0.0, al = 0.0;
  for (std::size_t k = 1; k <= (std::size_t)n; ++k) { ag += up[k]; al += dn[k]; }
  ag /= n; al /= n;
  for (std::size_t i = (std::size_t)n; i < m; ++i) {
    if (i > (std::size_t)n) { ag = (ag * (n - 1) + up[i]) / n; al = (al * (n - 1) + dn[i]) / n; }
    o[i] = (al < 1e-12) ? 100.0 : 100.0 - 100.0 / (1.0 + ag / al);
  }
  return S(o);
}
inline S atr(const S& h, const S& l, const S& c, int n) {
  std::size_t m = h.len();
  V tr(m);
  for (std::size_t i = 0; i < m; ++i) {
    double pc = (i == 0) ? c.at(0) : c.at(i - 1);
    double a = h.at(i) - l.at(i), b = std::fabs(h.at(i) - pc), d = std::fabs(l.at(i) - pc);
    // NaN-propagating on purpose: `a > b ? a : b` keeps the NUMBER when the other side is NaN,
    // while numpy's maximum returns NaN. One missing high or low would silently produce a true
    // range that looks perfectly ordinary.
    double t = (std::isnan(a) || std::isnan(b) || std::isnan(d)) ? na()
                                                                 : std::max(a, std::max(b, d));
    tr[i] = (i == 0) ? a : t;   // TR[0] = high - low, as in the engine
  }
  return sma(S(tr), n);
}
inline S add(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return x + y; }); }
inline S sub(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return x - y; }); }
inline S mul(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return x * y; }); }
inline S divi(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return y != 0.0 ? x / y : na(); }); }
inline S gt(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return x > y ? 1.0 : 0.0; }); }
inline S lt(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return x < y ? 1.0 : 0.0; }); }
inline S ge(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return x >= y ? 1.0 : 0.0; }); }
inline S le(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return x <= y ? 1.0 : 0.0; }); }
// et/non test "> 0" FIRST, exactly like the arbiter: on a NaN both must read false, so that a
// window that is not filled yet triggers nothing instead of triggering everything.
inline S and_(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return (x > 0.0 && y > 0.0) ? 1.0 : 0.0; }); }
inline S not_(const S& a) { return map1(a, [](double x) { return (x > 0.0) ? 0.0 : 1.0; }); }
inline S absv(const S& a) { return map1(a, [](double x) { return std::fabs(x); }); }
inline S max2(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return (std::isnan(x) || std::isnan(y)) ? na() : (x > y ? x : y); }); }
inline S min2(const S& a, const S& b) { return zip(a, b, [](double x, double y) { return (std::isnan(x) || std::isnan(y)) ? na() : (x < y ? x : y); }); }
inline S crossup(const S& a, const S& b) {
  std::size_t n = std::max(a.len(), b.len());
  if (n == 0) n = 1;
  V o(n, 0.0);
  for (std::size_t i = 0; i < n; ++i) {
    double prev = (i == 0) ? na() : (a.at(i - 1) - b.at(i - 1));
    o[i] = (a.at(i) > b.at(i) && prev <= 0.0) ? 1.0 : 0.0;
  }
  return S(o);
}

// +1 = long entry, -1 = short entry, 0 = nothing. One value per bar.
inline V signal(const V& open, const V& high, const V& low, const V& close,
                const V& volume) {
  S o(open), h(high), l(low), c(close), vol(volume);
  (void)o.scalar; (void)h.scalar; (void)l.scalar; (void)c.scalar; (void)vol.scalar;
  S long_entry = crossup(ema(c, 13), ema(c, 21));
  S short_entry = lt(0.0, 0.0);
  std::size_t n = close.size();
  V out(n, 0.0);
  for (std::size_t i = 0; i < n; ++i)
    out[i] = long_entry.at(i) > 0.0 ? 1.0 : (short_entry.at(i) > 0.0 ? -1.0 : 0.0);
  return out;
}

// --- THE POSITION MACHINE - what the engine did with each entry, replayed bar by bar -------------
struct Trade { std::size_t entry_idx, exit_idx; int side; double pnl_R, entry_price, stop, target; };

const int    ATR_PERIOD = 14;
const double SL_MULT = 0.75;        // stop distance, in ATR(ATR_PERIOD) of the signal bar
const double TP_MULT = 2.25;        // target distance, in ATR
const bool   TP_FROM_STOP = true;  // true: target = stop distance * RR (bit-exact with the kernel)
const double RR = 3.0;
const double BACKTEST_DRAG = 0.0; // audit only; not charged again live
const int    HOLD_BARS = 0;    // 0 = no timeout
const std::size_t FIRST_BAR = 1;
const bool   REENTER_SAME_BAR = true;
const bool   NEEDS_PREVIOUS_BAR = false;
const char*  DIRECTION = "L";
const bool   CLOSE_LAST_BAR = false;   // Session node: book the still-open position at the end

inline S long_raw(const S& o, const S& h, const S& l, const S& c, const S& vol) { (void)o; (void)h; (void)l; (void)c; (void)vol; return crossup(ema(c, 13), ema(c, 21)); }
inline S short_raw(const S& o, const S& h, const S& l, const S& c, const S& vol) { (void)o; (void)h; (void)l; (void)c; (void)vol; return crossup(ema(c, 21), ema(c, 13)); }
// bars this kernel skips entirely (no exit check, no entry) besides an undefined ATR; empty = none
inline S skip_extra(const S& o, const S& h, const S& l, const S& c, const S& vol) { (void)o; (void)h; (void)l; (void)c; (void)vol; return S(V()); }

inline double clampR(double x) { return std::max(-1.0, std::min(RR, x)); }

inline std::vector<Trade> trades(const V& open, const V& high, const V& low, const V& close, const V& volume) {
  S o(open), h(high), l(low), c(close), vol(volume);
  std::size_t n = close.size();
  S lg = long_raw(o, h, l, c, vol), sh = short_raw(o, h, l, c, vol), a = atr(h, l, c, ATR_PERIOD);
  S extra = skip_extra(o, h, l, c, vol);
  std::vector<bool> fc(n, false);
  if (CLOSE_LAST_BAR && n >= 1) { fc[n - 1] = true; if (n >= 2) fc[n - 2] = true; }
  std::vector<Trade> out;
  bool act = false, seen = false; int side = 0; double ep = 0.0, sl_d = 0.0, tp_d = 0.0;
  std::size_t ei = 0, sig_bar = 0;
  auto book = [&](std::size_t exit_i, double pnl) {
    out.push_back(Trade{ei, exit_i, side, pnl, ep, ep - side * sl_d, ep + side * tp_d});
  };
  if (n < 2) return out;
  for (std::size_t i = FIRST_BAR; i + 1 < n; ++i) {
    double av = a.at(i);
    if (!(av > 0.0) || !std::isfinite(av)) continue;    // skipped bars do not prime NEEDS_PREVIOUS_BAR
    if (act && fc[i]) { book(i, sl_d > 0.0 ? clampR(side * (c.at(i) - ep) / sl_d) : 0.0); act = false; }
    if (extra.len() > 0 && extra.at(i) > 0.0) continue;
    if (act) {
      bool closed = false; double pnl = 0.0;
      if (side == 1) { if (l.at(i) <= ep - sl_d) { closed = true; pnl = -1.0; } else if (h.at(i) >= ep + tp_d) { closed = true; pnl = RR; } }
      else           { if (h.at(i) >= ep + sl_d) { closed = true; pnl = -1.0; } else if (l.at(i) <= ep - tp_d) { closed = true; pnl = RR; } }
      if (closed) { book(i, pnl); act = false; }
      else if (HOLD_BARS > 0 && (long long)(i - sig_bar) >= HOLD_BARS) { book(i, sl_d > 0.0 ? clampR(side * (c.at(i) - ep) / sl_d) : 0.0); act = false; }
      if (act || !REENTER_SAME_BAR) { seen = true; continue; }
    }
    if (seen || !NEEDS_PREVIOUS_BAR) {
      int s = lg.at(i) > 0.0 ? 1 : (sh.at(i) > 0.0 ? -1 : 0);
      if (s != 0 && i + 1 < n) {
        ei = i + 1; ep = o.at(ei); sl_d = av * SL_MULT; tp_d = TP_FROM_STOP ? sl_d * RR : av * TP_MULT;
        side = s; sig_bar = i; act = true;
      }
    }
    seen = true;
  }
  std::vector<Trade> kept;
  for (std::size_t k = 0; k < out.size(); ++k) {
    if ((DIRECTION[0] == 'L' && DIRECTION[1] == '\0' && out[k].side < 0) ||
        (DIRECTION[0] == 'S' && out[k].side > 0)) continue;
    kept.push_back(out[k]);
  }
  return kept;
}

}  // namespace stochastly

#ifdef STOCHASTLY_MAIN
// A runner, so this file can be CHECKED and not merely compiled. Reads bars as CSV on stdin -
// open,high,low,close,volume, one bar per line, oldest first - and prints one line per trade:
// entry_idx,exit_idx,side,pnl_R. Build:  g++ -std=c++17 -O2 -DSTOCHASTLY_MAIN strategy.cpp -o strategy
#include <cstdio>
#include <iostream>
#include <sstream>
#include <string>
int main() {
  stochastly::V o, h, l, c, v;
  std::string line;
  while (std::getline(std::cin, line)) {
    if (line.empty()) continue;
    for (std::size_t i = 0; i < line.size(); ++i) if (line[i] == ',') line[i] = ' ';
    std::istringstream is(line);
    double a, b, d, e, f;
    if (!(is >> a >> b >> d >> e >> f)) continue;
    o.push_back(a); h.push_back(b); l.push_back(d); c.push_back(e); v.push_back(f);
  }
  std::vector<stochastly::Trade> t = stochastly::trades(o, h, l, c, v);
  for (std::size_t i = 0; i < t.size(); ++i)
    std::printf("%llu,%llu,%d,%.15g\n", (unsigned long long)t[i].entry_idx, (unsigned long long)t[i].exit_idx, t[i].side, t[i].pnl_R);
  return 0;
}
#endif
