From 4a9208db9d68d46ecb1e20b7417df0620e22e9e8 Mon Sep 17 00:00:00 2001 From: claude_dev Date: Wed, 19 Aug 2026 22:37:16 +0800 Subject: [PATCH] =?UTF-8?q?feat(portfolio):=20B2=E5=AE=9A=E5=AF=B8?= =?UTF-8?q?=E8=99=9A=E6=8B=9F=E5=8C=96InstancePortfolio=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E2=80=94=E2=80=94=E7=AD=96=E7=95=A5=E5=86=B3=E7=AD=96=E5=B1=82?= =?UTF-8?q?=E8=84=B1=E7=A6=BB=E5=85=A8=E8=B4=A6=E6=88=B7=E6=B1=A1=E6=9F=93?= =?UTF-8?q?(spec=C2=A7B2+A2=E6=B8=85=E5=8D=95issue#29)=E2=80=94=E2=80=94?= =?UTF-8?q?=E2=91=A0live=5Fportfolio=5Fproxy:InstanceContextProxy(portfoli?= =?UTF-8?q?o=E2=86=92=E8=B4=A6=E6=9C=AC=E8=A7=86=E5=9B=BE,=E5=85=B6?= =?UTF-8?q?=E4=BD=99=E5=B1=9E=E6=80=A7=E9=80=8F=E4=BC=A0=E7=9C=9Fcontext,p?= =?UTF-8?q?roperty+=5F=5Fgetattr=5F=5F=E5=AE=9E=E7=8E=B0)+InstancePortfoli?= =?UTF-8?q?o(=E6=AF=8F=E6=AC=A1=E8=AE=BF=E9=97=AE=E7=8E=B0=E7=AE=97)?= =?UTF-8?q?=E2=91=A1=E8=A6=86=E7=9B=96A2=E6=B8=85=E5=8D=95=E5=85=A8?= =?UTF-8?q?=E9=9B=86:available=5Fcash/cash=3D=E8=B4=A6=E6=9C=AC=E7=8E=B0?= =?UTF-8?q?=E9=87=91(168=E4=B8=87/=E5=8F=AA=E5=AE=9A=E5=AF=B8=E6=B1=A1?= =?UTF-8?q?=E6=9F=93=E6=A0=B9=E6=B2=BB=E7=82=B9,.cash=20fallback=E5=90=8C?= =?UTF-8?q?=E6=BA=90);positions=3D=E8=B4=A6=E6=9C=ACjq=E5=AF=B9=E8=B1=A1(t?= =?UTF-8?q?otal=5Famount/amount/closeable=5Famount(T+1)/avg=5Fcost(?= =?UTF-8?q?=E5=8F=B0=E8=B4=A6=E5=8F=A3=E5=BE=84=3D=E6=88=90=E4=BA=A4?= =?UTF-8?q?=E4=BB=B7)/cost=5Fbasis/security)+price/last=5Fsale=5Fprice?= =?UTF-8?q?=E4=BB=8E=E7=9C=9Fportfolio=E5=90=8C=E5=90=8D=E6=A0=87=E7=9A=84?= =?UTF-8?q?=E9=80=8F=E4=BC=A0(=E5=B8=82=E5=9C=BA=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E9=9D=9E=E6=89=80=E6=9C=89=E6=9D=83,=E7=BC=BA=E2=86=92None?= =?UTF-8?q?=E6=AD=A2=E6=8D=9F=E8=B7=B3=E8=BF=87);value/market=5Fvalue/tota?= =?UTF-8?q?l=5Fvalue=3D=E7=8E=B0=E4=BB=B7=C3=97=E9=87=8F(=E7=BC=BA?= =?UTF-8?q?=E4=BB=B7=E2=86=920=E5=81=8F=E4=BF=9D=E5=AE=88=E4=B8=8D?= =?UTF-8?q?=E8=AF=AF=E5=8D=96);total=5Fvalue/positions=5Fvalue=3D=E8=B4=A6?= =?UTF-8?q?=E6=9C=ACequity(=E7=8E=B0=E4=BB=B7=E4=BC=98=E5=85=88=E6=88=90?= =?UTF-8?q?=E6=9C=AC=E5=85=9C=E5=BA=95);locked=5Fcash=3D0=E2=91=A2?= =?UTF-8?q?=E6=B8=85=E5=8D=95=E5=A4=96=E5=B1=9E=E6=80=A7AttributeError=20f?= =?UTF-8?q?ail-fast(=E7=9C=9F=E5=80=BC=3D=E5=85=A8=E8=B4=A6=E6=88=B7?= =?UTF-8?q?=E6=95=B0=E5=AD=97,=E9=9D=99=E9=BB=98=E9=80=8F=E4=BC=A0=3D?= =?UTF-8?q?=E6=B1=A1=E6=9F=93=E5=A4=8D=E5=8F=91,=E5=AE=81=E5=8F=AF?= =?UTF-8?q?=E5=B4=A9=3DA1=E5=90=8C=E6=AC=BEhouse=20style)=E2=91=A3wrap=5Fs?= =?UTF-8?q?cheduler=E5=8C=85=E8=A3=85facade=E7=9A=84run=5Fdaily/run=5Fmont?= =?UTF-8?q?hly(=E5=8F=91=E7=94=9F=E5=9C=A8=E7=AD=96=E7=95=A5initialize?= =?UTF-8?q?=E6=B3=A8=E5=86=8C=E4=BB=BB=E5=8A=A1=E4=B9=8B=E5=89=8D,?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E4=BB=A3=E7=A0=81=E9=9B=B6=E6=94=B9=E5=8A=A8?= =?UTF-8?q?);functools.wraps=E4=BF=9D=E4=BB=BB=E5=8A=A1=E5=90=8D;=E6=97=A0?= =?UTF-8?q?=E8=B4=A6=E6=9C=AC=E5=8E=9F=E6=A0=B7=E8=BF=94=E5=9B=9E=3D?= =?UTF-8?q?=E5=9B=9E=E6=B5=8B/=E5=BD=B1=E5=AD=90=E9=9B=B6=E6=94=B9?= =?UTF-8?q?=E5=8A=A8=E2=91=A4live=5Fstrategy.=5Fsetup=E6=8E=A5=E7=BA=BF(?= =?UTF-8?q?=5Fledger=20None=E2=86=92wrap=E7=9B=B4=E9=80=9A);=E5=BC=95?= =?UTF-8?q?=E6=93=8E=E5=86=85=E9=83=A8=E6=92=AE=E5=90=88/=E9=A3=8E?= =?UTF-8?q?=E6=8E=A7=E4=BB=8D=E7=9C=8B=E7=9C=9F=E5=AE=9E=E8=B4=A6=E6=88=B7?= =?UTF-8?q?;+13=E6=B5=8B=E8=AF=95(=E5=AE=9A=E5=AF=B8=E7=8E=B0=E9=87=91/T+1?= =?UTF-8?q?/=E7=8E=B0=E4=BB=B7=E9=80=8F=E4=BC=A0/=E7=BC=BA=E4=BB=B7?= =?UTF-8?q?=E4=BF=9D=E5=AE=88/equity=E5=85=9C=E5=BA=95/fail-fast/=E8=B0=83?= =?UTF-8?q?=E5=BA=A6=E4=BB=A3=E7=90=86=E6=B3=A8=E5=85=A5/=E6=97=A0?= =?UTF-8?q?=E8=B4=A6=E6=9C=AC=E9=9B=B6=E6=94=B9=E5=8A=A8/=5Fsetup=E6=8E=A5?= =?UTF-8?q?=E7=BA=BF=E4=B8=A4=E6=80=81);portfolio=20373=E7=BB=BF=20[vps]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sanguo_portfolio/live_portfolio_proxy.py | 193 +++++++++++++++++ sanguo_portfolio/live_strategy.py | 6 + tests/portfolio/test_live_portfolio_proxy.py | 213 +++++++++++++++++++ 3 files changed, 412 insertions(+) create mode 100644 sanguo_portfolio/live_portfolio_proxy.py create mode 100644 tests/portfolio/test_live_portfolio_proxy.py diff --git a/sanguo_portfolio/live_portfolio_proxy.py b/sanguo_portfolio/live_portfolio_proxy.py new file mode 100644 index 0000000..bda9931 --- /dev/null +++ b/sanguo_portfolio/live_portfolio_proxy.py @@ -0,0 +1,193 @@ +"""B2 定寸虚拟化:InstancePortfolio 代理(spec §multi-strategy-instance-budget §B2)。 + +共享 QMT 账户下,策略读 ``context.portfolio`` 会看到全账户(995万)→ 定寸被污染 +(channel_test 168万/只 vs 影子 16.8万)。本模块把**策略决策层**看到的 portfolio +换成实例账本视图;引擎内部(撮合/风控/下单)仍看真实账户。 + +覆盖范围(A2 属性清单,策略 session issue#29 核对): +- ``available_cash`` / ``cash``:实例账本现金(定寸污染点,核心) +- ``positions``:账本视图的 jq 风格对象——``total_amount``/``amount``/ + ``closeable_amount``(T+1)/``avg_cost``/``cost_basis``/``security`` + + ``price``/``last_sale_price``(现价从真 portfolio 同名标的透传=市场数据非所有权, + 缺则 None)/``value``/``market_value``/``total_value``(现价缺失时 0,策略当 0 + 处理偏保守不误卖) +- ``total_value``/``positions_value``:账本 equity(现金+Σ市值,现价缺则成本价) +- ``locked_cash``:0(账本无锁现语义) + +**未覆盖属性 fail-fast**(AttributeError):A2 确认策略不读;真值是全账户数字, +静默透传=污染复发,宁可崩(house style 同 A1 的 TypeError fail-fast)。 +非 portfolio 属性(current_dt/previous_date 等)由 context 代理透传真 context。 +""" +from __future__ import annotations + +import functools +import logging +from typing import Any, Callable, Dict, Optional + +logger = logging.getLogger(__name__) + + +class InstancePosition: + """实例持仓的 jq 风格对象(账本数量/成本 + 真实账户现价透传)。""" + + def __init__(self, security: str, total_amount: int, + closeable_amount: int, avg_cost: float, + price: Optional[float]) -> None: + self.security = security + self.total_amount = int(total_amount) + self.amount = self.total_amount + self.closeable_amount = int(closeable_amount) + self.avg_cost = float(avg_cost) + self.cost_basis = self.avg_cost + self.price = float(price) if price else None + self.last_sale_price = self.price + + @property + def value(self) -> float: + """现价缺失 → 0(channel_test _safe_value 语义:取不到当 0,偏保守)。""" + return self.price * self.total_amount if self.price else 0.0 + + @property + def market_value(self) -> float: + return self.value + + @property + def total_value(self) -> float: + return self.value + + +class InstancePortfolio: + """每次访问现算的实例账本 portfolio 视图。""" + + def __init__(self, ledger: Any, + real_portfolio: Callable[[], Any]) -> None: + self._ledger = ledger + self._real_portfolio = real_portfolio + + @property + def available_cash(self) -> float: + return float(self._ledger.cash) + + @property + def cash(self) -> float: + # 策略族 _available_cash 先读 available_cash 再 fallback .cash——两口径同源 + return float(self._ledger.cash) + + @property + def positions(self) -> Dict[str, InstancePosition]: + real = self._real_positions() + real_by_sym = _index_real_positions(real) + out: Dict[str, InstancePosition] = {} + for sym, info in self._ledger.positions_view(_today()).items(): + rp = real_by_sym.get(sym) + price = None + if rp is not None: + price = (getattr(rp, "price", None) + or getattr(rp, "last_sale_price", None)) + out[sym] = InstancePosition( + security=sym, + total_amount=int(info.get("amount", 0)), + closeable_amount=int(info.get("closeable_amount", 0)), + avg_cost=float(info.get("avg_cost", 0.0)), + price=price, + ) + return out + + @property + def total_value(self) -> float: + real_by_sym = _index_real_positions(self._real_positions()) + prices: Dict[str, float] = {} + for sym, rp in real_by_sym.items(): + p = getattr(rp, "price", None) or getattr(rp, "last_sale_price", None) + if p: + prices[sym] = float(p) + _cash, _mv, total = self._ledger.equity(prices) + return float(total) + + @property + def positions_value(self) -> float: + return self.total_value - float(self._ledger.cash) + + @property + def locked_cash(self) -> float: + return 0.0 # 账本无锁现语义(A2:策略不读;给安全值) + + # 其余属性(AttributeError fail-fast):真值是全账户数字,透传=污染复发 + def __getattr__(self, name: str) -> Any: + raise AttributeError( + f"InstancePortfolio 未覆盖 portfolio.{name}(A2 清单外;" + f"真值=全账户数字,拒绝静默透传——如策略确需,请扩本代理)") + + def _real_positions(self) -> Any: + try: + rp = self._real_portfolio() + except Exception: # noqa: BLE001 - 真 portfolio 拿不到 → 无现价可用 + return {} + if rp is None: + return {} + try: + return getattr(rp, "positions", None) or {} + except Exception: # noqa: BLE001 + return {} + + +def _index_real_positions(real_positions: Any) -> Dict[str, Any]: + """真 portfolio.positions(dict 或 list)→ {symbol: position_obj}。""" + out: Dict[str, Any] = {} + if isinstance(real_positions, dict): + for k, v in real_positions.items(): + out[str(k)] = v + else: + for p in real_positions or []: + sec = getattr(p, "security", None) or getattr(p, "symbol", None) + if sec: + out[str(sec)] = p + return out + + +def _today() -> str: + from datetime import date as _date + return _date.today().isoformat() + + +class InstanceContextProxy: + """context 代理:``portfolio`` → InstancePortfolio,其余透传真 context。""" + + def __init__(self, real_context: Any, ledger: Any) -> None: + object.__setattr__(self, "_real", real_context) + object.__setattr__(self, "_ledger", ledger) + + @property + def portfolio(self) -> InstancePortfolio: + real = object.__getattribute__(self, "_real") + ledger = object.__getattribute__(self, "_ledger") + return InstancePortfolio( + ledger, lambda: getattr(real, "portfolio", None)) + + def __getattr__(self, name: str) -> Any: + return getattr(object.__getattribute__(self, "_real"), name) + + +def make_proxy_context(context: Any, ledger: Optional[Any]) -> Any: + """有账本 → 代理 context;无(回测/影子/测试) → 原 context 原样。""" + if ledger is None: + return context + return InstanceContextProxy(context, ledger) + + +def wrap_scheduler(bt_sched: Callable, ledger: Optional[Any]) -> Callable: + """包装 run_daily/run_monthly:经其注册的回调收到的 context 换成代理。 + + 策略代码零改动——虚拟化发生在 bullet_trade 调度器 → 策略函数之间。 + """ + if ledger is None: + return bt_sched + + def sched(func: Callable, *args: Any, **kw: Any) -> Any: + @functools.wraps(func) + def wrapped(context: Any, *cb_args: Any, **cb_kw: Any) -> Any: + return func(make_proxy_context(context, ledger), + *cb_args, **cb_kw) + return bt_sched(wrapped, *args, **kw) + logger.info("定寸虚拟化已启用: run_daily/run_monthly 回调注入实例账本视图") + return sched diff --git a/sanguo_portfolio/live_strategy.py b/sanguo_portfolio/live_strategy.py index 33249a3..574d808 100644 --- a/sanguo_portfolio/live_strategy.py +++ b/sanguo_portfolio/live_strategy.py @@ -122,6 +122,12 @@ def _setup(context): strategy.broker.get_instance_positions = _ledger.positions_view logger.info("instance 台账通道已注入: get_instance_positions (cash=%.2f 持仓 %d 只)", _ledger.cash, len(_ledger.positions)) + # B2 定寸虚拟化:经 facade 注册的定时回调收到代理 context(portfolio=实例 + # 账本视图),策略定寸/资金守卫不再被全账户污染;引擎内部仍看真实账户。 + # 无账本(回测/影子) → wrap_scheduler 原样返回,行为零改动。 + from .live_portfolio_proxy import wrap_scheduler as _wrap_sched + strategy.broker.run_daily = _wrap_sched(bt_run_daily, _ledger) + strategy.broker.run_monthly = _wrap_sched(bt_run_monthly, _ledger) # A 股费用 + 滑点(与回测默认一致) set_order_cost( OrderCost( diff --git a/tests/portfolio/test_live_portfolio_proxy.py b/tests/portfolio/test_live_portfolio_proxy.py new file mode 100644 index 0000000..d35d53e --- /dev/null +++ b/tests/portfolio/test_live_portfolio_proxy.py @@ -0,0 +1,213 @@ +"""Tests for B2 定寸虚拟化(spec §B2 + A2 属性清单 issue#29)。 + +核心断言:策略决策层看到的 portfolio = 实例账本视图(现金/持仓/定寸不再被 +全账户污染);现价从真 portfolio 透传(市场数据非所有权);未覆盖属性 +fail-fast;无账本(回测/影子)零改动。 +""" +from types import SimpleNamespace + +import pytest + +from sanguo_portfolio.live_instance_ledger import ( + LiveInstanceLedger, set_active, +) +from sanguo_portfolio.live_portfolio_proxy import ( + InstanceContextProxy, InstancePortfolio, make_proxy_context, + wrap_scheduler, +) + + +class _RealPos: + def __init__(self, security, price, amount=0): + self.security = security + self.price = price + self.last_sale_price = price + self.total_amount = amount + + +@pytest.fixture() +def ledger(): + led = LiveInstanceLedger(initial_cash=1_000_000) + # 买入 510300@4.74×4200(费用=est) → cash 减,持仓进账本 + led.apply_trade(is_buy=True, symbol="510300.XSHG", price=4.74, + volume=4200, trade_id="t1", trade_date="2026-08-19") + return led + + +def _real_portfolio(positions): + return SimpleNamespace(positions=positions) + + +def _portfolio(ledger, real_positions=None): + return InstancePortfolio( + ledger, lambda: _real_portfolio(real_positions or {})) + + +# ---------------- 现金/定寸(A2 核心:污染点) ---------------- + +def test_available_cash_is_ledger_not_account(ledger): + """定寸读到的现金=账本现金,不是共享账户 995 万。""" + p = _portfolio(ledger) + # 账本现金 = 100万 − 4200×4.74 − 佣金 max(19908×0.0003,5)=5.9724 → 980086.0276 + assert p.available_cash == pytest.approx( + 1_000_000 - 19_908 - 5.9724, rel=1e-9) + assert p.cash == p.available_cash # _available_cash 的 .cash fallback 同源 + + +def test_positions_from_ledger_with_t1(ledger): + """持仓=账本视图:当日买入 T+1 → closeable=0,amount=总量,成本=账本均价。""" + p = _portfolio(ledger, real_positions={ + "510300.XSHG": _RealPos("510300.XSHG", 4.80)}) + pos = p.positions["510300.XSHG"] + assert pos.security == "510300.XSHG" + assert pos.total_amount == 4200 + assert pos.amount == 4200 + assert pos.closeable_amount == 0 # 今日买入,T+1 锁定 + assert pos.avg_cost == pytest.approx(4.74) # 台账口径=成交价(不含费) + assert pos.cost_basis == pos.avg_cost + # 现价从真 portfolio 透传(市场数据非所有权) + assert pos.price == 4.80 + assert pos.last_sale_price == 4.80 + assert pos.value == pytest.approx(4.80 * 4200) + + +def test_price_missing_value_zero(ledger): + """真 portfolio 无同名标的 → price None,value 0(策略当 0 处理,偏保守)。""" + p = _portfolio(ledger, real_positions={}) + pos = p.positions["510300.XSHG"] + assert pos.price is None + assert pos.value == 0.0 + assert pos.market_value == 0.0 + + +def test_positions_empty_ledger(ledger): + """账本无持仓 → 空 dict(轮换退化为只买不卖=正确行为)。""" + empty = LiveInstanceLedger(initial_cash=100_000) + p = _portfolio(empty) + assert p.positions == {} + + +def test_real_positions_list_form_indexed(ledger): + """真 positions 为 list(bullet_trade 兼容路径)→ 按 .security 索引。""" + p = _portfolio(ledger, real_positions=[ + _RealPos("600036.XSHG", 38.0), _RealPos("510300.XSHG", 4.75)]) + assert p.positions["510300.XSHG"].price == 4.75 + + +# ---------------- equity / 未覆盖属性 ---------------- + +def test_total_value_is_ledger_equity(ledger): + """total_value=账本 equity(现价优先,缺价成本兜底),非全账户。""" + p = _portfolio(ledger, real_positions={ + "510300.XSHG": _RealPos("510300.XSHG", 4.80)}) + cash = ledger.cash + assert p.total_value == pytest.approx(cash + 4.80 * 4200) + assert p.positions_value == pytest.approx(4.80 * 4200) + + +def test_total_value_cost_fallback(ledger): + """缺现价 → 账本 equity 的成本价兜底。""" + p = _portfolio(ledger) + _c, _mv, total = ledger.equity({}) + assert p.total_value == pytest.approx(total) + + +def test_locked_cash_zero_and_uncovered_fail_fast(ledger): + """locked_cash 安全值 0;清单外属性 AttributeError(拒绝静默透传全账户数字)。""" + p = _portfolio(ledger) + assert p.locked_cash == 0.0 + with pytest.raises(AttributeError, match="inout"): + _ = p.inout + + +# ---------------- context 代理 ---------------- + +def test_context_proxy_passthrough_and_portfolio(ledger): + real = SimpleNamespace(current_dt="2026-08-19 09:35:00", + previous_date="2026-08-18", + portfolio=_real_portfolio({})) + proxy = InstanceContextProxy(real, ledger) + assert proxy.current_dt == "2026-08-19 09:35:00" + assert proxy.previous_date == "2026-08-18" + assert isinstance(proxy.portfolio, InstancePortfolio) + assert proxy.portfolio.cash == ledger.cash + + +def test_make_proxy_context_none_ledger_identity(): + ctx = SimpleNamespace(x=1) + assert make_proxy_context(ctx, None) is ctx + + +# ---------------- 调度器包装 ---------------- + +def test_wrap_scheduler_callbacks_get_proxy(ledger): + """经包装注册的回调收到代理 context(portfolio=账本视图)。""" + captured = {} + + def bt_sched(func, time, **kw): + captured["func"] = func + captured["time"] = time + + sched = wrap_scheduler(bt_sched, ledger) + seen_ctx = {} + + def strategy_task(context): + seen_ctx["cash"] = context.portfolio.available_cash + seen_ctx["dt"] = context.current_dt + + strategy_task.__name__ = "strategy_task" + sched(strategy_task, "09:35") + # 调度器侧拿到的 wrapped 保留原函数名(任务命名不受影响) + assert captured["func"].__name__ == "strategy_task" + real_ctx = SimpleNamespace( + current_dt="2026-08-19 09:35:00", + portfolio=_real_portfolio( + {"510300.XSHG": _RealPos("510300.XSHG", 4.80)})) + captured["func"](real_ctx) + assert seen_ctx["cash"] == pytest.approx(ledger.cash) + assert seen_ctx["dt"] == "2026-08-19 09:35:00" + + +def test_wrap_scheduler_none_ledger_passthrough(): + """无账本 → 原调度器原样返回(回测/影子零改动)。""" + def bt_sched(func, *a, **kw): + return "registered" + + assert wrap_scheduler(bt_sched, None) is bt_sched + + +def test_setup_wraps_facade_schedulers(monkeypatch): + """_setup 接线:有账本 → facade.run_daily 被换包装;无账本 → 原样。""" + from sanguo_portfolio import live_strategy + + def _patch(monkeypatch): + import bullet_trade.core as bt_core + import bullet_trade.data.api as bt_data_api + + def fake_run_daily(func, t, **kw): + return None + + monkeypatch.setattr(bt_core, "run_daily", fake_run_daily) + monkeypatch.setattr(bt_core, "run_monthly", + lambda f, d, t, **kw: None) + monkeypatch.setattr(bt_data_api, "get_data_provider", + lambda: SimpleNamespace()) + return bt_core + + bt_core = _patch(monkeypatch) + monkeypatch.setenv("SANGUO_LIVE_STRATEGY", "channel_test") + try: + live_strategy._STATE.update(strategy=None, wired=False) + set_active(LiveInstanceLedger(initial_cash=100_000)) + live_strategy._setup(SimpleNamespace()) + assert live_strategy._STATE["strategy"].broker.run_daily \ + is not bt_core.run_daily # 已包装 + + live_strategy._STATE.update(strategy=None, wired=False) + set_active(None) + live_strategy._setup(SimpleNamespace()) + assert live_strategy._STATE["strategy"].broker.run_daily \ + is bt_core.run_daily # 原样 + finally: + live_strategy._STATE.update(strategy=None, wired=False) + set_active(None)