reorganized data files and enhance backtesting structure, monte carlo sim

This commit is contained in:
moen0
2026-04-13 01:30:34 +02:00
parent 373e589297
commit 7d53f8589a
26 changed files with 65383 additions and 766 deletions
+54 -16
View File
@@ -1,27 +1,65 @@
def find_fvgs(candles):
def find_fvgs(candles, min_gap_size=0.0, impulse_multiplier=0.0):
"""
Find Fair Value Gaps in candle data.
Args:
candles: list of Candle objects
min_gap_size: minimum gap size in price units to filter noise (0 = no filter)
impulse_multiplier: minimum body-to-avg ratio for the middle candle (0 = no filter)
"""
fvgs = []
avg_body = 0
if impulse_multiplier > 0 and len(candles) > 20:
bodies = [abs(c.close - c.open) for c in candles[:20]]
avg_body = sum(bodies) / len(bodies)
for i in range(2, len(candles)):
c1 = candles[i - 2]
c2 = candles[i - 1]
c3 = candles[i]
# Bullish
# Impulse check on middle candle
if impulse_multiplier > 0 and avg_body > 0:
middle_body = abs(c2.close - c2.open)
if middle_body < avg_body * impulse_multiplier:
continue
# Update rolling average
avg_body = (avg_body * 19 + middle_body) / 20
# Bullish FVG
if c1.high < c3.low:
fvgs.append({
"index": i - 1,
"type": "bullish",
"top": c3.low,
"bottom": c1.high
})
gap_size = c3.low - c1.high
if gap_size >= min_gap_size:
fvgs.append({
"index": i - 1,
"type": "bullish",
"top": c3.low,
"bottom": c1.high,
"mitigated": False,
})
# bearish
# Bearish FVG
elif c1.low > c3.high:
fvgs.append({
"index": i - 1,
"type": "bearish",
"top": c1.low,
"bottom": c3.high
})
gap_size = c1.low - c3.high
if gap_size >= min_gap_size:
fvgs.append({
"index": i - 1,
"type": "bearish",
"top": c1.low,
"bottom": c3.high,
"mitigated": False,
})
return fvgs
# Mark mitigated FVGs
for fvg in fvgs:
if fvg["mitigated"]:
continue
if fvg["type"] == "bullish":
if c3.low <= fvg["bottom"]:
fvg["mitigated"] = True
elif fvg["type"] == "bearish":
if c3.high >= fvg["top"]:
fvg["mitigated"] = True
return fvgs
+4 -4
View File
@@ -19,7 +19,7 @@ def find_liquidity_levels(swings, tolerance=0.015, max_distance=100):
"price": avg_price,
"type": "equal_highs",
"count": len(cluster),
"indexes": [s["index"] for s in cluster]
"indexes": [s["index"] for s in cluster],
})
used.add(i)
@@ -30,7 +30,7 @@ def find_liquidity_levels(swings, tolerance=0.015, max_distance=100):
cluster = [l1]
for j, l2 in enumerate(lows):
if j != i and j not in used:
if abs(h1["price"] - h2["price"]) <= tolerance and abs(h1["index"] - h2["index"]) <= max_distance:
if abs(l1["price"] - l2["price"]) <= tolerance and abs(l1["index"] - l2["index"]) <= max_distance:
cluster.append(l2)
used.add(j)
if len(cluster) >= 2:
@@ -39,8 +39,8 @@ def find_liquidity_levels(swings, tolerance=0.015, max_distance=100):
"price": avg_price,
"type": "equal_lows",
"count": len(cluster),
"indexes": [s["index"] for s in cluster]
"indexes": [s["index"] for s in cluster],
})
used.add(i)
return levels
return levels
+27 -16
View File
@@ -1,31 +1,42 @@
def find_order_blocks(candles, structure, min_impulse=0.10):
def find_order_blocks(candles, structure, min_impulse=0.10, min_ob_size=0.0):
"""
Find Order Blocks based on structure breaks.
Args:
candles: list of Candle objects
structure: list of structure points from detect_structure
min_impulse: legacy param (unused, kept for compat)
min_ob_size: minimum OB size in price units (0 = no filter)
"""
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
})
size = candles[j].open - candles[j].close
if size >= min_ob_size:
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
})
size = candles[j].close - candles[j].open
if size >= min_ob_size:
obs.append({
"index": j,
"type": "bearish",
"top": candles[j].close,
"bottom": candles[j].open,
})
break
return obs
return obs
+21 -2
View File
@@ -5,24 +5,43 @@ SESSIONS_EST = {
"london": (time(2, 0), time(5, 0)),
"new_york": (time(7, 0), time(10, 0)),
"london_close": (time(10, 0), time(12, 0)),
"london_ny_overlap": (time(8, 0), time(10, 0)),
}
def in_session(candle_time, session_name):
if session_name == "all":
return True
if session_name not in SESSIONS_EST:
return True
t = candle_time.time()
start, end = SESSIONS_EST[session_name]
if start > end: # crosses midnight
if start > end:
return t >= start or t < end
return start <= t < end
def get_session(candle_time):
for name in SESSIONS_EST:
if name == "all":
continue
if in_session(candle_time, name):
return name
return "off_hours"
def filter_by_session(candles, session_name):
return [c for c in candles if in_session(c.time_open, session_name)]
def in_day_filter(candle_time, allowed_days):
if not allowed_days:
return True
return candle_time.weekday() in allowed_days
def get_asian_range(candles):
asian = filter_by_session(candles, "asian")
if not asian:
@@ -31,4 +50,4 @@ def get_asian_range(candles):
"high": max(c.high for c in asian),
"low": min(c.low for c in asian),
"mid": (max(c.high for c in asian) + min(c.low for c in asian)) / 2,
}
}