Skip to content

fix: backtest historical date range - paginated getCandlesByRange#77

Merged
fray-cloud merged 1 commit intodevfrom
fix/#75
Apr 2, 2026
Merged

fix: backtest historical date range - paginated getCandlesByRange#77
fray-cloud merged 1 commit intodevfrom
fix/#75

Conversation

@fray-cloud
Copy link
Copy Markdown
Owner

@fray-cloud fray-cloud commented Apr 2, 2026

Summary

  • Fixes No candle data available for the specified date range error when running backtests on historical dates
  • Root cause: fetchHistoricalCandles() only fetched latest 200 candles then filtered, missing any data older than ~33 days (4h) or ~8 days (1h)
  • Adds getCandlesByRange(symbol, interval, startTime, endTime) to IExchangeRest interface with full pagination in each adapter:
    • Binance: forward walk with startTime/endTime params, 1000 candles/page
    • Bybit: forward walk with start/end params, 1000 candles/page
    • Upbit: backward walk via to param, 200 candles/page
  • backtests.service.ts now calls getCandlesByRange directly (no more manual pagination in service layer)

Closes #75

Test plan

  • All 76 existing exchange-adapter unit tests pass (npm run test:unit)
  • Backtest with a date range > 33 days ago on Binance (e.g., 60-day window on 4h candles) returns candle data
  • Backtest with recent date range still works (no regression)
  • Redis cache key unchanged — cached results still served correctly

🤖 Generated with Claude Code

Summary by Sourcery

Implement paginated, date-range candle fetching across exchange adapters and wire it into backtesting and UI configuration.

New Features:

  • Add getCandlesByRange(symbol, interval, startTime, endTime) to the exchange REST interface and implement it for Binance, Bybit, and Upbit with proper pagination over arbitrary historical ranges.
  • Support enumerated parameter options in flow node definitions and render them as select dropdowns in the node inspector UI.

Bug Fixes:

  • Fix missing historical candle data in backtests by fetching all candles within the requested date range instead of only the latest page.

Enhancements:

  • Normalize symbols to uppercase before fetching historical and backtesting data to ensure consistent cache keys and DB lookups.
  • Extend node parameter metadata to carry optional value lists, improving configurability and reducing invalid user input in flow nodes.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Apr 2, 2026

Reviewer's Guide

Implements exchange-specific, fully paginated historical candle fetching via a new getCandlesByRange API on all REST adapters, updates the backtest service to rely on this range-based pagination (preserving Redis cache keys), and enhances the flow-node inspector UI to support select-style param inputs using declarative options metadata.

Sequence diagram for backtest historical candle fetching with getCandlesByRange

sequenceDiagram
  actor Trader
  participant WebApp
  participant WorkerService_BacktestsService as BacktestsService
  participant Redis
  participant ExchangeAdapter as IExchangeRest_Adapter
  participant ExchangeApi as Exchange_HTTP_API

  Trader->>WebApp: Configure backtest (exchange, symbol, interval, date range)
  WebApp->>WorkerService_BacktestsService: Start backtest request

  WorkerService_BacktestsService->>Redis: GET backtest:candles:exchange:SYMBOL:interval:start:end
  alt Cache hit
    Redis-->>WorkerService_BacktestsService: Cached candles
    WorkerService_BacktestsService-->>WebApp: Use cached candles for backtest
  else Cache miss
    Redis-->>WorkerService_BacktestsService: null
    WorkerService_BacktestsService->>ExchangeAdapter: getCandlesByRange(symbol, interval, startTime, endTime)
    activate ExchangeAdapter
    loop Paginated candle fetch
      ExchangeAdapter->>ExchangeApi: GET /candles with pagination params
      ExchangeApi-->>ExchangeAdapter: Page of candles (newest first or oldest first)
      ExchangeAdapter->>ExchangeAdapter: Normalize order and filter by startTime/endTime
      ExchangeAdapter->>ExchangeAdapter: Append or prepend to allCandles
    end
    ExchangeAdapter-->>WorkerService_BacktestsService: allCandles
    deactivate ExchangeAdapter

    alt Candles found
      WorkerService_BacktestsService->>Redis: SET cacheKey allCandles EX CANDLE_CACHE_TTL
    else No candles
      WorkerService_BacktestsService->>Redis: (no write)
    end

    WorkerService_BacktestsService-->>WebApp: Candles for backtest
  end

  WebApp-->>Trader: Show backtest results
Loading

Sequence diagram for NodeInspector param editing with options-based select inputs

sequenceDiagram
  actor User
  participant WebApp
  participant FlowEditor
  participant NodeInspector
  participant NODE_TYPE_REGISTRY

  User->>FlowEditor: Select node in strategy flow
  FlowEditor->>NODE_TYPE_REGISTRY: Lookup NodeTypeInfo for node.type
  NODE_TYPE_REGISTRY-->>FlowEditor: NodeTypeInfo (params with options metadata)
  FlowEditor->>NodeInspector: Render inspector with node and NodeTypeInfo

  NodeInspector->>NodeInspector: Split params into requiredParams and optionalParams
  loop For each ParamDefinition
    NodeInspector->>NodeInspector: Determine options for param.key
    alt ParamDefinition.options is defined and non-empty
      NodeInspector->>NodeInspector: Render ParamInput as <select> with options
    else No options
      NodeInspector->>NodeInspector: Render ParamInput as boolean toggle, number, or text input
    end
  end

  User->>NodeInspector: Change param via select or input
  NodeInspector->>FlowEditor: updateNodeConfig(nodeId, updatedConfig)
  FlowEditor-->>User: Updated node configuration reflected in UI
Loading

Class diagram for exchange REST adapters and new getCandlesByRange API

classDiagram
  class Candle {
    +string exchange
    +string symbol
    +string interval
    +string open
    +string high
    +string low
    +string close
    +string volume
    +number timestamp
  }

  class OrderResult {
    +string exchange
    +string symbol
    +string status
    +string side
    +string type
    +string price
    +string amount
    +string filled
  }

  class Market {
    +string symbol
    +string baseAsset
    +string quoteAsset
  }

  class IExchangeRest {
    <<interface>>
    +createOrder(symbol, side, type, amount, price) Promise~OrderResult~
    +getOrder(symbol, orderId) Promise~OrderResult~
    +cancelOrder(symbol, orderId) Promise~OrderResult~
    +getMarkets() Promise~Market[]~
    +getCandles(symbol, interval, limit) Promise~Candle[]~
    +getCandlesByRange(symbol, interval, startTime, endTime) Promise~Candle[]~
  }

  class BinanceRest {
    +string exchangeId
    +getCandles(symbol, interval, limit) Promise~Candle[]~
    +getCandlesByRange(symbol, interval, startTime, endTime) Promise~Candle[]~
    +createOrder(symbol, side, type, amount, price) Promise~OrderResult~
    +getOrder(symbol, orderId) Promise~OrderResult~
    +cancelOrder(symbol, orderId) Promise~OrderResult~
    +getMarkets() Promise~Market[]~
    -mapOrderResult(o)
  }

  class BybitRest {
    +string exchangeId
    +getCandles(symbol, interval, limit) Promise~Candle[]~
    +getCandlesByRange(symbol, interval, startTime, endTime) Promise~Candle[]~
    +createOrder(symbol, side, type, amount, price) Promise~OrderResult~
    +getOrder(symbol, orderId) Promise~OrderResult~
    +cancelOrder(symbol, orderId) Promise~OrderResult~
    +getMarkets() Promise~Market[]~
    -mapOrder(o)
  }

  class UpbitRest {
    +string exchangeId
    +getCandles(symbol, interval, limit) Promise~Candle[]~
    +getCandlesByRange(symbol, interval, startTime, endTime) Promise~Candle[]~
    +createOrder(symbol, side, type, amount, price) Promise~OrderResult~
    +getOrder(symbol, orderId) Promise~OrderResult~
    +cancelOrder(symbol, orderId) Promise~OrderResult~
    +getMarkets() Promise~Market[]~
    -mapOrderResponse(o)
  }

  class BacktestsService {
    -redis
    -fetchHistoricalCandles(exchange, symbol, interval, startDate, endDate) Promise~Candle[]~
  }

  class DataService {
    -loadFromDb(exchange, symbol, interval, startTime, endTime) Promise~OhlcvCandle[]~
    +getHistoricalCandles(exchange, symbol, interval, startTime, endTime) Promise~OhlcvCandle[]~
  }

  class OhlcvCandle {
    +string exchange
    +string symbol
    +string interval
    +string open
    +string high
    +string low
    +string close
    +string volume
    +number timestamp
  }

  IExchangeRest <|.. BinanceRest
  IExchangeRest <|.. BybitRest
  IExchangeRest <|.. UpbitRest

  BacktestsService --> IExchangeRest : uses getCandlesByRange
  DataService --> IExchangeRest : may use exchange adapters externally
  DataService --> OhlcvCandle
  BinanceRest --> Candle
  BybitRest --> Candle
  UpbitRest --> Candle
Loading

Class diagram for flow node param definitions and NodeInspector options support

classDiagram
  class ParamDefinition {
    +string key
    +boolean required
    +string[] options
  }

  class PortDefinition {
    +string name
    +string type
  }

  class NodeTypeInfo {
    +string subtype
    +string label
    +PortDefinition[] inputs
    +PortDefinition[] outputs
    +Record~string, unknown~ defaultConfig
    +ParamDefinition[] params
  }

  class NODE_TYPE_REGISTRY {
    <<map>>
    +Record~string, NodeTypeInfo~
  }

  class NodeConfig {
    +Record~string, unknown~ values
  }

  class Node {
    +string id
    +string type
    +NodeConfig config
  }

  class ParamInput {
    +paramKey string
    +value unknown
    +options string[]
    +onChange(val) void
  }

  class NodeInspector {
    -node Node
    -registry NodeTypeInfo
    -requiredParams ParamDefinition[]
    -optionalParams ParamDefinition[]
    +render()
  }

  NODE_TYPE_REGISTRY --> NodeTypeInfo : values
  NodeTypeInfo --> ParamDefinition : has
  NodeTypeInfo --> PortDefinition : has
  Node --> NodeConfig : has
  NodeInspector --> Node : inspects
  NodeInspector --> NodeTypeInfo : reads metadata
  NodeInspector --> ParamInput : renders
  ParamInput --> ParamDefinition : uses key and options

  class RsiNodeTypeInfo {
    +defaultConfig period, source
    +params period, source
  }

  class MacdConditionNodeTypeInfo {
    +defaultConfig operator, threshold
    +params operator, threshold
  }

  class CrossOverConditionNodeTypeInfo {
    +defaultConfig direction
    +params direction
  }

  class AndOrConditionNodeTypeInfo {
    +defaultConfig operator
    +params operator
  }

  class MarketOrderNodeTypeInfo {
    +defaultConfig side, amount
    +params side, amount
  }

  NODE_TYPE_REGISTRY --> RsiNodeTypeInfo
  NODE_TYPE_REGISTRY --> MacdConditionNodeTypeInfo
  NODE_TYPE_REGISTRY --> CrossOverConditionNodeTypeInfo
  NODE_TYPE_REGISTRY --> AndOrConditionNodeTypeInfo
  NODE_TYPE_REGISTRY --> MarketOrderNodeTypeInfo

  RsiNodeTypeInfo --> ParamDefinition : source.options [close, open, high, low]
  MacdConditionNodeTypeInfo --> ParamDefinition : operator.options [<, >, <=, >=, ==]
  CrossOverConditionNodeTypeInfo --> ParamDefinition : direction.options [above, below]
  AndOrConditionNodeTypeInfo --> ParamDefinition : operator.options [AND, OR]
  MarketOrderNodeTypeInfo --> ParamDefinition : side.options [buy, sell]
Loading

File-Level Changes

Change Details Files
Add exchange-specific paginated historical candle range fetching to all REST adapters via a new getCandlesByRange method.
  • Extend IExchangeRest with getCandlesByRange(symbol, interval, startTime, endTime) returning Candle[].
  • Implement Binance getCandlesByRange using /api/v3/klines with startTime/endTime, 1000-candle pages, forward pagination, and interval-based pageStart advancement.
  • Implement Bybit getCandlesByRange using /v5/market/kline with category=spot, start/end window parameters, 1000-candle pages, forward pagination, and response validation on retCode.
  • Implement Upbit getCandlesByRange using backward pagination via to ISO timestamp, 200-candle pages, range filtering, and chronological assembly.
  • Introduce per-adapter parseIntervalMs helpers to compute interval duration in ms for page stepping across 1m–1d intervals.
packages/exchange-adapters/src/interfaces/exchange-rest.ts
packages/exchange-adapters/src/binance/binance.rest.ts
packages/exchange-adapters/src/bybit/bybit.rest.ts
packages/exchange-adapters/src/upbit/upbit.rest.ts
Refactor backtest and historical data services to use the new range-based candle fetching while preserving caching behavior and symbol normalization.
  • Change BacktestsService.fetchHistoricalCandles to delegate to adapter.getCandlesByRange instead of manually paginating getCandles, while keeping the existing Redis cache key format and TTL.
  • Normalize symbols to uppercase in BacktestsService.fetchHistoricalCandles before cache key construction and adapter calls.
  • Normalize symbols to uppercase in DataService.getHistoricalCandles before DB lookup to keep storage and retrieval consistent with adapter expectations.
apps/worker-service/src/backtests/backtests.service.ts
apps/worker-service/src/backtesting/data.service.ts
Enhance flow-node param configuration to support constrained options rendered as inputs driven by metadata.
  • Extend ParamDefinition with an optional options:string[] field and populate it for several node types (e.g., RSI source, comparison operators, crossover direction, logical operators, order side).
  • Update NodeInspector to pass options through to ParamInput for both required and optional params, and to treat options-bearing params as select controls.
  • Update ParamInput to render a styled when options are provided, falling back to the existing boolean/button/text input behavior otherwise, and to use PARAM_VALUE_LABELS for display labels.
packages/types/src/flow.ts
apps/web/src/components/flows/node-inspector.tsx

Possibly linked issues

  • #fix: 백테스트 과거 날짜 범위 지원 불가 (최근 200캔들만 조회): PR이 fetchHistoricalCandles를 기간 페이지네이션 방식으로 수정해 이슈의 과거 백테스트 캔들 조회 버그를 해결한다.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 issues, and left some high level feedback:

  • The parseIntervalMs helper is duplicated in each exchange adapter; consider extracting this to a shared utility (or the common types layer) to keep the interval mapping consistent and avoid drift between adapters.
  • Each getCandlesByRange implementation hard-codes MAX_PAGES = 100, which can silently truncate very long ranges; it may be safer to derive the max pages from the requested time span or surface an explicit error/Log when the page cap is hit while still within the requested range.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `parseIntervalMs` helper is duplicated in each exchange adapter; consider extracting this to a shared utility (or the common types layer) to keep the interval mapping consistent and avoid drift between adapters.
- Each `getCandlesByRange` implementation hard-codes `MAX_PAGES = 100`, which can silently truncate very long ranges; it may be safer to derive the max pages from the requested time span or surface an explicit error/Log when the page cap is hit while still within the requested range.

## Individual Comments

### Comment 1
<location path="packages/exchange-adapters/src/bybit/bybit.rest.ts" line_range="243-244" />
<code_context>
+      '4h': '240',
+      '1d': 'D',
+    };
+    const bybitInterval = INTERVAL_MAP[interval] || '1';
+    const intervalMs = parseIntervalMs(interval);
+    const PAGE_LIMIT = 1000;
+    const allCandles: Candle[] = [];
</code_context>
<issue_to_address>
**issue (bug_risk):** Bybit candle interval handling has the same silent fallback-to-1m behavior as Upbit.

An unsupported `interval` currently falls back to Bybit `'1'` with `intervalMs` as 1 minute, which can silently distort backtest data. Consider validating `interval` against `INTERVAL_MAP` and throwing for unsupported values instead of defaulting.
</issue_to_address>

### Comment 2
<location path="apps/web/src/components/flows/node-inspector.tsx" line_range="49-64" />
<code_context>
   onChange: (val: unknown) => void;
+  options?: string[];
 }) {
+  if (options && options.length > 0) {
+    return (
+      <select
+        value={String(value)}
+        onChange={(e) => onChange(e.target.value)}
+        className="rounded border border-border bg-background px-2 py-1 text-xs text-foreground outline-none focus:border-primary"
+      >
+        {options.map((opt) => (
+          <option key={opt} value={opt}>
+            {PARAM_VALUE_LABELS[opt] ?? opt}
+          </option>
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Handling values not present in `options` would make the select more robust to future config changes.

If the stored config has a value not in `options`, the `<select>` will get a `value` with no matching `<option>`, which triggers React warnings and can confuse users. Consider checking `!options.includes(String(value))` and either falling back to a safe default or rendering an explicit “unknown value” option to keep the UI consistent.

```suggestion
}) {
  if (options && options.length > 0) {
    const stringValue = String(value);
    const hasMatchingOption = options.includes(stringValue);

    return (
      <select
        value={stringValue}
        onChange={(e) => onChange(e.target.value)}
        className="rounded border border-border bg-background px-2 py-1 text-xs text-foreground outline-none focus:border-primary"
      >
        {!hasMatchingOption && (
          <option value={stringValue} disabled>
            {PARAM_VALUE_LABELS[stringValue] ?? `Unknown value: ${stringValue}`}
          </option>
        )}
        {options.map((opt) => (
          <option key={opt} value={opt}>
            {PARAM_VALUE_LABELS[opt] ?? opt}
          </option>
        ))}
      </select>
    );
  }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +243 to +244
const bybitInterval = INTERVAL_MAP[interval] || '1';
const intervalMs = parseIntervalMs(interval);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Bybit candle interval handling has the same silent fallback-to-1m behavior as Upbit.

An unsupported interval currently falls back to Bybit '1' with intervalMs as 1 minute, which can silently distort backtest data. Consider validating interval against INTERVAL_MAP and throwing for unsupported values instead of defaulting.

Comment on lines 49 to +64
}) {
if (options && options.length > 0) {
return (
<select
value={String(value)}
onChange={(e) => onChange(e.target.value)}
className="rounded border border-border bg-background px-2 py-1 text-xs text-foreground outline-none focus:border-primary"
>
{options.map((opt) => (
<option key={opt} value={opt}>
{PARAM_VALUE_LABELS[opt] ?? opt}
</option>
))}
</select>
);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Handling values not present in options would make the select more robust to future config changes.

If the stored config has a value not in options, the <select> will get a value with no matching <option>, which triggers React warnings and can confuse users. Consider checking !options.includes(String(value)) and either falling back to a safe default or rendering an explicit “unknown value” option to keep the UI consistent.

Suggested change
}) {
if (options && options.length > 0) {
return (
<select
value={String(value)}
onChange={(e) => onChange(e.target.value)}
className="rounded border border-border bg-background px-2 py-1 text-xs text-foreground outline-none focus:border-primary"
>
{options.map((opt) => (
<option key={opt} value={opt}>
{PARAM_VALUE_LABELS[opt] ?? opt}
</option>
))}
</select>
);
}
}) {
if (options && options.length > 0) {
const stringValue = String(value);
const hasMatchingOption = options.includes(stringValue);
return (
<select
value={stringValue}
onChange={(e) => onChange(e.target.value)}
className="rounded border border-border bg-background px-2 py-1 text-xs text-foreground outline-none focus:border-primary"
>
{!hasMatchingOption && (
<option value={stringValue} disabled>
{PARAM_VALUE_LABELS[stringValue] ?? `Unknown value: ${stringValue}`}
</option>
)}
{options.map((opt) => (
<option key={opt} value={opt}>
{PARAM_VALUE_LABELS[opt] ?? opt}
</option>
))}
</select>
);
}

Fixes the backtest failure when using past date ranges. The previous
fetchHistoricalCandles() only fetched the latest 200 candles and
filtered by date, which excluded any historical window older than ~33
days (4h) or ~8 days (1h).

Changes:
- Add getCandlesByRange(symbol, interval, startTime, endTime) to
  IExchangeRest interface
- BinanceRest: forward-paginate via startTime/endTime params (1000/page)
- BybitRest: forward-paginate via start/end params (1000/page)
- UpbitRest: backward-paginate via `to` param (200/page)
- backtests.service.ts: replace simplified filter with getCandlesByRange

Closes #75

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@fray-cloud fray-cloud merged commit ee3c0ec into dev Apr 2, 2026
3 checks passed
@fray-cloud fray-cloud deleted the fix/#75 branch April 2, 2026 01:17
fray-cloud added a commit that referenced this pull request Apr 2, 2026
* feat(web): implement P2 sprint — strategy status badge, notification feed RT, tooltip + terminology

- Strategy card: add live status badge (대기/신호감지/주문실행/리스크차단/오류) and realized PnL
  using new useStrategyRuntime hook (polls last log + performance API at 30s/60s)
- Notification feed: pipe WebSocket notification:received events to feed store so
  the bell panel populates in real time (previously only toasts were shown)
- Tooltip component: new /ui/tooltip.tsx — CSS-only hover tooltip, no new deps
- Terminology: en.json updated with plain-language labels (Signal-only, Auto-execute,
  Simulation, Live Trading) + tooltip copy; strategy card now renders these via i18n
  with cursor-help tooltips on mode/tradingMode badges

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* docs: update CONTRIBUTING.md with GitHub Issue sync and PR authorship rules

- Add GitHub Issue/Milestone sync rules per board direction (PRO-56)
- Clarify that feature PRs must be opened by developers, not team leads
- Document Paperclip Approval flow for dev → main release PRs

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: commit missing indicator strategies, Candle migration, notification-feed, and .gitignore update (PRO-69)

These files were present on disk but never tracked — discovered during feat/#53 branch cleanup.

- apps/worker-service: add combination, multi-timeframe, and trend-regime indicator strategies with tests
- packages/database/prisma/migrations/20260401000000_add_candle_table: add migration for Candle table (pairs with existing schema.prisma Candle model fix)
- apps/web: add notification-feed component and use-notification-feed-store (referenced in feat(web) P2 sprint commit but not staged)
- .gitignore: broaden tmp/ rule from tmp/test-results/ to tmp/ to suppress screenshot artifacts

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(worker-service): add CandleOHLCV/MultiTimeframeData types and register combination/trend-regime strategies (PRO-72)

- strategy.interface.ts: add CandleOHLCV and MultiTimeframeData types
- ITradingStrategy.evaluate(): add optional candles? and multiTimeframe? params
- strategies.service.ts: register CombinationStrategy and TrendRegimeStrategy in strategyMap

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(worker-service): multi-TF service layer — register MultiTimeframeStrategy + HTF candle fetch (PRO-73)

- Register MultiTimeframeStrategy in strategyMap
- Add getCandleOHLCV() to fetch full OHLCV data (needed for volume/ATR)
- evaluateStrategy fetches htf1/htf2 close prices and primary OHLCV for multi-timeframe type
- HTF intervals configurable via strategy.config.htf1Interval/htf2Interval (defaults: 4h/1d)
- Redis caching reused for HTF candles using existing CANDLE_CACHE_TTL (4h→1800s, 1d→3600s)
- DB migration (add_candle_table) already present in feat/#48

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(test): fix strategy-card test — add missing mocks and getByText ambiguity

- Add vi.mock for next-intl useTranslations (missing context error)
- Add vi.mock for useStrategyRuntime with correct StrategyRuntime shape (realizedPnl: null)
- Add vi.mock for @/components/icons to avoid icon rendering issues
- Fix 'displays exchange/symbol' assertion to use getAllByText (multiple matches)

All 49 web unit tests now pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): improve flow node port UX — type colors, tooltips, section labels (PRO-80)

- Port handles colored by data type (Candle[], number, boolean, OrderResult, etc.)
- Required ports shown as solid filled; optional ports as hollow with colored border
- Input/output ports rendered as labeled rows inside node body ('입력' / '출력' sections)
- Hover tooltip on each port showing name, Korean type label, required/optional status
- Connection guide: valid target handles pulse green during drag; connection line styled as dashed indigo
- Refactored BaseNode to row-based port layout so handles align visually with port labels

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat: 플로우 UI 파라미터 한글화 구현 (PRO-81)

PRO-78 용어집 기반으로 플로우 UI 전체 텍스트를 트레이더 친화적 한글로 전환

- NODE_TYPE_REGISTRY 노드 레이블 한글화 (RSI 지표, MACD 지표, 볼린저 밴드 등)
- base-node.tsx: 포트 이름 및 파라미터 키 한글 표시 (PORT_NAME_LABELS, PARAM_LABELS)
- node-inspector.tsx: 파라미터 레이블/값 한글 번역 적용 (PARAM_LABELS, PARAM_VALUE_LABELS)
- 기본 알림 메시지 한글화 ('신호 발생!')

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 플로우 UI 조건 노드 도움말 및 빈 캔버스 가이드 추가 (PRO-82)

- 각 노드 헤더에 ? 버튼 추가 — 클릭 시 노드 설명, 파라미터 힌트, 사용 예시 팝오버 표시
- 필수 입력 포트가 연결되지 않으면 ⚠ 경고 배지 및 빨간 포트 강조 표시
- 빈 캔버스에 시작 가이드 오버레이 — 노드 유형별 단계 안내 표시
- node-help-data.ts: 모든 주요 노드(RSI, MACD, 볼린저밴드, 크로스오버 등) 한국어 설명 추가

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 플로우 생성 기본 템플릿 UI 구현 (PRO-83)

- flow-templates.ts: 4개 전략 템플릿 정의 (MACD 골든크로스, RSI 과매수/과매도, EMA 골든크로스, RSI+MACD 복합)
- TemplatePickerModal: 템플릿 선택 모달 (빈 플로우 + 4개 템플릿 카드)
- FlowsPage: 새 플로우 버튼 클릭 시 템플릿 피커 표시, 선택한 템플릿의 노드/엣지를 자동 배치

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 플로우 노드 선택적 파라미터 토글 UI 추가 (PRO-84)

- ParamDefinition 타입 추가 및 NodeTypeInfo에 params 필드 추가
- NODE_TYPE_REGISTRY 각 노드에 필수/선택적 파라미터 구분 정의
  - RSI: period(필수), source(선택)
  - MACD: fastPeriod/slowPeriod(필수), signalPeriod(선택)
  - Bollinger: period(필수), stdDev(선택)
  - alert: message(선택)
- getRequiredConfig 헬퍼 함수 추가: 새 노드 생성 시 필수 파라미터만 초기화
- node-inspector: 선택적 파라미터에 체크박스 토글 UI 추가
  - 활성화: 입력 필드 표시 및 사용자 값 수정 가능
  - 비활성화: 기본값 회색 처리로 표시, config에서 제거
- use-flow-store: updateNodeConfig에서 undefined 값 처리로 파라미터 제거 지원
- node-palette/flow-canvas: 신규 노드 생성 시 선택적 파라미터 초기 비활성화

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 볼린저 밴드 돌파 전략 템플릿 추가 (PRO-88)

PRO-79 설계 문서의 템플릿 3번(볼린저 밴드 돌파 전략)을 flow-templates.ts에 추가.
Trading Lead PR 리뷰(PRO-87)에서 지적된 누락 항목 보완.

- 캔들스트림 → EMA(1, 종가) + 볼린저밴드(BB 20, 2) 각각 연결
- 종가 crosses_above BB 상단 → 매수(10%, stopLoss 3%, takeProfit 5%)
- 종가 crosses_below BB 하단 → 매도(100%)

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
fray-cloud added a commit that referenced this pull request Apr 6, 2026
* feat(backend): Strategy 테이블 order 필드 추가 및 reorder API 구현 (PRO-64)

- Prisma schema에 Strategy.order Int @default(0) 필드 추가
- 마이그레이션: order 컬럼 추가 + createdAt 기준 초기값 backfill
- GET /strategies: createdAt desc → [order asc, createdAt asc] 정렬 변경
- PATCH /strategies/reorder: 순서 일괄 업데이트 엔드포인트 추가
- StrategyResponse DTO에 order 필드 포함
- ReorderStrategiesHandler 단위 테스트 추가

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(portfolio): add asset card view with toggle, fix backtesting CI (PRO-48)

* chore: add gstack skill routing rules to CLAUDE.md

* feat: 비주얼 플로우 전략 빌더 Phase 1 — 엔진 코어

- Prisma: Flow, Backtest, BacktestTrace 모델 추가
- Kafka: FLOW_BACKTEST_REQUESTED/COMPLETED 토픽 + 이벤트
- Types: FlowDefinition, NodeTypeRegistry, Zod-free 검증 스키마
- FlowCompiler: Kahn's algorithm DAG 검증, 위상 정렬, 캔들별 실행
- Nodes: CandleStream, RSI, Threshold, MarketOrder (4개 초기 구현)
- Tests: 33개 전체 통과 (컴파일러 검증 14 + 노드 19)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat: 비주얼 플로우 전략 빌더 Phase 2-4 — API CQRS, React Flow UI, Backtest Engine

Phase 2: API Server CQRS
- Flow CRUD + Toggle + Backtest 요청 9개 REST 엔드포인트
- FlowsKafkaProducer/Consumer — backtest 이벤트 Kafka 연동
- WebSocket backtest:completed 실시간 이벤트

Phase 3: React Flow UI
- @xyflow/react 기반 노드 빌더 (드래그&드롭, 포트 타입 검증)
- 4종 커스텀 노드 (data/indicator/condition/order) 색상별 구분
- NodePalette, NodeInspector, FlowToolbar, TimelineSlider
- Zustand flow store + React Query hooks
- /flows 목록 + /flows/[id] 빌더 페이지

Phase 4: Backtest Engine + Timeline Debugger
- BacktestService — Kafka consumer, 캔들 페치, flow 실행, trace 저장
- 노드 Glow 효과 (녹색=fired, 빨간색=blocked)
- WebSocket 백테스트 완료 리스너 + 자동 trace 로딩

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-001 — replace hardcoded dark colors with CSS variables

All flow builder components now use theme-aware CSS variables
(bg-card, border-border, text-foreground, text-muted-foreground)
instead of hardcoded hex colors (#0f1117, #1a1a24, zinc-*).
This fixes light mode rendering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-003 — add icons for color-blind trace accessibility

Trace state now shows CheckCircle2/XCircle icons alongside green/red
glow effects, so color-blind users can distinguish fired vs blocked.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-009 — add focus-visible states for keyboard navigation

All interactive elements in flow builder now show focus rings
for keyboard navigation (WCAG AA compliance).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* style(design): FINDING-010 — extract hardcoded strings to i18n

All flow builder components now use useTranslations('flows')
instead of hardcoded Korean strings. Added 26 new translation
keys to ko.json and en.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: flows i18n crash — use separate useTranslations for common namespace

t('loading', { ns: 'common' }) is not valid next-intl syntax.
Use useTranslations('common') separately. Also add onNodeDoubleClick
handler to prevent unhandled events.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: node-inspector hooks order crash on node click

useFlowStore(backtestStatus) was called after early return,
violating React hooks rules. Moved all hooks before conditional
return to ensure consistent call order.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix: flow-card crash when backtest summary has error-only shape

Failed backtests store { error: "..." } in summary, not the full
BacktestSummary. Guard winRate/realizedPnl access with null check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(flows): 미구현 노드 6종 추가 및 실시간 실행 파이프라인 구현

- indicator-macd.node: MACD (macd/signal/histogram 출력)
- indicator-bollinger.node: Bollinger Bands (upper/middle/lower 출력)
- indicator-ema.node: EMA 지수 이동평균
- condition-crossover.node: 크로스오버 감지 (state 기반, above/below 방향)
- condition-and-or.node: AND/OR 로직 조합 노드
- order-alert.node: 알림 전용 터미널 노드
- NODE_REGISTRY에 모든 신규 노드 등록
- determineFired 함수 수정: 다중 출력 인디케이터(MACD, Bollinger) 지원
- FlowsService: 활성 Flow 실시간 평가 루프 (StrategiesService 패턴 적용)
  - 30초 폴링으로 활성 Flow 동기화
  - 캔들 인터벌 기반 평가 주기 자동 설정
  - FlowOrderAction → DB Order 생성 → Kafka TRADING_ORDER_REQUESTED 발행
  - RiskService 연동 (stopLoss, dailyMaxLoss, maxPositionSize)
  - 연속 동일 side 주문 중복 방지
- FlowsModule 생성 및 AppModule 등록
- nodes.test.ts: 신규 노드 38개 테스트 케이스 추가 (총 52개 통과)

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(orders): responsive mobile card view with open/closed tabs

Replace horizontal-scroll table on mobile (<md) with a card-based layout.
Each card shows symbol, direction (buy/sell), qty, price, and status.
Two tabs split orders into Open (pending/placed) and Completed.
Desktop table (≥md) is unchanged.

Adds openOrders / closedOrders translation keys to en.json and ko.json.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat: implement advanced portfolio risk management

Add VaR/CVaR, dynamic drawdown limits, ATR-based volatility sizing,
Kelly Criterion position sizing, tail risk monitoring, and
cross-symbol correlation matrix to RiskService.

- Extend RiskConfig and RiskCheckResult with new fields
- Add checkDrawdownLimit: pause strategy after X% drawdown from peak
- Add checkVarAndCVar: historical simulation VaR/CVaR checks
- Add applyAtrSizing: scale down position size when ATR > baseline
- Add applyKellySizing: win-rate-based half-Kelly position sizing
- Add getCorrelationMatrix: Pearson correlation across symbols
- Propagate adjustedQuantity through AutoTradeSaga RiskCheckStep
- 30 unit tests covering all new risk checks (all passing)

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(portfolio): add card view with toggle for asset list (PRO-48)

Add card view to the Portfolio assets section showing coin, current price,
holdings, valuation, and P&L with % color-coded. Toggle between card/table view
with LayoutGrid/LayoutList buttons. Card view is the default.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(worker-service): commit missing backtesting module to fix CI build

BacktestingModule was imported in app.module.ts but the source files were
never committed, causing TS2307 build errors in CI.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(database): add missing Candle model to Prisma schema

Migration 20260401000000_add_candle_table created the table but the model
definition was never added to schema.prisma. Adds Candle model with all
fields matching the migration, fixing TS2339 errors in backtesting/data.service.ts.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(ui): implement P1 UI/UX improvements — price flash, quick order, onboarding, risk dialog

- Add CSS @Keyframes flash-up/flash-down animations to globals.css
- Refactor TickerTable to use TickerRow component with per-row price flash animation on WebSocket updates; add onRowClick prop
- Wire QuickOrderPanel into Markets page — clicking a ticker row opens the slide-in order panel
- Add OnboardingWizard to root layout so new users see the 5-step wizard on first login
- Add real-trading risk confirmation dialog (Dialog component) to QuickOrderPanel and OrderForm when switching from paper to real mode
- Add i18n keys (en/ko) for the confirmation dialog

Closes PRO-38 P1 items

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>

* feat: 전략 카드 DnD 순서 변경 (PRO-50)

Squash merge of feat/#50 — 전략 카드 DnD 순서 변경

- Strategy 테이블 `order` 필드 추가 (PRO-64)
- `@dnd-kit/sortable` 기반 드래그 앤 드롭 순서 변경
- 옵티미스틱 UI + 자동 저장
- Sourcery 리뷰 반영: useMemo 전환, localStorage SSR guard 추가

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* [PRO-53] 대시보드 위젯 DnD 레이아웃 (#71)

* feat(web): mobile card view default for Markets and Portfolio

On screens smaller than md breakpoint, Markets and Portfolio now render
card-based layouts instead of wide tables, eliminating horizontal scroll.
Markets cards support left-swipe to open the QuickOrderPanel for fast
order entry. Desktop table views are unchanged.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): /accounts → /settings?tab=accounts 리다이렉트 (PRO-54)

API 키 관리가 Accounts와 Settings 두 곳에 중복되어 있던 문제를 해결.
/accounts 페이지를 /settings?tab=accounts로 리다이렉트하여 단일 진입점으로 통합.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 전략 카드 DnD 순서 변경 구현 (PRO-50)

@dnd-kit/sortable을 적용하여 드래그 앤 드롭으로 전략 카드 순서를 변경할 수 있도록 구현.
순서 변경 시 PATCH /strategies/reorder API를 호출하여 자동 저장.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 대시보드 위젯 DnD 레이아웃 구현 (PRO-53)

@dnd-kit/sortable 기반 드래그 앤 드롭 위젯 레이아웃 대시보드 구현.
위젯 순서를 localStorage에 저장하여 새로고침 후에도 유지.

- /dashboard 페이지 생성 (포트폴리오/전략/주문/시장 위젯)
- 위젯 그리드(2열) + GripVertical 핸들로 DnD 재배치
- 위젯 순서 localStorage 자동 저장/복원
- NavBar 및 MobileTabBar에 대시보드 링크 추가

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>

* docs(qa): add QA strategy, definition of done, and bug tracking docs (PRO-71) (#72)

Co-authored-by: Paperclip <noreply@paperclip.ing>

* feat: 플로우 기능 UI 개선 (#77) (#74)

* feat(web): implement P2 sprint — strategy status badge, notification feed RT, tooltip + terminology

- Strategy card: add live status badge (대기/신호감지/주문실행/리스크차단/오류) and realized PnL
  using new useStrategyRuntime hook (polls last log + performance API at 30s/60s)
- Notification feed: pipe WebSocket notification:received events to feed store so
  the bell panel populates in real time (previously only toasts were shown)
- Tooltip component: new /ui/tooltip.tsx — CSS-only hover tooltip, no new deps
- Terminology: en.json updated with plain-language labels (Signal-only, Auto-execute,
  Simulation, Live Trading) + tooltip copy; strategy card now renders these via i18n
  with cursor-help tooltips on mode/tradingMode badges

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* docs: update CONTRIBUTING.md with GitHub Issue sync and PR authorship rules

- Add GitHub Issue/Milestone sync rules per board direction (PRO-56)
- Clarify that feature PRs must be opened by developers, not team leads
- Document Paperclip Approval flow for dev → main release PRs

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: commit missing indicator strategies, Candle migration, notification-feed, and .gitignore update (PRO-69)

These files were present on disk but never tracked — discovered during feat/#53 branch cleanup.

- apps/worker-service: add combination, multi-timeframe, and trend-regime indicator strategies with tests
- packages/database/prisma/migrations/20260401000000_add_candle_table: add migration for Candle table (pairs with existing schema.prisma Candle model fix)
- apps/web: add notification-feed component and use-notification-feed-store (referenced in feat(web) P2 sprint commit but not staged)
- .gitignore: broaden tmp/ rule from tmp/test-results/ to tmp/ to suppress screenshot artifacts

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(worker-service): add CandleOHLCV/MultiTimeframeData types and register combination/trend-regime strategies (PRO-72)

- strategy.interface.ts: add CandleOHLCV and MultiTimeframeData types
- ITradingStrategy.evaluate(): add optional candles? and multiTimeframe? params
- strategies.service.ts: register CombinationStrategy and TrendRegimeStrategy in strategyMap

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(worker-service): multi-TF service layer — register MultiTimeframeStrategy + HTF candle fetch (PRO-73)

- Register MultiTimeframeStrategy in strategyMap
- Add getCandleOHLCV() to fetch full OHLCV data (needed for volume/ATR)
- evaluateStrategy fetches htf1/htf2 close prices and primary OHLCV for multi-timeframe type
- HTF intervals configurable via strategy.config.htf1Interval/htf2Interval (defaults: 4h/1d)
- Redis caching reused for HTF candles using existing CANDLE_CACHE_TTL (4h→1800s, 1d→3600s)
- DB migration (add_candle_table) already present in feat/#48

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix(test): fix strategy-card test — add missing mocks and getByText ambiguity

- Add vi.mock for next-intl useTranslations (missing context error)
- Add vi.mock for useStrategyRuntime with correct StrategyRuntime shape (realizedPnl: null)
- Add vi.mock for @/components/icons to avoid icon rendering issues
- Fix 'displays exchange/symbol' assertion to use getAllByText (multiple matches)

All 49 web unit tests now pass.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): improve flow node port UX — type colors, tooltips, section labels (PRO-80)

- Port handles colored by data type (Candle[], number, boolean, OrderResult, etc.)
- Required ports shown as solid filled; optional ports as hollow with colored border
- Input/output ports rendered as labeled rows inside node body ('입력' / '출력' sections)
- Hover tooltip on each port showing name, Korean type label, required/optional status
- Connection guide: valid target handles pulse green during drag; connection line styled as dashed indigo
- Refactored BaseNode to row-based port layout so handles align visually with port labels

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat: 플로우 UI 파라미터 한글화 구현 (PRO-81)

PRO-78 용어집 기반으로 플로우 UI 전체 텍스트를 트레이더 친화적 한글로 전환

- NODE_TYPE_REGISTRY 노드 레이블 한글화 (RSI 지표, MACD 지표, 볼린저 밴드 등)
- base-node.tsx: 포트 이름 및 파라미터 키 한글 표시 (PORT_NAME_LABELS, PARAM_LABELS)
- node-inspector.tsx: 파라미터 레이블/값 한글 번역 적용 (PARAM_LABELS, PARAM_VALUE_LABELS)
- 기본 알림 메시지 한글화 ('신호 발생!')

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 플로우 UI 조건 노드 도움말 및 빈 캔버스 가이드 추가 (PRO-82)

- 각 노드 헤더에 ? 버튼 추가 — 클릭 시 노드 설명, 파라미터 힌트, 사용 예시 팝오버 표시
- 필수 입력 포트가 연결되지 않으면 ⚠ 경고 배지 및 빨간 포트 강조 표시
- 빈 캔버스에 시작 가이드 오버레이 — 노드 유형별 단계 안내 표시
- node-help-data.ts: 모든 주요 노드(RSI, MACD, 볼린저밴드, 크로스오버 등) 한국어 설명 추가

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 플로우 생성 기본 템플릿 UI 구현 (PRO-83)

- flow-templates.ts: 4개 전략 템플릿 정의 (MACD 골든크로스, RSI 과매수/과매도, EMA 골든크로스, RSI+MACD 복합)
- TemplatePickerModal: 템플릿 선택 모달 (빈 플로우 + 4개 템플릿 카드)
- FlowsPage: 새 플로우 버튼 클릭 시 템플릿 피커 표시, 선택한 템플릿의 노드/엣지를 자동 배치

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 플로우 노드 선택적 파라미터 토글 UI 추가 (PRO-84)

- ParamDefinition 타입 추가 및 NodeTypeInfo에 params 필드 추가
- NODE_TYPE_REGISTRY 각 노드에 필수/선택적 파라미터 구분 정의
  - RSI: period(필수), source(선택)
  - MACD: fastPeriod/slowPeriod(필수), signalPeriod(선택)
  - Bollinger: period(필수), stdDev(선택)
  - alert: message(선택)
- getRequiredConfig 헬퍼 함수 추가: 새 노드 생성 시 필수 파라미터만 초기화
- node-inspector: 선택적 파라미터에 체크박스 토글 UI 추가
  - 활성화: 입력 필드 표시 및 사용자 값 수정 가능
  - 비활성화: 기본값 회색 처리로 표시, config에서 제거
- use-flow-store: updateNodeConfig에서 undefined 값 처리로 파라미터 제거 지원
- node-palette/flow-canvas: 신규 노드 생성 시 선택적 파라미터 초기 비활성화

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): 볼린저 밴드 돌파 전략 템플릿 추가 (PRO-88)

PRO-79 설계 문서의 템플릿 3번(볼린저 밴드 돌파 전략)을 flow-templates.ts에 추가.
Trading Lead PR 리뷰(PRO-87)에서 지적된 누락 항목 보완.

- 캔들스트림 → EMA(1, 종가) + 볼린저밴드(BB 20, 2) 각각 연결
- 종가 crosses_above BB 상단 → 매수(10%, stopLoss 3%, takeProfit 5%)
- 종가 crosses_below BB 하단 → 매도(100%)

Co-Authored-By: Paperclip <noreply@paperclip.ing>

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>

* claude: gstack update

* claude: update edit

* feat: apply select box to condition node enum parameters

- Add `options?: string[]` to ParamDefinition type
- Add options arrays to and-or (AND/OR), crossover (above/below),
  threshold operator (</>/<=/>==/==), market-order side (buy/sell),
  and rsi source (close/open/high/low) params
- ParamInput renders <select> dropdown when options are present,
  showing localized labels from PARAM_VALUE_LABELS

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: normalize symbol to uppercase before Binance API calls

Binance API rejects lowercase characters in the symbol parameter
(error -1100). Normalize symbol.toUpperCase() at the entry point of
both backtest paths so DB keys and API calls are always uppercase.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* fix: implement paginated getCandlesByRange for historical backtest data

Fixes the backtest failure when using past date ranges. The previous
fetchHistoricalCandles() only fetched the latest 200 candles and
filtered by date, which excluded any historical window older than ~33
days (4h) or ~8 days (1h).

Changes:
- Add getCandlesByRange(symbol, interval, startTime, endTime) to
  IExchangeRest interface
- BinanceRest: forward-paginate via startTime/endTime params (1000/page)
- BybitRest: forward-paginate via start/end params (1000/page)
- UpbitRest: backward-paginate via `to` param (200/page)
- backtests.service.ts: replace simplified filter with getCandlesByRange

Closes #75

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* docs: add branch cleanup policy to CONTRIBUTING.md (PRO-100)

PR 머지 후 feat/fix 브랜치 즉시 삭제 원칙과 로컬 브랜치 정리 명령어 추가.

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* [feat] #80 Multi-Timeframe 서비스 레이어 및 HTF 캔들/조합/추세 전략 추가

feat/#73 → dev squash merge

- MultiTimeframeStrategyService 등록 및 HTF 캔들 fetch (PRO-73)
- CandleOHLCV / MultiTimeframeData 타입 정의
- CombinationStrategy, TrendRegimeStrategy 등록 (PRO-72)
- 인디케이터 전략 누락 파일 커밋, Candle 마이그레이션 (PRO-69)
- strategy-card 테스트 픽스

Closes #80

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* [fix] #76 백테스트 심볼 슬래시 형식 처리 (BTC/USDT → BTCUSDT 변환)

fix/#76 → dev squash merge

- Binance API 호출 전 심볼에서 슬래시(/) 제거 처리 (BTC/USDT → BTCUSDT)
- 심볼 대문자 정규화 (.toUpperCase())
- 플로우 조건 노드 enum 파라미터에 select box UI 적용

Closes #76

Co-Authored-By: Paperclip <noreply@paperclip.ing>

* feat(web): add demo mode infrastructure (Phase 1)

- NEXT_PUBLIC_DEMO env variable and isDemo utility
- Middleware: skip auth in demo mode, redirect login/signup to /demo
- MSW setup with seed data for orders, strategies, flows, portfolio, activity
- MSW browser worker initialization in providers.tsx
- next.config: remove standalone output in demo mode for Vercel compatibility

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(web): landing page, demo UI, exchange public WebSocket (Phase 2-4)

Phase 2 - Landing page:
- /demo route with Coinbase-style design (Hero, Overview, Architecture, Tech Stack, Features, CTA, Footer)
- Standalone layout without NavBar/MobileTabBar

Phase 3 - Demo UI:
- DemoBanner component: "Demo Mode · Paper Trading Only"
- DemoDisabled wrapper: disabled + tooltip for restricted actions
- NavBar: hide settings/auth in demo, show nav without login
- MobileTabBar: filter demo-hidden items, show without auth
- Root layout: conditional rendering for /demo vs app pages

Phase 4 - Exchange public WebSocket:
- demo-ws.ts: Upbit/Binance public WebSocket direct connection
- Candle data via exchange public REST APIs (no auth)
- Tickers store: demo mode branch for direct exchange WS
- MSW handlers: candle proxy to real exchange APIs, empty tickers endpoint

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): replace Github icon with ExternalLink in demo landing

lucide-react v1.0 removed Github icon.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): resolve demo mode issues - WebSocket, icons, mutation blocking

- Save native WebSocket before MSW patches it (providers.tsx + global.d.ts)
- demo-ws.ts: use preserved NativeWebSocket for exchange connections
- exchange-icon.tsx: skip external logo URLs in demo mode (prevent 403)
- api-client.ts: block POST/PUT/PATCH/DELETE in demo with friendly error
  (auth endpoints excluded). Catches mutations before they reach MSW.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): use client-side AppShell for demo/app layout switching

Replace server-side x-pathname header approach with client-side
usePathname() in AppShell component. Fixes NavBar not showing when
navigating from /demo landing to /markets via Link click.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(web): redesign demo landing — voltagent-inspired dark theme

- Dark background (#050507) + neon green accent (#00d992) glow effects
- Hero: dot pattern, pulsing glow orb, gradient text
- Architecture: React Flow diagram with animated edges (flowing dots),
  custom dark nodes with glow borders, group containers (dashed boxes),
  responsive layout (mobile horizontal scroll)
- Tech Stack: logo marquee scroll with devicons CDN (original colors,
  dark logos inverted for visibility)
- Features: glow-border cards on dot pattern background
- CTA: glow button with green accent
- All sections with IntersectionObserver fade-in
- Removed Code Example section
- AppShell for client-side demo/app layout switching

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): add missing MSW mock data files, fix .gitignore

- /data/ instead of data/ to avoid ignoring nested data dirs
- Add seed data: demo-user, orders, strategies, flows, portfolio, activity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(web): remove style prop from lucide icon in architecture-flow

Lucide-react icons don't accept style prop. Wrap in span instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant