Signed-off-by: Dinger <quantdinger@gmail.com>
This commit is contained in:
Dinger
2026-05-28 22:40:02 +08:00
parent c613586c52
commit 57b54c598f
70 changed files with 8488 additions and 1482 deletions
@@ -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
'''