major update
Some checks failed
Deploy / lint (push) Failing after 8s
Deploy / test (push) Has been skipped
Deploy / deploy (push) Has been skipped

This commit is contained in:
Dennis Thiessen
2026-02-27 16:08:09 +01:00
parent 61ab24490d
commit 181cfe6588
71 changed files with 7647 additions and 281 deletions

View File

@@ -34,10 +34,32 @@ async def _get_ticker(db: AsyncSession, symbol: str) -> Ticker:
return ticker
def _compute_quality_score(
rr: float,
strength: int,
distance: float,
entry_price: float,
*,
w_rr: float = 0.35,
w_strength: float = 0.35,
w_proximity: float = 0.30,
rr_cap: float = 10.0,
) -> float:
"""Compute a quality score for a candidate S/R level.
Combines normalized R:R ratio, level strength, and proximity to entry
into a single 01 score using configurable weights.
"""
norm_rr = min(rr / rr_cap, 1.0)
norm_strength = strength / 100.0
norm_proximity = 1.0 - min(distance / entry_price, 1.0)
return w_rr * norm_rr + w_strength * norm_strength + w_proximity * norm_proximity
async def scan_ticker(
db: AsyncSession,
symbol: str,
rr_threshold: float = 3.0,
rr_threshold: float = 1.5,
atr_multiplier: float = 1.5,
) -> list[TradeSetup]:
"""Scan a single ticker for trade setups meeting the R:R threshold.
@@ -120,41 +142,65 @@ async def scan_ticker(
setups: list[TradeSetup] = []
# Long setup: target = nearest SR above, stop = entry - ATR × multiplier
# Check all resistance levels above and pick the one with the best quality score
if levels_above:
target = levels_above[0].price_level
stop = entry_price - (atr_value * atr_multiplier)
reward = target - entry_price
risk = entry_price - stop
if risk > 0 and reward > 0:
rr = reward / risk
if rr >= rr_threshold:
if risk > 0:
best_quality = 0.0
best_candidate_rr = 0.0
best_candidate_target = 0.0
for lv in levels_above:
reward = lv.price_level - entry_price
if reward > 0:
rr = reward / risk
if rr >= rr_threshold:
distance = lv.price_level - entry_price
quality = _compute_quality_score(rr, lv.strength, distance, entry_price)
if quality > best_quality:
best_quality = quality
best_candidate_rr = rr
best_candidate_target = lv.price_level
if best_candidate_rr > 0:
setups.append(TradeSetup(
ticker_id=ticker.id,
direction="long",
entry_price=round(entry_price, 4),
stop_loss=round(stop, 4),
target=round(target, 4),
rr_ratio=round(rr, 4),
target=round(best_candidate_target, 4),
rr_ratio=round(best_candidate_rr, 4),
composite_score=round(composite_score, 4),
detected_at=now,
))
# Short setup: target = nearest SR below, stop = entry + ATR × multiplier
# Check all support levels below and pick the one with the best quality score
if levels_below:
target = levels_below[0].price_level
stop = entry_price + (atr_value * atr_multiplier)
reward = entry_price - target
risk = stop - entry_price
if risk > 0 and reward > 0:
rr = reward / risk
if rr >= rr_threshold:
if risk > 0:
best_quality = 0.0
best_candidate_rr = 0.0
best_candidate_target = 0.0
for lv in levels_below:
reward = entry_price - lv.price_level
if reward > 0:
rr = reward / risk
if rr >= rr_threshold:
distance = entry_price - lv.price_level
quality = _compute_quality_score(rr, lv.strength, distance, entry_price)
if quality > best_quality:
best_quality = quality
best_candidate_rr = rr
best_candidate_target = lv.price_level
if best_candidate_rr > 0:
setups.append(TradeSetup(
ticker_id=ticker.id,
direction="short",
entry_price=round(entry_price, 4),
stop_loss=round(stop, 4),
target=round(target, 4),
rr_ratio=round(rr, 4),
target=round(best_candidate_target, 4),
rr_ratio=round(best_candidate_rr, 4),
composite_score=round(composite_score, 4),
detected_at=now,
))
@@ -177,7 +223,7 @@ async def scan_ticker(
async def scan_all_tickers(
db: AsyncSession,
rr_threshold: float = 3.0,
rr_threshold: float = 1.5,
atr_multiplier: float = 1.5,
) -> list[TradeSetup]:
"""Scan all tracked tickers for trade setups.