Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
6
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
|
||||
|
||||
@ -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/<timestamp>/ManeshTraderMac.app`
|
||||
3. Optional DMG packaging:
|
||||
- `APP_BUNDLE_PATH="dist-mac/<timestamp>/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.
|
||||
157
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/<timestamp>/ManeshTraderMac.app`.
|
||||
Output: `dist-mac/<timestamp>/<Scheme>.app`
|
||||
|
||||
Then package that app as DMG:
|
||||
Package as DMG:
|
||||
```bash
|
||||
APP_BUNDLE_PATH="dist-mac/<timestamp>/ManeshTraderMac.app" ./scripts/create_installer_dmg.sh
|
||||
APP_BUNDLE_PATH="dist-mac/<timestamp>/<Scheme>.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/<timestamp>/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/<timestamp>/dist/<RepoName>.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.
|
||||
|
||||
5
Run App.command
Executable file
@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
./run.sh
|
||||
@ -1,7 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
cd "$ROOT_DIR"
|
||||
|
||||
./run.sh
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 792 B After Width: | Height: | Size: 792 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
@ -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),
|
||||
@ -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.
|
||||
BIN
mac/src/App/EmbeddedBackend/WebBackend
Executable file
@ -1,5 +1,4 @@
|
||||
//
|
||||
///Users/mattbruce/Documents/Web/ManeshTrader/ManeshTraderMac/ManeshTraderMac/ContentView.swift ManeshTraderMacTests.swift
|
||||
// ManeshTraderMacTests
|
||||
//
|
||||
// Created by Matt Bruce on 2/13/26.
|
||||
@ -32,17 +32,17 @@
|
||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||
EAB6606E2F3FD5C000ED41BA /* ManeshTraderMac */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = ManeshTraderMac;
|
||||
path = App;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EAB6607C2F3FD5C100ED41BA /* ManeshTraderMacTests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = ManeshTraderMacTests;
|
||||
path = AppTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EAB660862F3FD5C100ED41BA /* ManeshTraderMacUITests */ = {
|
||||
isa = PBXFileSystemSynchronizedRootGroup;
|
||||
path = ManeshTraderMacUITests;
|
||||
path = AppUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXFileSystemSynchronizedRootGroup section */
|
||||
30
mac/src/README.md
Normal file
@ -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/<timestamp>/<Scheme>.app`
|
||||
3. Optional DMG packaging:
|
||||
- `APP_BUNDLE_PATH="dist-mac/<timestamp>/<Scheme>.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.
|
||||
30
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" "$@"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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" \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
33
web/run.sh
Executable file
@ -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"
|
||||
@ -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)
|
||||
@ -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:
|
||||
@ -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:
|
||||