programgarden 1.37.8__tar.gz → 1.37.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {programgarden-1.37.8 → programgarden-1.37.9}/PKG-INFO +3 -3
  2. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/context.py +9 -0
  3. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/executor.py +3 -0
  4. programgarden-1.37.9/programgarden/valuation.py +51 -0
  5. {programgarden-1.37.8 → programgarden-1.37.9}/pyproject.toml +3 -3
  6. {programgarden-1.37.8 → programgarden-1.37.9}/README.md +0 -0
  7. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/__init__.py +0 -0
  8. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/binding_validator.py +0 -0
  9. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/client.py +0 -0
  10. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/code_worker.py +0 -0
  11. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/database/__init__.py +0 -0
  12. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/database/checkpoint_manager.py +0 -0
  13. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/database/fill_reconciler.py +0 -0
  14. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/database/query_builder.py +0 -0
  15. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/database/workflow_position_tracker.py +0 -0
  16. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/database/workflow_risk_tracker.py +0 -0
  17. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/deep_fixtures.py +0 -0
  18. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/futures_pnl.py +0 -0
  19. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/node_runner.py +0 -0
  20. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/order_lifecycle.py +0 -0
  21. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/plugin/__init__.py +0 -0
  22. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/plugin/sandbox.py +0 -0
  23. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/providers/__init__.py +0 -0
  24. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/providers/llm_errors.py +0 -0
  25. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/providers/llm_provider.py +0 -0
  26. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/reconnect_handler.py +0 -0
  27. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/resolver.py +0 -0
  28. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/resource/__init__.py +0 -0
  29. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/resource/context.py +0 -0
  30. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/resource/limiter.py +0 -0
  31. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/resource/monitor.py +0 -0
  32. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/resource/throttle.py +0 -0
  33. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/semantic_rules.py +0 -0
  34. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/tools/__init__.py +0 -0
  35. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/tools/credential_tools.py +0 -0
  36. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/tools/definition_tools.py +0 -0
  37. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/tools/event_tools.py +0 -0
  38. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/tools/job_tools.py +0 -0
  39. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/tools/registry_tools.py +0 -0
  40. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/tools/sqlite_tools.py +0 -0
  41. {programgarden-1.37.8 → programgarden-1.37.9}/programgarden/validation_recommender.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: programgarden
3
- Version: 1.37.8
3
+ Version: 1.37.9
4
4
  Summary: ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진
5
5
  License-Expression: AGPL-3.0-or-later
6
6
  Author: 프로그램동산
@@ -16,8 +16,8 @@ Requires-Dist: croniter (>=6.0.0,<7.0.0)
16
16
  Requires-Dist: litellm (>=1.40.0)
17
17
  Requires-Dist: lxml (>=6.0.2,<7.0.0)
18
18
  Requires-Dist: programgarden-community (>=1.15.3,<2.0.0)
19
- Requires-Dist: programgarden-core (>=1.28.1,<2.0.0)
20
- Requires-Dist: programgarden-finance (>=1.9.7,<2.0.0)
19
+ Requires-Dist: programgarden-core (>=1.28.2,<2.0.0)
20
+ Requires-Dist: programgarden-finance (>=1.9.8,<2.0.0)
21
21
  Requires-Dist: psutil (>=6.0.0,<7.0.0)
22
22
  Requires-Dist: psycopg2-binary (>=2.9.11,<3.0.0)
23
23
  Requires-Dist: pydantic (>=2.0.0,<3.0.0)
@@ -1968,6 +1968,7 @@ class ExecutionContext:
1968
1968
  current_prices: Dict[str, float],
1969
1969
  account_positions: Optional[Dict[str, Any]],
1970
1970
  currency: Optional[str] = "USD",
1971
+ account_valuation: Optional[Dict[str, Any]] = None,
1971
1972
  ) -> None:
1972
1973
  """Notify all listeners about workflow P&L update (확장 버전).
1973
1974
 
@@ -2015,6 +2016,12 @@ class ExecutionContext:
2015
2016
  product=product,
2016
2017
  start_date=None, # 전체 기간
2017
2018
  )
2019
+ from .valuation import workflow_valuation_snapshot
2020
+
2021
+ workflow_valuation = workflow_valuation_snapshot(
2022
+ base_workflow_result,
2023
+ account_positions if account_positions is not None else ({} if account_valuation else None),
2024
+ now, product)
2018
2025
 
2019
2026
  # 2. 계좌 전체 수익률 계산
2020
2027
  account_result = self._calculate_account_pnl(
@@ -2098,6 +2105,8 @@ class ExecutionContext:
2098
2105
  "anomaly_count": base_workflow_result.get("anomaly_count", 0),
2099
2106
  "currency": currency,
2100
2107
  "personal_metrics": personal_metrics,
2108
+ "account_valuation": account_valuation,
2109
+ "workflow_valuation": workflow_valuation,
2101
2110
 
2102
2111
  # 신규 필드: 워크플로우 상품별
2103
2112
  "workflow_overseas_stock_pnl_rate": base_workflow_result.get("workflow_overseas_stock_pnl_rate"),
@@ -5610,6 +5610,8 @@ class BrokerNodeExecutor(NodeExecutorBase):
5610
5610
  "current_price": float(pos_item.current_price) if hasattr(pos_item, 'current_price') else 0,
5611
5611
  "pnl_rate": float(pos_item.pnl_rate) if hasattr(pos_item, 'pnl_rate') else 0,
5612
5612
  "product": product, # 상품 유형 (overseas_stock)
5613
+ "currency": pos_item.currency_code.strip().upper()
5614
+ if "currency_code" in pos_item.model_fields_set else None,
5613
5615
  }
5614
5616
 
5615
5617
  self._start_background_task(context,
@@ -5620,6 +5622,7 @@ class BrokerNodeExecutor(NodeExecutorBase):
5620
5622
  current_prices=current_prices,
5621
5623
  account_positions=account_positions if account_positions else None,
5622
5624
  currency=pnl_info.currency if hasattr(pnl_info, 'currency') else "USD",
5625
+ account_valuation=tracker.get_valuation_snapshot(),
5623
5626
  ),
5624
5627
  notification=True,
5625
5628
  )
@@ -0,0 +1,51 @@
1
+ """Currency-scoped open workflow valuation, independent of realized trades."""
2
+
3
+ from dataclasses import asdict, is_dataclass
4
+ from decimal import Decimal, InvalidOperation
5
+ import re
6
+
7
+
8
+ def workflow_valuation_snapshot(result, account_positions, observed_at, product):
9
+ if product != "overseas_stock" or not isinstance(account_positions, dict):
10
+ return None
11
+ positions = result.get("workflow_positions")
12
+ if not isinstance(positions, list):
13
+ return None
14
+ if positions and result.get("workflow_pnl_rate") is None:
15
+ return None
16
+ groups = {}
17
+ seen = set()
18
+ for item in positions:
19
+ row = asdict(item) if is_dataclass(item) else item
20
+ if not isinstance(row, dict):
21
+ return None
22
+ symbol = row.get("symbol")
23
+ account = account_positions.get(symbol)
24
+ if not isinstance(account, dict) or symbol in seen:
25
+ return None
26
+ seen.add(symbol)
27
+ currency = account.get("currency")
28
+ if not isinstance(currency, str) or not re.fullmatch(r"[A-Z]{3}", currency):
29
+ return None
30
+ values = {}
31
+ for key in ("quantity", "avg_price", "current_price", "pnl_amount"):
32
+ value = row.get(key)
33
+ if value is None or isinstance(value, bool):
34
+ return None
35
+ try:
36
+ values[key] = Decimal(str(value))
37
+ except InvalidOperation:
38
+ return None
39
+ if not values[key].is_finite() or (key != "pnl_amount" and values[key] <= 0):
40
+ return None
41
+ # Valuation is an estimate from the workflow's own retained cost basis.
42
+ pnl = values["pnl_amount"]
43
+ if abs(pnl - (values["current_price"] - values["avg_price"]) * values["quantity"]) > Decimal("0.0001"):
44
+ return None
45
+ group = groups.setdefault(currency, {"earned": Decimal(0), "lost": Decimal(0), "net": Decimal(0)})
46
+ group["earned"] += max(pnl, Decimal(0))
47
+ group["lost"] += max(-pnl, Decimal(0))
48
+ group["net"] = group["earned"] - group["lost"]
49
+ return {"version": 1, "product": product, "basis": "workflow_open_positions_gross",
50
+ "observed_at": observed_at.isoformat(),
51
+ "groups": [{"currency": currency, **group} for currency, group in sorted(groups.items())]}
@@ -5,7 +5,7 @@ authors = [
5
5
  homepage = "https://programgarden.com"
6
6
  requires-python = ">=3.12"
7
7
  name = "programgarden"
8
- version = "1.37.8"
8
+ version = "1.37.9"
9
9
  license = "AGPL-3.0-or-later"
10
10
  description = "ProgramGarden - 노드 기반 자동매매 DSL 실행 엔진"
11
11
  readme = "README.md"
@@ -36,8 +36,8 @@ litellm = ">=1.40.0"
36
36
  # 로 통째 실패한다(1.35.1 의 조용한 TypeError 보다 강한 모듈 로드 실패).
37
37
  # (1.26.0 이후로도 context.py 는 WorkflowPnLEvent(**event_data) 에 personal_metrics
38
38
  # 를 무조건 실으므로 그 하한 근거도 이 상한에 포함된다.)
39
- programgarden-core = "^1.28.1"
40
- programgarden-finance = "^1.9.7"
39
+ programgarden-core = "^1.28.2"
40
+ programgarden-finance = "^1.9.8"
41
41
  programgarden-community = "^1.15.3"
42
42
 
43
43
  [tool.poetry.group.dev.dependencies]
File without changes