reorganized data files and enhance backtesting structure, monte carlo sim
This commit is contained in:
+54
-16
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user