← Todos los ejemplos
Ejemplo 42
Vwap monitor
a VWAP that watches, as an institutional desk computes it -- every trade at the price it printed, merged bar by bar into a running mean 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 levels named "vwap_monitor" on the active chart (replaced at every bar); sounds the alert chime on a close beyond a band.
- Cómo ejecutarlo
- Script Hooks -> add this script to "Always on (monitor)", grant market.read, workspace.read and workspace.write. It starts with the terminal and
- 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.0market >=0.1.3, <0.2.0
Código
// Purpose: a VWAP that watches, as an institutional desk computes it -- every trade at the price it printed, merged bar by bar into a running mean and // spread (West's update: exact, and stable at a price of 60 000) -- kept in the program's own variables between bars. It draws the session VWAP and its // two-deviation bands on the active chart, and raises an alert when price closes beyond a band, once per excursion (it is armed again when price comes // back inside by half a deviation). **It states what it did not see**: a bar the capture did not record is counted, and while any is missing the VWAP is // labelled incomplete and no alert is raised on it. // 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 levels named "vwap_monitor" on the active chart (replaced at every bar); sounds the alert chime on a close beyond a band. // Run: Script Hooks -> add this script to "Always on (monitor)", grant market.read, workspace.read and workspace.write. It starts with the terminal and // rests between bars. 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"; requires "market" ">=0.1.3, <0.2.0"; import std.time; import std.math; const OWNER: String = "vwap_monitor"; const MAX_WAKES: Int = 0; const SESSION: Duration = 1d; const CATCH_UP: Duration = 6h; const STEP: Duration = 1min; const BAR: String = "1m"; const BAND: Float = 2.0; const REARM: Float = 1.5; // Everything the monitor remembers between bars. struct State { session: Timestamp, cursor: Timestamp, weight: Float, mean: Float, m2: Float, bars: Int, missing: Int, side: Int, } fn fresh(session: Timestamp) -> State { return State { session: session, cursor: session, weight: 0.0, mean: 0.0, m2: 0.0, bars: 0, missing: 0, side: 0 }; } // Merges one bar's cells into the running mean and spread, trade price by trade price. fn merge(state: State, cells: List<flow.Cell>) -> State { let mut next = state; for cell in cells { let volume = to_float(cell.buy.value()) + to_float(cell.sell.value()); if volume > 0.0 { let at = to_float(cell.price.amount()); let total = next.weight + volume; let delta = at - next.mean; next.mean = next.mean + delta * volume / total; next.m2 = next.m2 + volume * delta * (at - next.mean); next.weight = total; } } return next; } // What the closed bars from the cursor to `now` add. Read in pieces so a whole day is never one request. fn catch_up(state: State, symbol: String, 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(symbol, BAR, next.cursor, to, price(0.1, USDT))?; for bar in fp.bars { if bar.known { next = merge(next, bar.cells); next.bars = next.bars + 1; } else { next.missing = next.missing + 1; } next.cursor = bar.open_time + STEP; } // Nothing more in this piece: on to the next (and never standing still). // 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); } // Draws the VWAP and its bands, and raises the alert when a close went beyond one. Returns the state with its side updated. fn report(chart: workspace.Chart, state: State) -> Result<State, Error> { let mut next = state; plot.clear(chart.chart, OWNER)?; if next.weight <= 0.0 { return Ok(next); } let spread = math.sqrt(next.m2 / next.weight); let incomplete = next.missing > 0; let note = if incomplete { " (incomplete: " + to_string(next.missing) + " bar(s) not recorded)" } else { "" }; plot.level(chart.chart, OWNER, price(decimal_from_float(next.mean, 2), USDT), "VWAP" + note, "#e6b43c")?; plot.level(chart.chart, OWNER, price(decimal_from_float(next.mean + BAND * spread, 2), USDT), "+" + to_string(BAND) + " sigma", "#9a7a2c")?; plot.level(chart.chart, OWNER, price(decimal_from_float(next.mean - BAND * spread, 2), USDT), "-" + to_string(BAND) + " sigma", "#9a7a2c")?; if spread <= 0.0 || incomplete { return Ok(next); } let candles = market.chart_candles(chart.symbol, chart.timeframe, 1)?; match candles.last() { Some(candle) => { let z = (to_float(candle.close.amount()) - next.mean) / spread; if z >= BAND && next.side != 1 { next.side = 1; alert.notify("VWAP band", chart.symbol + " closed " + to_string(z) + " sigma above the session VWAP")?; } if z <= 0.0 - BAND && next.side != -1 { next.side = -1; alert.notify("VWAP band", chart.symbol + " closed " + to_string(0.0 - z) + " sigma below the session VWAP")?; } if z < REARM && z > 0.0 - REARM { next.side = 0; } } None => {} } 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) => { // A new session starts from nothing: yesterday's trades are not today's VWAP. let session = time.floor(wake.now, SESSION); if session != state.session { state = fresh(session); } match catch_up(state, chart.symbol, wake.now) { Ok(next) => { state = report(chart, next)?; } Err(why) => print("no reading this bar: " + why.code + ": " + why.message), } } None => {} } } print("vwap " + to_string(state.mean) + " over " + to_string(state.bars) + " bar(s), " + to_string(state.missing) + " not recorded"); return Ok(()); }