'"Provides Analysis of candlestick and chart patterns for price movement
Scanned 6/12/2026
Install via CLI
openskills install paulpas/agent-skill-router---
name: technical-price-action-patterns
compatibility: opencode
completeness: 95
content-types:
- code
- guidance
- config
- do-dont
description: '"Provides Analysis of candlestick and chart patterns for price movement
prediction"'
license: MIT
maturity: stable
metadata:
domain: trading
output-format: code
related-skills: fundamentals-trading-plan, technical-cycle-analysis
role: implementation
scope: implementation
triggers: analysis, candlestick, chart, technical price action patterns, technical-price-action-patterns
archetypes:
- tactical
anti_triggers:
- brainstorming
- vague ideation
- no risk management
response_profile:
verbosity: low
directive_strength: high
abstraction_level: operational
version: "1.0.0"
---
**Role:** Identify high-probability price patterns to forecast market direction
**Philosophy:** Price action reflects all market participants' collective sentiment; patterns reveal institutional order flow
## Key Principles
1. **Pattern Recognition**: Candlestick formations signal reversals or continuations
2. **Confirmation Required**: Patterns need volume or follow-through for validity
3. **Timeframe Hierarchy**: Patterns on higher timeframes carry more weight
4. **Risk Management**: Pattern failures must have predefined stop-loss levels
5. **Context Matters**: Patterns in trending markets behave differently than range-bound
## Implementation Guidelines
### Structure
- Core logic: technical_analysis/price_patterns.py
- Helper functions: technical_analysis/pattern_helpers.py
- Tests: tests/test_price_patterns.py
### Patterns to Follow
- Use numpy arrays for efficient pattern matching
- Return pattern confidence scores, not binary signals
- Support multiple timeframe analysis
## Adherence Checklist
Before completing your task, verify:
- [ ] All patterns have minimum 50-sample backtested accuracy
- [ ] Pattern detection runs in under 100ms per candle
- [ ] Volume confirmation is optional but documented
- [ ] Multiple pattern alerts trigger ensemble logic
- [ ] False positive rate is tracked per pattern type
Relative paths in this skill (e.g., scripts/, reference/) are relative to this base directory.
## Python Implementation
```python
import numpy as np
from typing import List, Dict, Tuple
from dataclasses import dataclass
from enum import Enum
class PatternType(Enum):
REVERSAL = "reversal"
CONTINUATION = "continuation"
consolidation = "consolidation"
@dataclass
class PatternResult:
pattern_type: PatternType
name: str
confirmation: float # 0-1 confidence score
trend_context: str
volume_profile: Dict[str, float]
class PricePatternDetector:
"""Detects candlestick and chart patterns across multiple timeframes."""
def __init__(self, lookback: int = 100):
self.lookback = lookback
self.min_strength = 0.6
def detect_all_patterns(
self, candles: np.ndarray, timeframe: str = "1h"
) -> List[PatternResult]:
"""Run all pattern detection algorithms on candle data."""
patterns = []
for pattern_func in [
self._detect_doji,
self._detect_hammer,
self._detect_engulfing,
self._detect_morning_star,
self._detect_head_and_shoulders,
]:
result = pattern_func(candles)
if result and result.confirmation >= self.min_strength:
patterns.append(result)
return patterns
def _detect_doji(self, candles: np.ndarray) -> PatternResult:
"""Detect Doji patterns indicating indecision."""
if len(candles) < 1:
return None
current = candles[-1]
body = abs(current['close'] - current['open'])
wick = current['high'] - current['low']
# Doji: body is very small relative to wick
body_ratio = body / wick if wick > 0 else 1
is_doji = body_ratio < 0.1
return PatternResult(
pattern_type=PatternType.REVERSAL,
name="doji" if is_doji else None,
confirmation=1 - body_ratio if is_doji else 0,
trend_context="neutral",
volume_profile=self._analyze_volume_profile(candles)
)
def _detect_hammer(self, candles: np.ndarray) -> PatternResult:
"""Detect Hammer pattern indicating bullish reversal."""
if len(candles) < 1:
return None
current = candles[-1]
body = abs(current['close'] - current['open'])
wick_upper = current['high'] - max(current['open'], current['close'])
wick_lower = min(current['open'], current['close']) - current['low']
# Hammer: small body, long lower wick, little upper wick
body_ratio = body / (wick_lower + 1e-8)
wick_ratio = wick_upper / (wick_lower + 1e-8)
is_hammer = body_ratio > 0.3 and wick_ratio < 0.5
return PatternResult(
pattern_type=PatternType.REVERSAL,
name="hammer" if is_hammer else None,
confirmation=body_ratio * (1 - wick_ratio) if is_hammer else 0,
trend_context="bullish" if is_hammer else "neutral",
volume_profile=self._analyze_volume_profile(candles)
)
def _detect_engulfing(self, candles: np.ndarray) -> PatternResult:
"""Detect Bullish/Bearish Engulfing patterns."""
if len(candles) < 2:
return None
prev, curr = candles[-2], candles[-1]
prev_body = prev['close'] - prev['open']
curr_body = curr['close'] - curr['open']
# Bullish Engulfing: previous bearish, current bullish and larger
is_bullish = prev_body < 0 and curr_body > 0 and abs(curr_body) > abs(prev_body)
# Bearish Engulfing: previous bullish, current bearish and larger
is_bearish = prev_body > 0 and curr_body < 0 and abs(curr_body) > abs(prev_body)
name = "bullish_engulfing" if is_bullish else "bearish_engulfing" if is_bearish else None
pattern_type = PatternType.REVERSAL if name else None
return PatternResult(
pattern_type=pattern_type,
name=name,
confirmation=abs(curr_body) / (abs(prev_body) + 1e-8) if name else 0,
trend_context="bullish" if is_bullish else "bearish" if is_bearish else "neutral",
volume_profile=self._analyze_volume_profile(candles)
)
def _detect_morning_star(self, candles: np.ndarray) -> PatternResult:
"""Detect Morning Star reversal pattern."""
if len(candles) < 3:
return None
c1, c2, c3 = candles[-3], candles[-2], candles[-1]
# Morning Star: bearish, small body (doji/spinning top), bullish
is_bearish_1 = c1['close'] < c1['open']
is_small_2 = abs(c2['close'] - c2['open']) < (c2['high'] - c2['low']) * 0.3
is_bullish_3 = c3['close'] > c3['open']
gap_up = c2['close'] < c3['open'] # Price gaps up
is_morning_star = is_bearish_1 and is_small_2 and is_bullish_3 and gap_up
return PatternResult(
pattern_type=PatternType.REVERSAL,
name="morning_star" if is_morning_star else None,
confirmation=0.8 if is_morning_star else 0,
trend_context="bullish" if is_morning_star else "neutral",
volume_profile=self._analyze_volume_profile(candles[-3:])
)
def _detect_head_and_shoulders(self, candles: np.ndarray) -> PatternResult:
"""Detect Head and Shoulders reversal pattern."""
if len(candles) < 5:
return None
# Simplified detection: find local maxima
highs = [(i, c['high']) for i, c in enumerate(candles[-5:])]
# Check for H&S structure: L-H-L-H-L
left Shoulder = highs[0][1]
head = max(highs[1][1], highs[3][1])
right_shoulder = highs[4][1]
is_hns = (
left_shoulder < head and
right_shoulder < head and
abs(left_shoulder - right_shoulder) / head < 0.1 # Symmetry
)
return PatternResult(
pattern_type=PatternType.REVERSAL,
name="head_and_shoulders" if is_hns else None,
confirmation=0.7 if is_hns else 0,
trend_context="bearish" if is_hns else "neutral",
volume_profile=self._analyze_volume_profile(candles[-5:])
)
def _analyze_volume_profile(self, candles: np.ndarray) -> Dict[str, float]:
"""Analyze volume characteristics."""
if len(candles) == 0:
return {"avg": 0, "std": 0, "current": 0}
volumes = [c['volume'] for c in candles]
return {
"avg": float(np.mean(volumes)),
"std": float(np.std(volumes)),
"current": float(volumes[-1]) if volumes else 0,
"volatility_ratio": float(np.std(volumes) / (np.mean(volumes) + 1e-8))
}
```
---
---
### Pattern 2: Risk-Managed Trading Logic with Validation
```python
from __future__ import annotations
import logging
from dataclasses import dataclass
from typing import Optional
logger = logging.getLogger(__name__)
@dataclass(frozen=True)
class TradeSignal:
"""Immutable trade signal with all required validation constraints."""
symbol: str
side: str # "buy" or "sell"
price: float
quantity: float
confidence: float # 0.0 to 1.0
reason: str
def validate(self) -> bool:
"""Validate that the trade signal meets all business constraints."""
if self.quantity <= 0:
raise ValueError(f"Quantity must be positive, got {self.quantity}")
if self.price <= 0:
raise ValueError(f"Price must be positive, got {self.price}")
if not 0.0 <= self.confidence <= 1.0:
raise ValueError(f"Confidence must be between 0 and 1, got {self.confidence}")
return True
def generate_trade_signal(
symbol: str,
side: str,
price: float,
quantity: float,
confidence: float,
reason: str,
) -> TradeSignal:
"""Generate a validated trade signal with guard clause checks."""
if side not in ("buy", "sell"):
raise ValueError(f"Invalid side '{side}', must be 'buy' or 'sell'")
signal = TradeSignal(
symbol=symbol,
side=side,
price=price,
quantity=quantity,
confidence=confidence,
reason=reason,
)
signal.validate()
logger.info("Trade signal generated: %s %s %.4f @ %.2f (confidence=%.2f)",
symbol, side, quantity, price, confidence)
return signal
def execute_with_risk_check(signal: TradeSignal, max_position_pct: float = 0.05) -> dict:
"""Execute a trade signal after applying risk management checks."""
adjusted_quantity = signal.quantity
if signal.side == "buy" and signal.quantity > max_position_pct:
logger.warning("Position %s exceeds max %.1f%% — capping to %.4f",
signal.symbol, max_position_pct * 100, max_position_pct)
adjusted_quantity = max_position_pct
return {
"symbol": signal.symbol,
"side": signal.side,
"price": signal.price,
"quantity": adjusted_quantity,
"capped": adjusted_quantity < signal.quantity,
"confidence": signal.confidence,
"status": "submitted",
}
```
## Constraints
### MUST DO
- Implement indicator calculations using rolling windows with explicit lookback periods; never use full-history data for online indicators
- Validate signal generation by confirming alignment across multiple independent indicators before acting on a single signal
- Calculate all price-based indicators (SMA, EMA, RSI) on closing prices unless specifically designed for tick data
- Include proper handling of missing/NaN candles in indicator pipelines — forward-fill only within session boundaries
- Log signal generation with the full context window of indicator values that led to each signal
### MUST NOT DO
- Do not use look-ahead bias: never reference future bars or prices when calculating indicators during backtesting
- Avoid recalculating all indicators from scratch on every tick — maintain running state for efficiency
- Never combine indicators with different timeframes without explicit resampling and clear documentation of the alignment logic
- Do not generate signals based on a single indicator crossover; require confirmation from price action or volume
- Avoid hardcoding parameter values (e.g., RSI period = 14) without testing regime-specific optima
## Live References
> Authoritative documentation links for this skill's domain. The model follows markdown links at load time to resolve external references and inline content.
- [Price Action Trading Guide](https://www.investopedia.com/trading/price-action-trading/)
- [Candlestick Patterns Encyclopedia](https://www.investopedia.com/trading/candlestick-patterns-trading/)
- [Support and Resistance Levels](https://www.investopedia.com/terms/s/support_resistance.asp)
- [Chart Pattern Recognition](https://en.wikipedia.org/wiki/Chart_pattern)
- [Price Action Trading Strategies](https://www.investopedia.com/trading/price-action-trading/)
No comments yet. Be the first to comment!