本文へ移動
← すべてのサンプル

サンプル 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

カテゴリ
Workspace:チャートやパネルを読む・変更する
権限
market.readworkspace.readworkspace.write
実行する前に
a terminal connected to a backend that records the footprint of the chart's symbol; the script runs as an "Always on" hook
何をするか
DRAWS marks named "delta_divergence" on the active chart (kept for the session); sounds the alert chime on a divergence.
実行方法
Script Hooks -> add this script to "Always on (monitor)", grant market.read, workspace.read and workspace.write. Set MAX_WAKES to a number to
言語
>=0.1.1, <0.2.0
ホスト
>=0.1.8, <0.2.0
必要なもの
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

ソース

// 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(());
}