mirror of
https://github.com/OpenByteInc/QuantDinger.git
synced 2026-09-29 17:07:04 +08:00
@@ -3,6 +3,16 @@ Canonical grid trading bot script (ScriptStrategy).
|
||||
|
||||
Upper/lower bounds may be updated at runtime by TradingExecutor via grid_runtime
|
||||
(adaptive bounds + waterfall protection). Read bounds with ctx.param() each bar.
|
||||
|
||||
Hedge mode (P0-1, May 2026):
|
||||
* Position state is read from ctx.position.long_size / short_size, which are
|
||||
hydrated independently from the qd_strategy_positions table (one row per
|
||||
side). This makes "neutral" grids actually neutral — long and short legs
|
||||
are tracked separately instead of being netted into a single scalar.
|
||||
* Order intent is declared explicitly via ctx.close_short / ctx.open_long /
|
||||
ctx.close_long / ctx.open_short instead of ctx.buy / ctx.sell so the
|
||||
executor never has to guess whether a buy means "cover the short leg" or
|
||||
"stack more longs".
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -17,8 +27,6 @@ def on_init(ctx):
|
||||
ctx.param("adaptiveBounds", True)
|
||||
ctx.param("waterfallProtection", True)
|
||||
ctx.param("prev_price", 0.0)
|
||||
ctx.param("long_exposure", 0.0)
|
||||
ctx.param("short_exposure", 0.0)
|
||||
ctx.param("waterfall_pause", False)
|
||||
ctx.log("grid bot init")
|
||||
|
||||
@@ -60,8 +68,12 @@ def on_bar(ctx, bar):
|
||||
ctx._params["prev_price"] = price
|
||||
return
|
||||
|
||||
long_exp = float(ctx.param("long_exposure", 0) or 0)
|
||||
short_exp = float(ctx.param("short_exposure", 0) or 0)
|
||||
# Hedge-mode position view: long_size / short_size are independent legs.
|
||||
long_size = float(getattr(ctx.position, "long_size", 0) or 0)
|
||||
short_size = float(getattr(ctx.position, "short_size", 0) or 0)
|
||||
|
||||
# Per-bar exposure budget — caps long+short notional so a runaway market
|
||||
# can't keep stacking new grid trades forever.
|
||||
budget = float(ctx.balance or ctx.equity or 0)
|
||||
if budget <= 0:
|
||||
budget = amt * grid_count * 2
|
||||
@@ -71,40 +83,46 @@ def on_bar(ctx, bar):
|
||||
|
||||
for lv in levels:
|
||||
if prev >= lv > price and crossed_down:
|
||||
# Price crossed a grid line going down -> buy.
|
||||
if direction in ("long", "neutral"):
|
||||
if short_exp > 0:
|
||||
use = min(amt, short_exp)
|
||||
ctx.buy(price=price, amount=use)
|
||||
short_exp -= use
|
||||
if use < amt and long_exp + (amt - use) <= budget:
|
||||
ctx.buy(price=price, amount=amt - use)
|
||||
long_exp += amt - use
|
||||
elif long_exp + amt <= budget:
|
||||
ctx.buy(price=price, amount=amt)
|
||||
long_exp += amt
|
||||
elif direction == "short" and short_exp + amt <= budget:
|
||||
ctx.sell(price=price, amount=amt)
|
||||
short_exp += amt
|
||||
if short_size > 0:
|
||||
# First retire as much of the short leg as the grid step
|
||||
# allows. This realises the matched-grid profit.
|
||||
use = min(amt, short_size)
|
||||
ctx.close_short(amount=use, price=price, reason="grid_buy_cover")
|
||||
short_size -= use
|
||||
leftover = amt - use
|
||||
if leftover > 0 and (long_size + leftover) <= budget:
|
||||
ctx.open_long(amount=leftover, price=price, reason="grid_buy_open")
|
||||
long_size += leftover
|
||||
elif (long_size + amt) <= budget:
|
||||
ctx.open_long(amount=amt, price=price, reason="grid_buy_open")
|
||||
long_size += amt
|
||||
elif direction == "short" and short_size > 0:
|
||||
# Pure-short grid: buying back reduces the short leg.
|
||||
use = min(amt, short_size)
|
||||
ctx.close_short(amount=use, price=price, reason="grid_buy_cover")
|
||||
short_size -= use
|
||||
elif prev <= lv < price and crossed_up:
|
||||
# Price crossed a grid line going up -> sell.
|
||||
if direction in ("short", "neutral"):
|
||||
if long_exp > 0:
|
||||
use = min(amt, long_exp)
|
||||
ctx.sell(price=price, amount=use)
|
||||
long_exp -= use
|
||||
if use < amt and short_exp + (amt - use) <= budget:
|
||||
ctx.sell(price=price, amount=amt - use)
|
||||
short_exp += amt - use
|
||||
elif short_exp + amt <= budget:
|
||||
ctx.sell(price=price, amount=amt)
|
||||
short_exp += amt
|
||||
elif direction == "long" and long_exp > 0:
|
||||
use = min(amt, long_exp)
|
||||
ctx.sell(price=price, amount=use)
|
||||
long_exp -= use
|
||||
if long_size > 0:
|
||||
use = min(amt, long_size)
|
||||
ctx.close_long(amount=use, price=price, reason="grid_sell_take")
|
||||
long_size -= use
|
||||
leftover = amt - use
|
||||
if leftover > 0 and (short_size + leftover) <= budget:
|
||||
ctx.open_short(amount=leftover, price=price, reason="grid_sell_open")
|
||||
short_size += leftover
|
||||
elif (short_size + amt) <= budget:
|
||||
ctx.open_short(amount=amt, price=price, reason="grid_sell_open")
|
||||
short_size += amt
|
||||
elif direction == "long" and long_size > 0:
|
||||
use = min(amt, long_size)
|
||||
ctx.close_long(amount=use, price=price, reason="grid_sell_take")
|
||||
long_size -= use
|
||||
|
||||
ctx._params["prev_price"] = price
|
||||
ctx._params["long_exposure"] = long_exp
|
||||
ctx._params["short_exposure"] = short_exp
|
||||
'''
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user