Core Concepts

This section explains the fundamental concepts and design principles behind openseries.

Architecture Overview

openseries is built around two main classes that inherit from Pydantic’s BaseModel:

  • OpenTimeSeries: Manages individual financial time series

  • OpenFrame: Manages collections of OpenTimeSeries objects

Both classes provide:

  • Type safety through Pydantic validation

  • Immutable data - original data is preserved

  • Consistent API - similar methods across both classes

  • Financial focus - methods designed for financial analysis

The OpenTimeSeries Class

Core Properties

Every OpenTimeSeries has these fundamental properties:

# Create a sample series using openseries simulation
from openseries import ReturnSimulation, ValueType
import datetime as dt

simulation = ReturnSimulation.from_lognormal(
     number_of_sims=1,
     trading_days=100,
     mean_annual_return=0.25,  # ~0.001 daily
     mean_annual_vol=0.32,     # ~0.02 daily
     trading_days_in_year=252,
     seed=42
)

series = OpenTimeSeries.from_df(
     dframe=simulation.to_dataframe(name="Sample Asset", end=dt.date(2023, 12, 31)),
     valuetype=ValueType.RTRN
).to_cumret()  # Convert returns to cumulative prices

# Core properties
print(f"Name: {series.label}")
print(f"Length: {series.length}")
print(f"First date: {series.first_idx}")
print(f"Last date: {series.last_idx}")
print(f"Value type: {series.valuetype}")

Data Immutability

The original data is never modified:

# Original data is preserved
original_dates = series.dates      # List of date strings
original_values = series.values    # List of float values

# Working data is in the tsdf DataFrame
working_data = series.tsdf         # pandas DataFrame

# Transformations modify the original object (method chaining)
series.value_to_ret()    # Modifies original series
print(f"Series length: {series.length}")  # Usually length - 1

Value Types

The ValueType enum identifies what the series represents:

from openseries import ValueType

# Common value types
print(ValueType.PRICE)      # "Price(Close)"
print(ValueType.RTRN)       # "Return(Total)"
print(ValueType.ROLLVOL)    # "Rolling volatility"

# Check series type
print(f"Series type: {series.valuetype}")

# Type changes with transformations
series.value_to_ret()  # Modifies original
print(f"Returns type: {series.valuetype}")

The OpenFrame Class

Managing Multiple Series

OpenFrame manages collections of OpenTimeSeries:

from openseries import OpenFrame

# Create multiple series using openseries simulation
simulation = ReturnSimulation.from_lognormal(
     number_of_sims=3,
     trading_days=100,
     mean_annual_return=0.25,  # ~0.001 daily
     mean_annual_vol=0.32,     # ~0.02 daily
     trading_days_in_year=252,
     seed=42
)

# Create OpenFrame with multiple series from simulation
frame = OpenFrame(
     constituents=[
          OpenTimeSeries.from_df(
                dframe=simulation.to_dataframe(name="Asset", end=dt.date(2023, 12, 31)),
                column_nmbr=serie,
                valuetype=ValueType.RTRN,
          ).to_cumret()  # Convert returns to cumulative prices
          for serie in range(simulation.number_of_sims)
     ]
)

# Frame properties
print(f"Number of series: {frame.item_count}")
print(f"Column names: {frame.columns_lvl_zero}")
print(f"Common length: {frame.length}")

Data Alignment

OpenFrame concatenates series data but does not automatically align them. The library provides explicit methods for alignment that require user choice:

# Series with different date ranges are concatenated (not aligned)
print("Individual series lengths:")
print(frame.lengths_of_items)

print(f"Frame length (concatenated): {frame.length}")

# Explicit alignment methods require user choice:

# 1. Truncate to common date range
frame.trunc_frame()

# 2. Align to business day calendar (modifies original)
frame.align_index_to_local_cdays(countries="US")

# 3. Handle missing values (modifies original)
frame.value_nan_handle(method="fill")

# 4. Merge with explicit join strategy
frame.merge_series(how="inner")
frame.merge_series(how="outer")

Financial Calculations

Return Calculations

openseries uses standard financial formulas:

# Simple returns: (P_t / P_{t-1}) - 1
series.value_to_ret()  # Modifies original

# Log returns: ln(P_t / P_{t-1})
series.value_to_log()  # Modifies original

# Cumulative returns: rebasing to start at 1.0 (modifies original)
series.to_cumret()

Annualization

Metrics are annualized using the actual number of observations per year:

# Automatic calculation of periods per year
print(f"Periods per year: {series.periods_in_a_year:.1f}")

# Annualized return (geometric mean)
annual_return = series.geo_ret
print(f"Annualized return: {annual_return:.2%}")

# Annualized volatility
annual_vol = series.vol
print(f"Annualized volatility: {annual_vol:.2%}")

Risk Metrics

Risk calculations follow industry standards:

# Value at Risk (95% confidence)
var_95 = series.var_down
print(f"95% VaR: {var_95:.2%}")

# Conditional Value at Risk (Expected Shortfall)
cvar_95 = series.cvar_down
print(f"95% CVaR: {cvar_95:.2%}")

# Maximum Drawdown
max_dd = series.max_drawdown
print(f"Maximum Drawdown: {max_dd:.2%}")

# Sortino Ratio (downside deviation)
sortino = series.sortino_ratio
print(f"Sortino Ratio: {sortino:.2f}")

Date Handling

Business Day Calendars

openseries integrates with business day calendars:

# Align to specific country's business days (modifies original)
series.align_index_to_local_cdays(countries="US")

# Multiple countries (intersection of business days) (modifies original)
series.align_index_to_local_cdays(countries=["US", "GB"])

# Custom markets using pandas-market-calendars (modifies original)
series.align_index_to_local_cdays(markets="NYSE")

Resampling

Convert between different frequencies:

# Resample to month-end (modifies original)
series.resample_to_business_period_ends(freq="BME")

# Resample to quarter-end (modifies original)
series.resample_to_business_period_ends(freq="BQE")

# Custom resampling (modifies original)
series.resample(freq="W")

Data Validation

Type Safety

Pydantic ensures data integrity:

# Dates must be valid ISO format strings
# This will fail with a validation error
invalid_series = OpenTimeSeries.from_arrays(
     dates=["invalid-date"],
     values=[100.0]
)

# Values must be numeric
# This will fail with a validation error
invalid_series = OpenTimeSeries.from_arrays(
     dates=["2023-01-01"],
     values=["not a number"]
)

Consistency Checks

The library performs consistency checks:

# Dates and values must have same length
# Mixed value types in OpenFrame are detected
# Date alignment issues are caught

Method Categories

openseries methods fall into several categories:

Properties vs Methods

  • Properties: Return calculated values (e.g., series.vol)

  • Methods: Perform operations or take parameters (e.g., series.vol_func())

# Property - uses full series
volatility = series.vol

# Method - can specify date range
recent_vol = series.vol_func(months_from_last=12)

Transformation Methods

Methods that modify the original object (return self for chaining):

# Data transformations (modify original)
series.value_to_ret()        # Prices to returns
series.to_drawdown_series()  # Drawdown series
series.to_cumret()           # Cumulative returns

# Time transformations (modify original)
series.resample_to_business_period_ends(freq="BME")
series.align_index_to_local_cdays(countries="US")

Methods that return new objects:

# Analysis methods (return new objects)
rolling_vol = series.rolling_vol(observations=30)
rolling_ret = series.rolling_return(observations=30)

Analysis Methods

Methods that return calculated values:

# Rolling calculations
rolling_vol = series.rolling_vol(observations=30)
rolling_corr = frame.rolling_corr(observations=60)

# Statistical analysis
beta = frame.beta()
tracking_error = frame.tracking_error_func()

Export Methods

Methods for saving results:

# File exports
series.to_xlsx("analysis.xlsx")
series.to_json("data.json")

# Visualization
series.plot_series()
series.plot_histogram()

Best Practices

Data Loading

# Prefer from_df for pandas data
series = OpenTimeSeries.from_df(dframe=dataframe['Close'])
series.set_new_label(lvl_zero="Asset")

# Use from_arrays for custom data
series = OpenTimeSeries.from_arrays(dates=date_list, values=value_list)

# Always set meaningful names
series.set_new_label(lvl_zero="Descriptive Name")

Analysis Workflow

# 1. Load and validate data
series = OpenTimeSeries.from_df(dframe=data['Close'])
series.set_new_label(lvl_zero="Asset")

# 2. Basic analysis
metrics = series.all_properties()

# 3. Specific calculations
series.to_drawdown_series()  # Convert to drawdown (modifies original)
rolling_metrics = series.rolling_vol(observations=252)  # Returns DataFrame

# 4. Visualization
series.plot_series()

# 5. Export results
series.to_xlsx(fiilename="analysis.xlsx")

Memory Management

# Original data is preserved - use deepcopy if needed
series_copy = OpenTimeSeries.from_deepcopy(series)

# Large datasets - consider resampling (modifies original)
series.resample_to_business_period_ends(freq="BME")

# Clean up intermediate results
del intermediate_series

Portfolio Construction

OpenFrame provides several built-in weight strategies for portfolio construction:

from openseries.owntypes import MaxDiversificationNaNError, MaxDiversificationNegativeWeightsError

# Available weight strategies
strategies = {
     'eq_weights': 'Equal weights for all assets',
     'inv_vol': 'Inverse volatility weighting (risk parity)',
     'max_div': 'Maximum diversification optimization',
     'min_vol_overweight': 'Minimum volatility overweight strategy'
}

# Example with error handling
# This may fail with MaxDiversificationNaNError or MaxDiversificationNegativeWeightsError
portfolio_df = frame.make_portfolio(name="Max Div", weight_strat="max_div")

Understanding these core concepts will help you use openseries effectively and build more sophisticated financial analysis workflows.