File: quool/strategy.py
Base class for trading strategies. Coordinates Source (market data) and Broker (execution).
from quool import Strategyclass MyStrategy(Strategy):
def init(self, **kwargs): # Called once before the first iteration
pass
def preupdate(self, **kwargs): # Called after each iteration, before update()
pass
def update(self, **kwargs): # Core logic — place orders here
pass
def stop(self, **kwargs): # Called once at the end
passStrategy(
source: Source, # Market data provider
broker: Broker, # Execution and accounting interface
logger: logging.Logger = None, # Logger (default: DEBUG-level class-named logger)
)strategy.backtest(
benchmark: pd.Series = None, # Benchmark net value series
history: bool = False, # Store full history during backtest
**kwargs, # Forwarded to init(), preupdate(), update()
) -> AnyReturns the evaluation summary from Evaluator.report().
Raises: ValueError if no delivery data is available for evaluation.
Blocking scheduler (foreground process).
strategy.run(
store: str = None, # Path to persist broker state
history: bool = False,
trigger: str = "interval", # APScheduler trigger type
trigger_kwargs: dict = None, # Trigger arguments (default: {'seconds': 30})
**kwargs,
)Background scheduler (non-blocking).
strategy.arun(
store: str = None,
history: bool = False,
trigger: str = "interval",
trigger_kwargs: dict = None,
scheduler: BackgroundScheduler = None, # Existing scheduler to reuse
**kwargs,
) -> BackgroundSchedulerResumes an existing job with the class name if found; otherwise creates a new one.
All return the submitted Order (or None if no adjustment needed).
strategy.buy(
code: str, # Instrument code
quantity: int, # Requested quantity
exectype: str = MARKET, # Execution type
limit: float = None,
trigger: float = None,
id: str = None,
valid: str = None,
) -> Orderstrategy.sell(code, quantity, exectype=MARKET, limit=None, trigger=None, id=None, valid=None) -> OrderClose the entire position in an instrument (submits SELL for full position).
strategy.close(
code: str,
exectype: str = MARKET,
limit: float = None,
trigger: float = None,
id: str = None,
valid: str = None,
) -> Order or NoneReturns None if no position exists.
Adjust position to a target notional value.
strategy.order_target_value(
code: str,
value: float, # Target position value in currency units
exectype: str = MARKET,
limit: float = None,
trigger: float = None,
id: str = None,
valid: str = None,
) -> Order or NoneComputes: delta = target_value - current_position * close_price, then submits a BUY/SELL for the delta quantity.
Returns None if instrument not in source data index.
Adjust position to a target portfolio percentage.
strategy.order_target_percent(
code: str,
percent: float, # Target fraction (e.g., 0.10 for 10%)
exectype: str = MARKET,
limit: float = None,
trigger: float = None,
id: str = None,
valid: str = None,
) -> Order or NoneComputes: target_value = portfolio_value * percent, then delegates to order_target_value().
strategy.get_value() -> float # Total portfolio value (positions + cash)strategy.get_positions() -> pd.Series # Position quantities indexed by codestrategy.cancel(order_or_id: str | Order) -> OrderRaises: KeyError if order id not found.
Serialize broker state to a dictionary.
strategy.dump(history: bool = True) -> dictReconstruct a Strategy from serialized broker state.
Strategy.load(
cls,
data: dict, # Serialized broker state
commission: FixedRateCommission, # Commission model
slippage: FixedRateSlippage, # Slippage model
source: Source, # Market data source
logger: logging.Logger = None,
) -> Strategystrategy.log(message: str, level: str = "DEBUG")Prefixes log message with current timestamp.
Notification hook for order status changes. Default behavior logs the order.
strategy.notify(order: Order)Override to handle filled orders, rejections, or cancellations.