Overall Statistics
Total Orders
2775
Average Win
0.55%
Average Loss
-0.31%
Compounding Annual Return
4.491%
Drawdown
29.700%
Expectancy
0.073
Start Equity
10000
End Equity
12661.22
Net Profit
26.612%
Sharpe Ratio
0.02
Sortino Ratio
0.024
Probabilistic Sharpe Ratio
0.300%
Loss Rate
62%
Win Rate
38%
Profit-Loss Ratio
1.79
Alpha
-0.014
Beta
0.226
Annual Standard Deviation
0.139
Annual Variance
0.019
Information Ratio
-0.415
Tracking Error
0.173
Treynor Ratio
0.013
Total Fees
$2825.94
Estimated Strategy Capacity
$2200000.00
Lowest Capacity Asset
GDXU XJSPWMCOQ4BP
Portfolio Turnover
17.30%
Drawdown Recovery
433
# =============================================================================
#  WARNING: RISK ONLY CASINO MONEY WITH THIS STRATEGY
#  PLEASE WAIT FOR MINIMUM 3 MONTHS OF OOS PERFORMANCE
#  Multi-Sleeve Momentum Rotation (Gold / Growth-Tech / Financials)
# =============================================================================

#  Gold-miner gate (GDXU/GDXD, RSI(10)) wraps everything. Inside that, a
#  QQQ 90d/70d horizons define a recent-vs-prior momentum-acceleration split.
#  paths:
#    Path A (recent QQQ momentum > prior momentum): a SPY-vs-200SMA gate into
#      either (a) RSI(10) overbought cascade across
#      SPY/IOO/TQQQ/VTV/XLF -> UVXY/BIL/BTAL, with a bull-trend branch that
#      picks the best 20d risk-adjusted-momentum candidate from 11 leveraged
#      ETFs (AAPX/NVDL/BITX/TSLR/FBL/GGLL/AMZZ/AMZU/RGTI/PLTR/BABA/CONL) if its
#      own underlying stock has positive 10d return-momentum, or (b) a
#      TQQQ/SPY RSI<30/31 dip-buy, or a TQQQ-SMA20 momentum-vs-bear
#      hedge branch (TECS/SOXS/SQQQ) with its own top-1-momentum leveraged-
#      stock-ETF picker.
#    Path B (recent QQQ momentum <= prior momentum): TLT-vs-QQQ gate into
#      GDXD, or a FAS (3x financials)-trend-vs-SMA50/200 gate choosing
#      between (a) a top-3-by-20d-momentum basket across 12 financial/
#      fintech names (V/SOFI/MA/BX/SCHW/KKR/BN/WELL/VTR/BAM/HOOD/IBKR) plus
#      the same leveraged-stock-ETF basket as a 13th candidate, (b) a top-3-
#      by-lowest-RSI(10) contrarian dip-buy among those 12 names, or (c) a
#      FAS-vs-SMA100/RSI(10) gate into TMF/FAZ/leveraged-stock-basket
#      (top-1 by 15d momentum) or an AGQ-vs-FAS RSI(10) switcher.
# 
#  -------------------------------------------------------------------------
#  Decision computed once daily right after the session close (indicators
#  finalize then); orders are MarketOnOpenOrder, filled at the NEXT
#  session's open. No look-ahead.
#
# =============================================================================

from AlgorithmImports import *
from tree_data import TREE_JSON
import json
import numpy as np
from datetime import timedelta


class MultiSleeveMomentumRotation(QCAlgorithm):

    PRICE_WINDOW_SIZE = 210  # covers the largest lookback used (200) + buffer
    LEVERAGE = 1.0
    CASH_BUFFER_PCT = 0.04

    # Untuned risk overlay: keep the decision-tree path, but prevent any one
    # leveraged terminal leaf from becoming the whole portfolio.
    RISK_TARGET_ANNUAL_VOL = 0.20
    RISK_LOOKBACK = 20
    MAX_SINGLE_ASSET_WEIGHT = 0.50

    # The published 15% stop was selected in-sample from four alternatives.
    # Leave the mechanism available, but disable it for this repaired baseline.
    ENABLE_INTRADAY_SL = False
    SL_PCT = 0.15
    INTRADAY_CHECK_MINUTES = 5

    def initialize(self) -> None: 
        self.set_start_date(2021, 1, 1)
        self.set_cash(10_000)
        self.set_brokerage_model(
            BrokerageName.INTERACTIVE_BROKERS_BROKERAGE,
            AccountType.MARGIN,
        )
        self.set_benchmark("SPY")
        self.settings.minimum_order_margin_portfolio_percentage = 0.0

        self.tree = json.loads(TREE_JSON)
        self._normalize_tree(self.tree)

        # -- Discover every ticker referenced (as a tradable asset or as an
        #    indicator input) and every (ticker, RSI window) pair needed.
        all_tickers = set()
        self._collect_tickers(self.tree, all_tickers)

        rsi_pairs = set()
        self._collect_rsi_pairs(self.tree, rsi_pairs)

        # -- Subscribe & build per-ticker daily price history + RSI indicators
        self._syms = {}
        self._price_window = {}
        for t in sorted(all_tickers):
            sym = self.add_equity(t, Resolution.MINUTE).symbol
            self._syms[t] = sym
            self._price_window[t] = RollingWindow[float](self.PRICE_WINDOW_SIZE)

        self._rsi = {}
        for ticker, window in rsi_pairs:
            if ticker not in self._syms:
                continue
            self._rsi[(ticker, window)] = self.rsi(
                self._syms[ticker], window, MovingAverageType.WILDERS, Resolution.DAILY
            )

        self.set_warm_up(self.PRICE_WINDOW_SIZE + 5, Resolution.DAILY)

        # -- Seed price windows from history so day-0 evaluation has data
        for t, sym in self._syms.items():
            hist = self.history(sym, self.PRICE_WINDOW_SIZE + 5, Resolution.DAILY)
            if not hist.empty:
                closes = hist["close"].values
                for c in closes:  # history is oldest->newest; last add becomes window[0]
                    if c > 0:
                        self._price_window[t].add(float(c))

        self._trade_count = 0
        self._day_open = {}  # Symbol -> today's open, for the intraday SL

        # -- Feed daily closes into the rolling windows right after each
        #    session close, then compute the rebalance target and place
        #    MarketOnOpenOrders for the next session's open.
        self.schedule.on(
            self.date_rules.every_day(),
            self.time_rules.after_market_close("SPY", 0),
            self._update_price_windows,
        )
        self.schedule.on(
            self.date_rules.every_day(),
            self.time_rules.after_market_close("SPY", 1),
            self._rebalance,
        )

        # -- Snapshot today's open for every held name, then poll every few
        #    minutes during market hours for the intraday stop loss.
        self.schedule.on(
            self.date_rules.every_day(),
            self.time_rules.after_market_open("SPY", 1),
            self._snapshot_day_open,
        )
        self.schedule.on(
            self.date_rules.every_day(),
            self.time_rules.every(timedelta(minutes=self.INTRADAY_CHECK_MINUTES)),
            self._check_intraday_sl,
        )

    def _normalize_tree(self, node) -> None:
        """Repair internally inconsistent tree expressions without changing
        its overall branching structure."""

        cond = node.get("cond")
        if isinstance(cond, list) and len(cond) >= 3 and cond[0] in ("fn_gt", "fn_lt"):
            left, right = cond[1], cond[2]
            if (
                isinstance(left, list) and isinstance(right, list)
                and left and right
                and left[0] == "fn_relative_strength_index"
                and right[0] == "fn_relative_strength_index"
            ):
                common_window = max(int(left[2]), int(right[2]))
                left[2] = common_window
                right[2] = common_window

        for key in ("c", "th", "el"):
            for child in node.get(key, []):
                self._normalize_tree(child)

    # ── Tree-requirement discovery ──────────────────────────────────

    def _collect_tickers(self, node, out: set) -> None:
        if node.get("t") == "node_asset":
            out.add(node["tk"])
        for expr_key in ("cond", "sf"):
            if expr_key in node:
                self._scan_expr_tickers(node[expr_key], out)
        for key in ("c", "th", "el"):
            for ch in node.get(key, []):
                self._collect_tickers(ch, out)

    def _scan_expr_tickers(self, expr, out: set) -> None:
        if isinstance(expr, list):
            for item in expr:
                self._scan_expr_tickers(item, out)
        elif isinstance(expr, str) and expr.startswith("EQUITIES::"):
            out.add(expr.split("::")[1].split("//")[0])

    def _collect_rsi_pairs(self, node, out: set) -> None:
        for expr_key in ("cond",):
            if expr_key in node:
                self._scan_expr_rsi(node[expr_key], None, out)
        if node.get("t") == "node_filter":
            rsi_window = self._rsi_window_in_expr(node["sf"])
            if rsi_window is not None:
                for ch in node["c"]:
                    candidates = set()
                    self._collect_tickers(ch, candidates)
                    for ticker in candidates:
                        out.add((ticker, rsi_window))
        for key in ("c", "th", "el"):
            for ch in node.get(key, []):
                self._collect_rsi_pairs(ch, out)

    def _scan_expr_rsi(self, expr, ref_ticker, out: set) -> None:
        if not isinstance(expr, list):
            return
        if expr[0] == "fn_relative_strength_index":
            tk = self._ticker_of(expr[1], ref_ticker)
            if tk:
                out.add((tk, expr[2]))
        for item in expr:
            self._scan_expr_rsi(item, ref_ticker, out)

    def _rsi_window_in_expr(self, expr):
        if not isinstance(expr, list):
            return None
        if expr[0] == "fn_relative_strength_index":
            return expr[2]
        for item in expr:
            r = self._rsi_window_in_expr(item)
            if r is not None:
                return r
        return None

    def _ticker_of(self, metric_close_expr, ref_ticker):
        arg = metric_close_expr[1]
        if isinstance(arg, list) and arg[0] == "reference":
            return ref_ticker
        return arg.split("::")[1].split("//")[0]

    def _representative_ticker(self, node):
        t = node.get("t")
        if t == "node_asset":
            return node["tk"]
        if t == "node_if" and "cond" in node:
            tk = self._first_ticker_in_expr(node["cond"])
            if tk:
                return tk
        for key in ("c", "th", "el"):
            for ch in node.get(key, []):
                tk = self._representative_ticker(ch)
                if tk:
                    return tk
        return None

    def _first_ticker_in_expr(self, expr):
        if isinstance(expr, list):
            for item in expr:
                if isinstance(item, str) and item.startswith("EQUITIES::"):
                    return item.split("::")[1].split("//")[0]
                tk = self._first_ticker_in_expr(item)
                if tk:
                    return tk
        return None

    # ── Metric / condition evaluation ───────────────────────────────

    def _eval_metric(self, expr, ref_ticker=None) -> float:
        head = expr[0]
        if head == "fn_constant":
            return expr[1]
        if head == "weight_every_fn":
            return self._eval_metric(expr[1], ref_ticker)
        if head == "metric_close":
            tk = self._ticker_of(expr, ref_ticker)
            w = self._price_window.get(tk)
            return w[0] if w and w.count > 0 else 0.0
        if head == "fn_relative_strength_index": 
            tk = self._ticker_of(expr[1], ref_ticker)
            window = expr[2]
            ind = self._rsi.get((tk, window))
            return (ind.current.value / 100.0) if ind and ind.is_ready else 0.5
        if head == "fn_simple_moving_average":
            inner, window = expr[1], expr[2]
            if inner[0] == "metric_close":
                tk = self._ticker_of(inner, ref_ticker)
                w = self._price_window.get(tk)
                if not w or w.count < window:
                    return 0.0
                return float(np.mean([w[i] for i in range(window)]))
            elif inner[0] == "fn_rate_of_return":
                tk = self._ticker_of(inner[1], ref_ticker)
                w = self._price_window.get(tk)
                if not w or w.count < window + 1:
                    return 0.0
                rets = [w[i] / w[i + 1] - 1.0 for i in range(window)]
                return float(np.mean(rets))
        if head == "fn_standard_deviation":
            inner, window = expr[1], expr[2]
            tk = self._ticker_of(inner[1], ref_ticker)
            w = self._price_window.get(tk)
            if not w or w.count < window + 1:
                return 0.0
            rets = [w[i] / w[i + 1] - 1.0 for i in range(window)]
            return float(np.std(rets))
        if head == "fn_cumulative_return":
            inner, window = expr[1], expr[2]
            tk = self._ticker_of(inner, ref_ticker)
            w = self._price_window.get(tk)
            if not w or w.count < window + 1:
                return 0.0
            return w[0] / w[window] - 1.0
        if head == "fn_rate_of_return":
            tk = self._ticker_of(expr[1], ref_ticker)
            w = self._price_window.get(tk)
            if not w or w.count < 2:
                return 0.0
            return w[0] / w[1] - 1.0
        raise ValueError(f"unhandled metric expr head: {head}")

    def _average_log_return(self, ticker, window):
        prices = self._price_window.get(ticker)
        if not prices or prices.count < window + 1:
            return None
        if prices[0] <= 0 or prices[window] <= 0:
            return None
        return float(np.log(prices[0] / prices[window]) / window)

    def _eval_cond(self, expr, ref_ticker=None) -> bool:
        head = expr[0]
        if head in ("fn_gt", "fn_lt"):
            left, right = expr[1], expr[2]
            left_value = right_value = None

            # Raw cumulative returns from unequal windows are not comparable.
            # For the same asset, reinterpret the two horizons as recent versus
            # prior average log momentum. Across assets, compare per-day log
            # returns so a 95-day series is commensurate with a 35-day series.
            if (
                isinstance(left, list) and isinstance(right, list)
                and left and right
                and left[0] == "fn_cumulative_return"
                and right[0] == "fn_cumulative_return"
            ):
                left_ticker = self._ticker_of(left[1], ref_ticker)
                right_ticker = self._ticker_of(right[1], ref_ticker)
                left_window, right_window = int(left[2]), int(right[2])

                if left_ticker == right_ticker and left_window != right_window:
                    full_window = max(left_window, right_window)
                    recent_window = abs(left_window - right_window)
                    prior_window = full_window - recent_window
                    prices = self._price_window.get(left_ticker)
                    if (
                        not prices or prices.count < full_window + 1
                        or recent_window <= 0 or prior_window <= 0
                        or prices[0] <= 0 or prices[recent_window] <= 0
                        or prices[full_window] <= 0
                    ):
                        return False
                    left_value = float(np.log(prices[0] / prices[recent_window]) / recent_window)
                    right_value = float(np.log(prices[recent_window] / prices[full_window]) / prior_window)
                elif left_window != right_window:
                    left_value = self._average_log_return(left_ticker, left_window)
                    right_value = self._average_log_return(right_ticker, right_window)

            if left_value is None or right_value is None:
                left_value = self._eval_metric(left, ref_ticker)
                right_value = self._eval_metric(right, ref_ticker)

            return left_value > right_value if head == "fn_gt" else left_value < right_value
        if head == "fn_or":
            return self._eval_cond(expr[1], ref_ticker) or self._eval_cond(expr[2], ref_ticker)
        raise ValueError(f"unhandled condition head: {head}")

    # ── Tree resolution -> target weights ───────────────────────────

    def _resolve(self, node) -> dict:
        t = node["t"]
        if t == "node_root":
            return self._resolve_children(node["c"])
        if t == "node_weight":
            return self._resolve_weighted(node["c"], node["w"])
        if t == "node_if":
            branch = node["th"] if self._eval_cond(node["cond"]) else node.get("el", [])
            return self._resolve_children(branch)
        if t == "node_asset":
            sym = self._syms.get(node["tk"])
            return {sym: 1.0} if sym is not None else {}
        if t == "node_filter":
            return self._resolve_filter(node)
        raise ValueError(f"unknown node type: {t}")

    def _resolve_children(self, children) -> dict:
        if not children:
            return {}
        combined = {}
        share = 1.0 / len(children)
        for ch in children:
            for sym, w in self._resolve(ch).items():
                combined[sym] = combined.get(sym, 0.0) + w * share
        return combined

    def _resolve_weighted(self, children, weight_spec) -> dict:
        kind = weight_spec[0]
        if kind == "weight_equal":
            return self._resolve_children(children)
        if kind == "weight_constants":
            weights = weight_spec[1]
            combined = {}
            for ch, wt in zip(children, weights):
                for sym, w in self._resolve(ch).items():
                    combined[sym] = combined.get(sym, 0.0) + w * wt
            return combined
        raise ValueError(f"unknown weight kind: {kind}")

    def _metric_required_observations(self, expr) -> int:
        if expr[0] == "weight_every_fn":
            return self._metric_required_observations(expr[1])
        head = expr[0]
        if head == "metric_close":
            return 1
        if head == "fn_rate_of_return":
            return 2
        if head in ("fn_standard_deviation", "fn_cumulative_return"):
            return int(expr[2]) + 1
        if head == "fn_simple_moving_average":
            extra = 1 if expr[1][0] == "fn_rate_of_return" else 0
            return int(expr[2]) + extra
        return 0

    def _score_filter_metric(self, sort_fn, ticker):
        metric = sort_fn[1] if sort_fn[0] == "weight_every_fn" else sort_fn
        if metric[0] == "fn_relative_strength_index":
            indicator = self._rsi.get((ticker, int(metric[2])))
            return float(indicator.current.value / 100.0) if indicator and indicator.is_ready else None

        prices = self._price_window.get(ticker)
        required = self._metric_required_observations(metric)
        if not prices or prices.count < required:
            return None

        # Replace "pick the most volatile" with an untuned 20-day
        # risk-adjusted-momentum score while retaining the top-1 filter shape.
        if metric[0] == "fn_standard_deviation":
            window = int(metric[2])
            returns = np.array([prices[i] / prices[i + 1] - 1.0 for i in range(window)])
            sigma = float(np.std(returns))
            return float(np.mean(returns) / sigma) if sigma > 1e-8 else None

        return self._eval_metric(sort_fn, ticker)

    def _resolve_filter(self, node) -> dict:
        sort_fn = node["sf"]
        scored = []
        for child in node["c"]:
            resolved = self._resolve(child)
            weighted_score = 0.0
            scored_weight = 0.0
            for symbol, weight in resolved.items():
                score = self._score_filter_metric(sort_fn, symbol.value)
                if score is not None:
                    weighted_score += weight * score
                    scored_weight += weight
            if scored_weight > 0:
                scored.append((weighted_score / scored_weight, resolved))

        scored.sort(key=lambda item: item[0], reverse=(node["dir"] == "desc"))
        selected = scored[:node["n"]]
        if not selected:
            return {}

        combined = {}
        share = 1.0 / len(selected)
        for _, resolved in selected:
            for symbol, weight in resolved.items():
                combined[symbol] = combined.get(symbol, 0.0) + weight * share
        return combined

    def _annualized_volatility(self, symbol):
        prices = self._price_window.get(symbol.value)
        if not prices or prices.count < self.RISK_LOOKBACK + 1:
            return None
        returns = np.array([
            prices[i] / prices[i + 1] - 1.0
            for i in range(self.RISK_LOOKBACK)
        ])
        sigma = float(np.std(returns) * np.sqrt(252.0))
        return sigma if sigma > 1e-8 else None

    def _apply_risk_overlay(self, raw_weights) -> dict:
        """Preserve the selected branch, scale its risky leaves to a 20%
        annual-volatility target, cap any single asset at 50%, and place the
        unallocated reserve in BIL."""
        bil = self._syms.get("BIL")
        adjusted = {}
        for symbol, raw_weight in raw_weights.items():
            if symbol == bil:
                adjusted[symbol] = adjusted.get(symbol, 0.0) + raw_weight
                continue
            volatility = self._annualized_volatility(symbol)
            if volatility is None:
                continue
            scale = min(1.0, self.RISK_TARGET_ANNUAL_VOL / volatility)
            target = min(raw_weight * scale, self.MAX_SINGLE_ASSET_WEIGHT)
            if target > 0:
                adjusted[symbol] = adjusted.get(symbol, 0.0) + target

        reserve = max(0.0, 1.0 - sum(adjusted.values()))
        if bil is not None and reserve > 0:
            adjusted[bil] = adjusted.get(bil, 0.0) + reserve
        return adjusted

    # ── Scheduled events ─────────────────────────────────────────────

    def _update_price_windows(self) -> None:
        for t, sym in self._syms.items():
            close = self.securities[sym].close
            if close > 0:
                self._price_window[t].add(float(close))

    def _snapshot_day_open(self) -> None:
        """Record the completed first-minute bar's official session open."""
        if not self.ENABLE_INTRADAY_SL:
            return
        for sym in self._syms.values():
            price = self.securities[sym].open
            if price > 0:
                self._day_open[sym] = float(price)

    def on_order_event(self, order_event: OrderEvent) -> None:
        if order_event.fill_quantity == 0 or order_event.fill_price <= 0:
            return
        symbol = order_event.symbol
        if self.portfolio[symbol].invested:
            self._day_open[symbol] = float(order_event.fill_price)
        else:
            self._day_open.pop(symbol, None)

    def _check_intraday_sl(self) -> None:
        if not self.ENABLE_INTRADAY_SL or self.is_warming_up:
            return
        if not self.is_market_open(self._syms.get("SPY")):
            return
        for h in list(self.portfolio.values()):
            if not h.invested:
                continue
            sym = h.symbol
            open_px = self._day_open.get(sym)
            price = self.securities[sym].price
            if not open_px or open_px <= 0 or price <= 0:
                continue
            chg = price / open_px - 1.0
            if chg <= -self.SL_PCT:
                self.liquidate(sym, tag=f"intraday SL {chg:.1%} vs open (limit {self.SL_PCT:.0%})")
                self.log(f"[SL] {self.time} {sym.value} {chg:.1%} vs open -> liquidated intraday")

    def _rebalance(self) -> None:
        if self.is_warming_up:
            return

        combined = self._apply_risk_overlay(self._resolve(self.tree))
        total_w = sum(combined.values())
        if total_w <= 0:
            return

        targets = set(combined)
        for h in list(self.portfolio.values()):
            if h.invested and h.symbol not in targets:
                self.market_on_open_order(h.symbol, -h.quantity)

        pv = self.portfolio.total_portfolio_value * self.LEVERAGE * (1.0 - self.CASH_BUFFER_PCT)

        # All orders here are MarketOnOpenOrder -- none settle until tomorrow's
        # open, so the exits submitted above can't be relied on to have freed
        # up any margin yet when a buy order below gets validated. Track a
        # local margin budget (starting from what's actually free right now,
        # with a safety haircut) and cap/skip buy orders against it instead of
        # submitting the full computed size and letting the broker reject it.
        margin_budget = self.portfolio.margin_remaining * 0.9
        MARGIN_RATE_ESTIMATE = 0.5  # matches this account's observed Reg-T rate

        for sym, wt in combined.items():
            price = self.securities[sym].price
            if price <= 0:
                continue
            target_qty = int(pv * wt / price)
            delta = target_qty - int(self.portfolio[sym].quantity)
            if delta == 0:
                continue
            if delta > 0:
                est_margin_needed = delta * price * MARGIN_RATE_ESTIMATE
                if est_margin_needed > margin_budget:
                    affordable_qty = int(margin_budget / (price * MARGIN_RATE_ESTIMATE))
                    if affordable_qty <= 0:
                        self.log(f"[MARGIN-SKIP] {self.time.date()} {sym.value} skipped, no margin budget left")
                        continue
                    self.log(f"[MARGIN-CAP] {self.time.date()} {sym.value} capped {delta} -> {affordable_qty} shares (insufficient margin)")
                    delta = affordable_qty
                    est_margin_needed = delta * price * MARGIN_RATE_ESTIMATE
                margin_budget -= est_margin_needed
            self.market_on_open_order(sym, delta)

        self._trade_count += 1
        net = "+".join(f"{round(w*100):.0f}%{s.value}"
                        for s, w in sorted(combined.items(), key=lambda x: -x[1]) if w > 0.005)
        self.log(f"[{self._trade_count:04d}] {self.time.date()} | net={net}")

    def on_end_of_algorithm(self) -> None:
        self.log(f"\n  Final NAV: ${self.portfolio.total_portfolio_value:>15,.2f}  |  Rebalances: {self._trade_count}")
# region imports
from AlgorithmImports import *
# endregion
TREE_JSON = '{"t":"node_root","c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::GDXU//USD"],10],["fn_constant",0.79]],"th":[{"t":"node_asset","tk":"GDXD"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_relative_strength_index",["metric_close","EQUITIES::GDXU//USD"],10],["fn_constant",0.3]],"th":[{"t":"node_asset","tk":"GDXU"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_cumulative_return",["metric_close","EQUITIES::QQQ//USD"],90],["fn_cumulative_return",["metric_close","EQUITIES::QQQ//USD"],70]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_cumulative_return",["metric_close","EQUITIES::GDXU//USD"],70],["fn_cumulative_return",["metric_close","EQUITIES::GDXU//USD"],75]],"th":[{"t":"node_asset","tk":"GDXU"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["metric_close","EQUITIES::SPY//USD"],["fn_simple_moving_average",["metric_close","EQUITIES::SPY//USD"],200]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::TQQQ//USD"],10],["fn_constant",0.79]],"th":[{"t":"node_asset","tk":"UVXY"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::SPXL//USD"],10],["fn_constant",0.8]],"th":[{"t":"node_asset","tk":"UVXY"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::SPY//USD"],10],["fn_constant",0.79]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::SPY//USD"],10],["fn_constant",0.81]],["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::IOO//USD"],10],["fn_constant",0.81]],["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::TQQQ//USD"],10],["fn_constant",0.81]],["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::VTV//USD"],10],["fn_constant",0.81]],["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::XLF//USD"],10],["fn_constant",0.81]]]]]],"th":[{"t":"node_asset","tk":"UVXY"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_constants",[0.75,0.25]],"c":[{"t":"node_asset","tk":"UVXY"},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"BTAL"}]}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::IOO//USD"],10],["fn_constant",0.79]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::IOO//USD"],10],["fn_constant",0.81]],["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::TQQQ//USD"],10],["fn_constant",0.81]],["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::VTV//USD"],10],["fn_constant",0.81]],["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::XLF//USD"],10],["fn_constant",0.81]]]]],"th":[{"t":"node_asset","tk":"UVXY"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_constants",[0.75,0.25]],"c":[{"t":"node_asset","tk":"UVXY"},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"BTAL"}]}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::TQQQ//USD"],10],["fn_constant",0.79]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::TQQQ//USD"],10],["fn_constant",0.81]],["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::VTV//USD"],10],["fn_constant",0.81]],["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::XLF//USD"],10],["fn_constant",0.81]]]],"th":[{"t":"node_asset","tk":"UVXY"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_constants",[0.75,0.25]],"c":[{"t":"node_asset","tk":"UVXY"},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"BTAL"}]}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::VTV//USD"],10],["fn_constant",0.79]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_or",["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::VTV//USD"],10],["fn_constant",0.81]],["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::XLF//USD"],10],["fn_constant",0.81]]],"th":[{"t":"node_asset","tk":"UVXY"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_constants",[0.75,0.25]],"c":[{"t":"node_asset","tk":"UVXY"},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"BTAL"}]}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::XLF//USD"],10],["fn_constant",0.79]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::XLF//USD"],10],["fn_constant",0.81]],"th":[{"t":"node_asset","tk":"UVXY"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_constants",[0.75,0.25]],"c":[{"t":"node_asset","tk":"UVXY"},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"BTAL"}]}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_relative_strength_index",["metric_close","EQUITIES::TQQQ//USD"],10],["fn_constant",0.3]],"th":[{"t":"node_asset","tk":"TQQQ"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_relative_strength_index",["metric_close","EQUITIES::SPY//USD"],10],["fn_constant",0.3]],"th":[{"t":"node_asset","tk":"SPXL"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["metric_close","EQUITIES::SPY//USD"],["fn_simple_moving_average",["metric_close","EQUITIES::SPY//USD"],200]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::XLK//USD"],10],["fn_relative_strength_index",["metric_close","EQUITIES::KMLM//USD"],10]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_filter","sf":["weight_every_fn",["fn_standard_deviation",["fn_rate_of_return",["metric_close",["reference","%"]]],20]],"n":1,"dir":"desc","c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::AAPX//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"AAPX"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::NVDL//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"NVDL"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::BITX//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"BITX"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::TSLA//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"TSLR"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::META//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"FBL"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::GGLL//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"GGLL"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::AMZN//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"AMZU"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::RGTI//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"RGTI"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::PLTR//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"PLTR"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::BABA//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"BABA"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::COIN//USD"]],10],["fn_constant",0.0]],"th":[{"t":"node_asset","tk":"CONL"}],"el":[{"t":"node_asset","tk":"BIL"},{"t":"node_asset","tk":"TQQQ"}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["metric_close","EQUITIES::KMLM//USD"],["fn_simple_moving_average",["metric_close","EQUITIES::KMLM//USD"],20]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_filter","sf":["weight_every_fn",["fn_simple_moving_average",["fn_rate_of_return",["metric_close",["reference","%"]]],15]],"n":1,"dir":"desc","c":[{"t":"node_asset","tk":"AAPX"},{"t":"node_asset","tk":"NVDL"},{"t":"node_asset","tk":"BITX"},{"t":"node_asset","tk":"TSLR"},{"t":"node_asset","tk":"FBL"},{"t":"node_asset","tk":"GGLL"},{"t":"node_asset","tk":"AMZZ"},{"t":"node_asset","tk":"RGTI"},{"t":"node_asset","tk":"PLTR"},{"t":"node_asset","tk":"BABA"},{"t":"node_asset","tk":"CONL"}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_asset","tk":"TECS"},{"t":"node_asset","tk":"SOXS"},{"t":"node_asset","tk":"SQQQ"}]}]}]}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::TLT//USD"],20],["fn_relative_strength_index",["metric_close","EQUITIES::PSQ//USD"],20]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_asset","tk":"QQQ"}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["metric_close","EQUITIES::TQQQ//USD"],["fn_simple_moving_average",["metric_close","EQUITIES::TQQQ//USD"],20]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_relative_strength_index",["metric_close","EQUITIES::PSQ//USD"],10],["fn_constant",0.35]],"th":[{"t":"node_asset","tk":"PSQ"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::AGG//USD"],20],["fn_relative_strength_index",["metric_close","EQUITIES::SH//USD"],60]],"th":[{"t":"node_asset","tk":"TQQQ"}],"el":[{"t":"node_asset","tk":"PSQ"}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::IEF//USD"],10],["fn_relative_strength_index",["metric_close","EQUITIES::PSQ//USD"],20]],"th":[{"t":"node_asset","tk":"PSQ"}],"el":[{"t":"node_asset","tk":"SQQQ"}]}]}]}]}]},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_cumulative_return",["metric_close","EQUITIES::QQQ//USD"],60],["fn_constant",-0.12]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::BND//USD"],10],["fn_relative_strength_index",["metric_close","EQUITIES::QQQ//USD"],10]],"th":[{"t":"node_asset","tk":"QLD"}],"el":[{"t":"node_asset","tk":"BTAL"}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["metric_close","EQUITIES::TQQQ//USD"],["fn_simple_moving_average",["metric_close","EQUITIES::TQQQ//USD"],20]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_relative_strength_index",["metric_close","EQUITIES::PSQ//USD"],10],["fn_constant",0.35]],"th":[{"t":"node_asset","tk":"PSQ"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::AGG//USD"],20],["fn_relative_strength_index",["metric_close","EQUITIES::SH//USD"],60]],"th":[{"t":"node_asset","tk":"TQQQ"}],"el":[{"t":"node_asset","tk":"PSQ"}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::IEF//USD"],10],["fn_relative_strength_index",["metric_close","EQUITIES::PSQ//USD"],20]],"th":[{"t":"node_asset","tk":"PSQ"}],"el":[{"t":"node_asset","tk":"SQQQ"}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_relative_strength_index",["metric_close","EQUITIES::TQQQ//USD"],10],["fn_constant",0.31]],"th":[{"t":"node_asset","tk":"TECL"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_relative_strength_index",["metric_close","EQUITIES::SPY//USD"],10],["fn_constant",0.3]],"th":[{"t":"node_asset","tk":"UPRO"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["metric_close","EQUITIES::TQQQ//USD"],["fn_simple_moving_average",["metric_close","EQUITIES::TQQQ//USD"],20]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_filter","sf":["weight_every_fn",["fn_relative_strength_index",["metric_close",["reference","%"]],10]],"n":1,"dir":"desc","c":[{"t":"node_asset","tk":"SQQQ"},{"t":"node_asset","tk":"TLT"}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_relative_strength_index",["metric_close","EQUITIES::SQQQ//USD"],10],["fn_constant",0.31]],"th":[{"t":"node_asset","tk":"SQQQ"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_asset","tk":"TQQQ"}]}]}]}]}]}]}]}]}]}]}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["fn_cumulative_return",["metric_close","EQUITIES::TLT//USD"],95],["fn_cumulative_return",["metric_close","EQUITIES::QQQ//USD"],35]],"th":[{"t":"node_asset","tk":"GDXD"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["metric_close","EQUITIES::SPY//USD"],["fn_simple_moving_average",["metric_close","EQUITIES::SPY//USD"],200]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::FAS//USD"]],50],["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::FAS//USD"]],200]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_filter","sf":["weight_every_fn",["fn_simple_moving_average",["fn_rate_of_return",["metric_close",["reference","%"]]],20]],"n":3,"dir":"desc","c":[{"t":"node_asset","tk":"V"},{"t":"node_asset","tk":"SOFI"},{"t":"node_asset","tk":"MA"},{"t":"node_asset","tk":"BX"},{"t":"node_asset","tk":"SCHW"},{"t":"node_asset","tk":"KKR"},{"t":"node_asset","tk":"BN"},{"t":"node_asset","tk":"WELL"},{"t":"node_asset","tk":"VTR"},{"t":"node_asset","tk":"BAM"},{"t":"node_asset","tk":"HOOD"},{"t":"node_asset","tk":"IBKR"},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::FAS//USD"]],10],["fn_simple_moving_average",["fn_rate_of_return",["metric_close","EQUITIES::QQQ//USD"]],20]],"th":[{"t":"node_asset","tk":"FAS"}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_filter","sf":["weight_every_fn",["fn_simple_moving_average",["fn_rate_of_return",["metric_close",["reference","%"]]],15]],"n":1,"dir":"desc","c":[{"t":"node_asset","tk":"AAPX"},{"t":"node_asset","tk":"NVDL"},{"t":"node_asset","tk":"BITX"},{"t":"node_asset","tk":"TSLR"},{"t":"node_asset","tk":"FBL"},{"t":"node_asset","tk":"GGLL"},{"t":"node_asset","tk":"AMZZ"},{"t":"node_asset","tk":"RGTI"},{"t":"node_asset","tk":"PLTR"},{"t":"node_asset","tk":"BABA"},{"t":"node_asset","tk":"CONL"}]}]}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_filter","sf":["weight_every_fn",["fn_relative_strength_index",["metric_close",["reference","%"]],10]],"n":3,"dir":"asc","c":[{"t":"node_asset","tk":"V"},{"t":"node_asset","tk":"SOFI"},{"t":"node_asset","tk":"MA"},{"t":"node_asset","tk":"BX"},{"t":"node_asset","tk":"SCHW"},{"t":"node_asset","tk":"KKR"},{"t":"node_asset","tk":"BN"},{"t":"node_asset","tk":"WELL"},{"t":"node_asset","tk":"VTR"},{"t":"node_asset","tk":"BAM"},{"t":"node_asset","tk":"HOOD"},{"t":"node_asset","tk":"IBKR"}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_lt",["metric_close","EQUITIES::FAS//USD"],["fn_simple_moving_average",["metric_close","EQUITIES::FAS//USD"],100]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_if","cond":["fn_gt",["fn_relative_strength_index",["metric_close","EQUITIES::FAS//USD"],10],["fn_constant",0.31]],"th":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_filter","sf":["weight_every_fn",["fn_simple_moving_average",["fn_rate_of_return",["metric_close",["reference","%"]]],15]],"n":1,"dir":"desc","c":[{"t":"node_asset","tk":"TMF"},{"t":"node_asset","tk":"FAZ"},{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_filter","sf":["weight_every_fn",["fn_simple_moving_average",["fn_rate_of_return",["metric_close",["reference","%"]]],15]],"n":1,"dir":"desc","c":[{"t":"node_asset","tk":"AAPX"},{"t":"node_asset","tk":"NVDL"},{"t":"node_asset","tk":"BITX"},{"t":"node_asset","tk":"TSLR"},{"t":"node_asset","tk":"FBL"},{"t":"node_asset","tk":"GGLL"},{"t":"node_asset","tk":"AMZZ"},{"t":"node_asset","tk":"RGTI"},{"t":"node_asset","tk":"PLTR"},{"t":"node_asset","tk":"BABA"},{"t":"node_asset","tk":"CONL"}]}]}]}]}],"el":[{"t":"node_weight","w":["weight_equal"],"c":[{"t":"node_filter","sf":["weight_every_fn",["fn_relative_strength_index",["metric_close",["reference","%"]],10]],"n":1,"dir":"desc","c":[{"t":"node_asset","tk":"AGQ"},{"t":"node_asset","tk":"FAS"}]}]}]}]}],"el":[{"t":"node_asset","tk":"FAS"}]}]}]}]}]}]}]}]}]}]}]}]}]}'