> For the complete documentation index, see [llms.txt](https://programgarden.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://programgarden.gitbook.io/docs/nodes/node_reference.md).

# 전체 노드 레퍼런스

ProgramGarden에서 사용할 수 있는 모든 노드의 상세 설명입니다. 각 노드는 워크플로우에서 하나의 **블록** 역할을 하며, 블록을 연결(엣지)하여 자동매매 전략을 만듭니다.

> **노드 수**: 12개 카테고리, 73개 노드
>
> **해외주식 / 해외선물 / 국내주식**: 대부분의 노드가 상품별로 분리되어 있습니다. 예를 들어 `OverseasStockBrokerNode`(해외주식), `OverseasFuturesBrokerNode`(해외선물), `KoreaStockBrokerNode`(국내주식)는 같은 기능이지만 상품이 다릅니다.

***

## 1. infra - 인프라/연결

워크플로우의 시작점, 증권사 연결, 데이터 흐름 제어를 담당하는 기반 노드들입니다.

### StartNode

워크플로우의 **진입점**입니다. 모든 워크플로우에 반드시 1개 있어야 합니다. StartNode 없이는 워크플로우가 실행되지 않습니다.

```json
{
  "id": "start",
  "type": "StartNode"
}
```

**출력**: `start` - 워크플로우 시작 신호

> **주의**: StartNode는 ScheduleNode와 함께 사용하면 스케줄에 따라 반복 실행됩니다. StartNode만 있으면 1회 실행 후 종료됩니다.

***

### BrokerNode (증권사 연결)

LS증권 API에 연결하는 노드입니다. **증권사 연결이 필요한 모든 노드**(계좌 조회, 시세 조회, 주문 등)는 이 노드가 먼저 있어야 합니다.

상품 유형에 따라 노드 타입이 다릅니다:

| 상품   | 노드 타입                       | credential 타입                | 모의투자         |
| ---- | --------------------------- | ---------------------------- | ------------ |
| 해외주식 | `OverseasStockBrokerNode`   | `broker_ls_overseas_stock`   | 미지원 (실전만 가능) |
| 해외선물 | `OverseasFuturesBrokerNode` | `broker_ls_overseas_futures` | 지원           |
| 국내주식 | `KoreaStockBrokerNode`      | `broker_ls_korea_stock`      | 미지원 (실전만 가능) |

**해외주식 연결 (실전 전용):**

```json
{
  "id": "broker",
  "type": "OverseasStockBrokerNode",
  "credential_id": "my-stock-cred",
  "paper_trading": false
}
```

**해외선물 연결 (모의투자):**

```json
{
  "id": "broker",
  "type": "OverseasFuturesBrokerNode",
  "credential_id": "my-futures-cred",
  "paper_trading": true
}
```

**국내주식 연결:**

```json
{
  "id": "broker",
  "type": "KoreaStockBrokerNode",
  "credential_id": "my-kr-cred"
}
```

| 필드              | 타입      |  필수 | 설명                                                                                                                 |
| --------------- | ------- | :-: | ------------------------------------------------------------------------------------------------------------------ |
| `credential_id` | string  |  ✅  | credentials 섹션에서 정의한 인증 ID                                                                                         |
| `paper_trading` | boolean |  ❌  | 모의투자 여부. **해외주식은 `false` 명시 필수** (기본값이 true여서 실전 연결이 실패). 해외선물은 `true`로 모의투자. 국내주식은 모의투자 미지원이므로 내부적으로 항상 false 강제. |

**출력**: `connection` - 증권사 연결 객체

> **자동 전달**: BrokerNode의 `connection`은 하위 노드(계좌, 시세, 주문 등)에 자동으로 전달됩니다. 엣지로 연결만 해주면 되고, `{{ nodes.broker.connection }}`처럼 직접 바인딩할 필요가 없습니다.

> **⚠️ OverseasStockBrokerNode는 `paper_trading: false` 필수**: Executor의 `paper_trading` 기본값은 `true`입니다. 해외주식 노드에서 이 필드를 생략하면 기본값(`true`)으로 처리되지만, LS증권은 해외주식 실거래만 제공하므로 연결이 즉시 거부되고 후속 노드(시세·계좌·주문 전부)가 전파 오류로 깨집니다. 따라서 **실거래 연결을 위해 반드시 `paper_trading: false`를 명시**해야 합니다.

| 노드                          | `paper_trading` 값 | 의미                           |
| --------------------------- | ----------------- | ---------------------------- |
| `OverseasStockBrokerNode`   | `false` **필수 명시** | 실거래 연결 (모의투자 모드 없음)          |
| `OverseasFuturesBrokerNode` | `true` / `false`  | `true` → 모의투자, `false` → 실거래 |
| `KoreaStockBrokerNode`      | (지정 불가)           | 내부적으로 항상 `false` 강제          |

> **주의 - credential 설정**: `credential_id`가 credentials 섹션의 ID와 정확히 일치해야 합니다. 불일치하면 연결에 실패합니다.

***

### ThrottleNode

실시간 데이터의 흐름을 제어하는 **속도 조절기**입니다. 실시간 노드(RealMarketDataNode, RealAccountNode 등)는 초당 수십 번 데이터를 보내는데, 이를 받아서 하위 노드가 너무 자주 실행되지 않도록 막아줍니다.

```json
{
  "id": "throttle",
  "type": "ThrottleNode",
  "mode": "latest",
  "interval_sec": 5,
  "pass_first": true
}
```

| 필드             | 타입                     | 기본값        | 설명                     |
| -------------- | ---------------------- | ---------- | ---------------------- |
| `mode`         | `"skip"` \| `"latest"` | `"latest"` | 쿨다운 중 데이터 처리 방식        |
| `interval_sec` | number                 | `5`        | 최소 실행 간격 (초), 0.1\~300 |
| `pass_first`   | boolean                | `true`     | 첫 번째 데이터를 즉시 통과시킬지 여부  |

**모드 설명:**

| 모드       | 동작                                     | 언제 쓰나요?                     |
| -------- | -------------------------------------- | --------------------------- |
| `skip`   | 쿨다운 중 들어오는 데이터를 전부 버림                  | 단순히 실행 빈도만 줄이고 싶을 때         |
| `latest` | 쿨다운 중 최신 데이터만 기억해두고, 쿨다운 끝나면 그 데이터로 실행 | 항상 최신 상태를 반영해야 할 때 **(권장)** |

**latest 모드 동작 예시:**

```
시간 →  0초    1초    2초    3초    4초    5초    6초
데이터    A     B     C     D      -     E     F
결과     통과   대기   대기   대기         D실행   통과
                                   (쿨다운 끝나면 가장 최신인 D 실행)
```

**필수 사용 패턴:**

{% @mermaid/diagram content="flowchart LR
A\[RealAccountNode] -->|빈번한 이벤트| B\[ThrottleNode] -->|5초마다| C\[ConditionNode] --> D\[OrderNode]" %}

> **중요**: 실시간 노드 → 주문 노드, 실시간 노드 → AI 에이전트를 **직접 연결하면 차단**됩니다. 반드시 ThrottleNode를 사이에 넣어야 합니다. 이는 초당 수십 번의 주문이 나가는 사고를 방지하기 위한 안전장치입니다.

> **팁**: `interval_sec`을 너무 짧게 설정하면 API 호출이 과도해져 증권사에서 제한당할 수 있습니다. 주문 관련 흐름에서는 최소 5초 이상을 권장합니다.

***

### SplitNode

리스트(배열) 데이터를 개별 항목으로 **분리**하여 하나씩 처리합니다. 예를 들어 관심종목 3개를 하나씩 분리해서 각각 차트 데이터를 조회할 때 사용합니다.

```json
{
  "id": "split",
  "type": "SplitNode",
  "parallel": false,
  "continue_on_error": true
}
```

| 필드                  | 타입      | 기본값     | 설명                         |
| ------------------- | ------- | ------- | -------------------------- |
| `parallel`          | boolean | `false` | 병렬 실행 여부 (true면 동시에 실행)    |
| `delay_ms`          | number  | `0`     | 항목 간 대기 시간 (밀리초, 0\~60000) |
| `continue_on_error` | boolean | `true`  | 하나가 에러 나도 나머지 계속 처리        |

**출력:**

* `item` - 현재 처리 중인 항목
* `index` - 현재 순번 (0부터 시작)
* `total` - 전체 항목 수

**사용 예시:**

{% @mermaid/diagram content="flowchart LR
A\["WatchlistNode<br/>(AAPL, NVDA, TSM)"] -->|3개 종목| B\["SplitNode<br/>(1개씩)"]
B -->|"1회차: AAPL"| C\[HistoricalDataNode]
B -->|"2회차: NVDA"| C
B -->|"3회차: TSM"| C" %}

하위 노드에서 현재 항목을 참조할 때는 `{{ item }}`을 사용합니다:

```json
{
  "id": "history",
  "type": "OverseasStockHistoricalDataNode",
  "symbol": "{{ item }}"
}
```

> **주의 - API 호출 제한**: 종목이 많을 때 `parallel: true`로 설정하면 증권사 API를 동시에 많이 호출하게 되어 제한당할 수 있습니다. 10개 이상의 종목을 처리할 때는 `parallel: false`로 하고 `delay_ms: 500` 정도를 권장합니다.

> **팁**: SplitNode 없이도 이전 노드가 배열을 출력하면 다음 노드가 자동으로 반복 실행됩니다 (Auto-Iterate). SplitNode은 `parallel`, `delay_ms` 등 세밀한 제어가 필요할 때 사용하세요. 자세한 내용은 [자동 반복 처리 가이드](/docs/workflow/auto_iterate_guide.md)를 참고하세요.

***

### AggregateNode

SplitNode으로 분리된 결과를 다시 **하나로 모으는** 노드입니다. 예를 들어 3개 종목의 RSI 결과를 모아서 조건을 통과한 종목만 필터링할 때 사용합니다.

```json
{
  "id": "aggregate",
  "type": "AggregateNode",
  "mode": "collect"
}
```

| 필드             | 타입     | 기본값         | 설명                               |
| -------------- | ------ | ----------- | -------------------------------- |
| `mode`         | string | `"collect"` | 집계 방식                            |
| `filter_field` | string | `"passed"`  | `filter` 모드에서 필터링할 필드명           |
| `value_field`  | string | `"value"`   | `sum`/`avg`/`min`/`max`에서 대상 필드명 |

**mode 옵션:**

| 모드               | 설명                 | 사용 예시                  |
| ---------------- | ------------------ | ---------------------- |
| `collect`        | 모든 결과를 배열로 모음      | 3개 종목의 차트 데이터를 하나의 배열로 |
| `filter`         | 특정 필드가 true인 것만 모음 | RSI 조건 통과한 종목만         |
| `sum`            | 숫자 합계              | 전체 포지션 금액 합계           |
| `avg`            | 숫자 평균              | 평균 수익률                 |
| `min` / `max`    | 최솟값 / 최댓값          | 가장 낮은 RSI 값            |
| `count`          | 개수                 | 조건 통과한 종목 수            |
| `first` / `last` | 첫 번째 / 마지막 항목      | -                      |

**출력:**

* `array` - 집계된 배열
* `value` - 집계된 단일 값 (sum, avg 등)
* `count` - 항목 수

**전형적인 패턴 (Split → 처리 → Aggregate):**

{% @mermaid/diagram content="flowchart LR
A\["WatchlistNode<br/>(3종목)"] --> B\["SplitNode<br/>(분리)"] --> C\["HistoricalDataNode<br/>(각각 조회)"] --> D\["ConditionNode<br/>(각각 평가)"] --> E\["AggregateNode<br/>(결과 취합)"]" %}

***

### IfNode (조건 분기)

조건을 평가하여 **true/false 두 갈래로 실행 흐름을 분기**하는 노드입니다. 프로그래밍의 `if-else`와 같은 역할입니다.

```json
{
  "nodes": [
    {"id": "account", "type": "OverseasStockAccountNode"},
    {"id": "if1", "type": "IfNode",
     "left": "{{ nodes.account.balance }}",
     "operator": ">=",
     "right": 1000000},
    {"id": "order", "type": "OverseasStockNewOrderNode"},
    {"id": "notify", "type": "FieldMappingNode"}
  ],
  "edges": [
    {"from": "account", "to": "if1"},
    {"from": "if1", "to": "order", "from_port": "true"},
    {"from": "if1", "to": "notify", "from_port": "false"}
  ]
}
```

위 예시는 계좌 잔고가 100만원 이상이면 주문 실행, 미만이면 알림 노드로 분기합니다.

**입력 필드:**

| 필드         | 타입     | 설명                 | 표현식                           |
| ---------- | ------ | ------------------ | ----------------------------- |
| `left`     | any    | 왼쪽 피연산자            | `{{ nodes.account.balance }}` |
| `operator` | string | 비교 연산자 (기본값: `==`) | -                             |
| `right`    | any    | 오른쪽 피연산자           | `{{ 1000000 }}`               |

**지원 연산자 (12종):**

| 연산자            | 설명                  | 예시                                      |
| -------------- | ------------------- | --------------------------------------- |
| `==`           | 같음                  | `"AAPL" == "AAPL"` → true               |
| `!=`           | 다름                  | `5 != 3` → true                         |
| `>`            | 초과                  | `100 > 50` → true                       |
| `>=`           | 이상                  | `100 >= 100` → true                     |
| `<`            | 미만                  | `50 < 100` → true                       |
| `<=`           | 이하                  | `100 <= 100` → true                     |
| `in`           | 포함됨                 | `"AAPL" in ["AAPL","TSLA"]` → true      |
| `not_in`       | 미포함                 | `"MSFT" not_in ["AAPL","TSLA"]` → true  |
| `contains`     | 문자열 포함              | `"Hello World" contains "World"` → true |
| `not_contains` | 문자열 미포함             | `"Hello" not_contains "World"` → true   |
| `is_empty`     | 빈값 (right 불필요)      | `[] is_empty` → true                    |
| `is_not_empty` | 비어있지 않음 (right 불필요) | `[1,2] is_not_empty` → true             |

> **타입 자동 변환**: 숫자 비교(`>`, `>=`, `<`, `<=`)에서 문자열 `"100"`과 숫자 `50`을 비교하면 자동으로 숫자로 변환합니다. 변환 불가능한 경우 `false`를 반환합니다.

**출력:**

| 포트       | 타입      | 설명                                |
| -------- | ------- | --------------------------------- |
| `true`   | any     | 조건 참 → upstream 데이터 pass-through  |
| `false`  | any     | 조건 거짓 → upstream 데이터 pass-through |
| `result` | boolean | 조건 평가 결과 (`true` / `false`)       |

**분기 연결 (from\_port):**

엣지에 `from_port`를 지정하여 어떤 브랜치의 후속 노드인지 명시합니다:

```json
{"from": "if1", "to": "buy_order", "from_port": "true"}
{"from": "if1", "to": "sell_order", "from_port": "false"}
```

`from_port`가 없는 엣지는 **항상 실행**됩니다 (합류 노드에 유용).

**활용 패턴:**

1. **잔고 확인 후 주문**: 잔고 >= 최소금액 → 매수 / 아니면 → 대기
2. **종목 필터링**: 종목코드 `in` 관심종목 리스트 → 분석 진행
3. **다단계 분기**: IfNode를 연속 배치하여 `if-elif-else` 구현

{% @mermaid/diagram content="flowchart LR
A\["AccountNode"] --> B\["IfNode<br/>잔고 >= 100만?"]
B -->|true| C\["NewOrderNode<br/>(매수)"]
B -->|false| D\["FieldMappingNode<br/>(알림)"]" %}

> **캐스케이딩 스킵**: false 브랜치의 하위 노드들은 자동으로 실행이 스킵됩니다. 여러 노드가 연결되어 있어도 스킵이 전파됩니다.

***

## 2. account - 계좌 조회

계좌 잔고, 보유종목, 미체결 주문을 조회하는 노드들입니다.

### AccountNode (1회성 계좌 조회)

REST API로 현재 시점의 계좌 정보를 **한 번** 조회합니다. 스냅샷을 찍는 것과 같습니다.

| 상품   | 노드 타입                        |
| ---- | ---------------------------- |
| 해외주식 | `OverseasStockAccountNode`   |
| 해외선물 | `OverseasFuturesAccountNode` |
| 국내주식 | `KoreaStockAccountNode`      |

```json
{
  "id": "account",
  "type": "OverseasStockAccountNode"
}
```

**출력:**

* `held_symbols` - 보유종목 코드 리스트 `["AAPL", "NVDA"]`
* `balance` - 예수금/매수가능금액
* `positions` - 보유종목 상세 정보

**positions 출력 예시:**

```json
[
  {
    "symbol": "AAPL",
    "exchange": "NASDAQ",
    "quantity": 10,
    "buy_price": 185.50,
    "current_price": 192.30,
    "pnl_rate": 3.67
  },
  {
    "symbol": "NVDA",
    "exchange": "NASDAQ",
    "quantity": 5,
    "buy_price": 880.00,
    "current_price": 920.50,
    "pnl_rate": 4.60
  }
]
```

> **주의**: AccountNode는 실행 시점의 데이터를 1회 조회합니다. 실시간으로 계속 업데이트되는 데이터가 필요하면 RealAccountNode을 사용하세요.

> **주의**: 보유종목이 없으면 `positions`는 빈 배열 `[]`을 반환합니다. 잔고가 0이면 `balance`는 0을 반환합니다. 에러가 아닙니다.

***

### OpenOrdersNode (미체결 주문 조회)

아직 체결되지 않은 주문 목록을 조회합니다. 지정가 주문을 넣었는데 아직 체결되지 않은 경우 등을 확인할 수 있습니다.

| 상품   | 노드 타입                           |
| ---- | ------------------------------- |
| 해외주식 | `OverseasStockOpenOrdersNode`   |
| 해외선물 | `OverseasFuturesOpenOrdersNode` |
| 국내주식 | `KoreaStockOpenOrdersNode`      |

```json
{
  "id": "openOrders",
  "type": "OverseasStockOpenOrdersNode"
}
```

**출력:**

* `open_orders` - 미체결 주문 목록
* `count` - 미체결 주문 수

**open\_orders 출력 예시:**

```json
[
  {
    "order_no": "12345",
    "symbol": "AAPL",
    "side": "buy",
    "order_type": "limit",
    "price": 180.00,
    "quantity": 10,
    "filled_quantity": 0
  }
]
```

> **팁**: CancelOrderNode와 `ScheduleNode` + `IfNode`를 조합하면 "30분 이내 미체결 시 자동 취소" 같은 로직을 직접 구현할 수 있습니다.

***

### RealAccountNode (실시간 계좌)

WebSocket으로 계좌 정보를 **실시간** 수신합니다. 주문이 체결되거나 시세가 변할 때마다 자동으로 업데이트됩니다.

| 상품   | 노드 타입                            |
| ---- | -------------------------------- |
| 해외주식 | `OverseasStockRealAccountNode`   |
| 해외선물 | `OverseasFuturesRealAccountNode` |
| 국내주식 | `KoreaStockRealAccountNode`      |

```json
{
  "id": "realAccount",
  "type": "OverseasStockRealAccountNode",
  "stay_connected": true,
  "sync_interval_sec": 60,
  "commission_rate": 0.25
}
```

| 필드                  | 타입      | 기본값    | 설명                                       |
| ------------------- | ------- | ------ | ---------------------------------------- |
| `stay_connected`    | boolean | `true` | 워크플로우 종료 후에도 WebSocket 연결 유지             |
| `sync_interval_sec` | number  | `60`   | REST API로 정확한 데이터 동기화하는 주기 (초, 10\~3600) |
| `commission_rate`   | number  | `0.25` | 수수료율 (%), 수익률 계산에 반영                     |
| `tax_rate`          | number  | `0.0`  | 세율 (%), 수익률 계산에 반영                       |

**출력:**

* `held_symbols` - 보유종목 코드
* `balance` - 예수금
* `open_orders` - 미체결 주문
* `positions` - 보유종목 (실시간 수익률 포함)

**AccountNode vs RealAccountNode 비교:**

| 항목     | AccountNode      | RealAccountNode      |
| ------ | ---------------- | -------------------- |
| 연결 방식  | REST API (1회 조회) | WebSocket (실시간 스트리밍) |
| 데이터 갱신 | 호출 시점 1번         | 이벤트 발생 시 자동 갱신       |
| 사용 시점  | 매매 전 잔고 확인       | 실시간 모니터링, 리스크 관리     |
| 리소스    | 가벼움              | 연결 유지 필요             |

> **주의**: RealAccountNode의 데이터는 매우 빈번하게 업데이트됩니다. 하위에 주문 노드를 연결할 때는 반드시 **ThrottleNode**를 사이에 넣어주세요.

> **팁 - commission\_rate**: 해외주식 수수료는 증권사마다 다릅니다. LS증권의 경우 약 0.25%입니다. 정확한 수익률 계산을 위해 실제 수수료율을 입력하세요.

***

### RealOrderEventNode (실시간 주문 이벤트)

주문이 접수/체결/정정/취소/거부될 때마다 실시간으로 이벤트를 수신합니다. 주문 상태를 모니터링할 때 사용합니다.

| 상품   | 노드 타입                               |
| ---- | ----------------------------------- |
| 해외주식 | `OverseasStockRealOrderEventNode`   |
| 해외선물 | `OverseasFuturesRealOrderEventNode` |
| 국내주식 | `KoreaStockRealOrderEventNode`      |

```json
{
  "id": "orderEvents",
  "type": "OverseasStockRealOrderEventNode",
  "stay_connected": true
}
```

| 필드               | 타입      | 기본값    | 설명                 |
| ---------------- | ------- | ------ | ------------------ |
| `stay_connected` | boolean | `true` | WebSocket 연결 유지 여부 |

**출력 (5개 포트):**

이벤트 종류별로 다른 출력 포트로 나갑니다:

| 포트          | 설명     | 언제 발생하나요?         |
| ----------- | ------ | ----------------- |
| `accepted`  | 주문 접수됨 | 주문이 거래소에 접수되었을 때  |
| `filled`    | 체결됨    | 주문이 실제로 체결되었을 때   |
| `modified`  | 정정 완료  | 주문 가격/수량이 정정되었을 때 |
| `cancelled` | 취소 완료  | 주문이 취소되었을 때       |
| `rejected`  | 거부됨    | 주문이 거래소에서 거부되었을 때 |

**사용 예시 - 체결 알림:**

{% @mermaid/diagram content="flowchart LR
A\[RealOrderEventNode] -->|filled| B\[TableDisplayNode]
A -->|rejected| C\["TableDisplayNode<br/>(에러 로그)"]" %}

> **팁**: `filled` 포트에 조건 로직을 연결하면 "체결되면 → 익절/손절 주문 자동 발생" 같은 흐름을 만들 수 있습니다.

***

## 3. market - 시세/종목

시세 조회, 종목 검색, 관심종목 관리를 담당하는 노드들입니다.

### MarketDataNode (현재가 조회)

REST API로 **당일 현재가**를 조회합니다. 단일 종목 기반으로 동작하며, 여러 종목을 조회하려면 SplitNode과 조합합니다.

| 상품   | 노드 타입                           |
| ---- | ------------------------------- |
| 해외주식 | `OverseasStockMarketDataNode`   |
| 해외선물 | `OverseasFuturesMarketDataNode` |
| 국내주식 | `KoreaStockMarketDataNode`      |

```json
{
  "id": "market",
  "type": "OverseasStockMarketDataNode",
  "symbol": "{{ item }}"
}
```

| 필드       | 타입     |  필수 | 설명    |
| -------- | ------ | :-: | ----- |
| `symbol` | object |  ✅  | 종목 정보 |

`symbol` 지정 방법:

* SplitNode과 함께: `"{{ item }}"` (자동으로 현재 종목 사용)
* 직접 지정: `{"exchange": "NASDAQ", "symbol": "AAPL"}`

**출력**: `value` - 시세 데이터

```json
{
  "symbol": "AAPL",
  "exchange": "NASDAQ",
  "price": 192.30,
  "change": 1.50,
  "change_pct": 0.79,
  "volume": 45123456,
  "open": 190.50,
  "high": 193.20,
  "low": 189.80,
  "close": 192.30
}
```

> **MarketDataNode vs HistoricalDataNode**: MarketDataNode은 **오늘 현재가 1건**만 조회합니다. RSI나 MACD 같은 기술적 지표를 계산하려면 과거 N일 데이터가 필요하므로 **HistoricalDataNode**을 사용하세요.

***

### FundamentalNode (종목 펀더멘털 조회)

LS증권 g3104 API로 **PER, EPS, 시가총액, 52주 고/저가** 등 펀더멘털 데이터를 조회합니다. **해외주식 전용**입니다 (해외선물은 파생상품이므로 펀더멘털 개념 없음).

| 상품   | 노드 타입                          |
| ---- | ------------------------------ |
| 해외주식 | `OverseasStockFundamentalNode` |
| 해외선물 | - (지원하지 않음)                    |

```json
{
  "id": "fundamental",
  "type": "OverseasStockFundamentalNode",
  "symbols": [
    {"exchange": "NASDAQ", "symbol": "AAPL"},
    {"exchange": "NASDAQ", "symbol": "MSFT"}
  ]
}
```

| 필드        | 타입     |  필수 | 설명                           |
| --------- | ------ | :-: | ---------------------------- |
| `symbol`  | object |  -  | 단일 종목 (`{exchange, symbol}`) |
| `symbols` | array  |  -  | 복수 종목 배열                     |

`symbol` 또는 `symbols` 중 하나를 지정합니다. SplitNode과 함께 사용 시 `"symbol": "{{ item }}"`.

**출력**: `value` / `values` — 펀더멘털 데이터

```json
{
  "exchange": "NASDAQ", "symbol": "AAPL", "name": "APPLE INC",
  "industry": "Technology", "nation": "USA", "exchange_name": "NASDAQ",
  "current_price": 264.35, "volume": 45123456, "change_percent": 1.25,
  "per": 33.39, "eps": 7.9,
  "market_cap": 3869057094000, "shares_outstanding": 14681100000,
  "high_52w": 288.62, "low_52w": 169.21, "exchange_rate": 1443.1
}
```

| 출력 필드                  | 설명            |
| ---------------------- | ------------- |
| `per`                  | PER (주가수익비율)  |
| `eps`                  | EPS (주당순이익)   |
| `market_cap`           | 시가총액          |
| `shares_outstanding`   | 발행주식수         |
| `high_52w` / `low_52w` | 52주 최고가 / 최저가 |
| `exchange_rate`        | 환율            |

> **MarketDataNode vs FundamentalNode**: MarketDataNode은 시세(가격/변동) 위주이고 PER/EPS도 포함합니다. FundamentalNode은 **시가총액, 업종, 52주 고/저가, 발행주식수** 등 종목 기본정보를 추가로 제공합니다.

> **AI 에이전트 도구**: `is_tool_enabled = true`이므로 AIAgentNode에서 도구(tool)로 직접 호출할 수 있습니다.

***

### HistoricalDataNode (과거 차트 조회)

과거 N일치 **OHLCV**(시가/고가/저가/종가/거래량) 차트 데이터를 조회합니다. RSI, MACD 같은 기술적 지표 계산에 필수입니다.

| 상품   | 노드 타입                               |
| ---- | ----------------------------------- |
| 해외주식 | `OverseasStockHistoricalDataNode`   |
| 해외선물 | `OverseasFuturesHistoricalDataNode` |

```json
{
  "id": "history",
  "type": "OverseasStockHistoricalDataNode",
  "symbol": "{{ item }}",
  "interval": "1d",
  "start_date": "{{ date.ago(90, format='yyyymmdd') }}",
  "end_date": "{{ date.today(format='yyyymmdd') }}"
}
```

| 필드           | 타입      | 기본값     | 설명                |
| ------------ | ------- | ------- | ----------------- |
| `symbol`     | object  | -       | 종목 정보             |
| `interval`   | string  | `"1d"`  | 데이터 주기            |
| `start_date` | string  | 3개월 전   | 조회 시작일 (YYYYMMDD) |
| `end_date`   | string  | 오늘      | 조회 종료일 (YYYYMMDD) |
| `adjust`     | boolean | `false` | 수정주가 적용 여부        |

**interval 옵션:**

| 값     | 주기   | 용도           |
| ----- | ---- | ------------ |
| `1m`  | 1분봉  | 단타/스캘핑       |
| `5m`  | 5분봉  | 단기 매매        |
| `15m` | 15분봉 | 단기 매매        |
| `1h`  | 1시간봉 | 스윙 트레이딩      |
| `1d`  | 일봉   | **가장 많이 사용** |
| `1w`  | 주봉   | 중장기 분석       |
| `1M`  | 월봉   | 장기 분석        |

**출력**: `value` - OHLCV 배열

```json
[
  {"date": "2025-01-10", "open": 190.0, "high": 193.5, "low": 189.2, "close": 192.3, "volume": 45000000},
  {"date": "2025-01-09", "open": 188.5, "high": 191.0, "low": 187.8, "close": 190.0, "volume": 38000000}
]
```

> **주의 - RSI 기간 설정**: RSI 14일을 계산하려면 최소 14일 이상의 데이터가 필요합니다. `start_date`를 넉넉하게 잡아주세요. 보통 RSI 14일 기준으로 최소 30일, 권장 90일 이상입니다.

> **주의 - 분봉 데이터**: 1분봉/5분봉은 조회 가능한 기간이 제한적입니다 (최근 수일). 일봉은 수년치 조회 가능합니다.

> **팁**: `adjust: true`로 설정하면 액면분할 등이 반영된 수정주가를 조회합니다. 장기 백테스트에 유용합니다.

***

### RealMarketDataNode (실시간 시세)

WebSocket으로 **실시간 시세**를 수신합니다. 체결이 발생할 때마다 데이터가 업데이트됩니다.

| 상품   | 노드 타입                               |
| ---- | ----------------------------------- |
| 해외주식 | `OverseasStockRealMarketDataNode`   |
| 해외선물 | `OverseasFuturesRealMarketDataNode` |

```json
{
  "id": "realMarket",
  "type": "OverseasStockRealMarketDataNode",
  "symbol": "{{ item }}",
  "stay_connected": true
}
```

| 필드               | 타입      | 기본값    | 설명                              |
| ---------------- | ------- | ------ | ------------------------------- |
| `symbol`         | object  | -      | 종목 (SplitNode의 `{{ item }}` 사용) |
| `stay_connected` | boolean | `true` | 워크플로우 종료 후에도 연결 유지              |

**출력:**

* `ohlcv_data` - OHLCV 실시간 데이터
* `data` - 전체 시세 (현재가, 호가, 거래량 등)

> **LS증권 미국 주식 거래 시간 (한국시간 기준)**:

| 구간    | 겨울 (서머타임 미적용)       | 여름 (서머타임 적용, 3\~11월) | 비고       |
| ----- | ------------------- | -------------------- | -------- |
| 주간거래  | 10:00 \~ 17:30      | 09:00 \~ 16:30       | 지정가만 가능  |
| 프리마켓  | 18:00 \~ 23:30      | 17:00 \~ 22:30       | 장전 시간외   |
| 정규장   | 23:30 \~ 06:00 (익일) | 22:30 \~ 05:00 (익일)  | 메인 거래 시간 |
| 애프터마켓 | 06:00 \~ 09:30      | 05:00 \~ 08:30       | 장후 시간외   |

> 실시간 시세는 위 거래 시간 중에 수신됩니다.

> **주의**: 실시간 노드 하위에 주문이나 AI 노드를 연결할 때는 반드시 ThrottleNode를 거쳐야 합니다.

***

### SymbolQueryNode (종목 검색)

종목 코드나 이름으로 종목을 검색합니다. "애플의 종목코드가 뭐지?" 할 때 사용합니다.

| 상품   | 노드 타입                            |
| ---- | -------------------------------- |
| 해외주식 | `OverseasStockSymbolQueryNode`   |
| 해외선물 | `OverseasFuturesSymbolQueryNode` |

```json
{
  "id": "query",
  "type": "OverseasStockSymbolQueryNode",
  "keyword": "AAPL"
}
```

**출력**: `symbols` - 검색 결과 종목 리스트

***

### WatchlistNode (관심종목)

사용자가 직접 지정하는 **관심종목 목록**입니다. 워크플로우에서 처리할 종목을 정의하는 가장 기본적인 방법입니다.

```json
{
  "id": "watchlist",
  "type": "WatchlistNode",
  "symbols": [
    {"exchange": "NASDAQ", "symbol": "AAPL"},
    {"exchange": "NASDAQ", "symbol": "NVDA"},
    {"exchange": "NYSE", "symbol": "TSM"}
  ]
}
```

| 필드        | 타입    |  필수 | 설명                              |
| --------- | ----- | :-: | ------------------------------- |
| `symbols` | array |  ✅  | 종목 목록 (`{exchange, symbol}` 배열) |

**출력**: `symbols` - 종목 리스트

> **주의 - exchange 필수**: 종목 코드만 넣으면 안 됩니다. `exchange`(거래소)와 `symbol`(종목코드)을 함께 지정해야 합니다.

| 거래소     | 코드       | 예시 종목                  |
| ------- | -------- | ---------------------- |
| 나스닥     | `NASDAQ` | AAPL, NVDA, TSLA, MSFT |
| 뉴욕증권거래소 | `NYSE`   | TSM, JPM, V, WMT       |
| 아멕스     | `AMEX`   | SPY, QQQ, GLD          |

***

### MarketUniverseNode (지수 구성종목)

나스닥100, S\&P500 같은 **대표 지수의 구성종목**을 자동으로 가져옵니다.

> 해외주식 전용입니다. Broker 연결이 필요하지 않습니다 (독립 실행).

```json
{
  "id": "universe",
  "type": "MarketUniverseNode",
  "universe": "NASDAQ100"
}
```

| 필드         | 타입     | 기본값           | 설명    |
| ---------- | ------ | ------------- | ----- |
| `universe` | string | `"NASDAQ100"` | 지수 이름 |

**지원 인덱스:**

| 인덱스         | 종목 수   | 설명             |
| ----------- | ------ | -------------- |
| `NASDAQ100` | \~101개 | 나스닥 대형주 100선   |
| `SP500`     | \~503개 | S\&P 500       |
| `SP100`     | \~100개 | S\&P 100 (대형주) |
| `DOW30`     | 30개    | 다우존스 산업평균      |

**출력**: `symbols` (종목 리스트), `count` (종목 수)

> **팁**: MarketUniverseNode + ScreenerNode을 조합하면 "나스닥100 중 시총 1000억 이상 + 거래량 100만주 이상" 같은 스크리닝이 가능합니다.

***

### ScreenerNode (종목 스크리닝)

시가총액, 거래량, 섹터 등의 **조건으로 종목을 필터링**합니다.

> 해외주식 전용입니다. Broker 연결이 필요하지 않습니다.

```json
{
  "id": "screener",
  "type": "ScreenerNode",
  "market_cap_min": 100000000000,
  "volume_min": 1000000,
  "sector": "Technology",
  "exchange": "NASDAQ",
  "max_results": 10
}
```

| 필드               | 타입     | 기본값   | 설명                                       |
| ---------------- | ------ | ----- | ---------------------------------------- |
| `symbols`        | array  | -     | 입력 종목 리스트 (미지정 시 전체 대상)                  |
| `market_cap_min` | number | -     | 최소 시가총액 (USD)                            |
| `market_cap_max` | number | -     | 최대 시가총액 (USD)                            |
| `volume_min`     | number | -     | 최소 일 거래량                                 |
| `sector`         | string | -     | 섹터 (Technology, Healthcare, Financial 등) |
| `exchange`       | string | -     | 거래소 (NASDAQ, NYSE, AMEX)                 |
| `max_results`    | number | `100` | 최대 결과 수 (1\~500)                         |

**출력**: `symbols` (조건 충족 종목), `count` (결과 수)

> **팁 - 시가총액 단위**: 미국 달러 기준입니다. 1000억 달러 = `100000000000`, 10억 달러 = `1000000000`.

***

### SymbolFilterNode (종목 집합 연산)

두 종목 리스트를 **비교/필터링**합니다. 집합 연산(차집합, 교집합, 합집합)을 수행합니다.

```json
{
  "id": "filter",
  "type": "SymbolFilterNode",
  "operation": "difference",
  "input_a": "{{ nodes.universe.symbols }}",
  "input_b": "{{ nodes.account.held_symbols }}"
}
```

| 필드          | 타입         | 기본값            | 설명          |
| ----------- | ---------- | -------------- | ----------- |
| `operation` | string     | `"difference"` | 연산 방식       |
| `input_a`   | expression | -              | 첫 번째 종목 리스트 |
| `input_b`   | expression | -              | 두 번째 종목 리스트 |

**operation 옵션:**

| 연산             | 의미               | 실전 예시                      |
| -------------- | ---------------- | -------------------------- |
| `difference`   | A에만 있는 것 (A - B) | 나스닥100 중 **아직 보유하지 않은** 종목 |
| `intersection` | A, B 모두에 있는 것    | 관심종목 중 **현재 보유 중인** 종목     |
| `union`        | A + B 합치기        | 두 리스트 합치기 (중복 제거)          |

**출력**: `symbols` (결과 리스트), `count` (개수)

**사용 예시 - 미보유 종목만 매수:**

{% @mermaid/diagram content="flowchart LR
A\[MarketUniverseNode] --> C\["SymbolFilterNode<br/>(difference)"] --> D\[...매수 로직]
B\[AccountNode] --> C" %}

### ExclusionListNode (거래 제외 종목)

거래에서 **제외할 종목**을 관리합니다. 수동 입력과 동적 바인딩을 결합할 수 있고, 입력 종목 리스트에서 자동 필터링도 가능합니다.

```json
{
  "id": "exclusion",
  "type": "ExclusionListNode",
  "symbols": [
    {"exchange": "NASDAQ", "symbol": "NVDA", "reason": "변동성 과다"},
    {"exchange": "NYSE", "symbol": "BA", "reason": "사고 리스크"}
  ],
  "default_reason": "리스크 관리",
  "input_symbols": "{{ nodes.watchlist.symbols }}"
}
```

| 필드                | 타입         | 기본값  | 설명                                      |
| ----------------- | ---------- | ---- | --------------------------------------- |
| `symbols`         | array      | `[]` | 수동 제외 종목 `[{exchange, symbol, reason}]` |
| `dynamic_symbols` | expression | -    | 동적 제외 종목 바인딩                            |
| `input_symbols`   | expression | -    | 필터링할 원본 종목 리스트                          |
| `default_reason`  | string     | `""` | 사유 미지정 시 기본 사유                          |

**출력**: `excluded` (제외 종목), `filtered` (필터링 결과), `count` (제외 수), `reasons` (종목별 사유)

**3가지 활용 패턴:**

| 패턴              | 설명                   | 설정                                         |
| --------------- | -------------------- | ------------------------------------------ |
| 선언만             | 제외 목록 정의, 다른 노드에서 참조 | `symbols`만 설정                              |
| 직접 필터링          | 입력 리스트에서 제외 종목 제거    | `symbols` + `input_symbols`                |
| SymbolFilter 연동 | 차집합 연산으로 제외 적용       | SymbolFilterNode의 `input_b`에 `excluded` 연결 |

**주문 안전장치**: ExclusionListNode가 워크플로우에 있으면, NewOrderNode에서 제외 종목 주문을 **자동 차단**합니다 (`ignore_exclusion: true`로 비활성화 가능).

### CurrencyRateNode (환율 조회)

**credential 없이** 실시간 환율을 조회합니다. ECB 데이터 기반의 frankfurter.app API를 사용합니다.

```json
{
  "id": "currency",
  "type": "CurrencyRateNode",
  "base_currency": "USD",
  "target_currencies": ["KRW", "JPY", "EUR"]
}
```

| 출력         | 타입     | 설명                                     |
| ---------- | ------ | -------------------------------------- |
| `rates`    | array  | `[{base, target, rate, timestamp}]` 배열 |
| `krw_rate` | number | USD/KRW 환율 (KRW가 target에 포함된 경우)       |

### FearGreedIndexNode (공포/탐욕 지수)

CNN의 Fear & Greed Index를 조회합니다. **credential 불필요**.

```json
{
  "id": "fg",
  "type": "FearGreedIndexNode"
}
```

| 출력               | 타입     | 설명                                                    |
| ---------------- | ------ | ----------------------------------------------------- |
| `value`          | number | 0\~100 (0=극도 공포, 100=극도 탐욕)                           |
| `label`          | string | Extreme Fear / Fear / Neutral / Greed / Extreme Greed |
| `previous_close` | number | 전일 종가 지수                                              |

> **팁**: IfNode와 연결하여 `value <= 25`이면 매수, `value >= 75`이면 매도하는 역발상 전략을 구성할 수 있습니다.

> **공통**: 외부 시장 데이터 노드는 retry 기본 활성화, rate limit, 실시간 노드 직접 연결 차단 (ThrottleNode 경유 필수) 설정을 가집니다.

### MarketStatusNode (실시간 장운영정보)

LS증권 JIF TR 기반 **실시간 장운영정보** 노드. 12개 시장(KOSPI/KOSDAQ/KRX\_FUTURES/NXT/KRX\_NIGHT/US/CN\_AM/CN\_PM/HK\_AM/HK\_PM/JP\_AM/JP\_PM)의 개장·종가·서킷브레이커·사이드카 상태를 WebSocket 으로 수신. 브로커 상품 종류(해외주식/해외선물/국내주식) 무관하게 사용 가능 (BrokerNode 세션 공유, broker 없이는 불가).

**⚠️ 해외선물 미지원**: CME, HKEX Futures, SGX 등은 JIF 범위 밖입니다. `markets` Literal 에서 명시적으로 제외되어 ValidationError 를 발생시킵니다.

```json
{
  "id": "market_status",
  "type": "MarketStatusNode",
  "markets": ["US"],
  "stay_connected": true,
  "include_extended_hours": false
}
```

| 필드                       | 타입                | 기본값     | 설명                                                                                                                        |
| ------------------------ | ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------- |
| `markets`                | `List[MarketKey]` | `[]`    | 구독할 시장 필터. 빈 리스트는 12개 전체. `['US']`, `['KOSPI','KOSDAQ']` 등 부분 선택 가능. 해외선물 키(`CME`, `SGX` 등) 주입 시 Pydantic ValidationError |
| `stay_connected`         | bool              | `true`  | `true` 면 워크플로우 실행 내내 JIF 구독 유지 (실시간 전이 감지), `false` 면 첫 스냅샷 수신 후 해제 — AI Agent Tool 일회성 질의용                               |
| `include_extended_hours` | bool              | `false` | `true` 면 프리마켓/시간외단일가/에프터마켓 등 시간외 세션도 `*_is_open` 편의 포트에서 `True` 로 반환                                                      |

| 출력 포트                 | 타입      | 설명                                                                                                                      |
| --------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `statuses`            | array   | 각 시장 스냅샷 배열 `[{market, jangubun, jstatus, jstatus_label, is_open, is_regular_open, is_extended_open, updated_at}, ...]` |
| `event`               | object  | 최근 전이 이벤트 `{market, prev_jstatus, jstatus, transition, label, timestamp}`                                               |
| `us_is_open`          | boolean | 미국장(NASDAQ/NYSE/AMEX 통합) 개장 여부                                                                                          |
| `kospi_is_open`       | boolean | 코스피 개장 여부                                                                                                               |
| `kosdaq_is_open`      | boolean | 코스닥 개장 여부                                                                                                               |
| `krx_futures_is_open` | boolean | 코스피 파생 개장 여부                                                                                                            |
| `hk_is_open`          | boolean | 홍콩 개장 여부 (오전/오후 중 하나라도 열려있으면 True)                                                                                      |
| `cn_is_open`          | boolean | 중국 개장 여부 (오전/오후 OR)                                                                                                     |
| `jp_is_open`          | boolean | 일본 개장 여부 (오전/오후 OR)                                                                                                     |

**주말/이벤트 미수신 시 초기값**: `stay_connected=true` 에서 bool 포트는 `False` 로 초기화 (보수적 기본값 — 주말 오주문 방지).

**사용 예제 — 위험관리 매도 게이트**

```json
{
  "nodes": [
    {"id": "broker", "type": "OverseasStockBrokerNode", "credential_id": "broker_cred"},
    {"id": "market_status", "type": "MarketStatusNode", "markets": ["US"], "stay_connected": true},
    {"id": "if_us_open", "type": "IfNode",
     "left": "{{ nodes.market_status.us_is_open }}", "operator": "==", "right": true}
  ],
  "edges": [
    {"from": "broker", "to": "market_status"},
    {"from": "broker", "to": "if_us_open"},
    {"from": "if_us_open", "to": "sell_order", "from_port": "true"},
    {"from": "if_us_open", "to": "telegram_market_closed", "from_port": "false"}
  ]
}
```

**AI Agent Tool 등록**: `is_tool_enabled=True`. LLM 이 "미국장 지금 열려있어?" 같은 자연어 질의에서 `markets=['US']` 로 호출 후 `us_is_open` 포트 반환. 자세한 내용은 `ai_agent_guide.md` 참조.

> **거래소 매핑 참고**: `US` 는 JIF 단일 코드로 NASDAQ, NYSE, AMEX, Dow Jones, S\&P 500 등 모든 미국 거래소를 포함합니다. 거래소별 세분화(NASDAQ 만 / NYSE 만)는 JIF 레벨에서 제공되지 않습니다.

***

## 4. condition - 조건 평가

매수/매도 조건을 판단하는 핵심 노드들입니다.

### ConditionNode

**플러그인 기반**으로 매수/매도 조건을 판단합니다. RSI, MACD, 볼린저밴드 등 다양한 기술적 지표를 플러그인으로 선택하여 사용합니다.

```json
{
  "id": "rsi",
  "type": "ConditionNode",
  "plugin": "RSI",
  "fields": {
    "period": 14,
    "threshold": 30,
    "direction": "below"
  }
}
```

| 필드          | 타입         |  필수 | 설명                        |
| ----------- | ---------- | :-: | ------------------------- |
| `plugin`    | string     |  ✅  | 사용할 플러그인 ID (RSI, MACD 등) |
| `fields`    | object     |  ❌  | 플러그인별 파라미터                |
| `positions` | expression | 조건부 | 포지션 데이터 (익절/손절 플러그인에서 필수) |

**플러그인별로 필요한 데이터가 다릅니다:**

| 플러그인 유형 | 필요 데이터       | 연결할 노드                 | 예시 플러그인                                |
| ------- | ------------ | ---------------------- | -------------------------------------- |
| 시계열 기반  | 과거 OHLCV 데이터 | HistoricalDataNode     | RSI, MACD, BollingerBands, VolumeSpike |
| 포지션 기반  | 보유종목 정보      | AccountNode의 positions | ProfitTarget, StopLoss                 |

**출력:**

* `result` - 조건 평가 결과

```json
{
  "passed": true,
  "passed_symbols": [
    {"symbol": "AAPL", "exchange": "NASDAQ", "rsi": 28.5}
  ],
  "analysis": [
    {"symbol": "AAPL", "rsi": 28.5, "signal": "oversold"},
    {"symbol": "NVDA", "rsi": 55.2, "signal": "neutral"}
  ]
}
```

* `passed`: 조건을 통과한 종목이 **하나라도** 있으면 `true`
* `passed_symbols`: 조건을 통과한 종목만
* `analysis`: 모든 종목의 분석 결과

**전형적인 사용 패턴:**

{% @mermaid/diagram content="flowchart LR
A\[BrokerNode] --> B\[WatchlistNode] --> C\[SplitNode] --> D\[HistoricalDataNode] --> E\["ConditionNode<br/>(RSI)"]
E --> F\["LogicNode<br/>(AND)"]
F --> G\[NewOrderNode]" %}

> **팁**: 하나의 워크플로우에 ConditionNode을 여러 개 넣고, LogicNode으로 조합할 수 있습니다. 예: "RSI 과매도 **AND** MACD 골든크로스일 때만 매수".

> 전체 플러그인 목록과 파라미터: [종목조건 플러그인](/docs/strategies/stock_condition.md)

***

### LogicNode

여러 조건을 **논리적으로 조합**합니다. "RSI **그리고** MACD", "RSI **또는** 볼린저밴드" 같은 복합 조건을 만듭니다.

```json
{
  "id": "logic",
  "type": "LogicNode",
  "operator": "all",
  "conditions": [
    {
      "is_condition_met": "{{ nodes.rsi.result }}",
      "passed_symbols": "{{ nodes.rsi.passed_symbols }}"
    },
    {
      "is_condition_met": "{{ nodes.macd.result }}",
      "passed_symbols": "{{ nodes.macd.passed_symbols }}"
    }
  ]
}
```

| 필드           | 타입     |  필수 | 설명                                  |
| ------------ | ------ | :-: | ----------------------------------- |
| `operator`   | string |  ✅  | 논리 연산자                              |
| `threshold`  | number | 조건부 | 임계값 (`at_least`, `weighted` 등에서 사용) |
| `conditions` | array  |  ✅  | 조건 목록                               |

**conditions 배열 항목:**

| 필드                 | 설명                                 |
| ------------------ | ---------------------------------- |
| `is_condition_met` | ConditionNode의 result 바인딩          |
| `passed_symbols`   | ConditionNode의 passed\_symbols 바인딩 |
| `weight`           | 가중치 (`weighted` 연산자에서만 사용, 0\~1)   |

**연산자 설명:**

| 연산자        | 의미                  | 실전 예시                               |
| ---------- | ------------------- | ----------------------------------- |
| `all`      | 모든 조건 만족 (AND)      | RSI 과매도 **그리고** MACD 골든크로스          |
| `any`      | 하나 이상 만족 (OR)       | RSI 과매도 **또는** 볼린저밴드 하단 이탈          |
| `not`      | 모든 조건 불만족           | RSI 과매수 **아닌** 종목                   |
| `xor`      | 정확히 하나만 만족          | 두 신호 중 하나만                          |
| `at_least` | N개 이상 만족            | 3개 조건 중 **2개 이상** (threshold: 2)    |
| `at_most`  | N개 이하 만족            | 최대 1개만 충족                           |
| `exactly`  | 정확히 N개 만족           | 정확히 2개 충족                           |
| `weighted` | 가중치 합이 threshold 이상 | RSI 40% + MACD 35% + BB 25% = 가중 점수 |

**weighted 예시 (스코어링):**

```json
{
  "id": "logic",
  "type": "LogicNode",
  "operator": "weighted",
  "threshold": 0.6,
  "conditions": [
    {
      "is_condition_met": "{{ nodes.rsi.result }}",
      "passed_symbols": "{{ nodes.rsi.passed_symbols }}",
      "weight": 0.4
    },
    {
      "is_condition_met": "{{ nodes.macd.result }}",
      "passed_symbols": "{{ nodes.macd.passed_symbols }}",
      "weight": 0.35
    },
    {
      "is_condition_met": "{{ nodes.bb.result }}",
      "passed_symbols": "{{ nodes.bb.result.passed_symbols }}",
      "weight": 0.25
    }
  ]
}
```

이 예시에서 RSI(40%) + MACD(35%) = 75% ≥ 60%(threshold)이면 매수 신호가 됩니다.

> 상세 가이드: [조건 조합 (LogicNode)](/docs/nodes/logic_guide.md)

***

## 5. order - 주문 실행

실제 매수/매도 주문을 실행하는 노드들입니다.

### NewOrderNode (신규 주문)

신규 매수/매도 주문을 실행합니다. **플러그인을 사용하지 않으며**, 고정 필드(`side`/`order_type`/`order`)를 직접 설정합니다. `order`는 `PositionSizingNode.order`를 바인딩하는 것이 표준 패턴입니다.

| 상품   | 노드 타입                         |
| ---- | ----------------------------- |
| 해외주식 | `OverseasStockNewOrderNode`   |
| 해외선물 | `OverseasFuturesNewOrderNode` |
| 국내주식 | `KoreaStockNewOrderNode`      |

```json
{
  "id": "order",
  "type": "OverseasStockNewOrderNode",
  "side": "buy",
  "order_type": "market",
  "order": "{{ nodes.sizing.order }}"
}
```

| 필드           | 타입                                     |  필수 | 설명                                                         |
| ------------ | -------------------------------------- | :-: | ---------------------------------------------------------- |
| `side`       | `"buy"` \| `"sell"`                    |  ✅  | 매수/매도                                                      |
| `order_type` | `"market"` \| `"limit"`                |  ✅  | 시장가/지정가                                                    |
| `price_type` | enum                                   |  ❌  | LS증권 호가 유형 (`limit`, `market`, `LOO`, `LOC`, `MOO`, `MOC`) |
| `order`      | `{symbol, exchange, quantity, price?}` |  ✅  | `PositionSizingNode.order` 바인딩 권장 (expression-only)        |

**출력**: `result` - 주문 결과 (배열)

```json
[
  {
    "order_id": "12345",
    "exchange": "NASDAQ",
    "symbol": "AAPL",
    "side": "buy",
    "quantity": 10,
    "price": 192.30,
    "status": "submitted"
  }
]
```

> **⚠️ `plugin: "MarketOrder"` / `"LimitOrder"` 은 존재하지 않는 예제였습니다**: 구 버전 가이드의 `plugin` + `fields: {side, amount_type, amount}` 패턴은 실제로 구현되지 않았으며 사용 시 `[log/error] 주문할 종목이 없습니다` 경고 후 조용히 no-op 됩니다. 반드시 위의 고정 필드 + `PositionSizingNode.order` 바인딩 패턴을 사용하세요.

> **중요 - 실시간 연결 차단**: 실시간 노드(RealMarketDataNode 등)에서 NewOrderNode으로 **직접 연결할 수 없습니다**. 초당 수십 번 주문이 나가는 사고를 방지하기 위해, 반드시 ThrottleNode를 사이에 넣어야 합니다.

> **주의 - 실전 주문**: 해외주식 BrokerNode은 모의투자를 지원하지 않으므로, 주문 노드를 연결하면 **실제 돈으로 거래**됩니다. 처음에는 소량으로 테스트하세요.

> **주의 - 재시도 비활성화**: 주문 노드는 네트워크 에러를 제외하고 자동 재시도가 **비활성화**되어 있습니다. 중복 주문을 방지하기 위한 안전장치입니다.

> 전체 주문 파이프라인 예시: [주문 노드 사용법](/docs/strategies/order_condition.md)

***

### ModifyOrderNode (주문 정정)

미체결 주문의 가격이나 수량을 정정합니다. **플러그인을 사용하지 않으며**, `order_id` + `new_price` / `new_quantity` 를 직접 설정합니다.

| 상품   | 노드 타입                            |
| ---- | -------------------------------- |
| 해외주식 | `OverseasStockModifyOrderNode`   |
| 해외선물 | `OverseasFuturesModifyOrderNode` |

```json
{
  "id": "modify",
  "type": "OverseasStockModifyOrderNode",
  "order_id": "{{ item.order_id }}",
  "new_price": "{{ finance.markup(item.price, 1.0) }}"
}
```

| 필드             | 타입      |  필수 | 설명          |
| -------------- | ------- | :-: | ----------- |
| `order_id`     | string  |  ✅  | 정정 대상 주문 ID |
| `new_price`    | number  |  ❌  | 변경할 가격      |
| `new_quantity` | integer |  ❌  | 변경할 수량      |

> **⚠️ `TrailingStop` 플러그인은 존재하지 않습니다**: 가격 추적 정정은 실시간 시세(`OverseasStockRealMarketDataNode` + `ThrottleNode`)와 `OverseasStockOpenOrdersNode`를 조합한 워크플로우에서 `new_price`를 표현식으로 직접 계산하여 구현하세요.

***

### CancelOrderNode (주문 취소)

미체결 주문을 취소합니다. **플러그인을 사용하지 않으며**, `order_id` 를 직접 설정합니다.

| 상품   | 노드 타입                            |
| ---- | -------------------------------- |
| 해외주식 | `OverseasStockCancelOrderNode`   |
| 해외선물 | `OverseasFuturesCancelOrderNode` |

```json
{
  "id": "cancel",
  "type": "OverseasStockCancelOrderNode",
  "order_id": "{{ item.order_id }}"
}
```

| 필드         | 타입     |  필수 | 설명        |
| ---------- | ------ | :-: | --------- |
| `order_id` | string |  ✅  | 취소할 주문 ID |

> **⚠️ `TimeStop` 플러그인은 존재하지 않습니다**: "N분 이내 미체결 시 자동 취소"는 `ScheduleNode`로 주기적으로 `OverseasStockOpenOrdersNode`를 조회하고, 미체결 시간을 `IfNode`로 판정한 뒤 `OverseasStockCancelOrderNode`를 호출하여 직접 구현하세요.

***

### PositionSizingNode (포지션 크기 계산)

**주문 수량을 자동으로 계산**합니다. "잔고의 10%만 투자" 같은 자금 관리 규칙을 적용합니다.

```json
{
  "id": "sizing",
  "type": "PositionSizingNode",
  "symbol": "{{ item }}",
  "balance": "{{ nodes.account.balance }}",
  "method": "fixed_percent",
  "max_percent": 10
}
```

| 필드               | 타입         | 기본값               | 설명                           |
| ---------------- | ---------- | ----------------- | ---------------------------- |
| `symbol`         | expression | -                 | 대상 종목                        |
| `balance`        | expression | -                 | 가용 잔고                        |
| `market_data`    | expression | -                 | 현재가 정보 (선택)                  |
| `method`         | string     | `"fixed_percent"` | 포지션 사이징 방법                   |
| `max_percent`    | number     | `10`              | 잔고 대비 비율 (%)                 |
| `fixed_amount`   | number     | -                 | 고정 금액 (fixed\_amount 방법 시)   |
| `fixed_quantity` | number     | `1`               | 고정 수량 (fixed\_quantity 방법 시) |

**method 옵션:**

| 방법               | 설명     | 사용 예시                  |
| ---------------- | ------ | ---------------------- |
| `fixed_percent`  | 잔고의 N% | "잔고의 10%씩 투자" (가장 일반적) |
| `fixed_amount`   | 고정 금액  | "종목당 $1000씩 투자"        |
| `fixed_quantity` | 고정 수량  | "종목당 10주씩 매수"          |
| `kelly`          | 켈리 공식  | 승률 기반 최적 비율 (고급)       |
| `atr_based`      | ATR 기반 | 변동성 기반 사이징 (고급)        |

**출력**: `order` - 주문 정보

```json
{
  "symbol": "AAPL",
  "exchange": "NASDAQ",
  "quantity": 5,
  "price": 192.30
}
```

**전형적인 사용 패턴:**

{% @mermaid/diagram content="flowchart LR
A\[AccountNode] -->|balance| C\["PositionSizingNode<br/>(잔고의 10%로 계산)"]
B\[ConditionNode] -->|종목| C
C -->|order| D\["NewOrderNode<br/>(계산된 수량으로 주문)"]" %}

> **팁**: PositionSizingNode의 출력(`order`)을 NewOrderNode의 `order` 필드에 바인딩하면 자동으로 계산된 수량으로 주문됩니다. 과도한 집중 투자를 방지하는 핵심 노드입니다.

***

## 6. risk - 리스크 관리

### PortfolioNode

멀티 전략 포트폴리오의 **자본 배분과 리밸런싱**을 관리합니다. 여러 전략에 자금을 어떻게 나눌지 결정합니다.

```json
{
  "id": "portfolio",
  "type": "PortfolioNode",
  "total_capital": 100000,
  "allocation_method": "equal",
  "rebalance_rule": "drift",
  "drift_threshold": 5.0,
  "reserve_percent": 10
}
```

| 필드                  | 타입     | 기본값       | 설명                |
| ------------------- | ------ | --------- | ----------------- |
| `total_capital`     | number | `100000`  | 총 투자 자본 (USD)     |
| `allocation_method` | string | `"equal"` | 자금 배분 방법          |
| `rebalance_rule`    | string | `"none"`  | 리밸런싱 규칙           |
| `drift_threshold`   | number | `5.0`     | 배분 비율 이탈 허용치 (%)  |
| `reserve_percent`   | number | `0`       | 현금 보유 비율 (%, 비상금) |

**배분 방법:**

| 방법            | 설명                        | 예시 (3개 전략, $100,000)       |
| ------------- | ------------------------- | -------------------------- |
| `equal`       | 균등 배분                     | 각각 $33,333                 |
| `custom`      | 사용자 지정                    | RSI 50%, MACD 30%, BB 20%  |
| `risk_parity` | 변동성 역비례 (변동성 낮은 전략에 더 많이) | 안정형 $45,000, 공격형 $25,000 등 |
| `momentum`    | 최근 수익률 비례 (잘 되는 전략에 더 많이) | 수익률 높은 전략에 자금 집중           |

**리밸런싱 규칙:**

| 규칙         | 설명                                          |
| ---------- | ------------------------------------------- |
| `none`     | 리밸런싱 안 함 (초기 배분 유지)                         |
| `periodic` | 주기적으로 리밸런싱 (daily/weekly/monthly/quarterly) |
| `drift`    | 배분 비율이 `drift_threshold`% 이상 벗어나면 리밸런싱      |
| `both`     | 주기 + 드리프트 둘 다 적용                            |

**출력**: `combined_equity` (통합 자산곡선), `allocation_weights` (배분 비율), `rebalance_signal` (리밸런싱 신호), `allocated_capital` (전략별 배분 금액)

> **팁 - reserve\_percent**: 항상 일정 비율은 현금으로 보유하는 것이 안전합니다. `reserve_percent: 10`이면 총 자본의 10%를 현금으로 유지합니다.

***

## 7. schedule - 스케줄/시간

### ScheduleNode

크론(cron) 스케줄에 따라 워크플로우를 **반복 실행**합니다. "평일 9시에 자동 실행", "15분마다 체크" 같은 스케줄을 설정합니다.

```json
{
  "id": "schedule",
  "type": "ScheduleNode",
  "cron": "0 */15 9-16 * * mon-fri",
  "timezone": "America/New_York"
}
```

| 필드         | 타입      | 기본값                  | 설명                  |
| ---------- | ------- | -------------------- | ------------------- |
| `cron`     | string  | `"0 */5 * * * *"`    | 크론 표현식 (6-field 권장) |
| `timezone` | string  | `"America/New_York"` | 타임존                 |
| `enabled`  | boolean | `true`               | 활성화 여부              |

> **cron 포맷**: 예제는 **6-field (`초 분 시 일 월 요일`)** 로 통일합니다. 5-field(`분 시 일 월 요일`)도 지원되지만 혼동 방지를 위해 초 필드를 명시하세요.

**자주 쓰는 크론 예시 (6-field):**

| 표현식                       | 설명                        |
| ------------------------- | ------------------------- |
| `0 30 9 * * *`            | 매일 오전 9:30 (뉴욕 시간)        |
| `0 */15 9-16 * * mon-fri` | 평일 9\~16시, 15분마다          |
| `0 0 9 * * mon`           | 매주 월요일 오전 9:00            |
| `0 0 23 * * *`            | 한국시간 기준 매일 08:00 (뉴욕 23시) |

**출력**: `trigger` - 스케줄 트리거 신호

> **주의 - timezone**: 미국 주식은 뉴욕 시간(`America/New_York`)으로 설정하세요. 한국 시간으로 설정하면 거래시간이 맞지 않습니다.

> **팁 - LS증권 미국 주식 거래 시간 (한국시간 기준)**:

| 구간    | 겨울 (서머타임 미적용)       | 여름 (서머타임 적용, 3\~11월) | 비고       |
| ----- | ------------------- | -------------------- | -------- |
| 주간거래  | 10:00 \~ 17:30      | 09:00 \~ 16:30       | 지정가만 가능  |
| 프리마켓  | 18:00 \~ 23:30      | 17:00 \~ 22:30       | 장전 시간외   |
| 정규장   | 23:30 \~ 06:00 (익일) | 22:30 \~ 05:00 (익일)  | 메인 거래 시간 |
| 애프터마켓 | 06:00 \~ 09:30      | 05:00 \~ 08:30       | 장후 시간외   |

> 한국시간 오전에도 주간거래로 미국 주식 매매가 가능합니다. 정규장 외 시간에는 지정가 주문만 가능하므로 `OverseasStockNewOrderNode`에서 `order_type: "limit"` + `order.price`를 명시하여 지정가 주문을 사용하세요.

> 상세 가이드: [스케줄 설정](/docs/nodes/schedule_guide.md)

***

### TradingHoursFilterNode

**거래시간 필터**입니다. 거래시간이 아니면 대기하다가, 거래시간이 시작되면 통과시킵니다.

```json
{
  "id": "tradingHours",
  "type": "TradingHoursFilterNode",
  "start": "09:30",
  "end": "16:00",
  "timezone": "America/New_York",
  "days": ["mon", "tue", "wed", "thu", "fri"]
}
```

| 필드         | 타입     |  필수 | 설명                                        |
| ---------- | ------ | :-: | ----------------------------------------- |
| `start`    | string |  ✅  | 시작 시간 (HH:MM)                             |
| `end`      | string |  ✅  | 종료 시간 (HH:MM)                             |
| `timezone` | string |  ✅  | 타임존                                       |
| `days`     | array  |  ✅  | 활성 요일 (mon, tue, wed, thu, fri, sat, sun) |

**동작 방식:**

| 상황          | 동작                          |
| ----------- | --------------------------- |
| 거래시간 내      | 즉시 통과                       |
| 거래시간 외      | 거래시간 시작까지 **대기** (blocking) |
| shutdown 요청 | 종료                          |

**출력**: `passed` (통과 여부), `blocked` (차단 여부)

> **주의**: 거래시간 외에 워크플로우를 시작하면 이 노드에서 **멈춰서 대기**합니다. ScheduleNode과 조합하여 거래시간 내에만 실행되도록 설정하는 것을 권장합니다.

***

## 8. data - 데이터 조회/저장

### SQLiteNode

로컬 SQLite 데이터베이스에 데이터를 **저장하고 조회**합니다. 매매 이력 저장, 최고가 추적 등에 활용합니다.

두 가지 모드를 지원합니다:

#### 모드 1: execute\_query (SQL 직접 작성)

SQL을 알고 있다면 직접 쿼리를 작성할 수 있습니다.

```json
{
  "id": "sqlite",
  "type": "SQLiteNode",
  "db_name": "trading.db",
  "operation": "execute_query",
  "query": "SELECT * FROM trade_history WHERE symbol = :symbol",
  "parameters": {"symbol": "AAPL"}
}
```

#### 모드 2: simple (SQL 없이 CRUD)

SQL 없이 테이블/액션/컬럼을 선택하여 사용합니다.

```json
{
  "id": "sqlite",
  "type": "SQLiteNode",
  "db_name": "trading.db",
  "operation": "simple",
  "table": "peak_tracker",
  "action": "upsert",
  "columns": ["symbol", "peak_price"],
  "values": {"symbol": "AAPL", "peak_price": 195.50},
  "on_conflict": "symbol"
}
```

| 필드            | 타입     |  필수 | 설명                                                   |
| ------------- | ------ | :-: | ---------------------------------------------------- |
| `db_name`     | string |  ✅  | DB 파일명 (자동 생성됨)                                      |
| `operation`   | string |  ✅  | `execute_query` 또는 `simple`                          |
| `query`       | string | 조건부 | SQL 쿼리 (execute\_query 모드)                           |
| `parameters`  | object |  ❌  | 쿼리 파라미터 (`:name` 형식)                                 |
| `table`       | string | 조건부 | 테이블명 (simple 모드)                                     |
| `action`      | string | 조건부 | `select` / `insert` / `update` / `delete` / `upsert` |
| `values`      | object | 조건부 | 저장할 값                                                |
| `on_conflict` | string | 조건부 | upsert 시 충돌 키 (예: `"symbol"`)                        |

**출력**: `rows` (조회 결과), `affected_count` (영향 행 수)

> **팁 - upsert**: "있으면 업데이트, 없으면 삽입"입니다. 최고가 추적처럼 한 종목당 하나의 레코드를 유지할 때 유용합니다.

***

### HTTPRequestNode

외부 HTTP API를 호출합니다. 뉴스 API, 경제 지표 API 등 외부 데이터를 가져올 때 사용합니다.

```json
{
  "id": "http",
  "type": "HTTPRequestNode",
  "method": "GET",
  "url": "https://api.example.com/news",
  "query_params": {"symbol": "AAPL", "limit": "5"},
  "timeout_seconds": 30
}
```

| 필드                | 타입     | 기본값     | 설명                                       |
| ----------------- | ------ | ------- | ---------------------------------------- |
| `method`          | string | `"GET"` | HTTP 메서드 (GET, POST, PUT, PATCH, DELETE) |
| `url`             | string | -       | 요청 URL                                   |
| `query_params`    | object | -       | URL 쿼리 파라미터                              |
| `body`            | object | -       | 요청 본문 (POST/PUT/PATCH)                   |
| `headers`         | object | -       | 추가 헤더                                    |
| `credential_id`   | string | -       | API 인증 정보                                |
| `timeout_seconds` | number | `30`    | 타임아웃 (초, 1\~300)                         |

**출력**: `response` (응답 데이터), `status_code` (HTTP 상태 코드), `success` (성공 여부), `error` (에러 메시지)

> **주의**: 실시간 노드에서 HTTPRequestNode으로 직접 연결하면 **경고**가 표시됩니다. ThrottleNode를 사용하세요.

> **팁**: 인증이 필요한 API는 `credential_id`로 credentials 섹션의 HTTP 인증 정보를 참조합니다. Bearer 토큰, 헤더 키, Basic 인증 등을 지원합니다.

***

### FieldMappingNode

데이터의 **필드명을 변환**합니다. 외부 API 응답의 필드명이 ProgramGarden 형식과 다를 때 매핑해줍니다.

```json
{
  "id": "mapping",
  "type": "FieldMappingNode",
  "data": "{{ nodes.http.response }}",
  "mappings": [
    {"from": "ticker", "to": "symbol"},
    {"from": "last_price", "to": "price"},
    {"from": "vol", "to": "volume"}
  ],
  "preserve_unmapped": true
}
```

| 필드                  | 타입         | 기본값    | 설명                                  |
| ------------------- | ---------- | ------ | ----------------------------------- |
| `data`              | expression | -      | 변환할 데이터                             |
| `mappings`          | array      | -      | 매핑 규칙 `[{from: "원래이름", to: "새이름"}]` |
| `preserve_unmapped` | boolean    | `true` | 매핑 규칙에 없는 필드도 유지할지 여부               |

**출력**: `mapped_data` (변환된 데이터)

**변환 전**: `{"ticker": "AAPL", "last_price": 192.30, "vol": 45000000}` **변환 후**: `{"symbol": "AAPL", "price": 192.30, "volume": 45000000}`

> **팁**: ConditionNode의 플러그인은 `symbol`, `open`, `high`, `low`, `close`, `volume` 같은 표준 필드명을 기대합니다. 외부 API 데이터를 ConditionNode에 넣기 전에 FieldMappingNode으로 필드명을 맞춰주세요.

***

### FileReaderNode (파일 리더)

파일을 읽어 텍스트/데이터로 변환합니다. PDF, TXT, CSV, JSON, MD, DOCX, XLSX 포맷을 지원합니다. AIAgentNode의 도구로도 사용 가능합니다.

```json
{
  "id": "reader",
  "type": "FileReaderNode",
  "file_paths": ["/app/data/report.pdf", "/app/data/prices.csv"],
  "format": "auto"
}
```

| 필드                 | 설명                                                             | 기본값     |
| ------------------ | -------------------------------------------------------------- | ------- |
| `file_paths`       | 파일 경로 배열                                                       | -       |
| `file_data_list`   | Base64 인코딩 파일 데이터 배열                                           | -       |
| `format`           | 포맷 (`auto`, `pdf`, `txt`, `csv`, `json`, `md`, `docx`, `xlsx`) | `auto`  |
| `pages`            | PDF 페이지 범위 (예: `"1-5"`, `"1,3,5"`)                             | 전체      |
| `encoding`         | 텍스트 인코딩                                                        | `utf-8` |
| `extract_tables`   | PDF 테이블 추출 (pdfplumber)                                        | `false` |
| `sheet_name`       | XLSX 시트명                                                       | 첫 번째 시트 |
| `max_file_size_mb` | 파일당 최대 크기(MB)                                                  | `10`    |

**출력 포트**: `texts` (텍스트 배열), `data_list` (파싱된 데이터 배열), `metadata` (파일 메타데이터 배열)

> **보안**: 파일 경로는 `/app/data/` 하위로 제한됩니다. 경로 탈출 공격이 자동 차단됩니다.

> **팁**: 복수 파일을 한 번에 처리하면 출력이 배열이 되어 auto-iterate로 파일별 AI 분석이 가능합니다.

***

## 9. display - 시각화

차트와 테이블을 생성합니다. 실시간 노드와 연결하면 데이터가 **자동으로 갱신**됩니다.

### TableDisplayNode (테이블)

데이터를 테이블(표) 형태로 표시합니다.

```json
{
  "id": "table",
  "type": "TableDisplayNode",
  "title": "보유 종목 현황",
  "data": "{{ nodes.account.positions }}",
  "columns": ["symbol", "quantity", "buy_price", "current_price", "pnl_rate"],
  "limit": 20,
  "sort_by": "pnl_rate",
  "sort_order": "desc"
}
```

| 필드           | 타입         | 기본값      | 설명                                |
| ------------ | ---------- | -------- | --------------------------------- |
| `title`      | string     | -        | 테이블 제목                            |
| `data`       | expression | -        | 표시할 데이터 (배열)                      |
| `columns`    | array      | 전체       | 표시할 컬럼 목록 (미지정 시 모든 컬럼)           |
| `limit`      | number     | `10`     | 최대 행 수 (1\~100)                   |
| `sort_by`    | string     | -        | 정렬 기준 컬럼                          |
| `sort_order` | string     | `"desc"` | `"asc"` (오름차순) 또는 `"desc"` (내림차순) |

***

### LineChartNode (라인 차트)

시계열 데이터를 라인 차트로 표시합니다. RSI 추이, 가격 변동 등을 시각화할 때 사용합니다.

```json
{
  "id": "chart",
  "type": "LineChartNode",
  "title": "RSI 추이",
  "data": "{{ nodes.rsi.result.analysis }}",
  "x_field": "date",
  "y_field": "rsi"
}
```

| 필드        | 타입         |  필수 | 설명                   |
| --------- | ---------- | :-: | -------------------- |
| `title`   | string     |  ❌  | 차트 제목                |
| `data`    | expression |  ✅  | 차트 데이터 (배열)          |
| `x_field` | string     |  ✅  | X축 필드명 (보통 `"date"`) |
| `y_field` | string     |  ✅  | Y축 필드명 (보통 값 필드)     |

> **주의**: `x_field`와 `y_field`를 반드시 명시해야 합니다. 자동 추론은 지원하지 않습니다.

***

### MultiLineChartNode (다중 라인 차트)

여러 시리즈(종목)를 **한 차트에** 표시합니다. 종목별 가격 비교에 유용합니다.

```json
{
  "id": "multiChart",
  "type": "MultiLineChartNode",
  "title": "종목별 가격 추이",
  "data": "{{ nodes.history.value }}",
  "x_field": "date",
  "y_field": "close",
  "series_key": "symbol"
}
```

> **주의**: `HistoricalDataNode`의 출력은 `.value`(단수, 배열) 또는 `.value.time_series`(단일 종목의 시계열)입니다. 구 버전에서 쓰던 `.values`(복수)는 존재하지 않습니다.

| 필드           | 타입     |  필수 | 설명                                    |
| ------------ | ------ | :-: | ------------------------------------- |
| `series_key` | string |  ✅  | 시리즈 구분 키 (예: `"symbol"`이면 종목별로 라인 분리) |

***

### CandlestickChartNode (캔들스틱 차트)

OHLCV 데이터를 **캔들스틱(봉) 차트**로 표시합니다. 주식 차트의 기본 형태입니다.

```json
{
  "id": "candle",
  "type": "CandlestickChartNode",
  "title": "AAPL 일봉",
  "data": "{{ nodes.history.value }}",
  "date_field": "date",
  "open_field": "open",
  "high_field": "high",
  "low_field": "low",
  "close_field": "close",
  "volume_field": "volume"
}
```

| 필드             | 타입     |  필수 | 설명     |
| -------------- | ------ | :-: | ------ |
| `date_field`   | string |  ✅  | 날짜 필드  |
| `open_field`   | string |  ✅  | 시가 필드  |
| `high_field`   | string |  ✅  | 고가 필드  |
| `low_field`    | string |  ✅  | 저가 필드  |
| `close_field`  | string |  ✅  | 종가 필드  |
| `volume_field` | string |  ❌  | 거래량 필드 |

***

### BarChartNode (바 차트)

범주형 데이터를 바(막대) 차트로 표시합니다.

```json
{
  "id": "bar",
  "type": "BarChartNode",
  "title": "종목별 수익률",
  "data": "{{ nodes.account.positions }}",
  "x_field": "symbol",
  "y_field": "pnl_rate"
}
```

***

### SummaryDisplayNode (요약 카드)

핵심 지표를 **요약 카드** 형태로 표시합니다. 대시보드의 KPI 카드처럼 한눈에 보이는 요약 정보입니다.

```json
{
  "id": "summary",
  "type": "SummaryDisplayNode",
  "title": "계좌 요약",
  "data": "{{ nodes.account }}"
}
```

***

## 10. analysis - 백테스트/분석

### BacktestEngineNode

과거 데이터로 전략을 **검증**합니다. 실제 돈을 투자하기 전에 "이 전략이 과거에 얼마나 수익이 났을까?"를 시뮬레이션합니다.

```json
{
  "id": "backtest",
  "type": "BacktestEngineNode",
  "initial_capital": 10000,
  "commission_rate": 0.001,
  "position_sizing": "kelly",
  "exit_rules": {
    "stop_loss_percent": 5,
    "take_profit_percent": 15
  }
}
```

| 필드                | 타입     | 설명                                          |
| ----------------- | ------ | ------------------------------------------- |
| `initial_capital` | number | 초기 투자 자본 (USD)                              |
| `commission_rate` | number | 수수료율 (0.001 = 0.1%)                         |
| `position_sizing` | string | 포지션 사이징: `fixed`, `percent`, `kelly`, `atr` |
| `exit_rules`      | object | 자동 청산 규칙                                    |

**exit\_rules 옵션:**

| 필드                    | 설명                          |
| --------------------- | --------------------------- |
| `stop_loss_percent`   | 손절 비율 (%) - 이 이상 손실나면 자동 매도 |
| `take_profit_percent` | 익절 비율 (%) - 이 이상 이익나면 자동 매도 |

**출력:**

* `equity_curve` - 자산 변동 곡선 (날짜별 자산 추이)
* `summary` - 성과 요약

```json
{
  "total_return": 25.3,
  "mdd": -8.2,
  "win_rate": 62.5,
  "sharpe_ratio": 1.85,
  "total_trades": 48
}
```

> **팁**: `summary`의 `sharpe_ratio`가 1.0 이상이면 괜찮은 전략, 2.0 이상이면 우수한 전략으로 평가됩니다. `mdd`(최대 낙폭)가 -20% 이하면 위험할 수 있습니다.

***

### BenchmarkCompareNode

여러 백테스트 결과를 **나란히 비교**합니다. "RSI 전략 vs Buy & Hold, 어떤 게 더 나았을까?"를 분석합니다.

```json
{
  "id": "compare",
  "type": "BenchmarkCompareNode",
  "strategies": [
    "{{ nodes.backtestRSI }}",
    "{{ nodes.backtestSPY }}"
  ],
  "ranking_metric": "sharpe"
}
```

| 필드               | 타입     |  필수 | 설명                     |
| ---------------- | ------ | :-: | ---------------------- |
| `strategies`     | array  |  ✅  | BacktestEngineNode 출력들 |
| `ranking_metric` | string |  ❌  | 순위 기준 (기본: `sharpe`)   |

**ranking\_metric 옵션:**

| 지표       | 설명               | 좋은 값              |
| -------- | ---------------- | ----------------- |
| `sharpe` | 위험 대비 수익 (샤프 비율) | 높을수록 좋음 (>1.0)    |
| `return` | 총 수익률            | 높을수록 좋음           |
| `mdd`    | 최대 낙폭            | 낮을수록(0에 가까울수록) 좋음 |
| `calmar` | 수익률 / MDD        | 높을수록 좋음           |

**출력:**

* `combined_curve` - 통합 자산 곡선 (비교 차트용)
* `comparison_metrics` - 전략별 비교 지표
* `ranking` - 순위

**사용 패턴:**

{% @mermaid/diagram content="flowchart LR
A\["BacktestEngineNode<br/>(내 RSI 전략)"] --> C\[BenchmarkCompareNode] --> D\[LineChartNode]
B\["BacktestEngineNode<br/>(SPY Buy\&Hold)"] --> C" %}

> **팁 - Buy & Hold 벤치마크**: BacktestEngineNode에 조건 신호 없이 데이터만 넣으면 **Buy & Hold** (사서 보유) 전략으로 자동 적용됩니다. 내 전략이 단순 보유보다 나은지 비교하세요.

***

## 11. ai - AI 에이전트

### LLMModelNode

LLM(대규모 언어 모델) API에 연결합니다. BrokerNode이 증권사에 연결하듯, LLMModelNode은 **AI 서비스에 연결**합니다.

```json
{
  "id": "llm",
  "type": "LLMModelNode",
  "credential_id": "my-openai-cred",
  "model": "gpt-4o",
  "temperature": 0.3,
  "max_tokens": 2000
}
```

| 필드              | 타입      | 기본값        | 설명                           |
| --------------- | ------- | ---------- | ---------------------------- |
| `credential_id` | string  | -          | LLM API 인증 ID                |
| `model`         | string  | `"gpt-4o"` | 사용할 모델명                      |
| `temperature`   | number  | `0.7`      | 응답의 창의성 (0.0=정확, 2.0=창의적)    |
| `max_tokens`    | number  | `1000`     | 최대 응답 길이 (토큰 수, 100\~128000) |
| `streaming`     | boolean | `false`    | 응답을 실시간으로 받을지 여부             |

**지원 LLM 제공자:**

| 제공자          | credential 타입      | 대표 모델                       |
| ------------ | ------------------ | --------------------------- |
| OpenAI       | `llm_openai`       | gpt-4o, gpt-4o-mini         |
| Anthropic    | `llm_anthropic`    | claude-sonnet-4-5-20250929  |
| Google       | `llm_google`       | gemini-2.0-flash            |
| Azure OpenAI | `llm_azure_openai` | (배포 이름 사용)                  |
| Ollama       | `llm_ollama`       | llama3, mistral (로컬 실행, 무료) |

**출력**: `connection` - LLM 연결 (`ai_model` 엣지로 AIAgentNode에 전달)

> **팁 - temperature**: 투자 분석처럼 정확성이 중요한 작업은 `temperature: 0.1~0.3`을 권장합니다. 창의적인 전략 아이디어가 필요하면 `0.7~1.0`으로 올리세요.

> **팁 - Ollama**: 무료로 AI를 사용하고 싶다면 Ollama를 로컬에 설치하여 사용할 수 있습니다. 단, 성능은 OpenAI/Anthropic보다 낮을 수 있습니다.

***

### AIAgentNode

AI 에이전트가 워크플로우의 다른 노드를 \*\*도구(Tool)\*\*로 활용하여 시장을 분석하고 의사결정합니다.

```json
{
  "id": "agent",
  "type": "AIAgentNode",
  "preset": "technical_analyst",
  "user_prompt": "AAPL, NVDA의 최근 차트를 분석하고 매수 적절성을 판단해주세요.",
  "output_format": "json",
  "max_tool_calls": 10,
  "cooldown_sec": 60
}
```

| 필드                | 타입     | 기본값        | 설명                                  |
| ----------------- | ------ | ---------- | ----------------------------------- |
| `preset`          | string | `"custom"` | 에이전트 역할 프리셋                         |
| `system_prompt`   | string | -          | AI에게 주는 역할 지시 (custom 모드에서 필수)      |
| `user_prompt`     | string | -          | AI에게 질문/지시 (표현식 사용 가능)              |
| `output_format`   | string | `"text"`   | 응답 형식: `text`, `json`, `structured` |
| `output_schema`   | object | -          | `structured` 모드에서 응답 구조 정의          |
| `max_tool_calls`  | number | `10`       | AI가 도구를 최대 몇 번 호출할 수 있는지 (1\~50)    |
| `timeout_seconds` | number | `60`       | 최대 실행 시간 (초)                        |
| `cooldown_sec`    | number | `60`       | 연속 실행 간 최소 대기 시간 (초, 1\~3600)       |

**프리셋 역할:**

| 프리셋                 | 역할      | 적합한 상황                    |
| ------------------- | ------- | ------------------------- |
| `technical_analyst` | 기술적 분석가 | RSI, MACD 등 차트 분석         |
| `risk_manager`      | 리스크 관리자 | 포지션 위험도 평가                |
| `news_analyst`      | 뉴스 분석가  | 뉴스/이벤트의 주가 영향 분석          |
| `strategist`        | 종합 전략가  | 전체 포트폴리오 전략 수립            |
| `custom`            | 사용자 정의  | `system_prompt`로 직접 역할 지정 |

**연결 방법 (3가지 엣지 타입):**

```json
{
  "edges": [
    {"from": "llm", "to": "agent", "type": "ai_model"},
    {"from": "history", "to": "agent", "type": "tool"},
    {"from": "market", "to": "agent", "type": "tool"},
    {"from": "broker", "to": "agent"}
  ]
}
```

| 엣지 타입       | 용도                                 |      필수     |
| ----------- | ---------------------------------- | :---------: |
| `ai_model`  | LLMModelNode → AIAgentNode (AI 연결) |    ✅ (1개)   |
| `tool`      | 기존 노드 → AIAgentNode (도구로 등록)       | ❌ (여러 개 가능) |
| `main` (기본) | 실행 순서                              |      -      |

**출력**: `response` - AI 응답

> **중요**: `tool` 엣지로 연결된 노드는 AI가 **필요할 때만 호출**합니다. 항상 실행되는 것이 아니라, AI가 "이 데이터가 필요하다"고 판단했을 때만 호출됩니다.

> **중요 - 실시간 연결 차단**: 실시간 노드에서 AIAgentNode으로 직접 연결할 수 없습니다. ThrottleNode를 사이에 넣어야 합니다.

> **주의 - Stateless**: AI 에이전트는 매 실행마다 **이전 대화를 기억하지 않습니다**. 필요한 정보는 `user_prompt`에 표현식으로 전달하세요.

> **주의 - API 비용**: LLM API는 사용량에 따라 비용이 발생합니다. `cooldown_sec`을 적절히 설정하여 과도한 호출을 방지하세요.

> 상세 가이드: [AI 에이전트 가이드](/docs/nodes/ai_agent_guide.md)

***

## 부록: 노드 카테고리 요약

| 카테고리        | 노드 수 | 용도                          | 주요 노드                                                                                                                                                                                                                                |
| ----------- | ---- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `infra`     | 8    | 시작/연결/흐름/분기                 | StartNode, BrokerNode, ThrottleNode, SplitNode, AggregateNode, IfNode                                                                                                                                                                |
| `account`   | 12   | 계좌 조회                       | AccountNode, OpenOrdersNode, RealAccountNode, RealOrderEventNode (해외주식/선물 + 국내주식)                                                                                                                                                    |
| `market`    | 23   | 시세/종목/펀더멘털/환율/심리/제외종목/장운영정보 | MarketDataNode, FundamentalNode, HistoricalDataNode, RealMarketDataNode, WatchlistNode, ScreenerNode, SymbolFilterNode, ExclusionListNode, CurrencyRateNode, FearGreedIndexNode, FundamentalDataNode, MarketStatusNode, KoreaStock\* |
| `condition` | 2    | 조건 평가                       | ConditionNode, LogicNode                                                                                                                                                                                                             |
| `order`     | 10   | 주문 실행                       | NewOrderNode, ModifyOrderNode, CancelOrderNode, PositionSizingNode (해외주식/선물 + 국내주식)                                                                                                                                                  |
| `risk`      | 1    | 리스크 관리                      | PortfolioNode                                                                                                                                                                                                                        |
| `schedule`  | 2    | 스케줄/시간                      | ScheduleNode, TradingHoursFilterNode                                                                                                                                                                                                 |
| `data`      | 4    | 데이터 조회/저장                   | SQLiteNode, HTTPRequestNode, FieldMappingNode, FileReaderNode                                                                                                                                                                        |
| `display`   | 6    | 시각화                         | TableDisplayNode, LineChartNode, MultiLineChartNode, CandlestickChartNode, BarChartNode, SummaryDisplayNode                                                                                                                          |
| `analysis`  | 2    | 백테스트                        | BacktestEngineNode, BenchmarkCompareNode                                                                                                                                                                                             |
| `ai`        | 2    | AI 에이전트                     | LLMModelNode, AIAgentNode                                                                                                                                                                                                            |
| `messaging` | 1    | 알림/메시징                      | TelegramNode                                                                                                                                                                                                                         |

***

## 다음 단계

* [워크플로우 구조 이해](/docs/workflow/structure.md) - 노드, 엣지, 인증의 개념부터
* [자동 반복 처리](/docs/workflow/auto_iterate_guide.md) - 여러 종목을 자동으로 처리하는 방법
* [표현식 문법](/docs/workflow/expression_guide.md) - `{{ }}` 표현식 작성법
* [조건 조합](/docs/nodes/logic_guide.md) - 여러 조건을 논리 연산으로 조합
* [종목조건 플러그인](/docs/strategies/stock_condition.md) - RSI, MACD 등 77개 전략 플러그인
* [AI 에이전트](/docs/nodes/ai_agent_guide.md) - LLM으로 시장 분석
