Collude Pro

# ─────────────────────────────────────────────
# │ MarketFragments.com | DNA & Market │
# │ info@marketfragments.com │
# │ www.marketfragments.com │
# ──────────────────────────────────────────────
# Time →
# │
# █ █ █│ █
# █ █ █ │ █ █
# █ █ █ │ █ █ █ ╭─╮
# █ █ █ │ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ █ █╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ ╰─────╯
# ──────┴──────────────────────────────────────────────────────────────
# T1 T2 T3 T4 T5 T6
#
# Name: ColludeScan Pro
# Author: Market Fragments team
# Enhanced DBSCAN Logic for Detecting Collusive Trader Groups and HFT
# Improvements for Robustness:
# - Corrected the neighbor counting logic in DBSCAN to use proper Euclidean distance (squared to avoid sqrt) across multiple features.
# - Incorporated a third feature (price percent change) into the clustering for better multi-dimensional analysis, making detection less prone to noise in individual features.
# - Unified the anomaly score into a single, coherent metric based on proper neighbor counts, removing the flawed separate scores.
# - Adjusted collusion detection to use >= minPoints for consistency with standard DBSCAN core point definitions (includes self).
# - Added safeguards for division by zero in percent change calculations to prevent NaN or infinite values.
# - Utilized the unused trendLength to compute a moving average of the anomaly score, and integrated MoveThreshold to flag significant changes in the DBSCAN trend as an additional collusion signal.
# - Kept computational complexity in check but noted that large 'length' values may slow performance; consider reducing if needed.
# - No normalization applied to keep it simple and compatible with ThinkScript limitations, but distanceThreshold may need tuning based on asset volatility (e.g., increase for high-vol assets).
# - HFT detection remains similar but added a volume check for robustness against false positives in low-liquidity periods.
declare upper;
input distanceThreshold = 5.0; # Increased default for percentage-based features; tune based on backtesting
input minPoints = 7; # Minimum points for a core point/cluster detection
input length = 200; # Reduced look-back for efficiency; adjust as needed
input lengthPchange = 1; # Percent change calculation length
input MoveThreshold = 400; # Threshold for significant DBSCAN trend change (absolute)
input trendLength = 50; # Moving average period for DBSCAN score trend
input volumeThreshold = 1000; # Volume threshold for significant activity
input priceChangeThreshold = 0.1; # Price change threshold (in %) for significant moves
input hftThreshold = 500; # Tick count threshold for high-frequency trading
input hftPeriod = 5; # Look-back period for HFT detection
# Core definitions
def c = close;
def v = volume;
def tickcnt = tick_count;
# Safe percent change calculations (handle zero division)
def Data1 = if tickcnt[lengthPchange] != 0 then 100 * (tickcnt / tickcnt[lengthPchange] - 1) else 0;
def Data2 = if v[lengthPchange] != 0 then 100 * (v / v[lengthPchange] - 1) else 0;
def Data3 = if c[lengthPchange] != 0 then 100 * (c / c[lengthPchange] - 1) else 0;
# Calculate neighbors using proper Euclidean distance squared (for 3 features)
def distSqThreshold = Sqr(distanceThreshold);
def neighbors = fold i = 0 to length - 1 with count = 0 do
if Sqr(Data1 - GetValue(Data1, i)) +
Sqr(Data2 - GetValue(Data2, i)) +
Sqr(Data3 - GetValue(Data3, i)) <= distSqThreshold
then count + 1
else count;
# DBSCAN anomaly score (number of neighbors, including self)
def anomalyScore = neighbors;
# Trend analysis on anomaly score for robustness
def dbscanTrend = Average(anomalyScore, trendLength);
def significantTrendChange = AbsValue(dbscanTrend - dbscanTrend[1]) > MoveThreshold;
# Collusive Trader Groups Detection (high density + significant activity + trend change)
def priceChange = if c[1] != 0 then 100 * (c- c[1]) / c[1] else 0;
def significantVolume = v> volumeThreshold;
def significantPriceChange = AbsValue(priceChange) > priceChangeThreshold;
def collusionDetected = (anomalyScore >= minPoints) and significantVolume and significantPriceChange and significantTrendChange;
# Plot potential collusive activity
plot CollusiveActivity = if collusionDetected then high+5 else Double.NaN;
CollusiveActivity.SetPaintingStrategy(PaintingStrategy.POINTS);
CollusiveActivity.SetDefaultColor(Color.ORANGE);
CollusiveActivity.SetLineWeight(5);
# High-Frequency Trading Analysis (added volume check for robustness)
def highTickCount = tickcnt > hftThreshold and v > volumeThreshold / 2; # Avoid HFT flags in low-volume bars
def hftDetected = Sum(highTickCount, hftPeriod) >= hftPeriod * 0.8; # Allow some flexibility (80% of period)
# Plot high-frequency trading activity
plot HighFrequencyTrading = if hftDetected then low else Double.NaN;
HighFrequencyTrading.SetPaintingStrategy(PaintingStrategy.POINTS);
HighFrequencyTrading.SetDefaultColor(Color.MAGENTA);
HighFrequencyTrading.SetLineWeight(3);
plot CalmTrading = if !hftDetected then low else Double.NaN;
CalmTrading.SetPaintingStrategy(PaintingStrategy.POINTS);
CalmTrading.SetDefaultColor(Color.black);
CalmTrading.SetLineWeight(3);
# second version
# ─────────────────────────────────────────────
# │ MarketFragments.com | DNA & Market │
# │ info@marketfragments.com │
# │ www.marketfragments.com │
# ──────────────────────────────────────────────
# Time →
# │
# █ █ █│ █
# █ █ █ │ █ █
# █ █ █ │ █ █ █ ╭─╮
# █ █ █ │ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ ╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ █ █╭─╯ ╰─╮
# █ █ █ │ █ █ █ █ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ █ ╰─╮ ╭─╯
# █ █ █ │ █ ╰─────╯
# ──────┴──────────────────────────────────────────────────────────────
# T1 T2 T3 T4 T5 T6
#
# Name: ColludeScan Pro
# Author: Market Fragments team
# Enhanced DBSCAN Logic for Detecting Collusive Trader Groups and HFT
# Key Improvements (Oct 2025): Fixed lambda with script; reinforced NaN guards; tuned HFT avg; min cnt for layer signal.
# Robustness: Multi-feature Euclidean dist, trend smoothing, safeguards. Tune for asset (e.g., higher distThreshold for crypto).
declare upper;
# Core Inputs
input distanceThreshold = 5.0; # Euclidean threshold for % features; tune via backtest (higher for vol assets)
input minPoints = 7; # Min neighbors for core point/cluster
input length =50; # Overall lookback; kept for historical max but not for folds
input neighborLookback = 50; # Reduced fold lookback for perf (vs. full length)
input includeSelf = yes; # Include current bar in neighbor count (DBSCAN standard)
input lengthPchange = 1; # % change lookback
input MoveThreshold = 10.0; # Trend change threshold (realistic: ~5-20% of max anomaly)
input trendLength = 50; # Smoothing period for anomaly trend
input volumeThreshold = 1000; # Min volume for signals
input priceChangeThreshold = 0.1; # % price move threshold
input hftThreshold = 100; # Tick threshold for HFT (tuned lower for intraday)
input hftPeriod = 5; # HFT burst lookback
input normalize = yes; # Simple z-score normalize features? (reduces scale bias)
input densityVelocity = yes; # Require rising anomaly for collusion (emerging density)?
input layerSignal = yes; # Enable second-layer signal?
input quietVolumeMult = 0.5; # Volume multiplier below threshold for "calm" plots
# Core data
def c = close;
def v = volume;
def tickcnt = tick_count();
# Safe Division Script (TOS-compliant sub-routine)
script safeDiv {
input value = 0;
plot result = if value != 0 then value else 1;
}
# Safe % changes using script
def Data1_raw = 100 * (tickcnt / safeDiv(value = tickcnt[lengthPchange]) - 1);
def Data2_raw = 100 * (v / safeDiv(value = v[lengthPchange]) - 1);
def Data3_raw = 100 * (c / safeDiv(value = c[lengthPchange]) - 1);
# Optional simple normalization (z-score over short window for perf; guarded for zero std)
def normLen = 20;
def mean1 = Average(Data1_raw, normLen);
def std1 = StDev(Data1_raw, normLen);
def Data1 = if normalize and std1 > 0 then (Data1_raw - mean1) / std1 else Data1_raw;
def mean2 = Average(Data2_raw, normLen);
def std2 = StDev(Data2_raw, normLen);
def Data2 = if normalize and std2 > 0 then (Data2_raw - mean2) / std2 else Data2_raw;
def mean3 = Average(Data3_raw, normLen);
def std3 = StDev(Data3_raw, normLen);
def Data3 = if normalize and std3 > 0 then (Data3_raw - mean3) / std3 else Data3_raw;
# Neighbors via Euclidean dist squared (optimized fold)
def distSqThreshold = Sqr(distanceThreshold);
def startFold = if includeSelf then 0 else 1;
def neighbors = fold i = startFold to neighborLookback - 1 with count = (if includeSelf then 1 else 0)
do count + (if Sqr(Data1 - GetValue(Data1, i)) + Sqr(Data2 - GetValue(Data2, i)) + Sqr(Data3 - GetValue(Data3, i)) <= distSqThreshold then 1 else 0);
# Anomaly score (neighbor density)
def anomalyScore = neighbors;
# Trend smoothing & change detection
def dbscanTrend = Average(anomalyScore, trendLength);
def significantTrendChange = AbsValue(dbscanTrend - dbscanTrend[1]) > MoveThreshold;
def secondLayerSignal;
# Collusion: High density + activity + trend + optional velocity
def priceChange = if c[1] != 0 then 100 * (c - c[1]) / c[1] else 0;
def significantVolume = v > volumeThreshold;
def significantPriceChange = AbsValue(priceChange) > priceChangeThreshold;
def risingDensity = !densityVelocity or (anomalyScore > anomalyScore[1]);
def baseCollusion = (anomalyScore >= minPoints) and significantVolume and significantPriceChange and significantTrendChange and risingDensity;
def collusionDetected = baseCollusion or (layerSignal and secondLayerSignal == 100); # Boost with new layer
# HFT: Burst detection with volume gate & rate (using Average for TOS optimization)
def highTickCount = tickcnt > hftThreshold and v > volumeThreshold * 0.5;
def hftBurstRate = Average(highTickCount, hftPeriod);
def hftDetected = hftBurstRate >= 0.8;
# Calm periods: Sparse plot only in truly quiet zones
def quietVolume = v < volumeThreshold * quietVolumeMult;
def calmDetected = !hftDetected and quietVolume;
# Completed New Layer: Daily HFT-period anomaly average vs. historical highs (min cnt for reliability)
def nday = GetDay() != GetDay()[1];
def isAnchor = nday and !nday[1];
def startDay = isAnchor;
def cnt = CompoundValue(1, if startDay then 0 else if hftDetected and cnt[1] == 0 then 1 else if hftDetected then cnt[1] + 1 else cnt[1], 0);
def SumAnom = CompoundValue(1, if startDay then 0 else if hftDetected and cnt[1] == 0 then anomalyScore else if hftDetected then SumAnom[1] + anomalyScore else SumAnom[1], 0);
def anomAverage = if cnt > 0 then SumAnom / cnt else 0;
def shortTermAnom = Average(anomalyScore, 3);
def histMaxAnom = Highest(anomAverage, length); # Global historical max during HFT periods
secondLayerSignal = if hftDetected and cnt >= 3 and shortTermAnom > anomAverage * 1.2 and shortTermAnom > histMaxAnom then 100 else 0; # 20% above daily avg + beats history + min samples
# Plots
plot CollusiveActivity = if collusionDetected then high + (high * 0.01) else Double.NaN; # Slight offset for visibility
CollusiveActivity.SetPaintingStrategy(PaintingStrategy.POINTS);
CollusiveActivity.SetDefaultColor(Color.ORANGE);
CollusiveActivity.SetLineWeight(5);
Alert(collusionDetected, "Collusive Activity Detected", Alert.BAR, Sound.Ding);
plot HighFrequencyTrading = if hftDetected then low - (low * 0.01) else Double.NaN;
HighFrequencyTrading.SetPaintingStrategy(PaintingStrategy.POINTS);
HighFrequencyTrading.SetDefaultColor(Color.MAGENTA);
HighFrequencyTrading.SetLineWeight(3);
Alert(hftDetected, "HFT Burst Detected", Alert.BAR, Sound.Bell);
plot CalmTrading = if calmDetected then low else Double.NaN; # Now sparse
CalmTrading.SetPaintingStrategy(PaintingStrategy.POINTS);
CalmTrading.SetDefaultColor(Color.GRAY);
CalmTrading.SetLineWeight(1);
# Second-layer bubble (enhanced)
AddChartBubble(layerSignal and secondLayerSignal == 100, close,
"Escalating Collusion: " + Round((shortTermAnom / histMaxAnom - 1) * 100, 1) + "% > Hist Max",
Color.GREEN, yes);
# Info Label: Current metrics for scanning (added % over hist)
AddLabel(yes, "Anomaly: " + Round(anomalyScore, 0) + " | Trend: " + Round(dbscanTrend, 1) + " | HFT Rate: " + Round(hftBurstRate * 100, 0) + "%" +
(if secondLayerSignal == 100 then " | Layer Sig: +" + Round((shortTermAnom / histMaxAnom - 1) * 100, 0) + "%" else ""),
if collusionDetected then Color.ORANGE else if hftDetected then Color.MAGENTA else Color.GRAY);

