Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
e941ef0d3c
commit
a5f6200636
@ -17,6 +17,11 @@ In-app help popup:
|
|||||||
- Uses a multi-screen, child-friendly guide implemented in `web/src/web_core/ui/help_content.py`
|
- Uses a multi-screen, child-friendly guide implemented in `web/src/web_core/ui/help_content.py`
|
||||||
- Includes dedicated screens for core setup, filters, training mode, backtest controls, and advanced panels
|
- Includes dedicated screens for core setup, filters, training mode, backtest controls, and advanced panels
|
||||||
|
|
||||||
|
Beginner interpretation panel:
|
||||||
|
- Main page always shows `What This Tool Means (Beginner Training)`
|
||||||
|
- Includes plain-English definitions of top metrics and a historical snapshot table for `1M`, `3M`, `6M`, `1Y`
|
||||||
|
- Each row includes a simple interpretation so non-traders can quickly understand whether a window was directional or noisy
|
||||||
|
|
||||||
## 2) Quick Start (Recommended)
|
## 2) Quick Start (Recommended)
|
||||||
From project root:
|
From project root:
|
||||||
|
|
||||||
@ -93,11 +98,15 @@ Tip: sign and notarize before sharing broadly, so macOS trust prompts are reduce
|
|||||||
- Real Bullish Bars
|
- Real Bullish Bars
|
||||||
- Real Bearish Bars
|
- Real Bearish Bars
|
||||||
- Fake Bars
|
- Fake Bars
|
||||||
13. Read `Trend Events` for starts and reversals.
|
13. Read `What This Tool Means (Beginner Training)`:
|
||||||
14. Use `Live Decision Guide` to translate trend state into a practical bias/action/invalidation workflow.
|
- Open `Plain-English metric guide` for quick definitions.
|
||||||
15. Keep `Show past behavior examples` ON while learning to review historical entry/exit outcomes.
|
- Review `1M / 3M / 6M / 1Y` snapshot rows to see how this symbol behaved recently.
|
||||||
16. Set `Display Timezone (US)` to your preferred timezone (default is `America/Chicago`, CST/CDT).
|
- Use `What this says` to understand if conditions were trend-friendly or choppy.
|
||||||
17. Choose `Use 24-hour time` ON for `13:00` style, or OFF for `1:00 PM` style.
|
14. Read `Trend Events` for starts and reversals.
|
||||||
|
15. Use `Live Decision Guide` to translate trend state into a practical bias/action/invalidation workflow.
|
||||||
|
16. Keep `Show past behavior examples` ON while learning to review historical entry/exit outcomes.
|
||||||
|
17. Set `Display Timezone (US)` to your preferred timezone (default is `America/Chicago`, CST/CDT).
|
||||||
|
18. Choose `Use 24-hour time` ON for `13:00` style, or OFF for `1:00 PM` style.
|
||||||
|
|
||||||
## 4.1) Advanced Features (Optional)
|
## 4.1) Advanced Features (Optional)
|
||||||
- `Advanced Signals`:
|
- `Advanced Signals`:
|
||||||
|
|||||||
@ -170,6 +170,11 @@ Gap handling (`hide_market_closed_gaps`):
|
|||||||
- `Help / Quick Start`
|
- `Help / Quick Start`
|
||||||
- Help appears in a dialog with multiple navigable screens (screen picker + previous/next).
|
- Help appears in a dialog with multiple navigable screens (screen picker + previous/next).
|
||||||
- Help copy is intentionally beginner-friendly and explains each major sidebar control group, including detailed backtest controls and why each setting matters.
|
- Help copy is intentionally beginner-friendly and explains each major sidebar control group, including detailed backtest controls and why each setting matters.
|
||||||
|
- Main page includes a beginner training block:
|
||||||
|
- `What This Tool Means (Beginner Training)`
|
||||||
|
- Plain-English definitions for top metrics (`Current Trend`, real/fake bars, `Signal Quality`, `Regime`, `Recent Fake Ratio`).
|
||||||
|
- Historical learning table with trailing windows (`1M`, `3M`, `6M`, `1Y`) computed from loaded data.
|
||||||
|
- Per-window interpretation text that summarizes whether behavior was trend-dominant, bearish-dominant, or choppy/noisy.
|
||||||
- The onboarding markdown remains project documentation; in-app help content is rendered from `web/src/web_core/ui/help_content.py`.
|
- The onboarding markdown remains project documentation; in-app help content is rendered from `web/src/web_core/ui/help_content.py`.
|
||||||
|
|
||||||
## 9. Outputs
|
## 9. Outputs
|
||||||
@ -178,6 +183,8 @@ Gap handling (`hide_market_closed_gaps`):
|
|||||||
- real bullish count
|
- real bullish count
|
||||||
- real bearish count
|
- real bearish count
|
||||||
- fake count
|
- fake count
|
||||||
|
- beginner training guide (plain-English metric glossary)
|
||||||
|
- historical learning snapshots (`1M`, `3M`, `6M`, `1Y`) including price change, bar-type counts, trend flips, and interpretation
|
||||||
- Live decision guide (optional):
|
- Live decision guide (optional):
|
||||||
- bias (long/short/neutral)
|
- bias (long/short/neutral)
|
||||||
- signal confirmation status
|
- signal confirmation status
|
||||||
|
|||||||
@ -36,7 +36,7 @@ from web_core.auth.profile_store import (
|
|||||||
from web_core.ui.sidebar_ui import render_sidebar
|
from web_core.ui.sidebar_ui import render_sidebar
|
||||||
from web_core.strategy import classify_bars, detect_trends
|
from web_core.strategy import classify_bars, detect_trends
|
||||||
from web_core.market.symbols import resolve_symbol_identity
|
from web_core.market.symbols import resolve_symbol_identity
|
||||||
from web_core.ui.training_ui import render_training_panel
|
from web_core.ui.training_ui import render_beginner_training_panel, render_training_panel
|
||||||
from web_core.time_display import format_timestamp
|
from web_core.time_display import format_timestamp
|
||||||
|
|
||||||
|
|
||||||
@ -180,6 +180,12 @@ def main() -> None:
|
|||||||
q1.metric("Signal Quality", f"{quality['score']} ({quality['label']})")
|
q1.metric("Signal Quality", f"{quality['score']} ({quality['label']})")
|
||||||
q2.metric("Regime", regime_label)
|
q2.metric("Regime", regime_label)
|
||||||
q3.metric("Recent Fake Ratio", f"{quality['fake_ratio']}%")
|
q3.metric("Recent Fake Ratio", f"{quality['fake_ratio']}%")
|
||||||
|
render_beginner_training_panel(
|
||||||
|
analyzed=analyzed_view,
|
||||||
|
trend_now=trend_now,
|
||||||
|
signal_quality=quality,
|
||||||
|
regime_label=regime_label,
|
||||||
|
)
|
||||||
|
|
||||||
run_advanced_panels = bool(sidebar_settings.get("advanced_auto_run", False) or sidebar_settings.get("run_advanced_now", False))
|
run_advanced_panels = bool(sidebar_settings.get("advanced_auto_run", False) or sidebar_settings.get("run_advanced_now", False))
|
||||||
if (bool(sidebar_settings["enable_multi_tf_confirmation"]) or bool(sidebar_settings["enable_compare_symbols"])) and not run_advanced_panels:
|
if (bool(sidebar_settings["enable_multi_tf_confirmation"]) or bool(sidebar_settings["enable_compare_symbols"])) and not run_advanced_panels:
|
||||||
|
|||||||
42
web/src/tests/test_training_ui.py
Normal file
42
web/src/tests/test_training_ui.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
from web_core.constants import TREND_BEAR, TREND_BULL, TREND_NEUTRAL
|
||||||
|
from web_core.ui.training_ui import build_learning_window_rows
|
||||||
|
|
||||||
|
|
||||||
|
def _make_analyzed(start: str = "2025-01-01", periods: int = 420) -> pd.DataFrame:
|
||||||
|
idx = pd.date_range(start, periods=periods, freq="D", tz="UTC")
|
||||||
|
closes = [100.0 + (i * 0.2) for i in range(periods)]
|
||||||
|
classifications_cycle = ["real_bull", "fake", "real_bear", "fake", "real_bull"]
|
||||||
|
trend_cycle = [TREND_BULL, TREND_BULL, TREND_BEAR, TREND_BEAR, TREND_NEUTRAL]
|
||||||
|
classifications = [classifications_cycle[i % len(classifications_cycle)] for i in range(periods)]
|
||||||
|
trends = [trend_cycle[i % len(trend_cycle)] for i in range(periods)]
|
||||||
|
return pd.DataFrame({"Close": closes, "classification": classifications, "trend_state": trends}, index=idx)
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_learning_window_rows_includes_standard_windows() -> None:
|
||||||
|
analyzed = _make_analyzed()
|
||||||
|
rows = build_learning_window_rows(analyzed)
|
||||||
|
|
||||||
|
assert list(rows["Window"]) == ["1M", "3M", "6M", "1Y"]
|
||||||
|
assert set(rows.columns) == {
|
||||||
|
"Window",
|
||||||
|
"Bars",
|
||||||
|
"Price Change %",
|
||||||
|
"Real Bull Bars",
|
||||||
|
"Real Bear Bars",
|
||||||
|
"Fake Bars",
|
||||||
|
"Trend Flips",
|
||||||
|
"What this says",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_learning_window_rows_fallbacks_with_short_history() -> None:
|
||||||
|
analyzed = _make_analyzed(periods=10)
|
||||||
|
rows = build_learning_window_rows(analyzed)
|
||||||
|
|
||||||
|
assert len(rows) == 1
|
||||||
|
assert rows.iloc[0]["Window"] == "All data"
|
||||||
|
assert int(rows.iloc[0]["Bars"]) == 10
|
||||||
@ -6,6 +6,126 @@ import streamlit as st
|
|||||||
from web_core.time_display import format_timestamp
|
from web_core.time_display import format_timestamp
|
||||||
|
|
||||||
|
|
||||||
|
def build_learning_window_rows(analyzed: pd.DataFrame) -> pd.DataFrame:
|
||||||
|
if analyzed.empty:
|
||||||
|
return pd.DataFrame(
|
||||||
|
columns=[
|
||||||
|
"Window",
|
||||||
|
"Bars",
|
||||||
|
"Price Change %",
|
||||||
|
"Real Bull Bars",
|
||||||
|
"Real Bear Bars",
|
||||||
|
"Fake Bars",
|
||||||
|
"Trend Flips",
|
||||||
|
"What this says",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
indexed = analyzed.sort_index()
|
||||||
|
|
||||||
|
def _all_data_row(message: str) -> pd.DataFrame:
|
||||||
|
first_close = float(indexed.iloc[0]["Close"]) if len(indexed) else 0.0
|
||||||
|
last_close = float(indexed.iloc[-1]["Close"]) if len(indexed) else 0.0
|
||||||
|
change_pct = round(((last_close - first_close) / first_close) * 100.0, 2) if first_close else 0.0
|
||||||
|
return pd.DataFrame(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Window": "All data",
|
||||||
|
"Bars": int(len(indexed)),
|
||||||
|
"Price Change %": change_pct,
|
||||||
|
"Real Bull Bars": int((indexed["classification"] == "real_bull").sum()),
|
||||||
|
"Real Bear Bars": int((indexed["classification"] == "real_bear").sum()),
|
||||||
|
"Fake Bars": int((indexed["classification"] == "fake").sum()),
|
||||||
|
"Trend Flips": int(max(0, (indexed["trend_state"] != indexed["trend_state"].shift(1)).sum() - 1)),
|
||||||
|
"What this says": message,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(indexed) < 2:
|
||||||
|
return _all_data_row("Not enough history yet. Load a longer period.")
|
||||||
|
|
||||||
|
latest_ts = indexed.index[-1]
|
||||||
|
earliest_ts = indexed.index[0]
|
||||||
|
if latest_ts - earliest_ts < pd.Timedelta(days=30):
|
||||||
|
return _all_data_row("Short history loaded. Increase Period for 1M/3M/6M/1Y views.")
|
||||||
|
|
||||||
|
windows = [("1M", 30), ("3M", 90), ("6M", 180), ("1Y", 365)]
|
||||||
|
rows: list[dict[str, object]] = []
|
||||||
|
for label, days in windows:
|
||||||
|
start_ts = latest_ts - pd.Timedelta(days=days)
|
||||||
|
window = indexed[indexed.index >= start_ts].copy()
|
||||||
|
if len(window) < 2:
|
||||||
|
continue
|
||||||
|
|
||||||
|
first_close = float(window.iloc[0]["Close"])
|
||||||
|
last_close = float(window.iloc[-1]["Close"])
|
||||||
|
change_pct = round(((last_close - first_close) / first_close) * 100.0, 2) if first_close else 0.0
|
||||||
|
bull_count = int((window["classification"] == "real_bull").sum())
|
||||||
|
bear_count = int((window["classification"] == "real_bear").sum())
|
||||||
|
fake_count = int((window["classification"] == "fake").sum())
|
||||||
|
trend_flips = int((window["trend_state"] != window["trend_state"].shift(1)).sum() - 1)
|
||||||
|
trend_flips = max(0, trend_flips)
|
||||||
|
|
||||||
|
if fake_count > max(bull_count, bear_count):
|
||||||
|
interpretation = "Mostly noisy/choppy. Keep risk small or stand aside."
|
||||||
|
elif bull_count > bear_count and change_pct > 0:
|
||||||
|
interpretation = "Buy-side pressure dominated this window."
|
||||||
|
elif bear_count > bull_count and change_pct < 0:
|
||||||
|
interpretation = "Sell-side pressure dominated this window."
|
||||||
|
else:
|
||||||
|
interpretation = "Mixed behavior. Wait for cleaner confirmation."
|
||||||
|
|
||||||
|
rows.append(
|
||||||
|
{
|
||||||
|
"Window": label,
|
||||||
|
"Bars": int(len(window)),
|
||||||
|
"Price Change %": change_pct,
|
||||||
|
"Real Bull Bars": bull_count,
|
||||||
|
"Real Bear Bars": bear_count,
|
||||||
|
"Fake Bars": fake_count,
|
||||||
|
"Trend Flips": trend_flips,
|
||||||
|
"What this says": interpretation,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if not rows:
|
||||||
|
return _all_data_row("Short history loaded. Increase Period for 1M/3M/6M/1Y views.")
|
||||||
|
|
||||||
|
return pd.DataFrame(rows)
|
||||||
|
|
||||||
|
|
||||||
|
def render_beginner_training_panel(
|
||||||
|
analyzed: pd.DataFrame,
|
||||||
|
trend_now: str,
|
||||||
|
signal_quality: dict[str, float | str],
|
||||||
|
regime_label: str,
|
||||||
|
) -> None:
|
||||||
|
st.subheader("What This Tool Means (Beginner Training)")
|
||||||
|
st.caption("This app is a chart interpreter. It labels candles, tracks trend state, and shows how similar signals behaved in the past.")
|
||||||
|
|
||||||
|
with st.expander("Plain-English metric guide", expanded=False):
|
||||||
|
st.markdown(
|
||||||
|
f"""
|
||||||
|
- `Current Trend`: **{trend_now}**. This is the app's directional state right now.
|
||||||
|
- `Real Bullish Bars`: candles that closed above the prior range (upside pressure).
|
||||||
|
- `Real Bearish Bars`: candles that closed below the prior range (downside pressure).
|
||||||
|
- `Fake Bars`: candles that stayed inside the prior range (noise, indecision).
|
||||||
|
- `Signal Quality`: **{signal_quality['score']} ({signal_quality['label']})**. Higher means cleaner recent structure.
|
||||||
|
- `Regime`: **{regime_label}**. `Trending` means directional movement; `Choppy` means frequent whipsaws.
|
||||||
|
- `Recent Fake Ratio`: **{signal_quality['fake_ratio']}%**. High values usually mean harder trading conditions.
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
window_rows = build_learning_window_rows(analyzed)
|
||||||
|
st.caption("Historical training snapshots from the loaded data window (1M/3M/6M/1Y).")
|
||||||
|
st.dataframe(window_rows, use_container_width=True)
|
||||||
|
st.info(
|
||||||
|
"How to use this: if most windows show high fake bars and many trend flips, treat signals as low-confidence. "
|
||||||
|
"If windows show consistent real-bar dominance with fewer flips, conditions are usually cleaner."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def render_training_panel(
|
def render_training_panel(
|
||||||
show_past_behavior: bool,
|
show_past_behavior: bool,
|
||||||
example_trades: pd.DataFrame,
|
example_trades: pd.DataFrame,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user