From bcf63487560d824b2f27d1e7a1d69eb92acc78f0 Mon Sep 17 00:00:00 2001 From: moen0 Date: Fri, 10 Apr 2026 19:54:56 +0200 Subject: [PATCH] fvg and ob indicators Front-end start --- .idea/material_theme_project_new.xml | 12 ++ .idea/misc.xml | 9 ++ .idea/modules.xml | 8 ++ backend/api/__init__.py | 0 backend/api/routes.py | 56 ++++++++ backend/indicators/fvg.py | 27 ++++ backend/indicators/order_blocks.py | 31 +++++ backend/run.py | 23 ++-- frontend/src/App.css | 184 +++++++++++++++++++++++++++ frontend/src/App.jsx | 53 ++++++++ 10 files changed, 394 insertions(+), 9 deletions(-) create mode 100644 .idea/material_theme_project_new.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 backend/api/__init__.py create mode 100644 backend/api/routes.py create mode 100644 backend/indicators/fvg.py create mode 100644 backend/indicators/order_blocks.py create mode 100644 frontend/src/App.css create mode 100644 frontend/src/App.jsx diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..0f2d12c --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..08f27e5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..e1815ec --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/backend/api/__init__.py b/backend/api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api/routes.py b/backend/api/routes.py new file mode 100644 index 0000000..3cea13f --- /dev/null +++ b/backend/api/routes.py @@ -0,0 +1,56 @@ +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from data.loader import load_candles, resample_candles +from indicators.market_structure import find_swing_points, detect_structure +from indicators.liquidity import find_liquidity_levels +from indicators.fvg import find_fvgs +from indicators.order_blocks import find_order_blocks +import sys +import os +sys.path.insert(0, os.path.dirname(os.path.dirname(__file__))) + +app = FastAPI() + +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/api/candles") +def get_candles(timeframe: int = 5): + candles_1m = load_candles("data/data.csv") + candles = resample_candles(candles_1m, period=timeframe) + + return { + "candles": [ + { + "time": c.time_open.isoformat(), + "open": c.open, + "high": c.high, + "low": c.low, + "close": c.close + } + for c in candles + ] + } + +@app.get("/api/indicators") +def get_indicators(timeframe: int = 5): + candles_1m = load_candles("data/data.csv") + candles = resample_candles(candles_1m, period=timeframe) + + swings = find_swing_points(candles) + structure = detect_structure(swings) + levels = find_liquidity_levels(swings) + fvgs = find_fvgs(candles) + obs = find_order_blocks(candles, structure) + + return { + "swings": swings, + "structure": structure, + "liquidity": levels, + "fvgs": fvgs, + "order_blocks": obs + } \ No newline at end of file diff --git a/backend/indicators/fvg.py b/backend/indicators/fvg.py new file mode 100644 index 0000000..af9ecfb --- /dev/null +++ b/backend/indicators/fvg.py @@ -0,0 +1,27 @@ +def find_fvgs(candles): + fvgs = [] + + for i in range(2, len(candles)): + c1 = candles[i - 2] + c2 = candles[i - 1] + c3 = candles[i] + + # Bullish + if c1.high < c3.low: + fvgs.append({ + "index": i - 1, + "type": "bullish", + "top": c3.low, + "bottom": c1.high + }) + + # bearish + elif c1.low > c3.high: + fvgs.append({ + "index": i - 1, + "type": "bearish", + "top": c1.low, + "bottom": c3.high + }) + + return fvgs \ No newline at end of file diff --git a/backend/indicators/order_blocks.py b/backend/indicators/order_blocks.py new file mode 100644 index 0000000..0d87ff9 --- /dev/null +++ b/backend/indicators/order_blocks.py @@ -0,0 +1,31 @@ +def find_order_blocks(candles, structure, min_impulse=0.10): + obs = [] + + for point in structure: + if point["label"] == "HH": + # Bullish break of structure, look back for last bearish candle + idx = point["index"] + for j in range(idx - 1, max(idx - 20, 0), -1): + if candles[j].close < candles[j].open: + obs.append({ + "index": j, + "type": "bullish", + "top": candles[j].open, + "bottom": candles[j].close + }) + break + + elif point["label"] == "LL": + # Bearish break of structure, look back for last bullish candle + idx = point["index"] + for j in range(idx - 1, max(idx - 20, 0), -1): + if candles[j].close > candles[j].open: + obs.append({ + "index": j, + "type": "bearish", + "top": candles[j].close, + "bottom": candles[j].open + }) + break + + return obs \ No newline at end of file diff --git a/backend/run.py b/backend/run.py index bc4b9fb..d8af128 100644 --- a/backend/run.py +++ b/backend/run.py @@ -1,10 +1,8 @@ -from data.loader import load_candles +from data.loader import load_candles, resample_candles from indicators.market_structure import find_swing_points, detect_structure from indicators.liquidity import find_liquidity_levels - -candles = load_candles("data/data.csv") -print(f"Loaded {len(candles)} candles") -from data.loader import load_candles, resample_candles +from indicators.fvg import find_fvgs +from indicators.order_blocks import find_order_blocks candles_1m = load_candles("data/data.csv") candles_3m = resample_candles(candles_1m, period=3) @@ -15,9 +13,16 @@ print(f"3m: {len(candles_3m)} candles") print(f"5m: {len(candles_5m)} candles") swings = find_swing_points(candles_5m) +structure = detect_structure(swings) levels = find_liquidity_levels(swings) -print(f"Swing points: {len(swings)}") -print(f"Liquidity levels: {len(levels)}") -for l in levels[:5]: - print(l) +fvgs = find_fvgs(candles_5m) +obs = find_order_blocks(candles_5m, structure) +print(f"Swing points: {len(swings)}") +print(f"Structure points: {len(structure)}") +print(f"Liquidity levels: {len(levels)}") +print(f"FVGs: {len(fvgs)}") +print(f"Order blocks: {len(obs)}") + +for o in obs[:5]: + print(o) \ No newline at end of file diff --git a/frontend/src/App.css b/frontend/src/App.css new file mode 100644 index 0000000..f90339d --- /dev/null +++ b/frontend/src/App.css @@ -0,0 +1,184 @@ +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ''; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx new file mode 100644 index 0000000..930e764 --- /dev/null +++ b/frontend/src/App.jsx @@ -0,0 +1,53 @@ +import { useEffect, useRef } from "react"; +import { createChart } from "lightweight-charts"; + +function App() { + const chartRef = useRef(null); + + useEffect(() => { + const chart = createChart(chartRef.current, { + width: window.innerWidth - 40, + height: 600, + layout: { + background: { color: "#1a1a2e" }, + textColor: "#e0e0e0", + }, + grid: { + vertLines: { color: "#2a2a3e" }, + horzLines: { color: "#2a2a3e" }, + }, + }); + + const candleSeries = chart.addCandlestickSeries({ + upColor: "#26a69a", + downColor: "#ef5350", + borderVisible: false, + wickUpColor: "#26a69a", + wickDownColor: "#ef5350", + }); + + fetch("http://localhost:8000/api/candles?timeframe=5") + .then((res) => res.json()) + .then((data) => { + const formatted = data.candles.map((c) => ({ + time: Math.floor(new Date(c.time).getTime() / 1000), + open: c.open, + high: c.high, + low: c.low, + close: c.close, + })); + candleSeries.setData(formatted); + }); + + return () => chart.remove(); + }, []); + + return ( +
+

noteQuant Backtester

+
+
+ ); +} + +export default App; \ No newline at end of file