feat(portfolio): strategy 字段全链透传(API→orchestrator→worker→runner_backtest + 前端选择器)
后端: PortfolioBacktestRequest.strategy → submit_portfolio(strategy=) → spec → portfolio_worker _build_argv --strategy(NAS local + Mac SSH 两分支) 前端: PortfolioBacktestReq.strategy? + 策略下拉(4选项) + 副标题动态 + onSubmit 透传 runner_backtest CLI 已支持 --strategy(choices 4 策略),本提交不动
This commit is contained in:
@@ -2,6 +2,7 @@ import { apiClient } from './client'
|
||||
|
||||
export interface PortfolioBacktestReq {
|
||||
pool: string
|
||||
strategy?: string
|
||||
start_date: string
|
||||
end_date: string
|
||||
initial_cash: number
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, onUnmounted, watch, nextTick } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { EChartsCoreOption } from 'echarts'
|
||||
import { useChart } from '@/composables/useChart'
|
||||
@@ -28,6 +28,7 @@ let pollTimer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const form = reactive({
|
||||
pool: 'hs300_subset',
|
||||
strategy: 'all_weather',
|
||||
start: '2024-01-01',
|
||||
end: '2024-02-29',
|
||||
cash: 1_000_000,
|
||||
@@ -39,6 +40,17 @@ const poolOptions = [
|
||||
{ label: '全市场(慢,非 MVP)', value: 'all' },
|
||||
]
|
||||
|
||||
const strategyOptions = [
|
||||
{ label: '全天候轮动', value: 'all_weather' },
|
||||
{ label: '牛熊动量', value: 'momentum_timing' },
|
||||
{ label: '价值精选', value: 'value_selection' },
|
||||
{ label: '小市值轮动', value: 'small_cap' },
|
||||
]
|
||||
|
||||
const strategyLabel = computed(
|
||||
() => strategyOptions.find((o) => o.value === form.strategy)?.label ?? form.strategy,
|
||||
)
|
||||
|
||||
// 净值曲线
|
||||
const equityEl = ref<HTMLDivElement>()
|
||||
const { setOption: setEquityOption } = useChart(equityEl)
|
||||
@@ -95,6 +107,7 @@ async function onSubmit(): Promise<void> {
|
||||
try {
|
||||
const tid = await postPortfolioBacktest({
|
||||
pool: form.pool,
|
||||
strategy: form.strategy,
|
||||
start_date: form.start,
|
||||
end_date: form.end,
|
||||
initial_cash: form.cash,
|
||||
@@ -150,7 +163,7 @@ async function onSubmit(): Promise<void> {
|
||||
<div>
|
||||
<h2 class="page-title">组合策略回测</h2>
|
||||
<p class="page-subtitle">
|
||||
BulletTrade + 全天候轮动策略 · 本地执行回测 · MVP 验证链路
|
||||
BulletTrade + {{ strategyLabel }} · 本地执行回测 · MVP 验证链路
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,6 +182,17 @@ async function onSubmit(): Promise<void> {
|
||||
</el-select>
|
||||
<span class="muted form-hint">MVP 默认 HS300 子集(20-30 只),快速验证链路</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="策略">
|
||||
<el-select v-model="form.strategy" style="width: 320px">
|
||||
<el-option
|
||||
v-for="opt in strategyOptions"
|
||||
:key="opt.value"
|
||||
:label="opt.label"
|
||||
:value="opt.value"
|
||||
/>
|
||||
</el-select>
|
||||
<span class="muted form-hint">选择回测策略</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="开始日期">
|
||||
<el-date-picker
|
||||
v-model="form.start"
|
||||
|
||||
@@ -33,6 +33,10 @@ class PortfolioBacktestRequest(BaseModel):
|
||||
end_date: str = Field(default="2024-02-29", description="YYYY-MM-DD")
|
||||
initial_cash: float = Field(default=1_000_000.0, description="初始资金(元)")
|
||||
benchmark: str = Field(default="000300.XSHG", description="基准代码")
|
||||
strategy: str = Field(
|
||||
default="all_weather",
|
||||
description="策略: all_weather/momentum_timing/value_selection/small_cap",
|
||||
)
|
||||
|
||||
|
||||
@router.post("/portfolio/backtest", dependencies=[Depends(verify_token)])
|
||||
@@ -43,6 +47,7 @@ async def run_portfolio_backtest(req: PortfolioBacktestRequest):
|
||||
end=req.end_date,
|
||||
cash=req.initial_cash,
|
||||
benchmark=req.benchmark,
|
||||
strategy=req.strategy,
|
||||
max_pool=30,
|
||||
provider_config=None,
|
||||
)
|
||||
|
||||
@@ -47,12 +47,13 @@ def run_portfolio_task(spec: dict) -> Any:
|
||||
end = spec["end"]
|
||||
cash = spec["cash"]
|
||||
benchmark = spec["benchmark"]
|
||||
strategy = spec.get("strategy", "all_weather")
|
||||
max_pool = spec["max_pool"]
|
||||
provider_config = spec.get("provider_config")
|
||||
db_path = spec.get("db_path", "")
|
||||
file_dir = spec.get("file_dir")
|
||||
|
||||
argv, cwd = _build_argv(start, end, cash, benchmark, max_pool, provider_config)
|
||||
argv, cwd = _build_argv(start, end, cash, benchmark, max_pool, provider_config, strategy)
|
||||
logger.info("[portfolio_worker] task=%s running: %s", task_id, " ".join(argv[3:]))
|
||||
|
||||
proc = subprocess.run(
|
||||
@@ -108,6 +109,7 @@ def run_portfolio_task(spec: dict) -> Any:
|
||||
def _build_argv(
|
||||
start: str, end: str, cash: float, benchmark: str,
|
||||
max_pool: int, provider_config: Optional[dict],
|
||||
strategy: str = "all_weather",
|
||||
) -> tuple[list[str], Optional[str]]:
|
||||
"""Three-machine adaptive argv construction.
|
||||
|
||||
@@ -122,6 +124,7 @@ def _build_argv(
|
||||
sys.executable, "-X", "utf8", "-m", "sanguo_portfolio.runner_backtest",
|
||||
"--json", "--start", start, "--end", end,
|
||||
"--cash", str(cash), "--benchmark", benchmark,
|
||||
"--strategy", strategy,
|
||||
"--max-pool", str(max_pool),
|
||||
]
|
||||
# NAS container: unified provider reads NAS authoritative data layer
|
||||
@@ -144,6 +147,7 @@ def _build_argv(
|
||||
f"{_VPS_PYTHON} -X utf8 -m sanguo_portfolio.runner_backtest --json "
|
||||
f"--start {start} --end {end} "
|
||||
f"--cash {cash} --benchmark {benchmark} "
|
||||
f"--strategy {strategy} "
|
||||
f"--max-pool {max_pool}"
|
||||
)
|
||||
ssh_argv = [
|
||||
|
||||
@@ -122,7 +122,8 @@ class Orchestrator:
|
||||
return task_id
|
||||
|
||||
async def submit_portfolio(self, start: str, end: str, cash: float,
|
||||
benchmark: str, max_pool: int = 30,
|
||||
benchmark: str, strategy: str = "all_weather",
|
||||
max_pool: int = 30,
|
||||
provider_config=None) -> str:
|
||||
"""Submit a portfolio backtest task asynchronously.
|
||||
|
||||
@@ -139,6 +140,7 @@ class Orchestrator:
|
||||
end=end,
|
||||
cash=cash,
|
||||
benchmark=benchmark,
|
||||
strategy=strategy,
|
||||
max_pool=max_pool,
|
||||
provider_config=provider_config,
|
||||
db_path=self.db_path,
|
||||
|
||||
Reference in New Issue
Block a user