Integration Patterns
Real-world patterns for integrating PyneCore into trading systems
Integration Patterns
PyneCore can be embedded into any Python application. This page covers common integration patterns with real-world trading frameworks.
DataFrame Bridge (FreqTrade, Backtrader, etc.)
Many trading frameworks work with pandas DataFrames. The bridge pattern converts between DataFrames and PyneCore’s OHLCV format:
import pandas as pd
from pynecore.core.script_runner import ScriptRunner
from pynecore.core.syminfo import SymInfo
from pynecore.types.ohlcv import OHLCV
def dataframe_to_ohlcv(df: pd.DataFrame) -> list[OHLCV]:
"""Convert a pandas DataFrame to a list of OHLCV objects."""
return [
OHLCV(
timestamp=int(row.Index.timestamp()),
open=float(row.open), high=float(row.high),
low=float(row.low), close=float(row.close),
volume=float(row.volume),
)
for row in df.itertuples()
]
def run_indicator(df, script_path, syminfo, inputs=None):
"""Run a PyneCore indicator, return results as dict of pd.Series."""
runner = ScriptRunner(
script_path=script_path,
ohlcv_iter=dataframe_to_ohlcv(df),
syminfo=syminfo,
inputs=inputs,
)
results = {}
for _candle, plot_data in runner.run_iter():
for key, value in plot_data.items():
results.setdefault(key, []).append(value)
return {
key: pd.Series(values, index=df.index[:len(values)])
for key, values in results.items()
}
Usage:
rsi_data = run_indicator(dataframe, Path("rsi.py"), syminfo)
dataframe["rsi"] = rsi_data["RSI"]
FreqTrade Integration
PyneCore integrates with FreqTrade in two ways:
Pattern 1: Indicators as Data Sources
Use Pine Script indicators for calculations, write entry/exit logic in Python:
class PyneIndicatorStrategy(IStrategy):
def populate_indicators(self, dataframe, metadata):
pair = metadata.get("pair", "BTC/USDT")
rsi = run_indicator(dataframe, Path("scripts/rsi.py"), syminfo)
dataframe["rsi"] = rsi.get("RSI")
return dataframe
def populate_entry_trend(self, dataframe, metadata):
dataframe.loc[dataframe["rsi"] < 30, "enter_long"] = 1
return dataframe
Pattern 2: Strategy Signals
Let a Pine Script strategy generate buy/sell signals, FreqTrade just executes them:
class PyneStrategySignals(IStrategy):
def populate_indicators(self, dataframe, metadata):
_indicators, trades = run_strategy(dataframe, Path("scripts/sma_cross.py"), syminfo)
dataframe["pyne_enter_long"] = 0
for trade in trades:
if trade.size > 0 and trade.entry_bar_index < len(dataframe):
dataframe.iloc[trade.entry_bar_index,
dataframe.columns.get_loc("pyne_enter_long")] = 1
return dataframe
For complete, runnable FreqTrade examples, see pynecore-examples/05-freqtrade-indicators and pynecore-examples/06-freqtrade-strategy.
Live Data Feed
Process bars as they arrive from an exchange:
import ccxt
import time
from pynecore.types.ohlcv import OHLCV
exchange = ccxt.binance({"enableRateLimit": True})
def live_candles(symbol, timeframe, warmup=200):
"""Yield OHLCV bars: historical warmup first, then poll for new bars."""
# Warmup: fetch historical bars
raw = exchange.fetch_ohlcv(symbol, timeframe, limit=warmup)
for bar in raw:
yield OHLCV(
timestamp=bar[0] // 1000,
open=bar[1], high=bar[2], low=bar[3], close=bar[4], volume=bar[5],
)
# Live: poll for new bars
last_ts = raw[-1][0]
while True:
time.sleep(10)
raw = exchange.fetch_ohlcv(symbol, timeframe, since=last_ts, limit=5)
for bar in raw:
if bar[0] > last_ts:
last_ts = bar[0]
yield OHLCV(
timestamp=bar[0] // 1000,
open=bar[1], high=bar[2], low=bar[3], close=bar[4], volume=bar[5],
)
# Use with ScriptRunner
runner = ScriptRunner(
script_path=Path("my_indicator.py"),
ohlcv_iter=live_candles("BTC/USDT", "1h"),
syminfo=syminfo,
)
for candle, plot_data in runner.run_iter():
rsi = plot_data.get("RSI")
if rsi < 30: # NA values return False for all comparisons — no special handling needed
print(f"RSI oversold: {rsi:.2f} at {candle.close}")
For a complete live CCXT example, see pynecore-examples/04-live-ccxt.
Multiple Indicators on Same Data
Run several scripts on the same data efficiently:
from pynecore.core.script_runner import ScriptRunner
data = list(ohlcv_source) # Materialize once if it's a generator
# Run each indicator separately
rsi_runner = ScriptRunner(script_path=Path("rsi.py"), ohlcv_iter=data, syminfo=syminfo)
bb_runner = ScriptRunner(script_path=Path("bollinger.py"), ohlcv_iter=data, syminfo=syminfo)
rsi_values = [plot.get("RSI") for _, plot in rsi_runner.run_iter()]
bb_values = [(plot.get("Upper"), plot.get("Lower")) for _, plot in bb_runner.run_iter()]
Parameter Optimization
Sweep over input combinations to find optimal parameters:
from itertools import product
results = []
for length, confirm in product(range(5, 30, 5), range(1, 4)):
runner = ScriptRunner(
script_path=Path("sma_crossover.py"),
ohlcv_iter=data,
syminfo=syminfo,
inputs={"Length": length, "Confirm bars": confirm},
)
trades = []
for _, _, new_trades in runner.run_iter():
trades.extend(new_trades)
if trades:
pnl = sum(t.profit for t in trades)
results.append({"length": length, "confirm": confirm, "pnl": pnl, "trades": len(trades)})
# Find best combination
best = max(results, key=lambda r: r["pnl"])
print(f"Best: Length={best['length']}, Confirm={best['confirm']} → P&L={best['pnl']:+.2f}")
Performance Tips
Materialize generators: If running multiple scripts on the same data, convert the OHLCV iterator to a list first (
list(reader.read_from(...))) to avoid re-reading from disk.Cache in live systems: When integrating with frameworks that re-call your indicator function on every new bar (like FreqTrade), cache computed values keyed by timestamp. Only run PyneCore on new bars — return cached values for bars you’ve already processed.
Batch processing: PyneCore processes ~16,000 bars/second. For most use cases (hourly/daily data), re-running from scratch is fast enough that caching isn’t necessary.
Complete Examples
For full, runnable examples covering all these patterns, see the pynecore-examples repository.