← すべてのサンプル
サンプル 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(()); }