Smart Money Concepts (SMC) is one of the most widely discussed price action frameworks in modern trading. Unlike traditional technical analysis that relies on lagging indicators, SMC focuses on understanding how institutional players – banks, hedge funds, and large market makers – move price. The framework revolves around identifying structural shifts, imbalance zones, and liquidity pools that reveal where smart money is accumulating or distributing positions.

In this tutorial, we’ll break down the core SMC concepts – Break of Structure (BOS), Change of Character (CHoCH), and Fair Value Gaps (FVG) – and walk through a complete Python implementation that detects these patterns on real market data using OpenAlgo and visualizes them with Plotly interactive charts.
What Are SMC Structures?
Answer Capsule
SMC structures map swing highs and lows to identify trend continuation (BOS) and trend reversal (CHoCH), helping traders read the footprint of institutional order flow.
Every trending market creates a series of swing highs and swing lows. SMC structures formalize how these swing points interact to signal either trend continuation or trend reversal.
Swing Highs and Swing Lows
A swing high is a price peak where the bars on either side have lower highs. A swing low is a price trough where the bars on either side have higher lows. These are the building blocks of market structure.
In an uptrend, price creates higher swing highs and higher swing lows. In a downtrend, price creates lower swing highs and lower swing lows. The moment this sequence breaks, the structure shifts.
Break of Structure (BOS)
A BOS confirms the existing trend direction. It occurs when price breaks past the most recent swing point in the direction of the current trend.
Bullish BOS: In an uptrend, when price breaks above the most recent swing high, it confirms buyers remain in control. The uptrend continues.
Bearish BOS: In a downtrend, when price breaks below the most recent swing low, it confirms sellers remain dominant. The downtrend continues.
Think of BOS as the market saying:
“The current trend is still intact – smart money hasn’t changed its mind.”

Change of Character (CHoCH)
A CHoCH signals a potential trend reversal. It’s the first structural break against the prevailing trend.
Bullish CHoCH: In a downtrend, when price breaks above the most recent swing high, it suggests institutional buyers may be stepping in. The downtrend may be ending.
Bearish CHoCH: In an uptrend, when price breaks below the most recent swing low, it warns that institutional sellers may be taking over. The uptrend may be reversing.
CHoCH is where smart money typically initiates its position changes, making it one of the most important signals in the SMC framework.
The Relationship Between BOS and CHoCH
The classification depends on the current trend direction at the time of the break:
| Current Trend | Break Direction | Signal |
|---|---|---|
| Bullish (uptrend) | Breaks above swing high | BOS (continuation) |
| Bullish (uptrend) | Breaks below swing low | CHoCH (reversal) |
| Bearish (downtrend) | Breaks below swing low | BOS (continuation) |
| Bearish (downtrend) | Breaks above swing high | CHoCH (reversal) |
What Are Fair Value Gaps (FVG)?
Answer Capsule
Fair Value Gaps are three-candle imbalance patterns where aggressive buying or selling creates a price gap between the first and third candles – zones where price is likely to return.
When institutional players execute large orders, they can move price so aggressively that it creates an imbalance – a zone where one side of the market completely overwhelmed the other. This shows up on the chart as a Fair Value Gap.

Bullish FVG
A bullish FVG forms when:
- The high of candle 1 is lower than the low of candle 3
- This means candle 2 moved up so aggressively that there’s a gap between candle 1’s high and candle 3’s low
This gap represents unfilled buy orders. Price often returns to this zone before continuing higher.
Bearish FVG
A bearish FVG forms when:
- The low of candle 1 is higher than the high of candle 3
- Candle 2 dropped so sharply that there’s a gap between candle 1’s low and candle 3’s high
This gap represents unfilled sell orders. Price often returns to this zone before continuing lower.
FVG Mitigation
When price revisits an FVG zone, the gap gets “mitigated” – the imbalance is being filled. There are two outcomes:
- Partial mitigation: Price enters the zone but doesn’t completely fill it. The FVG still holds some significance.
- Full mitigation: Price completely passes through the zone. The imbalance is fully resolved, and the FVG is invalidated.
Traders use unmitigated FVGs as potential entry zones – buying at bullish FVGs in uptrends, or selling at bearish FVGs in downtrends.
Fibonacci Retracement in SMC Context
Answer Capsule
Fibonacci levels drawn on the current structure range identify optimal trade entry (OTE) zones, with the 0.618–0.786 region being the sweet spot where smart money often enters positions.
SMC practitioners overlay Fibonacci retracement levels on the current structure (from the structure high to the structure low) to identify where price might find support or resistance during pullbacks.
The most watched levels in SMC trading are:
- 0.786 – Deep retracement, often the last line of defense before a structure break
- 0.705 – Upper boundary of the OTE zone
- 0.618 – The golden ratio, widely regarded as the most significant retracement level
- 0.5 – The equilibrium or 50% retracement
- 0.382 – Shallow retracement, often respected in strong trends
The 0.618 to 0.786 range is known as the Optimal Trade Entry (OTE) zone in SMC – this is where institutional traders most frequently enter positions during pullbacks.
Python Implementation Overview
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta
from pathlib import Path
import pandas as pd
import plotly.graph_objects as go
from openalgo import api
API_KEY = "openalgo-api-key"
HOST = "http://127.0.0.1:5000"
SYMBOL = "NIFTY"
EXCHANGE = "NSE_INDEX"
INTERVAL = "D"
LOOKBACK_DAYS = 180
# SMC parameters matching Pine Script
STRUCT_LOOKBACK = 10
USE_BODY_BREAK = True
STRUCT_HISTORY = 30
FVG_HISTORY = 20
REDUCE_MITIGATED_FVG = False
# Fibonacci levels: (value, color)
FIBO_LEVELS = [
(0.786, "#64b5f6"),
(0.705, "#f23645"),
(0.618, "#089981"),
(0.5, "#4caf50"),
(0.382, "#81c784"),
]
BOS_COLOR = "silver"
CHOCH_COLOR = "#ffca28"
CURRENT_STRUCT_COLOR = "#2196f3"
@dataclass
class StructureBreak:
start_idx: int
end_idx: int
level: float
label: str
direction: str
@dataclass
class FvgZone:
start_idx: int
end_idx: int
top: float
bottom: float
direction: str
mitigated: bool
@dataclass
class CurrentStructure:
high: float
low: float
high_start_idx: int
low_start_idx: int
direction: int # 0=unknown, 1=bearish, 2=bullish
def prepare_dataframe(df: pd.DataFrame) -> pd.DataFrame:
if df.empty:
raise ValueError("No historical data returned from OpenAlgo.")
data = df.copy()
if "timestamp" in data.columns:
data["timestamp"] = pd.to_datetime(data["timestamp"], errors="coerce")
data = data.set_index("timestamp")
else:
data.index = pd.to_datetime(data.index, errors="coerce")
data = data.sort_index().dropna(subset=["open", "high", "low", "close"])
if data.empty:
raise ValueError("No valid OHLC rows available after cleanup.")
return data
def _get_structure_highest_bar(highs: list[float], ci: int, lookback: int) -> int:
"""Pine Script get_structure_highest_bar. Returns actual bar index."""
eff = min(lookback, ci + 1)
start = ci - eff + 1
max_val, max_offset = -1e18, 0
for j in range(start, ci + 1):
if highs[j] >= max_val:
max_val = highs[j]
max_offset = j - ci
idx = 0
for i in range(lookback):
p0, p1, p2 = ci - i, ci - (i + 1), ci - (i + 2)
if p1 < 0 or p2 < 0:
continue
if highs[p1] > highs[p2] and highs[p0] <= highs[p1]:
offset = -(i + 1)
if offset >= max_offset:
idx = offset
return ci + (idx if idx != 0 else max_offset)
def _get_structure_lowest_bar(lows: list[float], ci: int, lookback: int) -> int:
"""Pine Script get_structure_lowest_bar. Returns actual bar index."""
eff = min(lookback, ci + 1)
start = ci - eff + 1
min_val, min_offset = 1e18, 0
for j in range(start, ci + 1):
if lows[j] <= min_val:
min_val = lows[j]
min_offset = j - ci
idx = 0
for i in range(lookback):
p0, p1, p2 = ci - i, ci - (i + 1), ci - (i + 2)
if p1 < 0 or p2 < 0:
continue
if lows[p1] < lows[p2] and lows[p0] >= lows[p1]:
offset = -(i + 1)
if offset >= min_offset:
idx = offset
return ci + (idx if idx != 0 else min_offset)
def detect_structure_breaks(
data: pd.DataFrame,
) -> tuple[list[StructureBreak], CurrentStructure]:
highs = data["high"].tolist()
lows = data["low"].tolist()
closes = data["close"].tolist()
n = len(data)
structures: list[StructureBreak] = []
s_high = highs[0]
s_low = lows[0]
s_high_idx = 0
s_low_idx = 0
s_dir = 0 # 0=unknown, 1=bearish(low broken), 2=bullish(high broken)
for i in range(1, n):
s_max = _get_structure_highest_bar(highs, i, STRUCT_LOOKBACK)
s_min = _get_structure_lowest_bar(lows, i, STRUCT_LOOKBACK)
bp_hi = closes[i] if USE_BODY_BREAK else highs[i]
bp_lo = closes[i] if USE_BODY_BREAK else lows[i]
# Previous bar break prices for multi-bar confirmation
def _pbp_lo(off):
j = i - off
return (closes[j] if USE_BODY_BREAK else lows[j]) if j >= 0 else None
def _pbp_hi(off):
j = i - off
return (closes[j] if USE_BODY_BREAK else highs[j]) if j >= 0 else None
# --- Check low break (bearish) ---
low_broken = False
if i >= 3:
b1, b2, b3 = _pbp_lo(1), _pbp_lo(2), _pbp_lo(3)
multi = (
bp_lo < s_low
and b1 is not None and b1 >= s_low
and b2 is not None and b2 >= s_low
and b3 is not None and b3 >= s_low
and (i - 1) > s_low_idx
and (i - 2) > s_low_idx
and (i - 3) > s_low_idx
)
cont = s_dir == 2 and bp_lo < s_low
low_broken = multi or cont
else:
low_broken = s_dir == 2 and bp_lo < s_low
# --- Check high break (bullish) — only if low not broken ---
high_broken = False
if not low_broken:
if i >= 3:
b1, b2, b3 = _pbp_hi(1), _pbp_hi(2), _pbp_hi(3)
multi = (
bp_hi > s_high
and b1 is not None and b1 <= s_high
and b2 is not None and b2 <= s_high
and b3 is not None and b3 <= s_high
and (i - 1) > s_high_idx
and (i - 2) > s_high_idx
and (i - 3) > s_high_idx
)
cont = s_dir == 1 and bp_hi > s_high
high_broken = multi or cont
else:
high_broken = s_dir == 1 and bp_hi > s_high
if low_broken:
lbl = "BOS" if s_dir == 1 else "CHoCH"
structures.append(StructureBreak(s_low_idx, i, s_low, lbl, "bearish"))
s_dir = 1
s_high_idx = s_max
s_low_idx = i
s_high = highs[s_max]
s_low = lows[i]
elif high_broken:
lbl = "BOS" if s_dir == 2 else "CHoCH"
structures.append(StructureBreak(s_high_idx, i, s_high, lbl, "bullish"))
s_dir = 2
s_high_idx = i
s_low_idx = s_min
s_high = highs[i]
s_low = lows[s_min]
else:
if highs[i] > s_high and s_dir in (0, 2):
if not USE_BODY_BREAK or not (
i >= 3
and (i - 1) > s_high_idx
and (i - 2) > s_high_idx
and (i - 3) > s_high_idx
):
s_high = highs[i]
s_high_idx = i
elif lows[i] < s_low and s_dir in (0, 1):
if not USE_BODY_BREAK or not (
i >= 3
and (i - 1) > s_low_idx
and (i - 2) > s_low_idx
and (i - 3) > s_low_idx
):
s_low = lows[i]
s_low_idx = i
current = CurrentStructure(s_high, s_low, s_high_idx, s_low_idx, s_dir)
return structures[-STRUCT_HISTORY:], current
def detect_fvg_zones(data: pd.DataFrame) -> list[FvgZone]:
highs = data["high"].tolist()
lows = data["low"].tolist()
n = len(data)
active: list[FvgZone] = []
for i in range(3, n):
# Pine: isBullishFVG = high[3] < low[1]
if highs[i - 3] < lows[i - 1]:
active.append(
FvgZone(i - 2, i, lows[i - 1], highs[i - 3], "bullish", False)
)
if len(active) > FVG_HISTORY + 1:
active.pop(0)
# Pine: isBearishFVG = low[3] > high[1]
if lows[i - 3] > highs[i - 1]:
active.append(
FvgZone(i - 2, i, lows[i - 3], highs[i - 1], "bearish", False)
)
if len(active) > FVG_HISTORY + 1:
active.pop(0)
# Dynamic mitigation (FVGDraw equivalent)
remaining: list[FvgZone] = []
for z in active:
if z.direction == "bullish":
if lows[i] <= z.bottom:
continue
if lows[i] < z.top:
z.mitigated = True
if REDUCE_MITIGATED_FVG:
z.top = lows[i]
else:
if highs[i] >= z.top:
continue
if highs[i] > z.bottom:
z.mitigated = True
if REDUCE_MITIGATED_FVG:
z.bottom = highs[i]
z.end_idx = i
remaining.append(z)
active = remaining
return active
def build_plot(
data: pd.DataFrame,
structures: list[StructureBreak],
fvgs: list[FvgZone],
current: CurrentStructure,
) -> go.Figure:
fig = go.Figure()
fig.add_trace(
go.Candlestick(
x=data.index,
open=data["open"],
high=data["high"],
low=data["low"],
close=data["close"],
name=SYMBOL,
increasing_line_color="#26a69a",
increasing_fillcolor="#26a69a",
decreasing_line_color="#ef5350",
decreasing_fillcolor="#ef5350",
)
)
# BOS / CHoCH lines
for s in structures:
x0 = data.index[s.start_idx]
x1 = data.index[s.end_idx]
xm = data.index[(s.start_idx + s.end_idx) // 2]
color = BOS_COLOR if s.label == "BOS" else CHOCH_COLOR
fig.add_shape(
type="line", x0=x0, x1=x1, y0=s.level, y1=s.level,
line=dict(color=color, width=1, dash="solid"),
)
fig.add_annotation(
x=xm, y=s.level, text=s.label, showarrow=False,
yanchor="bottom" if s.direction == "bullish" else "top",
font=dict(color=color, size=10),
bgcolor="rgba(10,10,10,0.55)",
)
# FVG boxes
for z in fvgs:
x0 = data.index[z.start_idx]
x1 = data.index[z.end_idx]
if z.mitigated:
fill = "rgba(130,130,130,0.24)"
else:
fill = (
"rgba(38,166,154,0.20)"
if z.direction == "bullish"
else "rgba(239,83,80,0.20)"
)
fig.add_shape(
type="rect", x0=x0, x1=x1, y0=z.bottom, y1=z.top,
line=dict(width=0), fillcolor=fill, layer="below",
)
xm = data.index[(z.start_idx + z.end_idx) // 2]
ym = (z.top + z.bottom) / 2
fig.add_annotation(
x=xm, y=ym, text="FVG", showarrow=False,
font=dict(color="white", size=9),
)
# Current structure high/low lines
x_end = data.index[-1]
for x0_idx, level in [
(current.high_start_idx, current.high),
(current.low_start_idx, current.low),
]:
fig.add_shape(
type="line",
x0=data.index[x0_idx], x1=x_end, y0=level, y1=level,
line=dict(color=CURRENT_STRUCT_COLOR, width=1, dash="solid"),
)
# Fibonacci levels on current structure
s_range = abs(current.high - current.low)
for fib_val, fib_color in FIBO_LEVELS:
if current.direction == 1:
price = current.low + s_range * fib_val
fib_x0 = data.index[current.high_start_idx]
elif current.direction == 2:
price = current.high - s_range * fib_val
fib_x0 = data.index[current.low_start_idx]
else:
continue
fig.add_shape(
type="line", x0=fib_x0, x1=x_end, y0=price, y1=price,
line=dict(color=fib_color, width=1, dash="solid"),
)
fig.add_annotation(
x=x_end, y=price,
text=f"{fib_val}({price:.2f})",
showarrow=False, xanchor="left",
font=dict(color=fib_color, size=9),
)
fig.update_layout(
title=f"{SYMBOL} Daily SMC Structure (BOS/CHoCH + FVG)",
template="plotly_dark",
xaxis_title="Date",
yaxis_title="Price",
xaxis_rangeslider_visible=False,
hovermode="x unified",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="left", x=0),
margin=dict(l=20, r=80, t=55, b=20),
)
fig.update_xaxes(showgrid=False)
fig.update_yaxes(gridcolor="#1f2937")
return fig
def main() -> None:
if not API_KEY:
raise ValueError("API_KEY is empty.")
end_date = datetime.now().date()
start_date = end_date - timedelta(days=LOOKBACK_DAYS)
client = api(api_key=API_KEY, host=HOST)
df = client.history(
symbol=SYMBOL,
exchange=EXCHANGE,
interval=INTERVAL,
start_date=start_date.strftime("%Y-%m-%d"),
end_date=end_date.strftime("%Y-%m-%d"),
)
data = prepare_dataframe(df)
structures, current = detect_structure_breaks(data)
fvgs = detect_fvg_zones(data)
fig = build_plot(data, structures, fvgs, current)
out_dir = Path(__file__).resolve().parent
csv_path = out_dir / f"{SYMBOL}_daily_smc.csv"
html_path = out_dir / f"{SYMBOL}_daily_smc_chart.html"
data.to_csv(csv_path)
fig.write_html(html_path, include_plotlyjs="cdn")
print(f"Saved {len(data)} rows to {csv_path}")
print(f"Saved interactive chart to {html_path}")
if __name__ == "__main__":
main()
Answer Capsule
The implementation uses OpenAlgo for market data, processes OHLC bars sequentially to detect swing points and structure breaks, identifies FVG zones with dynamic mitigation tracking, and renders everything as an interactive Plotly chart.
Now let’s look at how all these concepts translate into Python code. The implementation follows this pipeline:
- Fetch data – Pull historical OHLC data from OpenAlgo
- Detect swing points – Find relevant swing highs and lows within a lookback window
- Track structure breaks – Iterate bar-by-bar, maintaining structure state and detecting BOS/CHoCH
- Identify FVG zones – Scan for three-candle imbalance patterns and track mitigation
- Visualize – Build an interactive Plotly chart with all overlays
Configuration and Parameters
Answer Capsule
The script connects to a local OpenAlgo instance for market data and provides tunable parameters for structure lookback, break confirmation mode, and display history limits.
The SMC parameters control how the indicator behaves:
- STRUCT_LOOKBACK = 10
- USE_BODY_BREAK = True
- STRUCT_HISTORY = 30
- FVG_HISTORY = 20
- REDUCE_MITIGATED_FVG = False
USE_BODY_BREAK = True is a key setting. When enabled, the script uses the candle’s close price to confirm structure breaks instead of the wick (high/low). This filters out false signals caused by long wicks that briefly pierce a level without a true break. Most SMC traders prefer body-based confirmation for cleaner signals.
Swing Point Detection – Finding Structure Highs and Lows
Answer Capsule
The swing point functions scan within a lookback window to find pivot-like formations – bars whose high (or low) exceeds both neighbors – preferring true pivot points over raw highest/lowest values.
Finding the “right” swing high or low is more nuanced than simply grabbing the highest or lowest bar. A good swing point should be a genuine pivot – a bar that stands out structurally, not just the bar with the highest numerical value.
The algorithm works in two passes:
Pass 1: Find the absolute highest (or lowest) bar within the lookback window. This is the fallback.
Pass 2: Scan for pivot-like patterns – bars where the value is higher than the bar after it AND higher than or equal to the bar before it. If a valid pivot exists at or beyond the absolute extreme, it’s preferred.
The same logic applies in reverse for finding the lowest bar. The result is a bar index pointing to the most structurally meaningful swing point – which is exactly what we need for tracking structure highs and lows.
Structure Break Detection – The Core Engine
Answer Capsule
The detection engine iterates bar-by-bar maintaining running structure state, uses multi-bar confirmation to validate breaks, and classifies each break as BOS or CHoCH based on the current trend direction.
This is the heart of the SMC analysis. The function maintains a running state – current structure high, structure low, and trend direction – and checks each new bar for potential breaks.
Multi-Bar Confirmation
A valid structure break requires multi-bar confirmation to avoid false signals. For a low break to be confirmed:
- The current bar’s close must be below the structure low
- The previous three bars’ closes must have been AT or ABOVE the structure low
- The previous three bars must have formed AFTER the bar where the structure low was set
This ensures the break is genuine – price actually crossed a level that was holding, rather than continuing a move that was already below it.
There’s also a continuation condition: if the current direction is already established and price breaks in the same direction, no multi-bar confirmation is needed. This handles cases where the trend is accelerating.
State Updates After a Break
When a break occurs, the structure state is completely recalculated:
After a bearish break (low broken):
- Direction flips to bearish
- New structure high is set to the highest swing point in the lookback window
- New structure low is set to the current bar’s low
After a bullish break (high broken):
- Direction flips to bullish
- New structure high is set to the current bar’s high
- New structure low is set to the lowest swing point in the lookback window
When no break occurs, the structure high and low are simply updated if price makes new extremes in the direction of the current trend.
FVG Zone Detection and Dynamic Mitigation
Answer Capsule
FVG detection scans for three-candle gaps where high[3 bars ago] < low[1 bar ago] (bullish) or low[3 bars ago] > high[1 bar ago] (bearish), then tracks each zone’s mitigation status as new bars form.
The FVG detection is more straightforward than structure detection. On each bar, the script checks the three-candle pattern looking backwards, creates new FVG zones when gaps are found, and then checks all existing zones for mitigation.
The Mitigation Loop
For each active FVG zone, the current bar is checked:
Bullish FVG mitigation:
- If the current bar’s low drops below the FVG bottom – full mitigation (zone removed)
- If the current bar’s low enters the FVG zone but doesn’t cross it – partial mitigation (zone grayed out)
- If
REDUCE_MITIGATED_FVGis enabled, the zone’s top boundary shrinks to the current low
Bearish FVG mitigation:
- If the current bar’s high rises above the FVG top – full mitigation (zone removed)
- If the current bar’s high enters the zone – partial mitigation
The end_idx of each surviving zone is extended to the current bar, so FVG boxes stretch rightward on the chart until they’re fully mitigated.
Plotly Visualization
Answer Capsule
The chart layers candlesticks, BOS/CHoCH horizontal lines with labels, semi-transparent FVG rectangles, current structure levels, and color-coded Fibonacci retracement lines – all in a dark theme matching TradingView’s aesthetic.
The visualization uses Plotly’s shape and annotation system to overlay all SMC elements on a standard candlestick chart:
- BOS lines are drawn in silver with “BOS” labels
- CHoCH lines are drawn in yellow (#ffca28) with “CHoCH” labels
- FVG zones are semi-transparent rectangles – green for bullish, red for bearish, gray for mitigated
- Current structure high and low are shown as blue horizontal lines extending to the latest bar
- Fibonacci levels are drawn across the current structure range with price labels
The dark theme (plotly_dark template) gives the chart a professional trading terminal appearance.
How to Run
Answer Capsule
Install three pip packages, configure your OpenAlgo API key, run the script, and open the generated HTML file in your browser for an interactive SMC chart.
Step 1: Install Dependencies
pandas, plotly, openalgo
Step 2: Configure
Open the script and update:
- API_KEY
- HOST
- SYMBOL
- EXCHANGE
- INTERVAL
Step 3: Run
Run the Python file.
Step 4: View
The script generates:
- CSV file with OHLC data
- HTML interactive chart
Reading the Chart Output
Answer Capsule
Silver lines are BOS (trend continuation), yellow lines are CHoCH (reversal warnings), green/red rectangles are unmitigated FVGs (potential entry zones), gray rectangles are mitigated FVGs, and colored horizontal lines are Fibonacci retracement levels.
Customization Tips
Answer Capsule
Adjust lookback period for different timeframes, toggle body vs wick breaks for sensitivity, and modify history counts to control chart density based on your trading style.
Conclusion
Answer Capsule
This Python implementation gives you full programmatic control over SMC analysis – detect BOS/CHoCH breaks, track FVG zones, overlay Fibonacci levels, and visualize everything as interactive charts, all powered by real market data through OpenAlgo.
Smart Money Concepts provides a structured way to read price action through the lens of institutional trading behavior. By implementing BOS, CHoCH, and FVG detection in Python, you gain the flexibility to integrate these signals into automated trading systems, run backtests across multiple symbols and timeframes, and customize the logic beyond what any charting platform allows.
The combination of OpenAlgo for broker-agnostic data access, Python for computation, and Plotly for visualization creates a powerful, self-contained SMC analysis toolkit that runs entirely on your own infrastructure.