# -*- coding: utf-8 -*-
"""Console script for nelson_siegel_svensson."""
import sys
import click
import json
import numpy as np
from matplotlib import pyplot as plt
from .ns import NelsonSiegelCurve
from .nss import NelsonSiegelSvenssonCurve
from .calibrate import calibrate_ns_ols, calibrate_nss_ols
[docs]class Curve(click.ParamType):
"""Parameter type representing a curve (either Nelson-Siegel
or Nelson-Siegel-Svensson)"""
name = "curve"
[docs] def convert(self, value, param, ctx):
try:
decoded = json.loads(value)
if "beta3" in decoded:
return NelsonSiegelSvenssonCurve(**decoded)
else:
return NelsonSiegelCurve(**decoded)
except Exception as e:
self.fail("{} is not a valid curve: {}".format(value, e), param, ctx)
[docs]class FloatArray(click.ParamType):
"""Parameter type representing an array of floats"""
name = "floats"
[docs] def convert(self, value, param, ctx):
try:
decoded = json.loads(value)
return np.array([float(f) for f in decoded])
except Exception as e:
self.fail(
"{} is not a valid array of floats: {}".format(value, e), param, ctx
)
@click.group(name="nelson_siegel_svensson")
def cli_main(args=None):
"""Commandline interface for nelson_siegel_svensson."""
return 0
@click.command(name="calibrate")
@click.option(
"-t", "--times", type=FloatArray(), required=True, help="time points as JSON array."
)
@click.option(
"-y",
"--values",
type=FloatArray(),
required=True,
help="values corresponding to time points as JSON array.",
)
@click.option(
"--nelson-siegel-svensson/--nelson-siegel",
default=True,
help="whether to calibrate a Nelson-Siegel-Svensson (4 factor)"
+ " or a Nelson-Siegel (3 factor) curve. Defaults to"
+ " the former.",
)
@click.option(
"--initial-tau1",
type=click.FLOAT,
default=2.0,
show_default=True,
help="Initial value of tau1 (or tau for Nelson-Siegel) " + "for optimization.",
)
@click.option(
"--initial-tau2",
type=click.FLOAT,
default=5.0,
show_default=True,
help="Initial value of tau2 (ignored by Nelson-Siegel) " + "for optimization.",
)
def cli_calibrate(times, values, nelson_siegel_svensson, initial_tau1, initial_tau2):
"""Calibrate a curve to the given data points."""
if nelson_siegel_svensson:
curve, status = calibrate_nss_ols(times, values, (initial_tau1, initial_tau2))
else:
curve, status = calibrate_ns_ols(times, values, initial_tau1)
assert status.success
click.echo(json.dumps(vars(curve)))
@click.command(name="evaluate")
@click.option(
"-c",
"--curve",
type=Curve(),
required=True,
help="Parameters for curve as JSON object.",
)
@click.option(
"-t",
"--times",
type=FloatArray(),
required=True,
help="Evaluation times as JSON array.",
)
def cli_evaluate(curve, times):
"""Evaluate a curve at given points."""
click.echo(json.dumps(curve(times).tolist()))
@click.command(name="plot")
@click.option(
"-c",
"--curves",
type=Curve(),
required=True,
multiple=True,
help="Parameters for curve as JSON object; " + "accepts multiple curves.",
)
@click.option(
"-o",
"--output",
type=click.Path(file_okay=True, dir_okay=False, writable=True, resolve_path=True),
required=True,
help="Output file for the plot.",
)
@click.option(
"-f",
"--from-time",
type=click.FLOAT,
default=0.0,
show_default=True,
help="Left time point of the plot.",
)
@click.option(
"-t",
"--to-time",
type=click.FLOAT,
default=30.0,
show_default=True,
help="Right time point of the plot.",
)
def cli_plot(curves, output, from_time, to_time):
"""Plot a curve at given points."""
fig, ax = plt.subplots(nrows=1, ncols=1)
t = np.linspace(from_time, to_time, num=100)
for curve in curves:
y = curve(t)
ax.plot(t, y)
fig.savefig(output)
plt.close(fig)
cli_main.add_command(cli_calibrate)
cli_main.add_command(cli_evaluate)
cli_main.add_command(cli_plot)
if __name__ == "__main__":
sys.exit(cli_main()) # pragma: no cover