Ir al contenido
← Todos los ejemplos

Ejemplo 43

Delta divergence monitor

the divergence between price and the session's cumulative delta, watched bar by bar. It keeps the session's bars, its running delta and

Categoría
Espacio de trabajo: lee o cambia gráficos y paneles
Permisos
market.readworkspace.readworkspace.write
Antes de ejecutarlo
a terminal connected to a backend that records the footprint of the chart's symbol; the script runs as an "Always on" hook
Qué hace
DRAWS marks named "delta_divergence" on the active chart (kept for the session); sounds the alert chime on a divergence.
Cómo ejecutarlo
Script Hooks -> add this script to "Always on (monitor)", grant market.read, workspace.read and workspace.write. Set MAX_WAKES to a number to
Lenguaje
>=0.1.1, <0.2.0
Host
>=0.1.8, <0.2.0
Necesita
flow >=0.1.0, <0.2.0live >=0.1.0, <0.2.0workspace >=0.1.0, <0.2.0plot >=0.1.0, <0.2.0alert >=0.1.0, <0.2.0

Código

// Purpose: the divergence between price and the session's cumulative delta, watched bar by bar. It keeps the session's bars, its running delta and
// its last swing high and low in its own variables, and when a swing makes a higher high with a LOWER delta than the swing before it (or a lower low with a
// HIGHER delta) it marks the swing bar and raises an alert -- the move is being made by fewer aggressors than the last one. **A swing is known only
// once the bars after it have closed**, so the mark is placed on the swing and the alert at the bar that confirmed it, never earlier. **A bar the
// capture did not record breaks the delta chain**, so the session's bars and swings are dropped and begun again after it: a delta with a hole in it
// is not the session's delta.
// Category: workspace
// Permissions: market.read, workspace.read, workspace.write
// Preconditions: a terminal connected to a backend that records the footprint of the chart's symbol; the script runs as an "Always on" hook
// Side effects: DRAWS marks named "delta_divergence" on the active chart (kept for the session); sounds the alert chime on a divergence.
// Run: Script Hooks -> add this script to "Always on (monitor)", grant market.read, workspace.read and workspace.write. Set MAX_WAKES to a number to
// make it stop by itself (that is how it is tested); 0 is for ever.

lang ">=0.1.1, <0.2.0";
host "ttt" ">=0.1.8, <0.2.0";
requires "flow" ">=0.1.0, <0.2.0";
requires "live" ">=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";
import std.time;

const OWNER: String = "delta_divergence";
const MAX_WAKES: Int = 0;
const SESSION: Duration = 1d;
const CATCH_UP: Duration = 6h;
// The bars the divergence is read on: five-minute bars, so a swing is a few minutes of trading and not a few prints.
const STEP: Duration = 5min;
const BAR: String = "5m";
const LEFT: Int = 3;
const RIGHT: Int = 2;
// How much lower (or higher) the delta must be for the difference to count, in the symbol's own units.
const MIN_GAP: Float = 0.5;

struct State {
    session: Timestamp,
    cursor: Timestamp,
    times: List<Timestamp>,
    highs: List<Float>,
    lows: List<Float>,
    deltas: List<Float>,
    total: Float,
    high_at: Int,
    low_at: Int,
    found: Int,
    breaks: Int,
}

fn fresh(session: Timestamp) -> State {
    return State { session: session, cursor: session, times: [], highs: [], lows: [], deltas: [], total: 0.0, high_at: -1, low_at: -1, found: 0, breaks: 0 };
}

// A hole in the delta: what was gathered is dropped, and the session goes on from the bar after.
fn broken(state: State, resume: Timestamp) -> State {
    let mut next = fresh(state.session);
    next.cursor = resume;
    next.found = state.found;
    next.breaks = state.breaks + 1;
    return next;
}

// Whether the bar at `p` is the highest (`up`) or lowest of the LEFT bars before it and the RIGHT bars after it.
fn is_swing(xs: List<Float>, p: Int, up: Bool) -> Bool {
    let centre = xs.get(p).expect("a bar");
    let mut i = p - LEFT;
    while i <= p + RIGHT {
        if i != p {
            let other = xs.get(i).expect("a bar");
            if up && other >= centre {
                return false;
            }
            if !up && other <= centre {
                return false;
            }
        }
        i = i + 1;
    }
    return true;
}

fn point(x: Float) -> Price {
    return price(decimal_from_float(x, 2), USDT);
}

// Looks at the one bar that has just become confirmable, and compares it with the swing before it.
fn look(chart: workspace.Chart, state: State) -> Result<State, Error> {
    let mut next = state;
    let p = next.highs.len() - 1 - RIGHT;
    if p < LEFT {
        return Ok(next);
    }
    if is_swing(next.highs, p, true) {
        let now_high = next.highs.get(p).expect("a bar");
        let now_delta = next.deltas.get(p).expect("a bar");
        if next.high_at >= 0 {
            let before_high = next.highs.get(next.high_at).expect("a bar");
            let before_delta = next.deltas.get(next.high_at).expect("a bar");
            if now_high > before_high && now_delta < before_delta - MIN_GAP {
                plot.marker(chart.chart, OWNER, next.times.get(p).expect("a bar"), point(now_high), "bearish: higher high, delta " + to_string(now_delta) + " < " + to_string(before_delta), "red")?;
                alert.notify("Delta divergence", chart.symbol + ": higher high on a lower session delta (" + to_string(now_delta) + " against " + to_string(before_delta) + ")")?;
                print("bearish at bar " + to_string(p) + ": high " + to_string(now_high) + ", delta " + to_string(now_delta) + " against " + to_string(before_delta));
                next.found = next.found + 1;
            }
        }
        next.high_at = p;
    }
    if is_swing(next.lows, p, false) {
        let now_low = next.lows.get(p).expect("a bar");
        let now_delta = next.deltas.get(p).expect("a bar");
        if next.low_at >= 0 {
            let before_low = next.lows.get(next.low_at).expect("a bar");
            let before_delta = next.deltas.get(next.low_at).expect("a bar");
            if now_low < before_low && now_delta > before_delta + MIN_GAP {
                plot.marker(chart.chart, OWNER, next.times.get(p).expect("a bar"), point(now_low), "bullish: lower low, delta " + to_string(now_delta) + " > " + to_string(before_delta), "green")?;
                alert.notify("Delta divergence", chart.symbol + ": lower low on a higher session delta (" + to_string(now_delta) + " against " + to_string(before_delta) + ")")?;
                print("bullish at bar " + to_string(p) + ": low " + to_string(now_low) + ", delta " + to_string(now_delta) + " against " + to_string(before_delta));
                next.found = next.found + 1;
            }
        }
        next.low_at = p;
    }
    return Ok(next);
}

// Reads the closed bars from the cursor to `now` and watches each one as it is added.
fn catch_up(chart: workspace.Chart, state: State, now: Timestamp) -> Result<State, Error> {
    let mut next = state;
    while next.cursor + STEP <= now {
        let to = if next.cursor + CATCH_UP < now { next.cursor + CATCH_UP } else { now };
        let fp = flow.footprint(chart.symbol, BAR, next.cursor, to, price(0.1, USDT))?;
        for bar in fp.bars {
            // A bar nothing traded in is not a hole: it adds no swing and no delta. One the capture lost is a hole.
            if bar.known && !bar.cells.is_empty() {
                let mut high = to_float(bar.cells.first().expect("a cell").price.amount());
                let mut low = high;
                let mut delta: Float = 0.0;
                for cell in bar.cells {
                    let at = to_float(cell.price.amount());
                    if at > high {
                        high = at;
                    }
                    if at < low {
                        low = at;
                    }
                    delta = delta + to_float(cell.buy.value()) - to_float(cell.sell.value());
                }
                next.total = next.total + delta;
                next.times.push(bar.open_time);
                next.highs.push(high);
                next.lows.push(low);
                next.deltas.push(next.total);
                next = look(chart, next)?;
            } else if !bar.known {
                next = broken(next, bar.open_time);
            }
            next.cursor = bar.open_time + STEP;
        }
        // Only to the close of the last whole bar: the one still forming is read when it has closed.
        let reached = time.floor(to, STEP);
        if next.cursor < reached {
            next.cursor = reached;
        }
    }
    return Ok(next);
}

fn main() -> Result<Unit, Error> {
    let mut state = fresh(time.floor(live.now(), SESSION));
    let mut wakes = 0;
    while MAX_WAKES == 0 || wakes < MAX_WAKES {
        let wake = live.wait(30s)?;
        wakes = wakes + 1;
        match workspace.active_chart() {
            Some(chart) => {
                let session = time.floor(wake.now, SESSION);
                if session != state.session {
                    state = fresh(session);
                }
                match catch_up(chart, state, wake.now) {
                    Ok(next) => {
                        state = next;
                    }
                    Err(why) => print("no reading this bar: " + why.code + ": " + why.message),
                }
            }
            None => {}
        }
    }
    print(to_string(state.found) + " divergence(s) this session over " + to_string(state.highs.len()) + " bar(s); the delta chain was broken " + to_string(state.breaks) + " time(s)");
    return Ok(());
}