orb_open_type.rs
use liqport::prelude::*;
/// A 15-minute close beyond the 09:30-09:45 range enters that way.
impl OrbOpenType {
fn on_bar(&mut self, bar: &Candle) {
self.maybe_roll_day(bar); // a new NY day re-arms both sides
if !self.range_done_today(bar) { return; } // 09:45 to midnight
let (Some(hi), Some(lo)) = (self.orb.high(), self.orb.low())
else { return };
let (side, order) = if !self.fired_long && bar.close.to_f64() > hi {
self.fired_long = true; // each side once a day, taken or not
(Side::LONG, Order::long(self.instrument))
} else if !self.fired_short && bar.close.to_f64() < lo {
self.fired_short = true;
(Side::SHORT, Order::short(self.instrument))
} else { return };
if !self.positions.of(self.instrument).is_flat() { return; }
let label = self.open_type.read(|o| o.is_final().then(|| o.label()));
let (Some(Some(label)), Some(stop)) =
(label, self.stop_for(side, hi, lo, bar)) // far side + 2 ticks
else { return }; // no open type: no trade
self.orders.place(
order
.stop_loss(stop)
.take_profit(rr(1.5)) // 1.5R
.size(limit_risk_based(0.01, 30)) // 1% risk, max 30
.breakeven(Breakeven { trigger: rr(1.0), offset: Px::ZERO })
.tag(label.as_str()), // e.g. "open-drive"
);
}
}
const NY: &str = "America/New_York";
/// Setup: 15- and 1-minute candles, the range, its open type, three rules.
impl Strategy for OrbOpenType {
const NAME: &'static str = "OrbOpenType";
type Config = OrbOpenTypeConfig;
fn build(config: Self::Config, run: &mut Run<Self>)
-> Result<Self, BuildError> {
let instrument = run.instrument(&config.symbol)?;
let m15 = Candles::on(instrument, Timeframe::Mins(15));
run.subscribe(m15.calls(Self::on_bar))?;
let m1 = run.subscribe(Candles::on(instrument, Timeframe::Mins(1)))?;
let orb = run.subscribe(
Tbl::new((9, 30, 0), (9, 45, 0), AnchorTz::new(NY)).on(m1))?;
let open_type = run.subscribe(OpenType::new(OpenTypeConfig {
from_time: (9, 30, 0), to_time: (9, 45, 0), tz: NY.into(),
band_frac: 0.10, test_frac: 0.35, hold_frac: 0.25,
}).on(m1))?;
let flat_at = WallTime::hm(15, 58);
run.subscribe(ExecRuleConfig::CloseAtTime { // flat by 15:58
tz: NY.into(), time: flat_at, cancel_open_orders: true,
order_comment: "OrbOpenType EOD flat".into(),
})?;
run.subscribe(ExecRuleConfig::SessionEntryWindow { // then wait
tz: NY.into(), time: flat_at, close_buffer_minutes: 2,
fallback_resume: WallTime::hm(18, 0), // for the next session
})?;
// Flat, with no entries, from 8 hours before each contract roll.
run.subscribe(ExecRuleConfig::ContractRoll { lead_minutes: 480 })?;
let (orders, positions) = (run.orders(), run.positions());
Ok(Self::new(config, orders, positions, instrument, orb, open_type))
}
}
orb_open_type.py
from datetime import time
from liqport import Bracket, Instrument, Side, Strategy, limit_risk_based, rr
from liqport.auto_rules import CloseAtTimeRule, SessionEntryWindowRule
from liqport.indicators import TBLIndicator
class OrbOpenType(Strategy):
"""A 15-minute close beyond the 09:30-09:45 range enters that way."""
def on_bar(self, bar):
self.maybe_roll_day(bar) # a new NY day re-arms both sides
if not self.range_done_today(bar): # 09:45 to midnight
return
if not self.fired_long and bar.close > self.orb.high:
self.fired_long = True # each side once a day, taken or not
side, stop = Side.LONG, self.orb.low - self.sl_buffer
elif not self.fired_short and bar.close < self.orb.low:
self.fired_short = True
side, stop = Side.SHORT, self.orb.high + self.sl_buffer
else:
return
position = self.get_position(self.config.full_symbol)
if position.side == Side.FLAT and (label := self.open_type_label()):
self.bracket_manager.place_bracket(Bracket(
full_symbol=self.config.full_symbol, side=side,
stop_price=stop, take_price=rr(1.5), # 2 ticks out; 1.5R
sizing=limit_risk_based(0.01, 30), # 1% risk, max 30
breakeven_trigger=rr(1.0), # at 1R, stop to entry
info=label)) # e.g. "open-drive"
def on_start(self, run):
"""Setup: 15- and 1-minute candles, the range, two session rules."""
symbol, tz = self.config.full_symbol, self.config.timezone
run.subscribe(
Instrument(symbol, timeframe="15m"), callback=self.on_bar)
base = run.subscribe(Instrument(symbol, timeframe="1m"))
self.orb = run.subscribe(TBLIndicator(
from_time=time(9, 30), to_time=time(9, 45), tzname=tz),
apply_to=base)
self.register_auto_rule(CloseAtTimeRule( # flat by 15:58
full_symbol=symbol, timezone=tz, close_time=time(15, 58)))
self.register_auto_rule(SessionEntryWindowRule( # then wait to reopen
full_symbol=symbol, timezone=tz, close_time=time(15, 58)))