Add JoinQuant simulated trading automation
This commit is contained in:
@@ -102,6 +102,31 @@ dated by construction/signal date, while the simulator executes at the next
|
|||||||
available open. The calendar option shifts exported target files to that next
|
available open. The calendar option shifts exported target files to that next
|
||||||
trading date, so JoinQuant reads the same target on the same execution session.
|
trading date, so JoinQuant reads the same target on the same execution session.
|
||||||
|
|
||||||
|
For JoinQuant 模拟盘, the browser automation has two operational phases:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Before T open: upload frozen target(s), save strategy, and start/restart 模拟盘.
|
||||||
|
uv run python cli.py joinquant write-browser-config \
|
||||||
|
--out-path /tmp/chinese-equity-quant-realdata/joinquant_sim_config.json \
|
||||||
|
--strategy-url "https://www.joinquant.com/<your 模拟盘 page>" \
|
||||||
|
--flow sim-trade
|
||||||
|
|
||||||
|
uv run python cli.py joinquant run-browser-sim \
|
||||||
|
--manifest-path /tmp/chinese-equity-quant-realdata/joinquant_smoke_manifest.json \
|
||||||
|
--config-path /tmp/chinese-equity-quant-realdata/joinquant_sim_config.json \
|
||||||
|
--storage-state ~/.config/chinese-equity-quant/joinquant_storage_state.json \
|
||||||
|
--headed
|
||||||
|
|
||||||
|
# After T close: download/export JoinQuant fills, positions, and pnl, then
|
||||||
|
# ingest/reconcile. The same run-browser-sim command can do this if the config
|
||||||
|
# includes download actions, otherwise use the ingest/reconcile commands.
|
||||||
|
```
|
||||||
|
|
||||||
|
The default simulated-trading template includes selectors for saving the
|
||||||
|
strategy and clicking simulated-trading controls such as `模拟盘`, `模拟交易`,
|
||||||
|
`启动`, and `重启`. These selectors are intentionally configurable because the
|
||||||
|
JoinQuant web UI can differ by account and page version.
|
||||||
|
|
||||||
## Target-Shares Mode
|
## Target-Shares Mode
|
||||||
|
|
||||||
`target_shares` is the default and preferred correctness mode. The exported
|
`target_shares` is the default and preferred correctness mode. The exported
|
||||||
@@ -261,12 +286,13 @@ uv run python cli.py joinquant browser-login \
|
|||||||
--storage-state ~/.config/chinese-equity-quant/joinquant_storage_state.json
|
--storage-state ~/.config/chinese-equity-quant/joinquant_storage_state.json
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a selector/action config:
|
Create a selector/action config for a historical backtest:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run python cli.py joinquant write-browser-config \
|
uv run python cli.py joinquant write-browser-config \
|
||||||
--out-path /tmp/chinese-equity-quant-realdata/joinquant_browser_config.json \
|
--out-path /tmp/chinese-equity-quant-realdata/joinquant_browser_config.json \
|
||||||
--strategy-url "https://www.joinquant.com/<your strategy page>"
|
--strategy-url "https://www.joinquant.com/<your strategy page>" \
|
||||||
|
--flow backtest
|
||||||
```
|
```
|
||||||
|
|
||||||
If selectors need tuning, capture the logged-in page:
|
If selectors need tuning, capture the logged-in page:
|
||||||
@@ -293,6 +319,16 @@ download result CSVs, and take screenshots. When the configured downloads
|
|||||||
produce fills, positions, and PnL CSVs, the runner automatically calls
|
produce fills, positions, and PnL CSVs, the runner automatically calls
|
||||||
`joinquant ingest` and `joinquant reconcile`.
|
`joinquant ingest` and `joinquant reconcile`.
|
||||||
|
|
||||||
|
For forward testing / 模拟盘, create the config with `--flow sim-trade` and run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
uv run python cli.py joinquant run-browser-sim \
|
||||||
|
--manifest-path /tmp/chinese-equity-quant-realdata/joinquant_smoke_manifest.json \
|
||||||
|
--config-path /tmp/chinese-equity-quant-realdata/joinquant_sim_config.json \
|
||||||
|
--storage-state ~/.config/chinese-equity-quant/joinquant_storage_state.json \
|
||||||
|
--headed
|
||||||
|
```
|
||||||
|
|
||||||
Do not store raw JoinQuant passwords in this repository. The browser state file
|
Do not store raw JoinQuant passwords in this repository. The browser state file
|
||||||
is created with `0600` permissions and should live under `~/.config`, outside
|
is created with `0600` permissions and should live under `~/.config`, outside
|
||||||
the repo.
|
the repo.
|
||||||
|
|||||||
@@ -29,13 +29,24 @@ uv run python cli.py joinquant browser-login \
|
|||||||
|
|
||||||
uv run python cli.py joinquant write-browser-config \
|
uv run python cli.py joinquant write-browser-config \
|
||||||
--out-path /tmp/chinese-equity-quant-realdata/joinquant_browser_config.json \
|
--out-path /tmp/chinese-equity-quant-realdata/joinquant_browser_config.json \
|
||||||
--strategy-url "https://www.joinquant.com/..."
|
--strategy-url "https://www.joinquant.com/..." \
|
||||||
|
--flow backtest
|
||||||
|
|
||||||
uv run python cli.py joinquant run-browser-backtest \
|
uv run python cli.py joinquant run-browser-backtest \
|
||||||
--manifest-path /tmp/chinese-equity-quant-realdata/joinquant_smoke_manifest.json \
|
--manifest-path /tmp/chinese-equity-quant-realdata/joinquant_smoke_manifest.json \
|
||||||
--config-path /tmp/chinese-equity-quant-realdata/joinquant_browser_config.json \
|
--config-path /tmp/chinese-equity-quant-realdata/joinquant_browser_config.json \
|
||||||
--storage-state ~/.config/chinese-equity-quant/joinquant_storage_state.json
|
--storage-state ~/.config/chinese-equity-quant/joinquant_storage_state.json
|
||||||
|
|
||||||
|
uv run python cli.py joinquant write-browser-config \
|
||||||
|
--out-path /tmp/chinese-equity-quant-realdata/joinquant_sim_config.json \
|
||||||
|
--strategy-url "https://www.joinquant.com/<模拟盘 page>" \
|
||||||
|
--flow sim-trade
|
||||||
|
|
||||||
|
uv run python cli.py joinquant run-browser-sim \
|
||||||
|
--manifest-path /tmp/chinese-equity-quant-realdata/joinquant_smoke_manifest.json \
|
||||||
|
--config-path /tmp/chinese-equity-quant-realdata/joinquant_sim_config.json \
|
||||||
|
--storage-state ~/.config/chinese-equity-quant/joinquant_storage_state.json
|
||||||
|
|
||||||
uv run python cli.py joinquant export-targets \
|
uv run python cli.py joinquant export-targets \
|
||||||
--positions-path portfolio/run1.pq \
|
--positions-path portfolio/run1.pq \
|
||||||
--portfolio-name run1 \
|
--portfolio-name run1 \
|
||||||
@@ -84,3 +95,9 @@ export paths.
|
|||||||
Playwright. It reuses a saved browser login state, executes the configured UI
|
Playwright. It reuses a saved browser login state, executes the configured UI
|
||||||
actions, downloads JoinQuant CSVs when configured, and runs ingest/reconcile
|
actions, downloads JoinQuant CSVs when configured, and runs ingest/reconcile
|
||||||
automatically once all three CSVs are present.
|
automatically once all three CSVs are present.
|
||||||
|
|
||||||
|
`run-browser-sim` is the forward-test / 模拟盘 equivalent. Use a `--flow
|
||||||
|
sim-trade` config to upload the frozen next-session target file, save the
|
||||||
|
strategy, and start or restart the JoinQuant simulated-trading job. After close,
|
||||||
|
run it again with download actions or use `ingest` / `reconcile` directly on
|
||||||
|
exported CSVs.
|
||||||
|
|||||||
+126
-20
@@ -1,4 +1,4 @@
|
|||||||
"""Browser automation for JoinQuant cloud backtest runs.
|
"""Browser automation for JoinQuant cloud backtest and simulated-trading runs.
|
||||||
|
|
||||||
JoinQuant's public ``jqdatasdk`` is a data API; cloud strategy upload/run/export
|
JoinQuant's public ``jqdatasdk`` is a data API; cloud strategy upload/run/export
|
||||||
is exposed through the web application. This module keeps that automation
|
is exposed through the web application. This module keeps that automation
|
||||||
@@ -29,25 +29,14 @@ def _require_playwright():
|
|||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Playwright is required for JoinQuant browser automation. Install it "
|
"Playwright is required for JoinQuant browser automation. Install it "
|
||||||
"in this uv environment, then install Chromium:\n"
|
"in this uv environment, then install Chromium:\n"
|
||||||
" uv pip install playwright\n"
|
" uv sync --extra joinquant-browser\n"
|
||||||
" uv run playwright install chromium"
|
" uv run playwright install chromium"
|
||||||
) from exc
|
) from exc
|
||||||
return sync_playwright, PlaywrightTimeoutError
|
return sync_playwright, PlaywrightTimeoutError
|
||||||
|
|
||||||
|
|
||||||
def default_browser_config(strategy_url: str = "") -> dict[str, Any]:
|
def _backtest_actions() -> list[dict[str, Any]]:
|
||||||
"""Return a selector/action template for JoinQuant browser automation."""
|
return [
|
||||||
return {
|
|
||||||
"strategy_url": strategy_url,
|
|
||||||
"login_url": DEFAULT_LOGIN_URL,
|
|
||||||
"headless": False,
|
|
||||||
"timeout_ms": 120_000,
|
|
||||||
"notes": [
|
|
||||||
"Fill strategy_url and selectors after inspecting your JoinQuant strategy page.",
|
|
||||||
"Use `joinquant browser-snapshot` to capture HTML/screenshots for selector tuning.",
|
|
||||||
"The action list is declarative and runs in order.",
|
|
||||||
],
|
|
||||||
"actions": [
|
|
||||||
{"type": "goto", "url": "{strategy_url}"},
|
{"type": "goto", "url": "{strategy_url}"},
|
||||||
{
|
{
|
||||||
"type": "paste_text_file",
|
"type": "paste_text_file",
|
||||||
@@ -104,16 +93,88 @@ def default_browser_config(strategy_url: str = "") -> dict[str, Any]:
|
|||||||
"optional": True,
|
"optional": True,
|
||||||
},
|
},
|
||||||
{"type": "screenshot", "path": "{run_artifact_dir}/final.png"},
|
{"type": "screenshot", "path": "{run_artifact_dir}/final.png"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _sim_trade_actions() -> list[dict[str, Any]]:
|
||||||
|
return [
|
||||||
|
{"type": "goto", "url": "{strategy_url}"},
|
||||||
|
{
|
||||||
|
"type": "paste_text_file",
|
||||||
|
"selector": "textarea, .ace_text-input, .cm-content, .CodeMirror textarea",
|
||||||
|
"path": "{wrapper_path}",
|
||||||
|
"description": "Paste generated wrapper strategy into the simulated-trading strategy editor.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "set_input_files",
|
||||||
|
"selector": "input[type=file]",
|
||||||
|
"paths": "{target_csvs}",
|
||||||
|
"description": "Upload frozen target CSV files for 模拟盘.",
|
||||||
|
"optional": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "click",
|
||||||
|
"selector": "text=/保存|Save/i",
|
||||||
|
"description": "Save the strategy code and uploaded files.",
|
||||||
|
"optional": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wait_for_selector",
|
||||||
|
"selector": "text=/保存成功|已保存|Saved/i",
|
||||||
|
"timeout_ms": 120_000,
|
||||||
|
"optional": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "click",
|
||||||
|
"selector": "text=/模拟盘|模拟交易|启动模拟|运行模拟|启动|重启|Run Sim|Start|Restart/i",
|
||||||
|
"description": "Start or restart the JoinQuant simulated-trading job.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "wait_for_selector",
|
||||||
|
"selector": "text=/运行中|已启动|模拟交易运行|Started|Running/i",
|
||||||
|
"timeout_ms": 180_000,
|
||||||
|
"optional": True,
|
||||||
|
},
|
||||||
|
{"type": "screenshot", "path": "{run_artifact_dir}/sim_trade_final.png"},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def default_browser_config(strategy_url: str = "", *, flow: str = "backtest") -> dict[str, Any]:
|
||||||
|
"""Return a selector/action template for JoinQuant browser automation."""
|
||||||
|
if flow not in {"backtest", "sim-trade", "sim_trade"}:
|
||||||
|
raise ValueError("flow must be 'backtest' or 'sim-trade'")
|
||||||
|
normalized_flow = "sim-trade" if flow == "sim_trade" else flow
|
||||||
|
actions = _sim_trade_actions() if normalized_flow == "sim-trade" else _backtest_actions()
|
||||||
|
return {
|
||||||
|
"flow": normalized_flow,
|
||||||
|
"strategy_url": strategy_url,
|
||||||
|
"login_url": DEFAULT_LOGIN_URL,
|
||||||
|
"headless": False,
|
||||||
|
"timeout_ms": 120_000,
|
||||||
|
"notes": [
|
||||||
|
"Fill strategy_url and selectors after inspecting your JoinQuant strategy page.",
|
||||||
|
"Use `joinquant browser-snapshot` to capture HTML/screenshots for selector tuning.",
|
||||||
|
"The action list is declarative and runs in order.",
|
||||||
],
|
],
|
||||||
|
"actions": actions,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def write_browser_config_template(path: str | Path, *, strategy_url: str = "") -> Path:
|
def write_browser_config_template(
|
||||||
|
path: str | Path,
|
||||||
|
*,
|
||||||
|
strategy_url: str = "",
|
||||||
|
flow: str = "backtest",
|
||||||
|
) -> Path:
|
||||||
"""Write a JSON config template for browser automation."""
|
"""Write a JSON config template for browser automation."""
|
||||||
out_path = Path(path)
|
out_path = Path(path)
|
||||||
out_path.parent.mkdir(parents=True, exist_ok=True)
|
out_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
out_path.write_text(
|
out_path.write_text(
|
||||||
json.dumps(default_browser_config(strategy_url), indent=2, ensure_ascii=False) + "\n",
|
json.dumps(
|
||||||
|
default_browser_config(strategy_url, flow=flow),
|
||||||
|
indent=2,
|
||||||
|
ensure_ascii=False,
|
||||||
|
) + "\n",
|
||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
return out_path
|
return out_path
|
||||||
@@ -302,7 +363,7 @@ def _run_action(page: Any, action: dict[str, Any], context: dict[str, Any], time
|
|||||||
return record
|
return record
|
||||||
|
|
||||||
|
|
||||||
def run_browser_backtest(
|
def run_browser_flow(
|
||||||
*,
|
*,
|
||||||
manifest_path: str | Path,
|
manifest_path: str | Path,
|
||||||
config_path: str | Path,
|
config_path: str | Path,
|
||||||
@@ -310,12 +371,14 @@ def run_browser_backtest(
|
|||||||
out_dir: str | Path | None = None,
|
out_dir: str | Path | None = None,
|
||||||
headless: bool | None = None,
|
headless: bool | None = None,
|
||||||
auto_reconcile: bool = True,
|
auto_reconcile: bool = True,
|
||||||
|
flow_name: str | None = None,
|
||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
"""Run configured browser automation for a JoinQuant backtest."""
|
"""Run configured browser automation for a JoinQuant web workflow."""
|
||||||
sync_playwright, _ = _require_playwright()
|
sync_playwright, _ = _require_playwright()
|
||||||
manifest = load_json(manifest_path)
|
manifest = load_json(manifest_path)
|
||||||
config = load_json(config_path)
|
config = load_json(config_path)
|
||||||
artifact_dir = Path(out_dir or Path(str(manifest["joinquant_export_dir"])).parent / "browser_run")
|
flow = flow_name or str(config.get("flow") or "browser")
|
||||||
|
artifact_dir = Path(out_dir or Path(str(manifest["joinquant_export_dir"])).parent / f"browser_{flow}")
|
||||||
artifact_dir.mkdir(parents=True, exist_ok=True)
|
artifact_dir.mkdir(parents=True, exist_ok=True)
|
||||||
context_vars = _manifest_context(manifest, artifact_dir, config)
|
context_vars = _manifest_context(manifest, artifact_dir, config)
|
||||||
|
|
||||||
@@ -371,6 +434,7 @@ def run_browser_backtest(
|
|||||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||||
"manifest_path": str(manifest_path),
|
"manifest_path": str(manifest_path),
|
||||||
"config_path": str(config_path),
|
"config_path": str(config_path),
|
||||||
|
"flow": flow,
|
||||||
"storage_state": str(storage_state),
|
"storage_state": str(storage_state),
|
||||||
"artifact_dir": str(artifact_dir),
|
"artifact_dir": str(artifact_dir),
|
||||||
"actions": records,
|
"actions": records,
|
||||||
@@ -384,3 +448,45 @@ def run_browser_backtest(
|
|||||||
encoding="utf-8",
|
encoding="utf-8",
|
||||||
)
|
)
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def run_browser_backtest(
|
||||||
|
*,
|
||||||
|
manifest_path: str | Path,
|
||||||
|
config_path: str | Path,
|
||||||
|
storage_state: str | Path,
|
||||||
|
out_dir: str | Path | None = None,
|
||||||
|
headless: bool | None = None,
|
||||||
|
auto_reconcile: bool = True,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Run configured browser automation for a JoinQuant backtest."""
|
||||||
|
return run_browser_flow(
|
||||||
|
manifest_path=manifest_path,
|
||||||
|
config_path=config_path,
|
||||||
|
storage_state=storage_state,
|
||||||
|
out_dir=out_dir,
|
||||||
|
headless=headless,
|
||||||
|
auto_reconcile=auto_reconcile,
|
||||||
|
flow_name="backtest",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def run_browser_sim_trade(
|
||||||
|
*,
|
||||||
|
manifest_path: str | Path,
|
||||||
|
config_path: str | Path,
|
||||||
|
storage_state: str | Path,
|
||||||
|
out_dir: str | Path | None = None,
|
||||||
|
headless: bool | None = None,
|
||||||
|
auto_reconcile: bool = True,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""Run configured browser automation for JoinQuant 模拟盘."""
|
||||||
|
return run_browser_flow(
|
||||||
|
manifest_path=manifest_path,
|
||||||
|
config_path=config_path,
|
||||||
|
storage_state=storage_state,
|
||||||
|
out_dir=out_dir,
|
||||||
|
headless=headless,
|
||||||
|
auto_reconcile=auto_reconcile,
|
||||||
|
flow_name="sim-trade",
|
||||||
|
)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import click
|
|||||||
from plugins.joinquant.browser import (
|
from plugins.joinquant.browser import (
|
||||||
browser_snapshot,
|
browser_snapshot,
|
||||||
run_browser_backtest,
|
run_browser_backtest,
|
||||||
|
run_browser_sim_trade,
|
||||||
save_login_state,
|
save_login_state,
|
||||||
write_browser_config_template,
|
write_browser_config_template,
|
||||||
)
|
)
|
||||||
@@ -258,10 +259,17 @@ def browser_login_cmd(storage_state, login_url, headless, wait_seconds):
|
|||||||
|
|
||||||
@joinquant.command("write-browser-config")
|
@joinquant.command("write-browser-config")
|
||||||
@click.option("--out-path", required=True, help="Path for JSON browser automation config")
|
@click.option("--out-path", required=True, help="Path for JSON browser automation config")
|
||||||
@click.option("--strategy-url", default="", help="JoinQuant strategy edit/backtest URL")
|
@click.option("--strategy-url", default="", help="JoinQuant strategy edit/backtest/模拟盘 URL")
|
||||||
def write_browser_config_cmd(out_path, strategy_url):
|
@click.option(
|
||||||
|
"--flow",
|
||||||
|
type=click.Choice(["backtest", "sim-trade"]),
|
||||||
|
default="backtest",
|
||||||
|
show_default=True,
|
||||||
|
help="Browser automation template to write",
|
||||||
|
)
|
||||||
|
def write_browser_config_cmd(out_path, strategy_url, flow):
|
||||||
"""Write a browser automation selector/action config template."""
|
"""Write a browser automation selector/action config template."""
|
||||||
path = write_browser_config_template(out_path, strategy_url=strategy_url)
|
path = write_browser_config_template(out_path, strategy_url=strategy_url, flow=flow)
|
||||||
click.echo(f"Saved JoinQuant browser config template: {path}")
|
click.echo(f"Saved JoinQuant browser config template: {path}")
|
||||||
|
|
||||||
|
|
||||||
@@ -323,3 +331,40 @@ def run_browser_backtest_cmd(
|
|||||||
click.echo(f" {key}: {path}")
|
click.echo(f" {key}: {path}")
|
||||||
if report["reconcile_paths"]:
|
if report["reconcile_paths"]:
|
||||||
click.echo(f"Saved reconciliation summary: {report['reconcile_paths']['summary_md']}")
|
click.echo(f"Saved reconciliation summary: {report['reconcile_paths']['summary_md']}")
|
||||||
|
|
||||||
|
|
||||||
|
@joinquant.command("run-browser-sim")
|
||||||
|
@click.option("--manifest-path", required=True, help="Manifest from target preparation")
|
||||||
|
@click.option("--config-path", required=True, help="JSON simulated-trading browser config")
|
||||||
|
@click.option(
|
||||||
|
"--storage-state",
|
||||||
|
default="~/.config/chinese-equity-quant/joinquant_storage_state.json",
|
||||||
|
show_default=True,
|
||||||
|
)
|
||||||
|
@click.option("--out-dir", default=None, help="Browser-run artifact directory")
|
||||||
|
@click.option("--headed", is_flag=True, help="Run with a visible browser")
|
||||||
|
@click.option("--no-auto-reconcile", is_flag=True, help="Skip ingest/reconcile after downloads")
|
||||||
|
def run_browser_sim_cmd(
|
||||||
|
manifest_path,
|
||||||
|
config_path,
|
||||||
|
storage_state,
|
||||||
|
out_dir,
|
||||||
|
headed,
|
||||||
|
no_auto_reconcile,
|
||||||
|
):
|
||||||
|
"""Run JoinQuant 模拟盘 browser automation from manifest and config."""
|
||||||
|
report = run_browser_sim_trade(
|
||||||
|
manifest_path=manifest_path,
|
||||||
|
config_path=config_path,
|
||||||
|
storage_state=storage_state,
|
||||||
|
out_dir=out_dir,
|
||||||
|
headless=not headed,
|
||||||
|
auto_reconcile=not no_auto_reconcile,
|
||||||
|
)
|
||||||
|
click.echo(f"Saved browser sim-trade report: {report['report_path']}")
|
||||||
|
if report["downloaded"]:
|
||||||
|
click.echo("Downloaded JoinQuant CSVs:")
|
||||||
|
for key, path in report["downloaded"].items():
|
||||||
|
click.echo(f" {key}: {path}")
|
||||||
|
if report["reconcile_paths"]:
|
||||||
|
click.echo(f"Saved reconciliation summary: {report['reconcile_paths']['summary_md']}")
|
||||||
|
|||||||
@@ -559,6 +559,27 @@ def test_browser_config_template_and_placeholder_resolution(tmp_path):
|
|||||||
assert resolve_template("save:{expected_joinquant_csvs.fills}", context) == "save:/tmp/jq_fills.csv"
|
assert resolve_template("save:{expected_joinquant_csvs.fills}", context) == "save:/tmp/jq_fills.csv"
|
||||||
|
|
||||||
|
|
||||||
|
def test_sim_trade_browser_config_template(tmp_path):
|
||||||
|
config_path = write_browser_config_template(
|
||||||
|
tmp_path / "sim_config.json",
|
||||||
|
strategy_url="https://www.joinquant.com/sim",
|
||||||
|
flow="sim-trade",
|
||||||
|
)
|
||||||
|
config = json.loads(config_path.read_text())
|
||||||
|
|
||||||
|
assert config["flow"] == "sim-trade"
|
||||||
|
selectors = " ".join(
|
||||||
|
action.get("selector", "") for action in config["actions"]
|
||||||
|
)
|
||||||
|
assert "模拟盘" in selectors
|
||||||
|
assert "模拟交易" in selectors
|
||||||
|
assert any(
|
||||||
|
action["type"] == "screenshot"
|
||||||
|
and action["path"] == "{run_artifact_dir}/sim_trade_final.png"
|
||||||
|
for action in config["actions"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_joinquant_cli_browser_config_smoke(tmp_path):
|
def test_joinquant_cli_browser_config_smoke(tmp_path):
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
config_path = tmp_path / "browser_config.json"
|
config_path = tmp_path / "browser_config.json"
|
||||||
@@ -574,3 +595,17 @@ def test_joinquant_cli_browser_config_smoke(tmp_path):
|
|||||||
assert result.exit_code == 0, result.output
|
assert result.exit_code == 0, result.output
|
||||||
assert config_path.exists()
|
assert config_path.exists()
|
||||||
assert default_browser_config()["actions"]
|
assert default_browser_config()["actions"]
|
||||||
|
|
||||||
|
sim_config_path = tmp_path / "sim_browser_config.json"
|
||||||
|
result = runner.invoke(cli, [
|
||||||
|
"joinquant",
|
||||||
|
"write-browser-config",
|
||||||
|
"--out-path",
|
||||||
|
str(sim_config_path),
|
||||||
|
"--strategy-url",
|
||||||
|
"https://www.joinquant.com/sim",
|
||||||
|
"--flow",
|
||||||
|
"sim-trade",
|
||||||
|
])
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert json.loads(sim_config_path.read_text())["flow"] == "sim-trade"
|
||||||
|
|||||||
Reference in New Issue
Block a user