Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
4c1c6f31d5
commit
9453036d8e
@ -12,6 +12,10 @@ Trend logic:
|
|||||||
- Reversal requires 2 consecutive opposite real bars
|
- Reversal requires 2 consecutive opposite real bars
|
||||||
- Fake bars do not reverse trend
|
- Fake bars do not reverse trend
|
||||||
|
|
||||||
|
In-app help popup source:
|
||||||
|
- Primary: `web/src/web_core/help.html`
|
||||||
|
- Fallback: this `web/src/ONBOARDING.md`
|
||||||
|
|
||||||
## 2) Quick Start (Recommended)
|
## 2) Quick Start (Recommended)
|
||||||
From project root:
|
From project root:
|
||||||
|
|
||||||
@ -78,6 +82,8 @@ Tip: sign and notarize before sharing broadly, so macOS trust prompts are reduce
|
|||||||
- Real Bearish Bars
|
- Real Bearish Bars
|
||||||
- Fake Bars
|
- Fake Bars
|
||||||
7. Read `Trend Events` for starts and reversals.
|
7. Read `Trend Events` for starts and reversals.
|
||||||
|
8. Use `Live Decision Guide` to translate trend state into a practical bias/action/invalidation workflow.
|
||||||
|
9. Keep `Show past behavior examples` ON while learning to review historical entry/exit outcomes.
|
||||||
|
|
||||||
## 5) How To Read The Chart
|
## 5) How To Read The Chart
|
||||||
- Candle layer: full price action
|
- Candle layer: full price action
|
||||||
@ -85,8 +91,85 @@ Tip: sign and notarize before sharing broadly, so macOS trust prompts are reduce
|
|||||||
- Red triangle-down markers: `real_bear`
|
- Red triangle-down markers: `real_bear`
|
||||||
- Gray candles (if enabled): visually de-emphasized fake bars
|
- Gray candles (if enabled): visually de-emphasized fake bars
|
||||||
- Volume bars are colored by trend state
|
- Volume bars are colored by trend state
|
||||||
|
- Optional training overlays:
|
||||||
|
- blue circle: example long entry
|
||||||
|
- orange diamond: example short entry
|
||||||
|
- green X: example winning exit
|
||||||
|
- red X: example loss/flat exit
|
||||||
|
|
||||||
## 6) Recommended Settings By Asset
|
## 6) Training Mode (New)
|
||||||
|
Use these toggles in sidebar `Training & Guidance`:
|
||||||
|
- `Show live decision guide`: summarizes current bias, signal status, and invalidation rule.
|
||||||
|
- `Show past behavior examples`: shows historical hypothetical trades and outcomes.
|
||||||
|
- `Overlay example entries/exits on chart`: draws example entry/exit markers on candles.
|
||||||
|
- `Focus chart on selected example`: zooms chart around the clicked training row.
|
||||||
|
- `Max training examples`: controls how many recent closed examples to include.
|
||||||
|
|
||||||
|
Example-trade method:
|
||||||
|
- Enter on a confirmed trend-change bar close.
|
||||||
|
- Exit on the opposite confirmed trend-change bar close.
|
||||||
|
- Direction is `LONG` in bullish trend and `SHORT` in bearish trend.
|
||||||
|
|
||||||
|
## 6.1) Tutorial: Use This Like a Training Coach
|
||||||
|
Goal: learn what the model is signaling and what would have happened if you followed it.
|
||||||
|
|
||||||
|
### Step A: Set up a clean training view
|
||||||
|
1. In sidebar, choose a liquid symbol (`AAPL` or `BTC-USD`).
|
||||||
|
2. Start with:
|
||||||
|
- `Timeframe = 1d` (less noise)
|
||||||
|
- `Period = 6mo`
|
||||||
|
- `Ignore potentially live last bar = ON`
|
||||||
|
3. Turn ON:
|
||||||
|
- `Show live decision guide`
|
||||||
|
- `Show past behavior examples`
|
||||||
|
- `Overlay example entries/exits on chart`
|
||||||
|
- `Focus chart on selected example` (optional but recommended while learning)
|
||||||
|
4. Set `Max training examples = 20`.
|
||||||
|
|
||||||
|
### Step B: Read current signal (what to do now)
|
||||||
|
1. Look at `Live Decision Guide`.
|
||||||
|
2. Use `Bias` as your directional filter:
|
||||||
|
- `Long Bias`: only evaluate long ideas.
|
||||||
|
- `Short Bias`: only evaluate short ideas.
|
||||||
|
- `Stand Aside / Neutral`: wait for clearer confirmation.
|
||||||
|
3. Read `Signal Status`:
|
||||||
|
- `Fresh Confirmation`: a new active trend was confirmed on the latest closed bar.
|
||||||
|
- `No New Confirmation`: no fresh reversal confirmation on the latest closed bar.
|
||||||
|
4. Use `Invalidation rule` as the condition that would cancel your current bias.
|
||||||
|
|
||||||
|
### Step C: Study historical behavior (what would have happened)
|
||||||
|
1. Open `Past Behavior Examples (Training)`.
|
||||||
|
2. Review summary metrics:
|
||||||
|
- `Closed Examples`
|
||||||
|
- `Wins / Losses`
|
||||||
|
- `Example Win Rate`
|
||||||
|
- `Avg P/L per Example`
|
||||||
|
3. Read the latest row first, then scan older examples.
|
||||||
|
4. On chart, compare markers to price structure:
|
||||||
|
- blue circle: long entry
|
||||||
|
- orange diamond: short entry
|
||||||
|
- green X: winning exit
|
||||||
|
- red X: loss/flat exit
|
||||||
|
5. Click any row in the examples table:
|
||||||
|
- app highlights that exact trade path on chart
|
||||||
|
- app shows a plain-English explanation of that row
|
||||||
|
|
||||||
|
### Step D: Practice loop (recommended)
|
||||||
|
1. Pick one symbol/timeframe.
|
||||||
|
2. Hide the table briefly and decide what you would do from `Live Decision Guide`.
|
||||||
|
3. Re-open table and compare your decision to recent example outcomes.
|
||||||
|
4. Repeat across different regimes:
|
||||||
|
- trending periods
|
||||||
|
- choppy/sideways periods
|
||||||
|
5. Keep notes on where the model performs best/worst.
|
||||||
|
|
||||||
|
### Step E: Move from training to live monitoring
|
||||||
|
1. Keep `Show live decision guide` ON.
|
||||||
|
2. Keep `Ignore potentially live last bar` ON to reduce unfinished-bar noise.
|
||||||
|
3. Enable `Auto-refresh` only when actively monitoring.
|
||||||
|
4. Treat this as a decision-support layer, not an execution signal by itself.
|
||||||
|
|
||||||
|
## 7) Recommended Settings By Asset
|
||||||
### Stocks (swing)
|
### Stocks (swing)
|
||||||
- Timeframe: `1d`
|
- Timeframe: `1d`
|
||||||
- Period: `6mo` or `1y`
|
- Period: `6mo` or `1y`
|
||||||
@ -97,7 +180,7 @@ Tip: sign and notarize before sharing broadly, so macOS trust prompts are reduce
|
|||||||
- Period: `1mo-6mo`
|
- Period: `1mo-6mo`
|
||||||
- Enable auto-refresh only when monitoring live
|
- Enable auto-refresh only when monitoring live
|
||||||
|
|
||||||
## 7) Optional Filters
|
## 8) Optional Filters
|
||||||
### Ignore Wicks
|
### Ignore Wicks
|
||||||
Use when long wicks create false breakouts; compares close to previous body only.
|
Use when long wicks create false breakouts; compares close to previous body only.
|
||||||
|
|
||||||
@ -115,13 +198,13 @@ Compresses non-trading time on stock charts:
|
|||||||
|
|
||||||
Use OFF for 24/7 markets (for example many crypto workflows) when you want continuous time.
|
Use OFF for 24/7 markets (for example many crypto workflows) when you want continuous time.
|
||||||
|
|
||||||
## 8) Exports
|
## 9) Exports
|
||||||
- CSV: `Download classified data (CSV)`
|
- CSV: `Download classified data (CSV)`
|
||||||
- PDF chart: `Download chart (PDF)`
|
- PDF chart: `Download chart (PDF)`
|
||||||
|
|
||||||
If PDF fails, ensure `kaleido` is installed (already in `requirements.txt`).
|
If PDF fails, ensure `kaleido` is installed (already in `requirements.txt`).
|
||||||
|
|
||||||
## 9) Troubleshooting
|
## 10) Troubleshooting
|
||||||
### App won’t start
|
### App won’t start
|
||||||
```bash
|
```bash
|
||||||
./run.sh --setup-only
|
./run.sh --setup-only
|
||||||
@ -145,12 +228,12 @@ streamlit run app.py --server.port 8502
|
|||||||
### Exports crash with timestamp errors
|
### Exports crash with timestamp errors
|
||||||
- Pull latest project changes (export logic now handles named index columns)
|
- Pull latest project changes (export logic now handles named index columns)
|
||||||
|
|
||||||
## 10) Safety Notes
|
## 11) Safety Notes
|
||||||
- This app is analysis-only, no trade execution.
|
- This app is analysis-only, no trade execution.
|
||||||
- Backtest snapshot is diagnostic and simplistic.
|
- Backtest snapshot is diagnostic and simplistic.
|
||||||
- Not financial advice.
|
- Not financial advice.
|
||||||
|
|
||||||
## 11) Useful Commands
|
## 12) Useful Commands
|
||||||
Setup only:
|
Setup only:
|
||||||
```bash
|
```bash
|
||||||
./run.sh --setup-only
|
./run.sh --setup-only
|
||||||
|
|||||||
@ -12,7 +12,7 @@ Out of scope:
|
|||||||
- macOS shell/wrapper architecture and packaging
|
- macOS shell/wrapper architecture and packaging
|
||||||
|
|
||||||
## 2. Product Goal
|
## 2. Product Goal
|
||||||
Provide an analysis-only charting tool that classifies OHLC bars as real/fake, tracks trend state using only real bars, and exposes clear visual + exportable outputs.
|
Provide an analysis-only charting tool that classifies OHLC bars as real/fake, tracks trend state using only real bars, and exposes clear visual, training-oriented guidance, and exportable outputs.
|
||||||
|
|
||||||
## 3. Inputs and Data Pipeline
|
## 3. Inputs and Data Pipeline
|
||||||
1. User configures:
|
1. User configures:
|
||||||
@ -40,6 +40,11 @@ Normalization constraints:
|
|||||||
- `volume_sma_window`: `[2, 100]`, fallback `20`
|
- `volume_sma_window`: `[2, 100]`, fallback `20`
|
||||||
- `volume_multiplier`: `[0.1, 3.0]`, rounded to 0.1, fallback `1.0`
|
- `volume_multiplier`: `[0.1, 3.0]`, rounded to 0.1, fallback `1.0`
|
||||||
- `refresh_sec`: `[10, 600]`, fallback `60`
|
- `refresh_sec`: `[10, 600]`, fallback `60`
|
||||||
|
- `show_live_guide`: boolean, fallback `true`
|
||||||
|
- `show_past_behavior`: boolean, fallback `true`
|
||||||
|
- `show_trade_markers`: boolean, fallback `true`
|
||||||
|
- `focus_chart_on_selected_example`: boolean, fallback `false`
|
||||||
|
- `max_training_examples`: `[5, 100]`, fallback `20`
|
||||||
- booleans normalized from common truthy/falsy strings and numbers
|
- booleans normalized from common truthy/falsy strings and numbers
|
||||||
|
|
||||||
## 5. Classification Rules
|
## 5. Classification Rules
|
||||||
@ -86,6 +91,13 @@ Important:
|
|||||||
- `real_bull`: green triangle-up
|
- `real_bull`: green triangle-up
|
||||||
- `real_bear`: red triangle-down
|
- `real_bear`: red triangle-down
|
||||||
- Optional fake-bar de-emphasis via gray candle layer (`gray_fake`).
|
- Optional fake-bar de-emphasis via gray candle layer (`gray_fake`).
|
||||||
|
- Optional example-trade overlay markers (`show_trade_markers`):
|
||||||
|
- long entry marker
|
||||||
|
- short entry marker
|
||||||
|
- winning exit marker
|
||||||
|
- loss/flat exit marker
|
||||||
|
- If a past-behavior row is selected, chart highlights that trade window and path.
|
||||||
|
- Optional focused zoom around selected trade (`focus_chart_on_selected_example`).
|
||||||
- Volume subplot colored by trend state.
|
- Volume subplot colored by trend state.
|
||||||
|
|
||||||
Gap handling (`hide_market_closed_gaps`):
|
Gap handling (`hide_market_closed_gaps`):
|
||||||
@ -96,7 +108,7 @@ Gap handling (`hide_market_closed_gaps`):
|
|||||||
## 8. Help and Onboarding Behavior
|
## 8. Help and Onboarding Behavior
|
||||||
- Web-only fallback help entry exists in sidebar:
|
- Web-only fallback help entry exists in sidebar:
|
||||||
- `Help / Quick Start`
|
- `Help / Quick Start`
|
||||||
- Content source: `web/src/ONBOARDING.md`
|
- Content source: `web/src/web_core/help.html` (primary), `web/src/ONBOARDING.md` (fallback)
|
||||||
- Help appears in a dialog.
|
- Help appears in a dialog.
|
||||||
|
|
||||||
## 9. Outputs
|
## 9. Outputs
|
||||||
@ -105,10 +117,21 @@ Gap handling (`hide_market_closed_gaps`):
|
|||||||
- real bullish count
|
- real bullish count
|
||||||
- real bearish count
|
- real bearish count
|
||||||
- fake count
|
- fake count
|
||||||
|
- Live decision guide (optional):
|
||||||
|
- bias (long/short/neutral)
|
||||||
|
- signal confirmation status
|
||||||
|
- latest bar interpretation
|
||||||
|
- action + invalidation guidance
|
||||||
- Trend events table (latest events)
|
- Trend events table (latest events)
|
||||||
- Backtest snapshot:
|
- Backtest snapshot:
|
||||||
- signal at trend-change rows to active bull/bear states
|
- signal at trend-change rows to active bull/bear states
|
||||||
- next-bar close determines win/loss
|
- next-bar close determines win/loss
|
||||||
|
- Past behavior examples (optional training panel):
|
||||||
|
- historical examples using trend-confirmation entries and opposite-confirmation exits
|
||||||
|
- per-example direction, entry/exit timestamps, bars held, P/L%, and outcome
|
||||||
|
- aggregate example metrics (count, win/loss, win rate, average P/L)
|
||||||
|
- selectable table rows that drive chart highlight of chosen example
|
||||||
|
- plain-language explanation for selected example
|
||||||
- Exports:
|
- Exports:
|
||||||
- CSV always available
|
- CSV always available
|
||||||
- PDF via Plotly image export (requires Kaleido runtime)
|
- PDF via Plotly image export (requires Kaleido runtime)
|
||||||
|
|||||||
312
web/src/app.py
312
web/src/app.py
@ -5,13 +5,14 @@ from pathlib import Path
|
|||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
import plotly.graph_objects as go
|
||||||
import streamlit as st
|
import streamlit as st
|
||||||
import yfinance as yf
|
import yfinance as yf
|
||||||
from streamlit_autorefresh import st_autorefresh
|
from streamlit_autorefresh import st_autorefresh
|
||||||
|
|
||||||
from web_core.analytics import backtest_signals
|
from web_core.analytics import backtest_signals, simulate_trend_trades
|
||||||
from web_core.charting import build_figure
|
from web_core.charting import build_figure
|
||||||
from web_core.constants import INTERVAL_OPTIONS, PERIOD_OPTIONS
|
from web_core.constants import INTERVAL_OPTIONS, PERIOD_OPTIONS, TREND_BEAR, TREND_BULL, TREND_NEUTRAL
|
||||||
from web_core.data import fetch_ohlc, maybe_drop_live_bar
|
from web_core.data import fetch_ohlc, maybe_drop_live_bar
|
||||||
from web_core.exporting import df_for_export
|
from web_core.exporting import df_for_export
|
||||||
from web_core.strategy import classify_bars, detect_trends
|
from web_core.strategy import classify_bars, detect_trends
|
||||||
@ -70,6 +71,11 @@ def normalize_web_settings(raw: dict[str, Any] | None) -> dict[str, Any]:
|
|||||||
"volume_multiplier": 1.0,
|
"volume_multiplier": 1.0,
|
||||||
"gray_fake": True,
|
"gray_fake": True,
|
||||||
"hide_market_closed_gaps": True,
|
"hide_market_closed_gaps": True,
|
||||||
|
"show_live_guide": True,
|
||||||
|
"show_past_behavior": True,
|
||||||
|
"show_trade_markers": True,
|
||||||
|
"focus_chart_on_selected_example": False,
|
||||||
|
"max_training_examples": 20,
|
||||||
"enable_auto_refresh": False,
|
"enable_auto_refresh": False,
|
||||||
"refresh_sec": 60,
|
"refresh_sec": 60,
|
||||||
}
|
}
|
||||||
@ -112,6 +118,19 @@ def normalize_web_settings(raw: dict[str, Any] | None) -> dict[str, Any]:
|
|||||||
raw.get("hide_market_closed_gaps"),
|
raw.get("hide_market_closed_gaps"),
|
||||||
fallback=bool(defaults["hide_market_closed_gaps"]),
|
fallback=bool(defaults["hide_market_closed_gaps"]),
|
||||||
)
|
)
|
||||||
|
show_live_guide = _to_bool(raw.get("show_live_guide"), fallback=bool(defaults["show_live_guide"]))
|
||||||
|
show_past_behavior = _to_bool(raw.get("show_past_behavior"), fallback=bool(defaults["show_past_behavior"]))
|
||||||
|
show_trade_markers = _to_bool(raw.get("show_trade_markers"), fallback=bool(defaults["show_trade_markers"]))
|
||||||
|
focus_chart_on_selected_example = _to_bool(
|
||||||
|
raw.get("focus_chart_on_selected_example"),
|
||||||
|
fallback=bool(defaults["focus_chart_on_selected_example"]),
|
||||||
|
)
|
||||||
|
max_training_examples = _clamp_int(
|
||||||
|
raw.get("max_training_examples"),
|
||||||
|
fallback=int(defaults["max_training_examples"]),
|
||||||
|
minimum=5,
|
||||||
|
maximum=100,
|
||||||
|
)
|
||||||
enable_auto_refresh = _to_bool(raw.get("enable_auto_refresh"), fallback=bool(defaults["enable_auto_refresh"]))
|
enable_auto_refresh = _to_bool(raw.get("enable_auto_refresh"), fallback=bool(defaults["enable_auto_refresh"]))
|
||||||
refresh_sec = _clamp_int(
|
refresh_sec = _clamp_int(
|
||||||
raw.get("refresh_sec"),
|
raw.get("refresh_sec"),
|
||||||
@ -132,6 +151,11 @@ def normalize_web_settings(raw: dict[str, Any] | None) -> dict[str, Any]:
|
|||||||
"volume_multiplier": volume_multiplier,
|
"volume_multiplier": volume_multiplier,
|
||||||
"gray_fake": gray_fake,
|
"gray_fake": gray_fake,
|
||||||
"hide_market_closed_gaps": hide_market_closed_gaps,
|
"hide_market_closed_gaps": hide_market_closed_gaps,
|
||||||
|
"show_live_guide": show_live_guide,
|
||||||
|
"show_past_behavior": show_past_behavior,
|
||||||
|
"show_trade_markers": show_trade_markers,
|
||||||
|
"focus_chart_on_selected_example": focus_chart_on_selected_example,
|
||||||
|
"max_training_examples": max_training_examples,
|
||||||
"enable_auto_refresh": enable_auto_refresh,
|
"enable_auto_refresh": enable_auto_refresh,
|
||||||
"refresh_sec": refresh_sec,
|
"refresh_sec": refresh_sec,
|
||||||
}
|
}
|
||||||
@ -156,16 +180,25 @@ def save_web_settings(settings: dict[str, Any]) -> None:
|
|||||||
|
|
||||||
|
|
||||||
@st.cache_data(show_spinner=False)
|
@st.cache_data(show_spinner=False)
|
||||||
def load_help_markdown() -> str:
|
def load_help_content() -> tuple[str, bool]:
|
||||||
|
help_html_paths = [
|
||||||
|
Path(__file__).with_name("web_core").joinpath("help.html"),
|
||||||
|
Path(__file__).with_name("help.html"),
|
||||||
|
]
|
||||||
|
for help_html_path in help_html_paths:
|
||||||
|
if help_html_path.exists():
|
||||||
|
return help_html_path.read_text(encoding="utf-8"), True
|
||||||
|
|
||||||
onboarding_path = Path(__file__).with_name("ONBOARDING.md")
|
onboarding_path = Path(__file__).with_name("ONBOARDING.md")
|
||||||
if onboarding_path.exists():
|
if onboarding_path.exists():
|
||||||
return onboarding_path.read_text(encoding="utf-8")
|
return onboarding_path.read_text(encoding="utf-8"), False
|
||||||
return "Help content not found."
|
return "Help content not found.", False
|
||||||
|
|
||||||
|
|
||||||
@st.dialog("Help & Quick Start", width="large")
|
@st.dialog("Help & Quick Start", width="large")
|
||||||
def help_dialog() -> None:
|
def help_dialog() -> None:
|
||||||
st.markdown(load_help_markdown())
|
content, is_html = load_help_content()
|
||||||
|
st.markdown(content, unsafe_allow_html=is_html)
|
||||||
|
|
||||||
|
|
||||||
@st.cache_data(show_spinner=False, ttl=3600)
|
@st.cache_data(show_spinner=False, ttl=3600)
|
||||||
@ -239,6 +272,48 @@ def resolve_symbol_identity(symbol: str) -> dict[str, str]:
|
|||||||
return {"symbol": normalized_symbol, "name": "", "exchange": ""}
|
return {"symbol": normalized_symbol, "name": "", "exchange": ""}
|
||||||
|
|
||||||
|
|
||||||
|
def build_live_decision_guide(
|
||||||
|
trend_now: str,
|
||||||
|
previous_trend: str,
|
||||||
|
latest_classification: str,
|
||||||
|
) -> dict[str, str]:
|
||||||
|
if trend_now == TREND_BULL:
|
||||||
|
bias = "Long Bias"
|
||||||
|
action = "Prefer pullback longs and avoid fresh shorts while the bullish trend remains active."
|
||||||
|
invalidation = "Bullish bias is invalidated only after 2 consecutive real bearish bars confirm a reversal."
|
||||||
|
elif trend_now == TREND_BEAR:
|
||||||
|
bias = "Short Bias"
|
||||||
|
action = "Prefer short setups on pops and avoid fresh longs while the bearish trend remains active."
|
||||||
|
invalidation = "Bearish bias is invalidated only after 2 consecutive real bullish bars confirm a reversal."
|
||||||
|
else:
|
||||||
|
bias = "Stand Aside / Neutral"
|
||||||
|
action = "Wait for 2 consecutive real bars in one direction before taking directional exposure."
|
||||||
|
invalidation = "No active trend yet; avoid forcing trades in noisy ranges."
|
||||||
|
|
||||||
|
if trend_now in {TREND_BULL, TREND_BEAR} and trend_now != previous_trend:
|
||||||
|
confirmation = "Fresh Confirmation"
|
||||||
|
confirmation_detail = "Latest closed bar confirmed a new active trend."
|
||||||
|
else:
|
||||||
|
confirmation = "No New Confirmation"
|
||||||
|
confirmation_detail = "Latest closed bar did not confirm a new trend reversal."
|
||||||
|
|
||||||
|
classification_hint = {
|
||||||
|
"real_bull": "Latest bar closed above the previous range.",
|
||||||
|
"real_bear": "Latest bar closed below the previous range.",
|
||||||
|
"fake": "Latest bar stayed inside the previous range (noise).",
|
||||||
|
"unclassified": "Latest bar is unclassified.",
|
||||||
|
}.get(latest_classification, "Latest bar classification unavailable.")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"bias": bias,
|
||||||
|
"action": action,
|
||||||
|
"invalidation": invalidation,
|
||||||
|
"confirmation": confirmation,
|
||||||
|
"confirmation_detail": confirmation_detail,
|
||||||
|
"classification_hint": classification_hint,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
st.set_page_config(page_title="Real Bars vs Fake Bars Analyzer", layout="wide")
|
st.set_page_config(page_title="Real Bars vs Fake Bars Analyzer", layout="wide")
|
||||||
st.title("Real Bars vs Fake Bars Trend Analyzer")
|
st.title("Real Bars vs Fake Bars Trend Analyzer")
|
||||||
@ -354,6 +429,35 @@ def main() -> None:
|
|||||||
value=bool(effective_defaults["hide_market_closed_gaps"]),
|
value=bool(effective_defaults["hide_market_closed_gaps"]),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
st.header("Training & Guidance")
|
||||||
|
show_live_guide = st.checkbox(
|
||||||
|
"Show live decision guide",
|
||||||
|
value=bool(effective_defaults["show_live_guide"]),
|
||||||
|
help="Shows a plain-English interpretation of current trend state and confirmation status.",
|
||||||
|
)
|
||||||
|
show_past_behavior = st.checkbox(
|
||||||
|
"Show past behavior examples",
|
||||||
|
value=bool(effective_defaults["show_past_behavior"]),
|
||||||
|
help="Displays historical example trades based on trend confirmation and reversal signals.",
|
||||||
|
)
|
||||||
|
show_trade_markers = st.checkbox(
|
||||||
|
"Overlay example entries/exits on chart",
|
||||||
|
value=bool(effective_defaults["show_trade_markers"]),
|
||||||
|
help="Adds entry/exit markers for the training examples onto the main chart.",
|
||||||
|
)
|
||||||
|
focus_chart_on_selected_example = st.checkbox(
|
||||||
|
"Focus chart on selected example",
|
||||||
|
value=bool(effective_defaults["focus_chart_on_selected_example"]),
|
||||||
|
help="When a training row is selected, zooms chart around that trade window.",
|
||||||
|
)
|
||||||
|
max_training_examples = st.slider(
|
||||||
|
"Max training examples",
|
||||||
|
5,
|
||||||
|
100,
|
||||||
|
int(effective_defaults["max_training_examples"]),
|
||||||
|
5,
|
||||||
|
)
|
||||||
|
|
||||||
st.header("Monitoring")
|
st.header("Monitoring")
|
||||||
enable_auto_refresh = st.checkbox("Auto-refresh", value=bool(effective_defaults["enable_auto_refresh"]))
|
enable_auto_refresh = st.checkbox("Auto-refresh", value=bool(effective_defaults["enable_auto_refresh"]))
|
||||||
refresh_sec = st.slider("Refresh interval (seconds)", 10, 600, int(effective_defaults["refresh_sec"]), 10)
|
refresh_sec = st.slider("Refresh interval (seconds)", 10, 600, int(effective_defaults["refresh_sec"]), 10)
|
||||||
@ -375,6 +479,11 @@ def main() -> None:
|
|||||||
"volume_multiplier": float(volume_multiplier),
|
"volume_multiplier": float(volume_multiplier),
|
||||||
"gray_fake": bool(gray_fake),
|
"gray_fake": bool(gray_fake),
|
||||||
"hide_market_closed_gaps": bool(hide_market_closed_gaps),
|
"hide_market_closed_gaps": bool(hide_market_closed_gaps),
|
||||||
|
"show_live_guide": bool(show_live_guide),
|
||||||
|
"show_past_behavior": bool(show_past_behavior),
|
||||||
|
"show_trade_markers": bool(show_trade_markers),
|
||||||
|
"focus_chart_on_selected_example": bool(focus_chart_on_selected_example),
|
||||||
|
"max_training_examples": int(max_training_examples),
|
||||||
"enable_auto_refresh": bool(enable_auto_refresh),
|
"enable_auto_refresh": bool(enable_auto_refresh),
|
||||||
"refresh_sec": int(refresh_sec),
|
"refresh_sec": int(refresh_sec),
|
||||||
}
|
}
|
||||||
@ -433,6 +542,25 @@ def main() -> None:
|
|||||||
c3.metric("Real Bearish Bars", bear_count)
|
c3.metric("Real Bearish Bars", bear_count)
|
||||||
c4.metric("Fake Bars", fake_count)
|
c4.metric("Fake Bars", fake_count)
|
||||||
|
|
||||||
|
previous_trend = str(analyzed.iloc[-2]["trend_state"]) if len(analyzed) > 1 else TREND_NEUTRAL
|
||||||
|
latest_classification = str(latest["classification"])
|
||||||
|
live_guide = build_live_decision_guide(
|
||||||
|
trend_now=trend_now,
|
||||||
|
previous_trend=previous_trend,
|
||||||
|
latest_classification=latest_classification,
|
||||||
|
)
|
||||||
|
|
||||||
|
if show_live_guide:
|
||||||
|
st.subheader("Live Decision Guide")
|
||||||
|
g1, g2, g3 = st.columns(3)
|
||||||
|
g1.metric("Bias", live_guide["bias"])
|
||||||
|
g2.metric("Signal Status", live_guide["confirmation"])
|
||||||
|
g3.metric("Latest Bar", latest_classification)
|
||||||
|
st.caption(live_guide["confirmation_detail"])
|
||||||
|
st.caption(live_guide["classification_hint"])
|
||||||
|
st.info(live_guide["action"])
|
||||||
|
st.caption(f"Invalidation rule: {live_guide['invalidation']}")
|
||||||
|
|
||||||
alert_key = f"{symbol}-{interval}-{period}"
|
alert_key = f"{symbol}-{interval}-{period}"
|
||||||
newest_event = events[-1].event if events else ""
|
newest_event = events[-1].event if events else ""
|
||||||
previous_event = st.session_state.get(f"last_event-{alert_key}", "")
|
previous_event = st.session_state.get(f"last_event-{alert_key}", "")
|
||||||
@ -440,12 +568,184 @@ def main() -> None:
|
|||||||
st.warning(f"Alert: {newest_event}")
|
st.warning(f"Alert: {newest_event}")
|
||||||
st.session_state[f"last_event-{alert_key}"] = newest_event
|
st.session_state[f"last_event-{alert_key}"] = newest_event
|
||||||
|
|
||||||
|
example_trades = simulate_trend_trades(analyzed, max_examples=int(max_training_examples))
|
||||||
|
selected_trade: pd.Series | None = None
|
||||||
|
|
||||||
|
if show_past_behavior:
|
||||||
|
st.subheader("Past Behavior Examples (Training)")
|
||||||
|
if example_trades.empty:
|
||||||
|
st.info("No closed example trades yet. Expand the period/timeframe to include more trend reversals.")
|
||||||
|
else:
|
||||||
|
wins = int((example_trades["outcome"] == "Win").sum())
|
||||||
|
losses = int((example_trades["outcome"] == "Loss").sum())
|
||||||
|
total_examples = int(len(example_trades))
|
||||||
|
win_rate = round((wins / total_examples) * 100.0, 2) if total_examples else 0.0
|
||||||
|
avg_pnl = round(float(example_trades["pnl_pct"].mean()), 2)
|
||||||
|
|
||||||
|
t1, t2, t3, t4 = st.columns(4)
|
||||||
|
t1.metric("Closed Examples", total_examples)
|
||||||
|
t2.metric("Wins / Losses", f"{wins} / {losses}")
|
||||||
|
t3.metric("Example Win Rate", f"{win_rate}%")
|
||||||
|
t4.metric("Avg P/L per Example", f"{avg_pnl}%")
|
||||||
|
|
||||||
|
latest_example = example_trades.iloc[-1]
|
||||||
|
st.caption(
|
||||||
|
"Latest closed example: "
|
||||||
|
f"{latest_example['direction']} from {latest_example['entry_timestamp']} "
|
||||||
|
f"to {latest_example['exit_timestamp']} "
|
||||||
|
f"({latest_example['bars_held']} bars, {latest_example['pnl_pct']}%)."
|
||||||
|
)
|
||||||
|
st.caption("Click a row below to highlight that specific example on the chart.")
|
||||||
|
st.caption(
|
||||||
|
"Example method: enter on trend confirmation bar close and exit on opposite trend confirmation."
|
||||||
|
)
|
||||||
|
|
||||||
|
display_examples_raw = example_trades.iloc[::-1].reset_index(drop=True)
|
||||||
|
display_examples = display_examples_raw.copy()
|
||||||
|
display_examples["entry_timestamp"] = display_examples["entry_timestamp"].astype(str)
|
||||||
|
display_examples["exit_timestamp"] = display_examples["exit_timestamp"].astype(str)
|
||||||
|
|
||||||
|
table_event = st.dataframe(
|
||||||
|
display_examples,
|
||||||
|
use_container_width=True,
|
||||||
|
on_select="rerun",
|
||||||
|
selection_mode="single-row",
|
||||||
|
key=f"training-examples-{alert_key}",
|
||||||
|
)
|
||||||
|
selected_rows: list[int] = []
|
||||||
|
try:
|
||||||
|
selected_rows = list(table_event.selection.rows)
|
||||||
|
except Exception:
|
||||||
|
selected_rows = []
|
||||||
|
|
||||||
|
selected_row_state_key = f"selected-training-row-{alert_key}"
|
||||||
|
if selected_rows:
|
||||||
|
selected_row_idx = int(selected_rows[0])
|
||||||
|
st.session_state[selected_row_state_key] = selected_row_idx
|
||||||
|
else:
|
||||||
|
selected_row_idx = int(st.session_state.get(selected_row_state_key, 0))
|
||||||
|
|
||||||
|
if selected_row_idx < 0 or selected_row_idx >= len(display_examples_raw):
|
||||||
|
selected_row_idx = 0
|
||||||
|
|
||||||
|
selected_trade = display_examples_raw.iloc[selected_row_idx]
|
||||||
|
direction = str(selected_trade["direction"])
|
||||||
|
entry_ts = str(selected_trade["entry_timestamp"])
|
||||||
|
exit_ts = str(selected_trade["exit_timestamp"])
|
||||||
|
entry_price = float(selected_trade["entry_price"])
|
||||||
|
exit_price = float(selected_trade["exit_price"])
|
||||||
|
bars_held = int(selected_trade["bars_held"])
|
||||||
|
pnl_pct = float(selected_trade["pnl_pct"])
|
||||||
|
outcome = str(selected_trade["outcome"])
|
||||||
|
|
||||||
|
pnl_text = f"+{pnl_pct}%" if pnl_pct > 0 else f"{pnl_pct}%"
|
||||||
|
result_text = "profit" if pnl_pct > 0 else ("loss" if pnl_pct < 0 else "flat result")
|
||||||
|
st.info(
|
||||||
|
f"Selected example explained: {direction} entry at {entry_ts} ({entry_price}), "
|
||||||
|
f"exit at {exit_ts} ({exit_price}), held {bars_held} bars, {result_text} {pnl_text}, outcome: {outcome}."
|
||||||
|
)
|
||||||
|
|
||||||
fig = build_figure(
|
fig = build_figure(
|
||||||
analyzed,
|
analyzed,
|
||||||
gray_fake=gray_fake,
|
gray_fake=gray_fake,
|
||||||
interval=interval,
|
interval=interval,
|
||||||
hide_market_closed_gaps=hide_market_closed_gaps,
|
hide_market_closed_gaps=hide_market_closed_gaps,
|
||||||
)
|
)
|
||||||
|
if show_trade_markers and not example_trades.empty:
|
||||||
|
long_entries = example_trades[example_trades["direction"] == "LONG"]
|
||||||
|
short_entries = example_trades[example_trades["direction"] == "SHORT"]
|
||||||
|
win_exits = example_trades[example_trades["outcome"] == "Win"]
|
||||||
|
non_win_exits = example_trades[example_trades["outcome"] != "Win"]
|
||||||
|
|
||||||
|
if not long_entries.empty:
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=long_entries["entry_timestamp"],
|
||||||
|
y=long_entries["entry_price"],
|
||||||
|
mode="markers",
|
||||||
|
name="Example Entry (Long)",
|
||||||
|
marker=dict(color="#1565C0", size=9, symbol="circle"),
|
||||||
|
),
|
||||||
|
row=1,
|
||||||
|
col=1,
|
||||||
|
)
|
||||||
|
if not short_entries.empty:
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=short_entries["entry_timestamp"],
|
||||||
|
y=short_entries["entry_price"],
|
||||||
|
mode="markers",
|
||||||
|
name="Example Entry (Short)",
|
||||||
|
marker=dict(color="#EF6C00", size=9, symbol="diamond"),
|
||||||
|
),
|
||||||
|
row=1,
|
||||||
|
col=1,
|
||||||
|
)
|
||||||
|
if not win_exits.empty:
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=win_exits["exit_timestamp"],
|
||||||
|
y=win_exits["exit_price"],
|
||||||
|
mode="markers",
|
||||||
|
name="Example Exit (Win)",
|
||||||
|
marker=dict(color="#2E7D32", size=10, symbol="x"),
|
||||||
|
),
|
||||||
|
row=1,
|
||||||
|
col=1,
|
||||||
|
)
|
||||||
|
if not non_win_exits.empty:
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=non_win_exits["exit_timestamp"],
|
||||||
|
y=non_win_exits["exit_price"],
|
||||||
|
mode="markers",
|
||||||
|
name="Example Exit (Loss/Flat)",
|
||||||
|
marker=dict(color="#C62828", size=10, symbol="x"),
|
||||||
|
),
|
||||||
|
row=1,
|
||||||
|
col=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if selected_trade is not None:
|
||||||
|
selected_entry_ts = pd.Timestamp(selected_trade["entry_timestamp"])
|
||||||
|
selected_exit_ts = pd.Timestamp(selected_trade["exit_timestamp"])
|
||||||
|
selected_entry_price = float(selected_trade["entry_price"])
|
||||||
|
selected_exit_price = float(selected_trade["exit_price"])
|
||||||
|
selected_direction = str(selected_trade["direction"])
|
||||||
|
selected_outcome = str(selected_trade["outcome"])
|
||||||
|
|
||||||
|
path_color = "#43A047" if selected_outcome == "Win" else ("#EF6C00" if selected_outcome == "Flat" else "#E53935")
|
||||||
|
window_fill = "#BBDEFB" if selected_direction == "LONG" else "#FFE0B2"
|
||||||
|
|
||||||
|
fig.add_vrect(
|
||||||
|
x0=selected_entry_ts,
|
||||||
|
x1=selected_exit_ts,
|
||||||
|
fillcolor=window_fill,
|
||||||
|
opacity=0.18,
|
||||||
|
line_width=0,
|
||||||
|
row=1,
|
||||||
|
col=1,
|
||||||
|
)
|
||||||
|
fig.add_trace(
|
||||||
|
go.Scatter(
|
||||||
|
x=[selected_entry_ts, selected_exit_ts],
|
||||||
|
y=[selected_entry_price, selected_exit_price],
|
||||||
|
mode="lines+markers",
|
||||||
|
name="Selected Example Path",
|
||||||
|
line=dict(color=path_color, width=3, dash="dot"),
|
||||||
|
marker=dict(color=path_color, size=11, symbol="star"),
|
||||||
|
),
|
||||||
|
row=1,
|
||||||
|
col=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
if focus_chart_on_selected_example:
|
||||||
|
entry_pos = int(analyzed.index.get_indexer([selected_entry_ts], method="nearest")[0])
|
||||||
|
exit_pos = int(analyzed.index.get_indexer([selected_exit_ts], method="nearest")[0])
|
||||||
|
left_pos = max(0, min(entry_pos, exit_pos) - 4)
|
||||||
|
right_pos = min(len(analyzed) - 1, max(entry_pos, exit_pos) + 4)
|
||||||
|
fig.update_xaxes(range=[analyzed.index[left_pos], analyzed.index[right_pos]])
|
||||||
|
|
||||||
st.plotly_chart(fig, use_container_width=True)
|
st.plotly_chart(fig, use_container_width=True)
|
||||||
|
|
||||||
bt = backtest_signals(analyzed)
|
bt = backtest_signals(analyzed)
|
||||||
|
|||||||
81
web/src/tests/test_analytics.py
Normal file
81
web/src/tests/test_analytics.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from web_core.analytics import simulate_trend_trades
|
||||||
|
from web_core.constants import TREND_BEAR, TREND_BULL, TREND_NEUTRAL
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulate_trend_trades_returns_empty_when_insufficient_rows() -> None:
|
||||||
|
idx = pd.date_range("2025-01-01", periods=3, freq="D")
|
||||||
|
df = pd.DataFrame(
|
||||||
|
{
|
||||||
|
"Close": [100.0, 101.0, 102.0],
|
||||||
|
"trend_state": [TREND_NEUTRAL, TREND_BULL, TREND_BULL],
|
||||||
|
},
|
||||||
|
index=idx,
|
||||||
|
)
|
||||||
|
|
||||||
|
out = simulate_trend_trades(df)
|
||||||
|
|
||||||
|
assert out.empty
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulate_trend_trades_closes_trade_on_opposite_signal() -> None:
|
||||||
|
idx = pd.date_range("2025-01-01", periods=7, freq="D")
|
||||||
|
df = pd.DataFrame(
|
||||||
|
{
|
||||||
|
"Close": [95.0, 98.0, 100.0, 102.0, 104.0, 110.0, 108.0],
|
||||||
|
"trend_state": [
|
||||||
|
TREND_NEUTRAL,
|
||||||
|
TREND_NEUTRAL,
|
||||||
|
TREND_BULL,
|
||||||
|
TREND_BULL,
|
||||||
|
TREND_BULL,
|
||||||
|
TREND_BEAR,
|
||||||
|
TREND_BEAR,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
index=idx,
|
||||||
|
)
|
||||||
|
|
||||||
|
out = simulate_trend_trades(df)
|
||||||
|
|
||||||
|
assert len(out) == 1
|
||||||
|
trade = out.iloc[0]
|
||||||
|
assert trade["direction"] == "LONG"
|
||||||
|
assert trade["entry_timestamp"] == idx[2]
|
||||||
|
assert trade["exit_timestamp"] == idx[5]
|
||||||
|
assert trade["bars_held"] == 3
|
||||||
|
assert trade["pnl_pct"] == 10.0
|
||||||
|
assert trade["outcome"] == "Win"
|
||||||
|
|
||||||
|
|
||||||
|
def test_simulate_trend_trades_handles_multiple_flips_and_max_examples() -> None:
|
||||||
|
idx = pd.date_range("2025-01-01", periods=7, freq="D")
|
||||||
|
df = pd.DataFrame(
|
||||||
|
{
|
||||||
|
"Close": [90.0, 100.0, 103.0, 95.0, 92.0, 80.0, 82.0],
|
||||||
|
"trend_state": [
|
||||||
|
TREND_NEUTRAL,
|
||||||
|
TREND_BULL,
|
||||||
|
TREND_BULL,
|
||||||
|
TREND_BEAR,
|
||||||
|
TREND_BEAR,
|
||||||
|
TREND_BULL,
|
||||||
|
TREND_BULL,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
index=idx,
|
||||||
|
)
|
||||||
|
|
||||||
|
out = simulate_trend_trades(df, max_examples=1)
|
||||||
|
|
||||||
|
assert len(out) == 1
|
||||||
|
trade = out.iloc[0]
|
||||||
|
assert trade["direction"] == "SHORT"
|
||||||
|
assert trade["entry_timestamp"] == idx[3]
|
||||||
|
assert trade["exit_timestamp"] == idx[5]
|
||||||
|
assert trade["bars_held"] == 2
|
||||||
|
assert trade["pnl_pct"] == 15.79
|
||||||
|
assert trade["outcome"] == "Win"
|
||||||
@ -35,3 +35,95 @@ def backtest_signals(df: pd.DataFrame) -> dict[str, float | int]:
|
|||||||
trades = wins + losses
|
trades = wins + losses
|
||||||
win_rate = (wins / trades * 100.0) if trades else 0.0
|
win_rate = (wins / trades * 100.0) if trades else 0.0
|
||||||
return {"trades": trades, "wins": wins, "losses": losses, "win_rate": round(win_rate, 2)}
|
return {"trades": trades, "wins": wins, "losses": losses, "win_rate": round(win_rate, 2)}
|
||||||
|
|
||||||
|
|
||||||
|
def simulate_trend_trades(df: pd.DataFrame, max_examples: int = 20) -> pd.DataFrame:
|
||||||
|
if len(df) < 4:
|
||||||
|
return pd.DataFrame(
|
||||||
|
columns=[
|
||||||
|
"direction",
|
||||||
|
"entry_timestamp",
|
||||||
|
"exit_timestamp",
|
||||||
|
"entry_price",
|
||||||
|
"exit_price",
|
||||||
|
"bars_held",
|
||||||
|
"pnl_pct",
|
||||||
|
"outcome",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
trend_series = df["trend_state"]
|
||||||
|
trend_change = trend_series != trend_series.shift(1)
|
||||||
|
signal_idx = df.index[trend_change & trend_series.isin([TREND_BULL, TREND_BEAR])]
|
||||||
|
|
||||||
|
direction_by_trend = {
|
||||||
|
TREND_BULL: "LONG",
|
||||||
|
TREND_BEAR: "SHORT",
|
||||||
|
}
|
||||||
|
|
||||||
|
open_trade: dict[str, object] | None = None
|
||||||
|
closed_trades: list[dict[str, object]] = []
|
||||||
|
|
||||||
|
for idx in signal_idx:
|
||||||
|
pos = int(df.index.get_loc(idx))
|
||||||
|
trend_now = str(df.iloc[pos]["trend_state"])
|
||||||
|
direction = direction_by_trend[trend_now]
|
||||||
|
close_price = float(df.iloc[pos]["Close"])
|
||||||
|
|
||||||
|
if open_trade is None:
|
||||||
|
open_trade = {
|
||||||
|
"direction": direction,
|
||||||
|
"entry_timestamp": idx,
|
||||||
|
"entry_pos": pos,
|
||||||
|
"entry_price": close_price,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
|
if open_trade["direction"] == direction:
|
||||||
|
continue
|
||||||
|
|
||||||
|
entry_price = float(open_trade["entry_price"])
|
||||||
|
if str(open_trade["direction"]) == "LONG":
|
||||||
|
pnl_pct = ((close_price - entry_price) / entry_price) * 100.0
|
||||||
|
else:
|
||||||
|
pnl_pct = ((entry_price - close_price) / entry_price) * 100.0
|
||||||
|
|
||||||
|
closed_trades.append(
|
||||||
|
{
|
||||||
|
"direction": str(open_trade["direction"]),
|
||||||
|
"entry_timestamp": open_trade["entry_timestamp"],
|
||||||
|
"exit_timestamp": idx,
|
||||||
|
"entry_price": round(entry_price, 4),
|
||||||
|
"exit_price": round(close_price, 4),
|
||||||
|
"bars_held": pos - int(open_trade["entry_pos"]),
|
||||||
|
"pnl_pct": round(pnl_pct, 2),
|
||||||
|
"outcome": "Win" if pnl_pct > 0 else ("Loss" if pnl_pct < 0 else "Flat"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Flip into the new direction at the same confirmed reversal bar.
|
||||||
|
open_trade = {
|
||||||
|
"direction": direction,
|
||||||
|
"entry_timestamp": idx,
|
||||||
|
"entry_pos": pos,
|
||||||
|
"entry_price": close_price,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not closed_trades:
|
||||||
|
return pd.DataFrame(
|
||||||
|
columns=[
|
||||||
|
"direction",
|
||||||
|
"entry_timestamp",
|
||||||
|
"exit_timestamp",
|
||||||
|
"entry_price",
|
||||||
|
"exit_price",
|
||||||
|
"bars_held",
|
||||||
|
"pnl_pct",
|
||||||
|
"outcome",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
trades_df = pd.DataFrame(closed_trades)
|
||||||
|
if max_examples > 0 and len(trades_df) > max_examples:
|
||||||
|
trades_df = trades_df.iloc[-max_examples:].copy()
|
||||||
|
return trades_df
|
||||||
|
|||||||
80
web/src/web_core/help.html
Normal file
80
web/src/web_core/help.html
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
<h1>ManeshTrader Help</h1>
|
||||||
|
|
||||||
|
<h2>What This Tool Does</h2>
|
||||||
|
<p>
|
||||||
|
The app classifies each closed candle as <code>real_bull</code>, <code>real_bear</code>, or
|
||||||
|
<code>fake</code>, then tracks trend state from only the real bars.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>2 consecutive real bullish bars: bullish trend active</li>
|
||||||
|
<li>2 consecutive real bearish bars: bearish trend active</li>
|
||||||
|
<li>Reversal also requires 2 consecutive opposite real bars</li>
|
||||||
|
<li>Fake bars do not reverse trend on their own</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Quick Start</h2>
|
||||||
|
<ol>
|
||||||
|
<li>Choose <code>Symbol</code> (example: <code>AAPL</code> or <code>BTC-USD</code>).</li>
|
||||||
|
<li>Start with <code>Timeframe = 1d</code> and <code>Period = 6mo</code>.</li>
|
||||||
|
<li>Keep <code>Ignore potentially live last bar</code> ON.</li>
|
||||||
|
<li>Enable these in <code>Training & Guidance</code>:
|
||||||
|
<ul>
|
||||||
|
<li><code>Show live decision guide</code></li>
|
||||||
|
<li><code>Show past behavior examples</code></li>
|
||||||
|
<li><code>Overlay example entries/exits on chart</code></li>
|
||||||
|
<li><code>Focus chart on selected example</code> (recommended for learning)</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
<li>Set <code>Max training examples</code> to 20.</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>How To Use The New Training Features</h2>
|
||||||
|
|
||||||
|
<h3>1) Live Decision Guide (what to do now)</h3>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Bias</strong>: Long, Short, or Stand Aside / Neutral.</li>
|
||||||
|
<li><strong>Signal Status</strong>: Fresh confirmation or no new confirmation.</li>
|
||||||
|
<li><strong>Latest Bar</strong>: How the newest closed candle was classified.</li>
|
||||||
|
<li><strong>Action + Invalidation</strong>: Practical rule for what to prefer and when to stop.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>2) Past Behavior Examples (what would have happened)</h3>
|
||||||
|
<p>
|
||||||
|
Examples are hypothetical and use one rule:
|
||||||
|
enter at trend-confirmation bar close, exit at opposite trend-confirmation bar close.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><code>direction</code>: LONG (upside bias) or SHORT (downside bias)</li>
|
||||||
|
<li><code>entry_timestamp</code> / <code>exit_timestamp</code>: example start/end time</li>
|
||||||
|
<li><code>entry_price</code> / <code>exit_price</code>: example prices</li>
|
||||||
|
<li><code>bars_held</code>: number of candles held</li>
|
||||||
|
<li><code>pnl_pct</code>: percent gain/loss for that example</li>
|
||||||
|
<li><code>outcome</code>: Win, Loss, or Flat</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h3>3) Click A Row To Highlight It On Chart</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Click any row in the <strong>Past Behavior Examples</strong> table.</li>
|
||||||
|
<li>The chart highlights that exact entry-to-exit window.</li>
|
||||||
|
<li>A path line shows the selected trade from entry price to exit price.</li>
|
||||||
|
<li>A plain-English explanation appears under the table for the selected row.</li>
|
||||||
|
<li>If <code>Focus chart on selected example</code> is ON, the chart zooms into that trade.</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<h2>Marker Legend</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Green triangle-up: real bullish bar</li>
|
||||||
|
<li>Red triangle-down: real bearish bar</li>
|
||||||
|
<li>Blue circle: example long entry</li>
|
||||||
|
<li>Orange diamond: example short entry</li>
|
||||||
|
<li>Green X: example winning exit</li>
|
||||||
|
<li>Red X: example loss/flat exit</li>
|
||||||
|
<li>Star path + highlighted window: currently selected example row</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Important Notes</h2>
|
||||||
|
<ul>
|
||||||
|
<li>Analysis and training only. No trade execution.</li>
|
||||||
|
<li>Educational examples are simplified and not financial advice.</li>
|
||||||
|
<li>Use with risk controls before any real-money decision.</li>
|
||||||
|
</ul>
|
||||||
Loading…
Reference in New Issue
Block a user