diff --git a/Makefile b/Makefile index 7c6f132..a5c9806 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,13 @@ .PHONY: setup run test build-mac-selfcontained setup: - ./run.sh --setup-only + ./web/run.sh --setup-only run: - ./run.sh + ./web/run.sh test: setup - . .venv/bin/activate && pytest -q + . .venv/bin/activate && PYTHONPATH=web/src pytest -q web/src/tests build-mac-selfcontained: ./scripts/build_selfcontained_mac_app.sh diff --git a/ManeshTraderMac/README.md b/ManeshTraderMac/README.md deleted file mode 100644 index 22f2ded..0000000 --- a/ManeshTraderMac/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# ManeshTraderMac (Xcode Shell App) - -This Xcode app is a native macOS shell around the existing Streamlit trading app. - -## What It Does -- Auto-starts backend when the app launches -- Starts/stops a bundled backend executable from app resources -- Embeds the local UI using `WKWebView` at `http://127.0.0.1:8501` -- Keeps users inside the app window (no external browser required) - -## Build Self-Contained App -1. From project root, build the embedded backend + macOS app: - - `./scripts/build_selfcontained_mac_app.sh` -2. Output app bundle: - - `dist-mac//ManeshTraderMac.app` -3. Optional DMG packaging: - - `APP_BUNDLE_PATH="dist-mac//ManeshTraderMac.app" ./scripts/create_installer_dmg.sh` - -## Run in Xcode -1. Generate embedded backend binary: - - `./scripts/build_embedded_backend.sh` -2. Open `ManeshTraderMac/ManeshTraderMac.xcodeproj` -3. Build/Run the `ManeshTraderMac` scheme -4. The app auto-starts backend on launch - -## Notes -- Backend source of truth is the root web app (`app.py`, `manesh_trader/`). -- `scripts/build_embedded_backend.sh` compiles those files into: - - `ManeshTraderMac/ManeshTraderMac/EmbeddedBackend/ManeshTraderBackend` -- The Swift host launches that embedded binary directly from the installed app bundle. diff --git a/README.md b/README.md index 653db3c..576b2a4 100644 --- a/README.md +++ b/README.md @@ -1,154 +1,61 @@ -# Real Bars vs Fake Bars Trend Analyzer (Streamlit) +# Real Bars vs Fake Bars Trend Analyzer -A Python web app that implements the **Real Bars vs Fake Bars** strategy: -- Classifies each closed bar against the prior bar as `real_bull`, `real_bear`, or `fake` -- Ignores fake bars for trend sequence logic -- Starts/continues trend on 2+ same-direction real bars -- Reverses trend only after 2 consecutive real bars of the opposite direction +A Python web app wrapped by a native macOS shell. -## Features -- Symbol/timeframe/period inputs (Yahoo Finance via `yfinance`) -- Optional wick filtering (use previous candle body range) -- Optional volume filter (minimum Volume vs Volume-SMA) -- Trend events table (starts and reversals) -- Chart visualization (candles + real-bar markers + trend-colored volume) -- Market session gap handling for stocks via `Hide market-closed gaps (stocks)`: - - `1d`: removes weekend spacing - - intraday: removes weekends and closed-hour overnight spans -- Session alerts for newly detected trend events -- Export classified data to CSV -- Export chart to PDF (via Plotly + Kaleido) -- Lightweight backtest snapshot (signal at trend changes, scored by next-bar direction) +## Standardized Layout +- `web/src/`: web backend + Streamlit app source +- `mac/src/`: Xcode macOS shell app (`WKWebView` host) +- `scripts/`: build and packaging scripts +- `docs/`: architecture and supporting docs +- `skills/`: reusable project skills -## Project Structure -- `app.py`: Streamlit entrypoint and UI orchestration -- `manesh_trader/constants.py`: intervals, periods, trend labels -- `manesh_trader/models.py`: data models (`TrendEvent`) -- `manesh_trader/data.py`: market data fetch and live-bar safeguard -- `manesh_trader/strategy.py`: bar classification and trend detection logic -- `manesh_trader/analytics.py`: backtest snapshot metrics -- `manesh_trader/charting.py`: Plotly chart construction -- `manesh_trader/exporting.py`: export DataFrame transformation -- `requirements.txt`: dependencies +## Web Source +- `web/src/app.py`: Streamlit entrypoint and UI orchestration +- `web/src/web_core/`: strategy/data/chart/export modules +- `web/src/requirements.txt`: Python dependencies +- `web/src/ONBOARDING.md`: in-app onboarding guide content -## Onboarding -See `ONBOARDING.md` for a complete step-by-step tutorial. +## macOS Shell +- Project location: `mac/src/` (`*.xcodeproj` auto-discovered by scripts) +- Uses `WKWebView` and launches embedded backend executable from app resources. +- No external browser required. -## Xcode macOS Shell -An Xcode-based desktop shell lives in `ManeshTraderMac/`. - -It provides a native window that starts/stops an embedded local backend executable and renders the UI in `WKWebView` (no external browser). -Backend source of truth stays at project root (`app.py`, `manesh_trader/`), and packaging compiles those files into `ManeshTraderBackend` inside the app bundle. -See `ManeshTraderMac/README.md` for setup and run steps. +See `mac/src/README.md` for shell details. ## Setup -### Easiest (one command) +### Quick start ```bash ./run.sh ``` -### macOS Double-Click Launcher -Double-click `Run ManeshTrader.command` in Finder. - -If macOS blocks first launch, run once in Terminal: +### Setup only ```bash -xattr -d com.apple.quarantine "Run ManeshTrader.command" +./run.sh --setup-only ``` -### macOS App Wrapper (`.app`) -Build a native `.app` launcher: +### Tests ```bash -./scripts/create_mac_app.sh +make test ``` -This creates `ManeshTrader.app` in the project root. You can double-click it or move it to `/Applications`. - -Build a distributable installer DMG: -```bash -./scripts/create_installer_dmg.sh -``` -Output: `ManeshTrader-YYYYMMDD-HHMMSS.dmg` in project root. - -### Self-Contained macOS App (WKWebView + Embedded Backend) -Build an installable app where all backend logic ships inside `ManeshTraderMac.app`: +## Build Self-Contained macOS App ```bash ./scripts/build_selfcontained_mac_app.sh ``` -This outputs `dist-mac//ManeshTraderMac.app`. +Output: `dist-mac//.app` -Then package that app as DMG: +Package as DMG: ```bash -APP_BUNDLE_PATH="dist-mac//ManeshTraderMac.app" ./scripts/create_installer_dmg.sh +APP_BUNDLE_PATH="dist-mac//.app" ./scripts/create_installer_dmg.sh ``` -Note: for easiest install on other Macs, use Apple code signing + notarization before sharing publicly. - -### Standalone Streamlit App (Alternative) -This older path builds a standalone Streamlit wrapper app: +## Optional Standalone Streamlit App ```bash ./scripts/build_standalone_app.sh ``` -Output: `dist-standalone//dist/ManeshTrader.app` - -### Optional with Make -```bash -make run -make build-mac-selfcontained -``` - -### Manual -1. Create and activate a virtual environment: - -```bash -python3 -m venv .venv -source .venv/bin/activate -``` - -2. Install dependencies: - -```bash -pip install -r requirements.txt -``` - -3. Run the app: - -```bash -streamlit run app.py -``` - -4. Open the local URL printed by Streamlit (usually `http://localhost:8501`). - -## Usage -1. Set `Symbol` (examples: `AAPL`, `MSFT`, `BTC-USD`, `ETH-USD`). -2. Choose `Timeframe` and `Period`. -3. Optionally adjust filters: - - `Use previous body range (ignore wicks)` - - `Enable volume filter` - - `Hide market-closed gaps (stocks)` -4. Review: - - Current trend status - - Real/fake bar counts - - Trend events and chart markers -5. Export results from the **Export** section. - -## Strategy Logic Implemented -For closed bar `i` and previous closed bar `i-1`: -- Real Bullish Bar: `close[i] > high[i-1]` (or previous body high if wick filter enabled) -- Real Bearish Bar: `close[i] < low[i-1]` (or previous body low if wick filter enabled) -- Fake Bar: otherwise (inclusive range) - -Trend state machine: -- Two consecutive real bullish bars => bullish trend active -- Two consecutive real bearish bars => bearish trend active -- Active trend persists until **two consecutive real bars in opposite direction** -- Single opposite real bar is treated as noise/fluke and does not reverse trend +Output: `dist-standalone//dist/.app` ## Notes -- The app is analysis-only; no order execution. -- Yahoo Finance interval availability depends on symbol and lookback window. -- "Ignore potentially live last bar" is enabled by default to reduce incomplete-bar bias. -- Gap-hiding behavior is currently optimized for regular U.S. stock sessions; exchange holidays/half-days may still show occasional spacing. - -## Troubleshooting -- If data fetch fails, verify symbol spelling and use a compatible interval/period combination. -- If PDF export is unavailable, ensure `kaleido` is installed in the same environment. +- Analysis-only app; no trade execution. +- Yahoo Finance interval availability depends on symbol/lookback. +- For broad distribution, use code signing + notarization. diff --git a/Run App.command b/Run App.command new file mode 100755 index 0000000..40b8168 --- /dev/null +++ b/Run App.command @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +cd "$(dirname "$0")" +./run.sh diff --git a/Run ManeshTrader.command b/Run ManeshTrader.command deleted file mode 100755 index cf5c23e..0000000 --- a/Run ManeshTrader.command +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -ROOT_DIR="$(cd "$(dirname "$0")" && pwd)" -cd "$ROOT_DIR" - -./run.sh diff --git a/ManeshTrader_Architecture.md b/docs/architecture.md similarity index 100% rename from ManeshTrader_Architecture.md rename to docs/architecture.md diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AccentColor.colorset/Contents.json b/mac/src/App/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AccentColor.colorset/Contents.json rename to mac/src/App/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/Contents.json b/mac/src/App/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/Contents.json rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_128x128.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_128x128.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_128x128.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_128x128.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_16x16.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_16x16.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_16x16.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_16x16.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_256x256.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_256x256.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_256x256.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_256x256.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_32x32.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_32x32.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_32x32.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_32x32.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_512x512.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_512x512.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_512x512.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_512x512.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png b/mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png rename to mac/src/App/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png diff --git a/ManeshTraderMac/ManeshTraderMac/Assets.xcassets/Contents.json b/mac/src/App/Assets.xcassets/Contents.json similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/Assets.xcassets/Contents.json rename to mac/src/App/Assets.xcassets/Contents.json diff --git a/ManeshTraderMac/ManeshTraderMac/ContentView.swift b/mac/src/App/ContentView.swift similarity index 99% rename from ManeshTraderMac/ManeshTraderMac/ContentView.swift rename to mac/src/App/ContentView.swift index 4ee002f..281b5c0 100644 --- a/ManeshTraderMac/ManeshTraderMac/ContentView.swift +++ b/mac/src/App/ContentView.swift @@ -757,6 +757,9 @@ private final class TraderHost { private func bundledBackendExecutableURL() -> URL? { let fm = FileManager.default let candidates = [ + Bundle.main.url(forResource: "WebBackend", withExtension: nil, subdirectory: "EmbeddedBackend"), + Bundle.main.resourceURL?.appendingPathComponent("EmbeddedBackend/WebBackend"), + Bundle.main.url(forResource: "WebBackend", withExtension: nil), Bundle.main.url(forResource: "ManeshTraderBackend", withExtension: nil, subdirectory: "EmbeddedBackend"), Bundle.main.resourceURL?.appendingPathComponent("EmbeddedBackend/ManeshTraderBackend"), Bundle.main.url(forResource: "ManeshTraderBackend", withExtension: nil), diff --git a/ManeshTraderMac/ManeshTraderMac/EmbeddedBackend/README.md b/mac/src/App/EmbeddedBackend/README.md similarity index 50% rename from ManeshTraderMac/ManeshTraderMac/EmbeddedBackend/README.md rename to mac/src/App/EmbeddedBackend/README.md index 60582da..c011b39 100644 --- a/ManeshTraderMac/ManeshTraderMac/EmbeddedBackend/README.md +++ b/mac/src/App/EmbeddedBackend/README.md @@ -1,6 +1,6 @@ This folder is populated by `scripts/build_embedded_backend.sh`. -- Output binary: `ManeshTraderBackend` -- Source of truth for backend code: project root (`app.py`, `manesh_trader/`) +- Output binary: `WebBackend` +- Source of truth for backend code: `web/src/` (`app.py`, `web_core/`) Do not hand-edit generated binaries in this folder. diff --git a/mac/src/App/EmbeddedBackend/WebBackend b/mac/src/App/EmbeddedBackend/WebBackend new file mode 100755 index 0000000..4819df2 Binary files /dev/null and b/mac/src/App/EmbeddedBackend/WebBackend differ diff --git a/ManeshTraderMac/ManeshTraderMac/ManeshTraderMacApp.swift b/mac/src/App/ManeshTraderMacApp.swift similarity index 100% rename from ManeshTraderMac/ManeshTraderMac/ManeshTraderMacApp.swift rename to mac/src/App/ManeshTraderMacApp.swift diff --git a/ManeshTraderMac/ManeshTraderMacTests/ManeshTraderMacTests.swift b/mac/src/AppTests/ManeshTraderMacTests.swift similarity index 70% rename from ManeshTraderMac/ManeshTraderMacTests/ManeshTraderMacTests.swift rename to mac/src/AppTests/ManeshTraderMacTests.swift index 81871cf..ca3183f 100644 --- a/ManeshTraderMac/ManeshTraderMacTests/ManeshTraderMacTests.swift +++ b/mac/src/AppTests/ManeshTraderMacTests.swift @@ -1,5 +1,4 @@ // -///Users/mattbruce/Documents/Web/ManeshTrader/ManeshTraderMac/ManeshTraderMac/ContentView.swift ManeshTraderMacTests.swift // ManeshTraderMacTests // // Created by Matt Bruce on 2/13/26. diff --git a/ManeshTraderMac/ManeshTraderMacUITests/ManeshTraderMacUITests.swift b/mac/src/AppUITests/ManeshTraderMacUITests.swift similarity index 100% rename from ManeshTraderMac/ManeshTraderMacUITests/ManeshTraderMacUITests.swift rename to mac/src/AppUITests/ManeshTraderMacUITests.swift diff --git a/ManeshTraderMac/ManeshTraderMacUITests/ManeshTraderMacUITestsLaunchTests.swift b/mac/src/AppUITests/ManeshTraderMacUITestsLaunchTests.swift similarity index 100% rename from ManeshTraderMac/ManeshTraderMacUITests/ManeshTraderMacUITestsLaunchTests.swift rename to mac/src/AppUITests/ManeshTraderMacUITestsLaunchTests.swift diff --git a/ManeshTraderMac/ManeshTraderMac.xcodeproj/project.pbxproj b/mac/src/MacShell.xcodeproj/project.pbxproj similarity index 99% rename from ManeshTraderMac/ManeshTraderMac.xcodeproj/project.pbxproj rename to mac/src/MacShell.xcodeproj/project.pbxproj index dcf6860..1804bec 100644 --- a/ManeshTraderMac/ManeshTraderMac.xcodeproj/project.pbxproj +++ b/mac/src/MacShell.xcodeproj/project.pbxproj @@ -32,17 +32,17 @@ /* Begin PBXFileSystemSynchronizedRootGroup section */ EAB6606E2F3FD5C000ED41BA /* ManeshTraderMac */ = { isa = PBXFileSystemSynchronizedRootGroup; - path = ManeshTraderMac; + path = App; sourceTree = ""; }; EAB6607C2F3FD5C100ED41BA /* ManeshTraderMacTests */ = { isa = PBXFileSystemSynchronizedRootGroup; - path = ManeshTraderMacTests; + path = AppTests; sourceTree = ""; }; EAB660862F3FD5C100ED41BA /* ManeshTraderMacUITests */ = { isa = PBXFileSystemSynchronizedRootGroup; - path = ManeshTraderMacUITests; + path = AppUITests; sourceTree = ""; }; /* End PBXFileSystemSynchronizedRootGroup section */ diff --git a/ManeshTraderMac/ManeshTraderMac.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/mac/src/MacShell.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from ManeshTraderMac/ManeshTraderMac.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to mac/src/MacShell.xcodeproj/project.xcworkspace/contents.xcworkspacedata diff --git a/mac/src/README.md b/mac/src/README.md new file mode 100644 index 0000000..6ffd69a --- /dev/null +++ b/mac/src/README.md @@ -0,0 +1,30 @@ +# macOS Shell App + +Native macOS shell around the web app in `web/src/`. + +## What It Does +- Starts/stops bundled backend executable from app resources +- Hosts UI in `WKWebView` at local `127.0.0.1` URL +- Keeps user inside app window (no external browser) + +## Build Self-Contained App +From repo root: +1. Build embedded backend + macOS app: + - `./scripts/build_selfcontained_mac_app.sh` +2. Output app bundle: + - `dist-mac//.app` +3. Optional DMG packaging: + - `APP_BUNDLE_PATH="dist-mac//.app" ./scripts/create_installer_dmg.sh` + +## Run In Xcode +From repo root: +1. Generate embedded backend binary: + - `./scripts/build_embedded_backend.sh` +2. Open project: + - `mac/src/*.xcodeproj` +3. Build/Run scheme: + - default: project name (or set `MAC_SCHEME` in scripts) + +## Notes +- Web source of truth is `web/src/` (`web/src/app.py`, `web/src/web_core/`). +- Embedded backend binary is copied into the first `EmbeddedBackend/` folder discovered under the selected project directory. diff --git a/run.sh b/run.sh index 81f6a52..01138eb 100755 --- a/run.sh +++ b/run.sh @@ -1,32 +1,4 @@ #!/usr/bin/env bash set -euo pipefail -ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -VENV_DIR="$ROOT_DIR/.venv" -SETUP_ONLY=false - -if [[ "${1:-}" == "--setup-only" ]]; then - SETUP_ONLY=true -fi - -if [[ ! -d "$VENV_DIR" ]]; then - echo "Creating virtual environment..." - python3 -m venv "$VENV_DIR" -fi - -# shellcheck disable=SC1091 -source "$VENV_DIR/bin/activate" - -if [[ ! -f "$VENV_DIR/.deps_installed" ]] || [[ "$ROOT_DIR/requirements.txt" -nt "$VENV_DIR/.deps_installed" ]]; then - echo "Installing dependencies..." - pip install -r "$ROOT_DIR/requirements.txt" - touch "$VENV_DIR/.deps_installed" -fi - -if [[ "$SETUP_ONLY" == "true" ]]; then - echo "Setup complete." - exit 0 -fi - -echo "Starting Streamlit app..." -exec streamlit run "$ROOT_DIR/app.py" +exec "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/web/run.sh" "$@" diff --git a/scripts/build_embedded_backend.sh b/scripts/build_embedded_backend.sh index fb2d979..e8b3114 100755 --- a/scripts/build_embedded_backend.sh +++ b/scripts/build_embedded_backend.sh @@ -2,14 +2,43 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WEB_SRC_DIR="$ROOT_DIR/web/src" +MAC_SRC_DIR="${MAC_SRC_DIR:-$ROOT_DIR/mac/src}" PYTHON_BIN="$ROOT_DIR/.venv/bin/python" -LAUNCHER="$ROOT_DIR/backend_embedded_launcher.py" -APP_NAME="ManeshTraderBackend" +LAUNCHER="$WEB_SRC_DIR/backend_embedded_launcher.py" +BACKEND_BIN_NAME="${BACKEND_BIN_NAME:-WebBackend}" BUILD_ROOT="$ROOT_DIR/dist-backend-build" DIST_PATH="$BUILD_ROOT/dist" WORK_PATH="$BUILD_ROOT/build" SPEC_PATH="$BUILD_ROOT/spec" -TARGET_DIR="$ROOT_DIR/ManeshTraderMac/ManeshTraderMac/EmbeddedBackend" +PROJECT_PATH="${MAC_PROJECT_PATH:-}" +SCHEME="${MAC_SCHEME:-}" +TARGET_DIR="${EMBEDDED_BACKEND_DIR:-}" + +discover_scheme() { + local project_path="$1" + python3 - "$project_path" <<'PY' +import json +import subprocess +import sys + +project = sys.argv[1] +try: + output = subprocess.check_output( + ["xcodebuild", "-list", "-project", project, "-json"], + text=True, + stderr=subprocess.DEVNULL, + ) + payload = json.loads(output) +except Exception: + print("") + raise SystemExit(0) + +project_data = payload.get("project", {}) +schemes = project_data.get("schemes") or [] +print(schemes[0] if schemes else "") +PY +} if [[ ! -x "$PYTHON_BIN" ]]; then echo "Missing virtual environment. Run ./run.sh --setup-only first." >&2 @@ -21,30 +50,74 @@ if [[ ! -f "$LAUNCHER" ]]; then exit 1 fi +if [[ -z "$PROJECT_PATH" ]]; then + PROJECT_PATH="$(find "$MAC_SRC_DIR" -maxdepth 4 -name "*.xcodeproj" | sort | head -n 1)" +fi +if [[ -z "$PROJECT_PATH" ]]; then + echo "No .xcodeproj found under: $MAC_SRC_DIR" >&2 + echo "Set MAC_PROJECT_PATH to the project you want to build." >&2 + exit 1 +fi + +PROJECT_DIR="$(dirname "$PROJECT_PATH")" +if [[ -z "$SCHEME" ]]; then + SCHEME="$(discover_scheme "$PROJECT_PATH")" +fi +if [[ -z "$SCHEME" ]]; then + SCHEME="$(basename "$PROJECT_PATH" .xcodeproj)" +fi + +if [[ -z "$TARGET_DIR" ]]; then + DEFAULT_TARGET_DIR="$PROJECT_DIR/$SCHEME/EmbeddedBackend" + if [[ -d "$DEFAULT_TARGET_DIR" ]]; then + TARGET_DIR="$DEFAULT_TARGET_DIR" + else + TARGET_DIR="$(find "$PROJECT_DIR" -maxdepth 4 -type d -name EmbeddedBackend | sort | head -n 1)" + fi +fi +if [[ -z "$TARGET_DIR" ]]; then + TARGET_DIR="$PROJECT_DIR/$SCHEME/EmbeddedBackend" +fi + mkdir -p "$DIST_PATH" "$WORK_PATH" "$SPEC_PATH" "$TARGET_DIR" "$PYTHON_BIN" -m pip install -q pyinstaller -"$PYTHON_BIN" -m PyInstaller \ - --noconfirm \ - --clean \ - --onefile \ - --name "$APP_NAME" \ - --distpath "$DIST_PATH" \ - --workpath "$WORK_PATH" \ - --specpath "$SPEC_PATH" \ - --add-data "$ROOT_DIR/app.py:." \ - --add-data "$ROOT_DIR/manesh_trader:manesh_trader" \ - --add-data "$ROOT_DIR/ONBOARDING.md:." \ - --collect-all streamlit \ - --collect-all streamlit_autorefresh \ - --hidden-import yfinance \ - --hidden-import pandas \ - --collect-all plotly \ - --collect-all kaleido \ - "$LAUNCHER" +PYI_ARGS=( + --noconfirm + --clean + --onefile + --name "$BACKEND_BIN_NAME" + --distpath "$DIST_PATH" + --workpath "$WORK_PATH" + --specpath "$SPEC_PATH" + --add-data "$WEB_SRC_DIR/app.py:." + --collect-all streamlit + --collect-all streamlit_autorefresh + --hidden-import yfinance + --hidden-import pandas + --collect-all plotly + --collect-all kaleido +) -cp "$DIST_PATH/$APP_NAME" "$TARGET_DIR/$APP_NAME" -chmod +x "$TARGET_DIR/$APP_NAME" +for source_dir in "$WEB_SRC_DIR"/*; do + source_name="$(basename "$source_dir")" + if [[ ! -d "$source_dir" ]]; then + continue + fi + if [[ "$source_name" == "tests" ]] || [[ "$source_name" == "__pycache__" ]]; then + continue + fi + PYI_ARGS+=(--add-data "$source_dir:$source_name") +done -echo "Embedded backend updated: $TARGET_DIR/$APP_NAME" +if [[ -f "$WEB_SRC_DIR/ONBOARDING.md" ]]; then + PYI_ARGS+=(--add-data "$WEB_SRC_DIR/ONBOARDING.md:.") +fi + +"$PYTHON_BIN" -m PyInstaller "${PYI_ARGS[@]}" "$LAUNCHER" + +cp "$DIST_PATH/$BACKEND_BIN_NAME" "$TARGET_DIR/$BACKEND_BIN_NAME" +chmod +x "$TARGET_DIR/$BACKEND_BIN_NAME" + +echo "Embedded backend updated: $TARGET_DIR/$BACKEND_BIN_NAME" diff --git a/scripts/build_selfcontained_mac_app.sh b/scripts/build_selfcontained_mac_app.sh index 97578dc..2809753 100755 --- a/scripts/build_selfcontained_mac_app.sh +++ b/scripts/build_selfcontained_mac_app.sh @@ -2,15 +2,56 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -PROJECT_PATH="$ROOT_DIR/ManeshTraderMac/ManeshTraderMac.xcodeproj" -SCHEME="ManeshTraderMac" +MAC_SRC_DIR="${MAC_SRC_DIR:-$ROOT_DIR/mac/src}" +PROJECT_PATH="${MAC_PROJECT_PATH:-}" +SCHEME="${MAC_SCHEME:-}" CONFIGURATION="${CONFIGURATION:-Release}" DERIVED_DATA_PATH="$ROOT_DIR/dist-mac/derived-data" TIMESTAMP="$(date +%Y%m%d-%H%M%S)" OUTPUT_DIR="$ROOT_DIR/dist-mac/$TIMESTAMP" +discover_scheme() { + local project_path="$1" + python3 - "$project_path" <<'PY' +import json +import subprocess +import sys + +project = sys.argv[1] +try: + output = subprocess.check_output( + ["xcodebuild", "-list", "-project", project, "-json"], + text=True, + stderr=subprocess.DEVNULL, + ) + payload = json.loads(output) +except Exception: + print("") + raise SystemExit(0) + +project_data = payload.get("project", {}) +schemes = project_data.get("schemes") or [] +print(schemes[0] if schemes else "") +PY +} + "$ROOT_DIR/scripts/build_embedded_backend.sh" +if [[ -z "$PROJECT_PATH" ]]; then + PROJECT_PATH="$(find "$MAC_SRC_DIR" -maxdepth 4 -name "*.xcodeproj" | sort | head -n 1)" +fi +if [[ -z "$PROJECT_PATH" ]]; then + echo "No .xcodeproj found under: $MAC_SRC_DIR" >&2 + echo "Set MAC_PROJECT_PATH to the project you want to build." >&2 + exit 1 +fi +if [[ -z "$SCHEME" ]]; then + SCHEME="$(discover_scheme "$PROJECT_PATH")" +fi +if [[ -z "$SCHEME" ]]; then + SCHEME="$(basename "$PROJECT_PATH" .xcodeproj)" +fi + xcodebuild \ -project "$PROJECT_PATH" \ -scheme "$SCHEME" \ diff --git a/scripts/build_standalone_app.sh b/scripts/build_standalone_app.sh index 7c0de05..3166250 100755 --- a/scripts/build_standalone_app.sh +++ b/scripts/build_standalone_app.sh @@ -2,8 +2,9 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WEB_SRC_DIR="$ROOT_DIR/web/src" PYTHON_BIN="$ROOT_DIR/.venv/bin/python" -APP_NAME="ManeshTrader" +APP_NAME="${APP_NAME:-$(basename "$ROOT_DIR")}" if [[ ! -x "$PYTHON_BIN" ]]; then echo "Missing virtual environment. Run ./run.sh --setup-only first." >&2 @@ -20,22 +21,37 @@ SPEC_PATH="$OUT_ROOT/spec" mkdir -p "$DIST_PATH" "$WORK_PATH" "$SPEC_PATH" -"$PYTHON_BIN" -m PyInstaller \ - --noconfirm \ - --windowed \ - --name "$APP_NAME" \ - --distpath "$DIST_PATH" \ - --workpath "$WORK_PATH" \ - --specpath "$SPEC_PATH" \ - --add-data "$ROOT_DIR/app.py:." \ - --add-data "$ROOT_DIR/manesh_trader:manesh_trader" \ - --add-data "$ROOT_DIR/ONBOARDING.md:." \ - --collect-all streamlit \ - --collect-all streamlit_autorefresh \ - --hidden-import yfinance \ - --hidden-import pandas \ - --collect-all plotly \ - "$ROOT_DIR/desktop_launcher.py" +PYI_ARGS=( + --noconfirm + --windowed + --name "$APP_NAME" + --distpath "$DIST_PATH" + --workpath "$WORK_PATH" + --specpath "$SPEC_PATH" + --add-data "$WEB_SRC_DIR/app.py:." + --collect-all streamlit + --collect-all streamlit_autorefresh + --hidden-import yfinance + --hidden-import pandas + --collect-all plotly +) + +for source_dir in "$WEB_SRC_DIR"/*; do + source_name="$(basename "$source_dir")" + if [[ ! -d "$source_dir" ]]; then + continue + fi + if [[ "$source_name" == "tests" ]] || [[ "$source_name" == "__pycache__" ]]; then + continue + fi + PYI_ARGS+=(--add-data "$source_dir:$source_name") +done + +if [[ -f "$WEB_SRC_DIR/ONBOARDING.md" ]]; then + PYI_ARGS+=(--add-data "$WEB_SRC_DIR/ONBOARDING.md:.") +fi + +"$PYTHON_BIN" -m PyInstaller "${PYI_ARGS[@]}" "$WEB_SRC_DIR/desktop_launcher.py" APP_BUNDLE="$DIST_PATH/${APP_NAME}.app" if [[ ! -d "$APP_BUNDLE" ]]; then diff --git a/scripts/create_installer_dmg.sh b/scripts/create_installer_dmg.sh index e93fd2d..86b9bd2 100755 --- a/scripts/create_installer_dmg.sh +++ b/scripts/create_installer_dmg.sh @@ -2,12 +2,35 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -if [[ -d "$ROOT_DIR/ManeshTraderMac.app" ]]; then - APP_BUNDLE_DEFAULT="$ROOT_DIR/ManeshTraderMac.app" -else - APP_BUNDLE_DEFAULT="$ROOT_DIR/ManeshTrader.app" + +find_latest_app_bundle() { + local candidate + + candidate="$(find "$ROOT_DIR/dist-mac" -type d -name "*.app" 2>/dev/null | sort | tail -n 1 || true)" + if [[ -n "$candidate" ]]; then + echo "$candidate" + return 0 + fi + + candidate="$(find "$ROOT_DIR/dist-standalone" -type d -name "*.app" 2>/dev/null | sort | tail -n 1 || true)" + if [[ -n "$candidate" ]]; then + echo "$candidate" + return 0 + fi + + candidate="$(find "$ROOT_DIR" -maxdepth 1 -type d -name "*.app" | sort | head -n 1 || true)" + if [[ -n "$candidate" ]]; then + echo "$candidate" + return 0 + fi + + return 1 +} + +APP_BUNDLE="${APP_BUNDLE_PATH:-}" +if [[ -z "$APP_BUNDLE" ]]; then + APP_BUNDLE="$(find_latest_app_bundle || true)" fi -APP_BUNDLE="${APP_BUNDLE_PATH:-$APP_BUNDLE_DEFAULT}" if ! command -v create-dmg >/dev/null 2>&1; then echo "create-dmg not found. Install with: brew install create-dmg" >&2 @@ -15,7 +38,7 @@ if ! command -v create-dmg >/dev/null 2>&1; then fi if [[ ! -d "$APP_BUNDLE" ]]; then - echo "App bundle not found: $APP_BUNDLE" >&2 + echo "App bundle not found." >&2 echo "Set APP_BUNDLE_PATH to a built .app bundle or build one first." >&2 exit 1 fi diff --git a/scripts/create_mac_app.sh b/scripts/create_mac_app.sh index 7be73aa..c2b3d10 100755 --- a/scripts/create_mac_app.sh +++ b/scripts/create_mac_app.sh @@ -2,7 +2,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -APP_NAME="ManeshTrader" +APP_NAME="${APP_NAME:-$(basename "$ROOT_DIR")}" APP_PATH="$ROOT_DIR/${APP_NAME}.app" if ! command -v osacompile >/dev/null 2>&1; then diff --git a/web/run.sh b/web/run.sh new file mode 100755 index 0000000..8601f85 --- /dev/null +++ b/web/run.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +WEB_SRC_DIR="$ROOT_DIR/web/src" +VENV_DIR="$ROOT_DIR/.venv" +SETUP_ONLY=false + +if [[ "${1:-}" == "--setup-only" ]]; then + SETUP_ONLY=true +fi + +if [[ ! -d "$VENV_DIR" ]]; then + echo "Creating virtual environment..." + python3 -m venv "$VENV_DIR" +fi + +# shellcheck disable=SC1091 +source "$VENV_DIR/bin/activate" + +if [[ ! -f "$VENV_DIR/.deps_installed" ]] || [[ "$WEB_SRC_DIR/requirements.txt" -nt "$VENV_DIR/.deps_installed" ]]; then + echo "Installing dependencies..." + pip install -r "$WEB_SRC_DIR/requirements.txt" + touch "$VENV_DIR/.deps_installed" +fi + +if [[ "$SETUP_ONLY" == "true" ]]; then + echo "Setup complete." + exit 0 +fi + +echo "Starting Streamlit app..." +exec streamlit run "$WEB_SRC_DIR/app.py" diff --git a/ONBOARDING.md b/web/src/ONBOARDING.md similarity index 100% rename from ONBOARDING.md rename to web/src/ONBOARDING.md diff --git a/app.py b/web/src/app.py similarity index 96% rename from app.py rename to web/src/app.py index 55af93b..9a62571 100644 --- a/app.py +++ b/web/src/app.py @@ -9,14 +9,15 @@ import streamlit as st import yfinance as yf from streamlit_autorefresh import st_autorefresh -from manesh_trader.analytics import backtest_signals -from manesh_trader.charting import build_figure -from manesh_trader.constants import INTERVAL_OPTIONS, PERIOD_OPTIONS -from manesh_trader.data import fetch_ohlc, maybe_drop_live_bar -from manesh_trader.exporting import df_for_export -from manesh_trader.strategy import classify_bars, detect_trends +from web_core.analytics import backtest_signals +from web_core.charting import build_figure +from web_core.constants import INTERVAL_OPTIONS, PERIOD_OPTIONS +from web_core.data import fetch_ohlc, maybe_drop_live_bar +from web_core.exporting import df_for_export +from web_core.strategy import classify_bars, detect_trends -SETTINGS_PATH = Path.home() / ".manesh_trader" / "settings.json" +SETTINGS_PATH = Path.home() / ".web_local_shell" / "settings.json" +LEGACY_SETTINGS_PATH = Path.home() / ".manesh_trader" / "settings.json" def _clamp_int(value: Any, fallback: int, minimum: int, maximum: int) -> int: @@ -137,10 +138,11 @@ def normalize_web_settings(raw: dict[str, Any] | None) -> dict[str, Any]: def load_web_settings() -> dict[str, Any]: - if not SETTINGS_PATH.exists(): + source_path = SETTINGS_PATH if SETTINGS_PATH.exists() else LEGACY_SETTINGS_PATH + if not source_path.exists(): return normalize_web_settings(None) try: - payload = json.loads(SETTINGS_PATH.read_text(encoding="utf-8")) + payload = json.loads(source_path.read_text(encoding="utf-8")) if not isinstance(payload, dict): return normalize_web_settings(None) return normalize_web_settings(payload) diff --git a/backend_embedded_launcher.py b/web/src/backend_embedded_launcher.py similarity index 100% rename from backend_embedded_launcher.py rename to web/src/backend_embedded_launcher.py diff --git a/desktop_launcher.py b/web/src/desktop_launcher.py similarity index 100% rename from desktop_launcher.py rename to web/src/desktop_launcher.py diff --git a/requirements.txt b/web/src/requirements.txt similarity index 100% rename from requirements.txt rename to web/src/requirements.txt diff --git a/tests/conftest.py b/web/src/tests/conftest.py similarity index 100% rename from tests/conftest.py rename to web/src/tests/conftest.py diff --git a/tests/test_exporting.py b/web/src/tests/test_exporting.py similarity index 93% rename from tests/test_exporting.py rename to web/src/tests/test_exporting.py index 383e1a1..fd5d536 100644 --- a/tests/test_exporting.py +++ b/web/src/tests/test_exporting.py @@ -2,7 +2,7 @@ from __future__ import annotations import pandas as pd -from manesh_trader.exporting import df_for_export +from web_core.exporting import df_for_export def test_df_for_export_handles_named_datetime_index() -> None: diff --git a/tests/test_strategy.py b/web/src/tests/test_strategy.py similarity index 96% rename from tests/test_strategy.py rename to web/src/tests/test_strategy.py index 362bd53..53ef475 100644 --- a/tests/test_strategy.py +++ b/web/src/tests/test_strategy.py @@ -2,8 +2,8 @@ from __future__ import annotations import pandas as pd -from manesh_trader.constants import TREND_BEAR, TREND_BULL, TREND_NEUTRAL -from manesh_trader.strategy import classify_bars, detect_trends +from web_core.constants import TREND_BEAR, TREND_BULL, TREND_NEUTRAL +from web_core.strategy import classify_bars, detect_trends def make_df(rows: list[dict[str, float]]) -> pd.DataFrame: diff --git a/manesh_trader/__init__.py b/web/src/web_core/__init__.py similarity index 100% rename from manesh_trader/__init__.py rename to web/src/web_core/__init__.py diff --git a/manesh_trader/analytics.py b/web/src/web_core/analytics.py similarity index 100% rename from manesh_trader/analytics.py rename to web/src/web_core/analytics.py diff --git a/manesh_trader/charting.py b/web/src/web_core/charting.py similarity index 100% rename from manesh_trader/charting.py rename to web/src/web_core/charting.py diff --git a/manesh_trader/constants.py b/web/src/web_core/constants.py similarity index 100% rename from manesh_trader/constants.py rename to web/src/web_core/constants.py diff --git a/manesh_trader/data.py b/web/src/web_core/data.py similarity index 100% rename from manesh_trader/data.py rename to web/src/web_core/data.py diff --git a/manesh_trader/exporting.py b/web/src/web_core/exporting.py similarity index 100% rename from manesh_trader/exporting.py rename to web/src/web_core/exporting.py diff --git a/manesh_trader/models.py b/web/src/web_core/models.py similarity index 100% rename from manesh_trader/models.py rename to web/src/web_core/models.py diff --git a/manesh_trader/strategy.py b/web/src/web_core/strategy.py similarity index 100% rename from manesh_trader/strategy.py rename to web/src/web_core/strategy.py