Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions core/api/macro_bp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""
741 Pure Macro Matrix — Internal Regime Engine
===============================================
_compute_regime() is the primary interface — call it directly from server-side code.

The single HTTP route (/api/macro/<symbol>) is secret-gated via X-Macro-Secret header.
It exists ONLY for the Windows Robinhood executor (which can't import Python modules).

Public 741 regime data is a paid product — use GET /api/741macro (x402, 0.04 RLUSD).

Data source: Tradier daily OHLCV — DEVELOPER_MANIFESTO §3 compliant.
Cache: 1 hour per symbol (daily regime is intraday-stable).
"""
from __future__ import annotations

import os
import logging
import time
from datetime import datetime, timezone
from typing import Any, Dict, List

from flask import Blueprint, jsonify, request

logger = logging.getLogger("macro-matrix")

macro_bp = Blueprint("macro", __name__)

# Stack configuration — loaded from env so the methodology stays out of source.
# Set MACRO_STACK_CSV in Render environment variables.
_raw = os.environ.get("MACRO_STACK_CSV", "")
_STACK: List[int] = [int(x) for x in _raw.split(",") if x.strip().isdigit()] if _raw else []
_WARMUP = int(os.environ.get("MACRO_STACK_WARMUP", "50"))
_REQUIRED_BARS = (max(_STACK) + _WARMUP) if _STACK else 0
_CACHE_TTL = 3600
_cache: Dict[str, Dict[str, Any]] = {}


def _compute_regime(symbol: str) -> Dict[str, Any]:
"""
Compute 741 Pure Macro regime for a symbol.
Importable directly by server-side code — no HTTP call needed.
Returns regime + opaque layer values (L1–L5, short to long).
"""
if not _STACK:
logger.warning("[741-MACRO] MACRO_STACK_CSV not configured")
return {"symbol": symbol, "regime": "UNKNOWN", "status": "NOT_CONFIGURED"}

cached = _cache.get(symbol)
if cached and time.time() - cached["ts"] < _CACHE_TTL:
return cached["data"]

try:
from tradier_api import get_history_df
df = get_history_df(symbol, days=_REQUIRED_BARS + 60)
except Exception as e:
logger.warning(f"[741-MACRO] {symbol} data fetch error: {e}")
return {"symbol": symbol, "regime": "UNKNOWN", "status": "DATA_ERROR"}

if df is None or len(df) < _REQUIRED_BARS:
return {
"symbol": symbol,
"regime": "INSUFFICIENT_DATA",
"status": "INSUFFICIENT_DATA",
"bars": int(len(df)) if df is not None else 0,
}

close = df["Close"].astype(float)
ema_values = [float(close.ewm(span=p, adjust=False).mean().iloc[-1]) for p in _STACK]

price = float(close.iloc[-1])
spread = round((ema_values[0] - ema_values[-1]) / ema_values[-1] * 100, 2) if ema_values[-1] else 0.0

if all(ema_values[i] > ema_values[i + 1] for i in range(len(ema_values) - 1)):
regime = "PERFECT_BULLISH_REGIME"
elif all(ema_values[i] < ema_values[i + 1] for i in range(len(ema_values) - 1)):
regime = "PERFECT_BEARISH_REGIME"
else:
regime = "CONSOLIDATION_CHOP"

result = {
"symbol": symbol,
"status": "ok",
"regime": regime,
"price": round(price, 2),
"matrix_spread_pct": spread,
"layers": {f"L{i + 1}": round(v, 2) for i, v in enumerate(ema_values)},
"timestamp": datetime.now(timezone.utc).isoformat(),
}
_cache[symbol] = {"ts": time.time(), "data": result}
logger.info(f"[741-MACRO] {symbol} → {regime} (spread={spread:+.1f}%)")
return result


_SECRET = os.environ.get("MACRO_GATE_SECRET", "")


@macro_bp.route("/macro/<symbol>", methods=["GET"])
def macro_single(symbol: str):
"""Internal executor gate — requires X-Macro-Secret header. Not a public product."""
if not _SECRET or request.headers.get("X-Macro-Secret") != _SECRET:
return jsonify({"error": "unauthorized"}), 403
result = _compute_regime(symbol.upper().strip())
return jsonify({"status": "success", **result})
2 changes: 2 additions & 0 deletions core/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
from core.api.vapl_bp import vapl_bp
from core.vapl.middleware import install_vapl_middleware
from core.api.macro741_bp import macro741_bp
from core.api.macro_bp import macro_bp
import core.signal_history as signal_history
from core.legacy import start_whale_stalker, init_services, get_service, clean_data
from core.market_graph import get_graph
Expand Down Expand Up @@ -153,6 +154,7 @@ def create_app():
app.register_blueprint(iam_bp, url_prefix='/api/iam')
app.register_blueprint(vapl_bp)
app.register_blueprint(macro741_bp, url_prefix='/api')
app.register_blueprint(macro_bp, url_prefix='/api')

# Stellar Forge growth engine — feature-flagged, dormant unless enabled.
# Registers the affiliate/loyalty/payout surface only when explicitly turned
Expand Down
28 changes: 27 additions & 1 deletion iam_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@

logger = logging.getLogger("IAM-EXEC")

def _get_macro_regime(symbol: str) -> str:
"""
Returns the 741 Pure Macro regime for a symbol.
Direct Python import — no HTTP call, no public exposure of the paid product.
Fails open: UNKNOWN / INSUFFICIENT_DATA never block a trade.
Only PERFECT_BEARISH_REGIME blocks BUY orders.
"""
try:
from core.api.macro_bp import _compute_regime
return _compute_regime(symbol).get("regime", "UNKNOWN")
except Exception as e:
logger.warning(f"[IAM-EXEC] macro regime check failed for {symbol}: {e} — failing open")
return "UNKNOWN"

# ── Config ─────────────────────────────────────────────────────────────────────
def _env_bool(key: str, default: bool) -> bool:
return os.environ.get(key, str(default)).strip().lower() in ("true", "1", "yes")
Expand Down Expand Up @@ -198,7 +212,9 @@ def _execute_tradier(sym: str, action: str, resolution: dict, price: float) -> d
if _is_extended_hours():
logger.info(f"[IAM-EXEC] Extended hours: routing {sym} to equity (options unavailable)")
return _execute_tradier_equity(sym, action, price)
if instrument == "options" or (instrument == "auto" and sym in ("IWM", "SPY", "QQQ")):
# auto mode: try options on any symbol — chain availability is the natural gate.
# BUY signal → calls; SELL signal → puts. Falls back gracefully if no chain exists.
if instrument in ("options", "auto"):
return _execute_tradier_options(sym, action, resolution, price)
else:
return _execute_tradier_equity(sym, action, price)
Expand Down Expand Up @@ -379,6 +395,16 @@ def execute_from_resolution(sym: str, resolution: dict,
logger.info(f"[IAM-EXEC] {sym} blocked: {block_reason}")
return

# 741 Pure Macro Matrix gate — only blocks BUY; SELL/exits always proceed
if action == "BUY":
macro = _get_macro_regime(sym)
if macro == "PERFECT_BEARISH_REGIME":
logger.warning(
f"[IAM-EXEC] {sym} BUY blocked — 741 macro regime is PERFECT_BEARISH_REGIME"
)
return
logger.info(f"[IAM-EXEC] {sym} macro regime={macro} — BUY allowed")

mode = EXECUTION_MODE()
logger.info(
f"[IAM-EXEC] 🎯 {sym} {action} | window={time_window} | "
Expand Down
24 changes: 15 additions & 9 deletions iam_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,28 +25,34 @@
_SCAN_TOP_N = int(os.environ.get("IAM_SCAN_TOP_N", "50"))
_SCAN_INTERVAL = int(float(os.environ.get("IAM_SCAN_INTERVAL", "300")))
_INTER_DELAY = float(os.environ.get("IAM_SCAN_INTER_DELAY", "2.0"))
_URGENT_WINDOWS = {"IMMEDIATE", "NEAR_TERM"}
_INITIAL_DELAY = 120 # wait for market scanner to warm up before first pass
_URGENT_WINDOWS = {"IMMEDIATE", "NEAR_TERM"}
_INITIAL_DELAY = 120 # wait for market scanner to warm up before first pass
_MANDATORY_ANCHORS = {"AMC", "GME", "IWM"} # always resolved every pass regardless of universe


def _get_symbols() -> list:
"""
Dynamically pull symbol list from live state — no hardcoded tickers.
Dynamically pull symbol list from live state — 100% FETCH, no hardcoded watchlist.
Primary: state.scan_results (market scanner top candidates, ranked by squeeze score).
Fallback: state.quotes universe (all live-quoted symbols).
Mandatory anchors (AMC, GME, IWM) are always prepended so they're never dropped
by the TOP_N cap and never miss a pass regardless of scan-result ranking.
"""
from core.state import state

with state.lock:
candidates = list(state.scan_results)

if candidates:
return [r.get("symbol") for r in candidates[:_SCAN_TOP_N] if r.get("symbol")]

with state.lock:
fallback = list(state.quotes.keys())

return fallback[:_SCAN_TOP_N]
syms = [r.get("symbol") for r in candidates[:_SCAN_TOP_N] if r.get("symbol")]
else:
with state.lock:
syms = list(state.quotes.keys())[:_SCAN_TOP_N]

# Prepend mandatory anchors (deduplicated) so they're always first in the pass
seen = set(syms)
anchors = [s for s in sorted(_MANDATORY_ANCHORS) if s not in seen]
return anchors + syms


def _scan_pass():
Expand Down
57 changes: 56 additions & 1 deletion tools/robinhood_executor_sml.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,39 @@

# ── Configuration ──────────────────────────────────────────────────────────────
SQUEEZEOS_API_URL = os.environ.get("SQUEEZEOS_API_URL", "https://squeezeos-api.onrender.com")

_macro_cache: dict = {}
_MACRO_CACHE_TTL = 3600 # matches server-side 1-hour TTL
_MACRO_GATE_SECRET = os.environ.get("MACRO_GATE_SECRET", "")

# Always-watched anchors — injected into every oracle poll regardless of live universe
_MANDATORY_ANCHORS = {"AMC", "GME", "IWM"}

def _get_macro_regime(symbol: str) -> str:
"""
Query internal 741 macro gate on SqueezeOS server.
Requires MACRO_GATE_SECRET in executor.env — endpoint is not public.
Fails open: no secret configured or fetch error → UNKNOWN (never blocks trades).
Only PERFECT_BEARISH_REGIME blocks BUY orders.
"""
if not _MACRO_GATE_SECRET:
return "UNKNOWN" # no secret → fail open, never block trades
now = time.time()
cached = _macro_cache.get(symbol)
if cached and now - cached["ts"] < _MACRO_CACHE_TTL:
return cached["regime"]
try:
req = URLRequest(f"{SQUEEZEOS_API_URL}/api/macro/{symbol}",
headers={"User-Agent": "SqueezeOS-RH-Executor/2.0",
"X-Macro-Secret": _MACRO_GATE_SECRET})
with urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
regime = data.get("regime", "UNKNOWN")
except Exception as e:
logger.warning(f"[MACRO] {symbol} regime fetch failed: {e} — failing open")
regime = "UNKNOWN"
_macro_cache[symbol] = {"regime": regime, "ts": now}
return regime
ROBINHOOD_USER = os.environ.get("ROBINHOOD_USERNAME", "")
ROBINHOOD_PASS = os.environ.get("ROBINHOOD_PASSWORD", "")
POLL_INTERVAL_S = int(os.environ.get("POLL_INTERVAL_S", "180")) # poll every 3 minutes
Expand Down Expand Up @@ -288,6 +321,14 @@ def _execute(symbol: str, side: str, sml: dict, scan_counter: list):
logger.info(f"[EXEC] {symbol} god_stacked={god_count} < {MIN_GOD_STACKED} — skip")
return

# ── 741 Pure Macro Matrix gate (BUY only) ────────────────────────────────
if side == "buy":
macro = _get_macro_regime(symbol)
if macro == "PERFECT_BEARISH_REGIME":
logger.warning(f"[EXEC] {symbol} BUY blocked — 741 macro regime is PERFECT_BEARISH_REGIME")
return
logger.info(f"[EXEC] {symbol} macro regime={macro} — BUY allowed")

# ── Proprietary 5-EMA Stack Guardrail ───────────────────────────────────
try:
url = f"{SQUEEZEOS_API_URL}/api/ema/{symbol}"
Expand Down Expand Up @@ -632,6 +673,20 @@ def _poll_oracle() -> int:
except Exception as e:
logger.debug(f"[ORACLE] history fetch failed: {e}")

# ── 3. Mandatory anchors — always fetch AMC, GME, IWM even if absent from batch ──
for anchor in _MANDATORY_ANCHORS:
if anchor not in symbols_seen:
try:
req = URLRequest(f"{SQUEEZEOS_API_URL}/api/oracle/{anchor}",
headers={"User-Agent": "SqueezeOS-RH-Executor/2.0"})
with urlopen(req, timeout=10) as resp:
oracle_resp = json.loads(resp.read())
info = oracle_resp.get("oracle") or {}
if info.get("directive"):
symbols_seen[anchor] = info
except Exception as e:
logger.debug(f"[ORACLE] mandatory anchor {anchor} fetch failed: {e}")

if not symbols_seen:
return 0

Expand Down Expand Up @@ -683,7 +738,7 @@ def _poll_oracle() -> int:
def main():
global _rh_logged_in # explicitly declare global so Python never creates a local shadow
logger.info("=" * 60)
logger.info("SqueezeOS Robinhood Executor v3.2Wider Signal Net (GRID_LOCK + 60% oracle)")
logger.info("SqueezeOS Robinhood Executor v3.4Dynamic Universe + Mandatory Anchors (AMC/GME/IWM)")
logger.info(f" API : {SQUEEZEOS_API_URL}")
logger.info(f" Poll every : {POLL_INTERVAL_S}s")
logger.info(f" Hours : 4:00 AM–8:00 PM ET (pre-market + regular + after-hours)")
Expand Down
Loading