Skip to content
← All examples

Example 37

Stop sweep

the stop sweep: the last closed bar broke under the lows of the twenty bars before it, a spike of long liquidations went off in

Category
Workspace: reads or changes charts and panels
Permissions
market.readworkspace.readworkspace.write
Before you run it
a terminal connected to a backend that captures trades and liquidations, with a chart on a timeframe of a few minutes
What it does
DRAWS a mark named "stop_sweep" on the active chart (run again: replaced); sounds the alert chime on a sweep.
How to run it
grant market.read, workspace.read and workspace.write, Run. Attach it to "Every N seconds" (60 s) to keep watch.
Language
>=0.1.1, <0.2.0
Host
>=0.1.7, <0.2.0
Needs
market >=0.1.3, <0.2.0derivs >=0.1.0, <0.2.0flow >=0.1.0, <0.2.0workspace >=0.1.0, <0.2.0plot >=0.1.0, <0.2.0alert >=0.1.0, <0.2.0

Source

// Purpose: the stop sweep: the last closed bar broke under the lows of the twenty bars before it, a spike of long liquidations went off in
// it, and it closed back above them with buyers in control -- a mark on the chart where the sweep was. **It judges only what was recorded**:
// if the capture has no liquidations or no order flow for the bar, it says which and gives no verdict, because a bar with the liquidations
// missing looks exactly like a bar without a sweep.
// Category: workspace
// Permissions: market.read, workspace.read, workspace.write
// Preconditions: a terminal connected to a backend that captures trades and liquidations, with a chart on a timeframe of a few minutes
// Side effects: DRAWS a mark named "stop_sweep" on the active chart (run again: replaced); sounds the alert chime on a sweep.
// Run: grant market.read, workspace.read and workspace.write, Run. Attach it to "Every N seconds" (60 s) to keep watch.

lang ">=0.1.1, <0.2.0";
host "ttt" ">=0.1.7, <0.2.0";
requires "market" ">=0.1.3, <0.2.0";
requires "derivs" ">=0.1.0, <0.2.0";
requires "flow" ">=0.1.0, <0.2.0";
requires "workspace" ">=0.1.0, <0.2.0";
requires "plot" ">=0.1.0, <0.2.0";
requires "alert" ">=0.1.0, <0.2.0";

const OWNER: String = "stop_sweep";
const LOOKBACK: Int = 20;

fn main() -> Result<Unit, Error> {
    let chart = workspace.active_chart().expect("no chart is open");
    let candles = market.chart_candles(chart.symbol, chart.timeframe, LOOKBACK + 1)?;
    plot.clear(chart.chart, OWNER)?;
    if candles.len() < LOOKBACK + 1 {
        print("no verdict: only " + to_string(candles.len()) + " closed bars to compare with");
        return Ok(());
    }
    let bar = candles.last().expect("a bar");
    let width = bar.close_time - bar.open_time;

    // 1. Price: under the lows of the bars before it, and back above them at the close.
    let mut floor = candles.first().expect("a bar").low;
    let mut i = 0;
    while i < LOOKBACK {
        let earlier = candles.get(i).expect("a bar");
        if earlier.low < floor {
            floor = earlier.low;
        }
        i = i + 1;
    }
    if !(bar.low < floor && bar.close > floor) {
        print(chart.symbol + ": no sweep -- the bar did not take out the " + to_string(LOOKBACK) + "-bar low and close back above it");
        return Ok(());
    }

    // 2. Liquidations: the bar's bucket against the hour before. Unknown stays unknown.
    let hour = derivs.liquidations(chart.symbol, bar.open_time - width * 12, bar.close_time, width, price(50.0, USDT))?;
    let bucket = hour.buckets.last().expect("a bucket");
    if !bucket.known {
        print("no verdict: the bar took out the low, but the capture did not record its liquidations (" + to_string(hour.holes.len()) + " hole(s) in the range)");
        return Ok(());
    }
    let mut earlier_sum: Float = 0.0;
    let mut earlier_known = 0;
    for b in hour.buckets {
        if b.time != bucket.time {
            match b.longs {
                Some(longs) => {
                    earlier_sum = earlier_sum + to_float(longs.value());
                    earlier_known = earlier_known + 1;
                }
                None => {}
            }
        }
    }
    if earlier_known == 0 {
        print("no verdict: none of the bars before it had recorded liquidations to compare with");
        return Ok(());
    }
    let average = earlier_sum / to_float(earlier_known);
    let longs = to_float(bucket.longs.expect("known").value());
    if longs < average * 3.0 || longs <= 0.0 {
        print(chart.symbol + ": took out the low, but the long liquidations (" + to_string(longs) + ") were not 3x the average of " + to_string(average));
        return Ok(());
    }

    // 3. Order flow: buyers won the bar.
    let fp = flow.footprint(chart.symbol, chart.timeframe, bar.open_time, bar.close_time, price(50.0, USDT))?;
    let traded = fp.bars.last().expect("the bar");
    if !traded.known {
        print("no verdict: swept and liquidated, but the tape did not record the bar's order flow");
        return Ok(());
    }
    let mut delta: Float = 0.0;
    for cell in traded.cells {
        delta = delta + to_float(cell.buy.value()) - to_float(cell.sell.value());
    }
    if delta <= 0.0 {
        print(chart.symbol + ": swept the low with " + to_string(longs) + " longs liquidated, but sellers still won the bar (delta " + to_string(delta) + ")");
        return Ok(());
    }
    plot.marker(chart.chart, OWNER, bar.open_time, bar.low, "stop sweep: " + to_string(longs) + " longs, delta +" + to_string(delta), "green")?;
    alert.notify("Stop sweep", chart.symbol + ": low taken out, " + to_string(longs) + " longs liquidated, buyers took the bar")?;
    print(chart.symbol + ": STOP SWEEP -- " + to_string(longs) + " longs liquidated (" + to_string(longs / average) + " x the average), delta +" + to_string(delta));
    return Ok(());
}