Add ProofLine criteria documentation and Strategy Catalog v1 implementation
- Add acceptance criteria documentation (AC-0013 through AC-0023) - Add Line-0003 discovery documentation - Implement Strategy Catalog v1 with new database migration - Update portfolio API endpoints and schemas - Refactor strategy and portfolio services - Add strategy management CLI commands - Update domain models with strategy catalog support - Add comprehensive tests for v1 strategies - Update design documentation (data model, interfaces, runtime flows)
This commit is contained in:
+68
-4
@@ -469,9 +469,40 @@ class FakeRepo:
|
||||
def get_strategy_set(self, id: str) -> StrategySet | None:
|
||||
return self.strategy_set if self.strategy_set.id == id else None
|
||||
|
||||
def list_strategy_sets(self) -> list[StrategySet]:
|
||||
def list_strategy_sets(self, *, include_inactive: bool = True) -> list[StrategySet]:
|
||||
from datetime import datetime, timezone
|
||||
return sorted(self._strategy_sets.values(), key=lambda s: s.created_at or datetime.now(timezone.utc), reverse=True)
|
||||
values = list(self._strategy_sets.values())
|
||||
if not include_inactive:
|
||||
values = [item for item in values if item.status == "active"]
|
||||
return sorted(values, key=lambda s: s.created_at or datetime.now(timezone.utc), reverse=True)
|
||||
|
||||
def update_strategy_set(
|
||||
self,
|
||||
id: str,
|
||||
*,
|
||||
name: str | None = None,
|
||||
config: str | None = None,
|
||||
status: str | None = None,
|
||||
) -> StrategySet:
|
||||
strategy = self._strategy_sets.get(id)
|
||||
if strategy is None:
|
||||
raise ValueError("전략을 찾을 수 없습니다")
|
||||
final_name = name if name is not None else strategy.name
|
||||
final_config = config if config is not None else strategy.config
|
||||
final_status = status if status is not None else strategy.status
|
||||
if (final_name, final_config, final_status) == (
|
||||
strategy.name,
|
||||
strategy.config,
|
||||
strategy.status,
|
||||
):
|
||||
return strategy
|
||||
strategy.name = final_name
|
||||
strategy.config = final_config
|
||||
strategy.status = final_status
|
||||
strategy.nick = f"{strategy.name}:updated"
|
||||
strategy.revision += 1
|
||||
strategy.updated_at = datetime.now(timezone.utc)
|
||||
return strategy
|
||||
|
||||
def delete_strategy_set(self, id: str) -> None:
|
||||
if id in self._strategy_sets:
|
||||
@@ -670,8 +701,8 @@ def test_cli_create_buy_and_delete_flow() -> None:
|
||||
_TEST_ASSET_ID,
|
||||
"--cash-balance",
|
||||
"1000000",
|
||||
"--strategy-params",
|
||||
'{"target_asset_ratio": 0.5}',
|
||||
"--strategy-id",
|
||||
"set-1",
|
||||
"--force",
|
||||
],
|
||||
)
|
||||
@@ -717,6 +748,8 @@ def test_cli_write_command_prompts_before_create() -> None:
|
||||
_TEST_ASSET_ID,
|
||||
"--cash-balance",
|
||||
"1000000",
|
||||
"--strategy-id",
|
||||
"set-1",
|
||||
],
|
||||
input="y\n",
|
||||
)
|
||||
@@ -740,6 +773,8 @@ def test_cli_write_command_can_abort() -> None:
|
||||
_TEST_ASSET_ID,
|
||||
"--cash-balance",
|
||||
"1000000",
|
||||
"--strategy-id",
|
||||
"set-1",
|
||||
],
|
||||
input="n\n",
|
||||
)
|
||||
@@ -1434,3 +1469,32 @@ def test_cli_service_token_rebind_rejects_cross_user_daemon() -> None:
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "해당 토큰 사용자에 속하지 않습니다" in result.stdout
|
||||
|
||||
|
||||
def test_cli_strategy_commands_show_status_revision_and_preserve_id() -> None:
|
||||
repo = FakeRepo()
|
||||
runner = build_runner(repo)
|
||||
|
||||
listed = runner.invoke(runner.app, ["strategy", "list"])
|
||||
updated = runner.invoke(
|
||||
runner.app,
|
||||
["strategy", "update", "set-1", "--status", "inactive", "--name", "target_value_v2"],
|
||||
)
|
||||
no_change = runner.invoke(runner.app, ["strategy", "update", "set-1", "--status", "inactive"])
|
||||
|
||||
assert listed.exit_code == 0
|
||||
assert "Status" in listed.output and "Revision" in listed.output
|
||||
assert updated.exit_code == 0
|
||||
assert "ID: set-1" in updated.output
|
||||
assert "Status: inactive" in updated.output
|
||||
assert "Revision: 2" in updated.output
|
||||
assert no_change.exit_code == 0
|
||||
assert "Revision: 2" in no_change.output
|
||||
|
||||
|
||||
def test_cli_strategy_update_requires_a_field() -> None:
|
||||
repo = FakeRepo()
|
||||
runner = build_runner(repo)
|
||||
result = runner.invoke(runner.app, ["strategy", "update", "set-1"])
|
||||
assert result.exit_code == 1
|
||||
assert "at least one" in result.output
|
||||
|
||||
Reference in New Issue
Block a user