Skip to content

Commit 54157b1

Browse files
Pigbibicursoragent
andauthored
Add per-platform cash-only execution and smooth deleverage switching. (#145)
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent b74794c commit 54157b1

6 files changed

Lines changed: 31 additions & 14 deletions

File tree

.github/workflows/sync-cloud-run-env.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ jobs:
130130
INCOME_LAYER_ENABLED: ${{ vars.INCOME_LAYER_ENABLED }}
131131
INCOME_LAYER_START_USD: ${{ vars.INCOME_LAYER_START_USD }}
132132
INCOME_LAYER_MAX_RATIO: ${{ vars.INCOME_LAYER_MAX_RATIO }}
133+
FIRSTRADE_CASH_ONLY_EXECUTION: ${{ vars.FIRSTRADE_CASH_ONLY_EXECUTION }}
133134
CASH_ONLY_EXECUTION: ${{ vars.CASH_ONLY_EXECUTION }}
134135
DCA_MODE: ${{ vars.DCA_MODE }}
135136
DCA_BASE_INVESTMENT_USD: ${{ vars.DCA_BASE_INVESTMENT_USD }}

decision_mapper.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
from dataclasses import replace
55
from typing import Any
66

7-
from us_equity_strategies.cash_only_equity import build_portfolio_inputs_from_snapshot
7+
from us_equity_strategies.cash_only_equity import (
8+
build_portfolio_inputs_from_snapshot,
9+
resolve_weight_translation_equity,
10+
)
811
from quant_platform_kit.strategy_contracts import (
912
PositionTarget,
1013
StrategyContractValidationError,
@@ -307,32 +310,39 @@ def _normalize_to_value_decision(
307310
*,
308311
portfolio_inputs,
309312
runtime_metadata: Mapping[str, Any] | None,
313+
cash_only_execution: bool = True,
310314
) -> tuple[StrategyDecision, ValueTargetExecutionAnnotations | None]:
311315
target_mode = resolve_decision_target_mode(decision)
312316
no_execute = "no_execute" in set(decision.risk_flags)
313317
if target_mode == "value" and not no_execute:
314318
return decision, None
315319
if target_mode == "weight" and not no_execute:
316-
total_equity = float(portfolio_inputs.total_equity)
317-
if total_equity <= 0.0:
320+
total_equity, block_execution, deleverage_mode = resolve_weight_translation_equity(
321+
portfolio_inputs,
322+
cash_only_execution=cash_only_execution,
323+
)
324+
if block_execution:
318325
diagnostics = {
319326
**dict(runtime_metadata or {}),
320327
**dict(decision.diagnostics),
321328
"execution_blocked_reason": "non_positive_total_equity",
322-
"portfolio_total_equity": total_equity,
329+
"portfolio_total_equity": float(portfolio_inputs.total_equity),
323330
}
324331
return _build_zero_equity_no_execute_decision(
325332
decision,
326333
portfolio_inputs=portfolio_inputs,
327334
diagnostics=diagnostics,
328335
), _build_weight_translation_annotations(
329336
decision,
330-
total_equity=total_equity,
337+
total_equity=float(portfolio_inputs.total_equity),
331338
liquid_cash=float(portfolio_inputs.liquid_cash),
332339
runtime_metadata=runtime_metadata,
333340
)
341+
diagnostics = dict(decision.diagnostics)
342+
if deleverage_mode:
343+
diagnostics["cash_only_deleverage_mode"] = True
334344
translated = translate_decision_to_target_mode(
335-
decision,
345+
replace(decision, diagnostics=diagnostics) if deleverage_mode else decision,
336346
target_mode="value",
337347
total_equity=total_equity,
338348
)
@@ -416,6 +426,7 @@ def map_strategy_decision_to_plan(
416426
decision,
417427
portfolio_inputs=portfolio_inputs,
418428
runtime_metadata=runtime_metadata,
429+
cash_only_execution=cash_only_execution,
419430
)
420431
annotations = translated_annotations or _build_annotations(
421432
normalized_decision,

requirements.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
flask
22
gunicorn
33
firstrade==0.0.39
4-
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@bc5351d3567b2bb60416dc969062c37eedbbf65e
5-
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@5aa949df2962b70d6fdb11c33f7d150492795f16
4+
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@7b6e3ce33e6563db4794fa7b865db9ec428dc478
5+
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@78172596f64c6423a1ab304e53e5d0e18a865175
66
google-cloud-storage
77
google-auth
88
requests

runtime_config_support.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from quant_platform_kit.common.runtime_config import (
1010
resolve_bool_value,
11+
resolve_cash_only_execution_env,
1112
resolve_optional_float_env,
1213
resolve_strategy_runtime_path_settings,
1314
)
@@ -165,7 +166,10 @@ def load_platform_runtime_settings(
165166
tg_chat_id=os.getenv("GLOBAL_TELEGRAM_CHAT_ID"),
166167
dry_run_only=dry_run_only,
167168
runtime_target_enabled=_runtime_target_enabled_env(),
168-
cash_only_execution=_resolve_cash_only_execution_env(),
169+
cash_only_execution=resolve_cash_only_execution_env(
170+
os.environ,
171+
platform_env_prefix="FIRSTRADE",
172+
),
169173
live_trading_enabled=resolve_bool_value(os.getenv("FIRSTRADE_ENABLE_LIVE_TRADING")),
170174
run_strategy_on_http=resolve_bool_value(os.getenv("FIRSTRADE_RUN_STRATEGY_ON_HTTP")),
171175
live_order_ack=resolve_bool_value(os.getenv("FIRSTRADE_LIVE_ORDER_ACK")),
@@ -370,11 +374,6 @@ def _runtime_target_enabled_env() -> bool:
370374
return True if value is None else value
371375

372376

373-
def _resolve_cash_only_execution_env(name: str = "CASH_ONLY_EXECUTION") -> bool:
374-
value = _optional_bool_env(name)
375-
return True if value is None else value
376-
377-
378377
def _optional_ratio_env(name: str) -> float | None:
379378
value = resolve_optional_float_env(os.environ, name)
380379
if value is None:

scripts/build_cloud_run_env_sync_plan.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ def _should_add_local_src(candidate: Path) -> bool:
7676
"FIRSTRADE_MAX_ORDER_NOTIONAL_USD",
7777
"FIRSTRADE_MIN_RESERVED_CASH_USD",
7878
"FIRSTRADE_RESERVED_CASH_RATIO",
79+
"FIRSTRADE_CASH_ONLY_EXECUTION",
7980
"FIRSTRADE_SAFE_HAVEN_CASH_SUBSTITUTE_THRESHOLD_USD",
8081
"FIRSTRADE_SMOKE_SYMBOL",
8182
"FIRSTRADE_FEATURE_SNAPSHOT_PATH",

tests/test_runtime_config_support.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@ def test_cash_only_execution_defaults_true_and_loads_override(monkeypatch):
108108
assert default_settings.cash_only_execution is True
109109

110110
monkeypatch.setenv("CASH_ONLY_EXECUTION", "false")
111+
legacy_settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
112+
assert legacy_settings.cash_only_execution is False
113+
114+
monkeypatch.delenv("CASH_ONLY_EXECUTION", raising=False)
115+
monkeypatch.setenv("FIRSTRADE_CASH_ONLY_EXECUTION", "false")
111116
disabled_settings = load_platform_runtime_settings(project_id_resolver=lambda: "project-1")
112117
assert disabled_settings.cash_only_execution is False
113118

0 commit comments

Comments
 (0)