fvg and ob indicators
Front-end start
This commit is contained in:
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="MaterialThemeProjectNewConfig">
|
||||||
|
<option name="metadata">
|
||||||
|
<MTProjectMetadataState>
|
||||||
|
<option name="migrated" value="true" />
|
||||||
|
<option name="pristineConfig" value="false" />
|
||||||
|
<option name="userId" value="1a039ef7:19cde2699a1:-7ff9" />
|
||||||
|
</MTProjectMetadataState>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.12 (noteQuant-backtest)" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12 (noteQuant-backtest)" project-jdk-type="Python SDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/out" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/noteQuant-backtest.iml" filepath="$PROJECT_DIR$/noteQuant-backtest.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
+14
-9
@@ -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.market_structure import find_swing_points, detect_structure
|
||||||
from indicators.liquidity import find_liquidity_levels
|
from indicators.liquidity import find_liquidity_levels
|
||||||
|
from indicators.fvg import find_fvgs
|
||||||
candles = load_candles("data/data.csv")
|
from indicators.order_blocks import find_order_blocks
|
||||||
print(f"Loaded {len(candles)} candles")
|
|
||||||
from data.loader import load_candles, resample_candles
|
|
||||||
|
|
||||||
candles_1m = load_candles("data/data.csv")
|
candles_1m = load_candles("data/data.csv")
|
||||||
candles_3m = resample_candles(candles_1m, period=3)
|
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")
|
print(f"5m: {len(candles_5m)} candles")
|
||||||
|
|
||||||
swings = find_swing_points(candles_5m)
|
swings = find_swing_points(candles_5m)
|
||||||
|
structure = detect_structure(swings)
|
||||||
levels = find_liquidity_levels(swings)
|
levels = find_liquidity_levels(swings)
|
||||||
print(f"Swing points: {len(swings)}")
|
fvgs = find_fvgs(candles_5m)
|
||||||
print(f"Liquidity levels: {len(levels)}")
|
obs = find_order_blocks(candles_5m, structure)
|
||||||
for l in levels[:5]:
|
|
||||||
print(l)
|
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 (
|
||||||
|
<div style={{ padding: "20px", background: "#1a1a2e", minHeight: "100vh" }}>
|
||||||
|
<h1 style={{ color: "#e0e0e0", marginBottom: "10px" }}>noteQuant Backtester</h1>
|
||||||
|
<div ref={chartRef} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
Reference in New Issue
Block a user