deephaven / deephaven-core

Deephaven Community Core
Other
236 stars 79 forks source link

Python query strings can not use a python `float` as a `double` when calling numba functions #5594

Closed chipkent closed 4 weeks ago

chipkent commented 1 month ago

Python query string can not use a python float as a double in a query string.

First run this code:

""" Setup the risk management example. """

import math
import numpy as np
import numpy.typing as npt
import numba
from datetime import date, datetime, timedelta
from deephaven import time_table, empty_table, merge, dtypes as dht
from deephaven.table import Table

############################################################################################################
# Black-Scholes
#
# Write a Black-Scholes option pricing model in Python using Numba for vectorization.
############################################################################################################

@numba.vectorize(['float64(float64)'])
def norm_cdf(x):
    """ Cumulative distribution function for the standard normal distribution """
    return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0

@numba.vectorize(['float64(float64)'])
def norm_pdf(x):
    """ Probability density function for the standard normal distribution """
    return math.exp(-x**2 / 2.0) / math.sqrt(2.0 * math.pi)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_price(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option price for a call/put """

    if is_stock:
        return s

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    d2 = d1 - vol * np.sqrt(t)

    if is_call:
        return s * norm_cdf(d1) - k * np.exp(-r * t) * norm_cdf(d2)
    else:
        return k * np.exp(-r * t) * norm_cdf(-d2) - s * norm_cdf(-d1)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_delta(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option delta for a call/put """

    if is_stock:
        return 1.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    # d2 = d1 - vol * np.sqrt(T)

    if is_call:
        return norm_cdf(d1)
    else:
        return -norm_cdf(-d1)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean)'])
def black_scholes_gamma(s, k, r, t, vol, is_stock):
    """ Calculates the Black-Scholes option gamma """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    # d2 = d1 - vol * np.sqrt(T)

    return norm_pdf(d1) / (s * vol * np.sqrt(t))

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_theta(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option theta """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    d2 = d1 - vol * np.sqrt(t)

    if is_call:
        return - ((s * norm_pdf(d1) * vol) / (2 * np.sqrt(t))) - r * k * np.exp(-r * t) * norm_cdf(d2)
    else:
        return - ((s * norm_pdf(d1) * vol) / (2 * np.sqrt(t))) + r * k * np.exp(-r * t) * norm_cdf(-d2)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean)'])
def black_scholes_vega(s, k, r, t, vol, is_stock):
    """ Calculates the Black-Scholes option vega """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    return s * np.sqrt(t) * norm_pdf(d1)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_rho(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option rho """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    d2 = d1 - vol * np.sqrt(t)

    if is_call:
        return 0.01 * k * t * np.exp(-r * t) * norm_cdf(d2)
    else:
        return 0.01 * -k * t * np.exp(-r * t) * norm_cdf(-d2)

# ############################################################################################################
# # Underlying price simulation
# #
# # Simulate the price and volatility of a set of underlying securities
# ############################################################################################################
#
# usyms = ["AAPL", "GOOG", "MSFT", "AMZN", "FB", "TSLA", "NVDA", "INTC", "CSCO", "ADBE", "SPY", "QQQ", "DIA", "IWM", "GLD", "SLV", "USO", "UNG", "TLT", "IEF", "LQD", "HYG", "JNK"]
# usyms_array = dht.array(dht.string, usyms)
# last_price = {s: round(np.abs(np.random.normal(100, 30.0)), 2) for s in usyms}
# last_vol = {s: np.abs(np.random.normal(0.4, 0.2))+0.03 for s in usyms}
#
# def gen_sym() -> str:
#     """ Generate a random symbol """
#     return usyms[np.random.randint(0, len(usyms))]
#
# def gen_price(sym: str) -> float:
#     """ Generate a random price for a given symbol """
#     p = last_price[sym]
#     p += (np.random.random()-0.5)
#     p = abs(p)
#     last_price[sym] = p
#     return round(p,2)
#
# def gen_vol(sym: str) -> float:
#     """ Generate a random volatility for a given symbol """
#     v = last_vol[sym]
#     v += (np.random.random()-0.5)*0.01
#     v = abs(v)
#     last_vol[sym] = v
#     return v
#
# _underlying_securities = empty_table(1) \
#     .update(["Type=`STOCK`", "USym = usyms_array"]) \
#     .ungroup() \
#     .update([
#         "Strike = NULL_DOUBLE",
#         "Expiry = (Instant) null",
#         "Parity = (String) null",
#     ])
#
# _underlying_prices = time_table("PT00:00:00.1") \
#     .update([
#         "Type = `STOCK`",
#         "USym = gen_sym()",
#         "Strike = NULL_DOUBLE",
#         "Expiry = (Instant) null",
#         "Parity = (String) null",
#         "UBid = gen_price(USym)",
#         "UAsk = UBid + randomInt(1, 10)*0.01",
#         "VolBid = gen_vol(USym)",
#         "VolAsk = VolBid + randomInt(1, 10)*0.01",
#         "Bid = UBid",
#         "Ask = UAsk",
#     ])

############################################################################################################
# Simulate market and trading data
############################################################################################################

def simulate_market_data(usyms: list[str]) -> tuple[Table, Table, Table, Table]:
    """ Simulate market data for a set of underlying securities and options.

    Args:
        usyms: List of underlying symbols

    Returns:
        Tuple of tables containing the simulated securities, price history, trade history, betas
    """

    ############################################################################################################
    # Underlying price simulation
    #
    # Simulate the price and volatility of a set of underlying securities
    ############################################################################################################

    # noinspection PyUnusedLocal
    usyms_array = dht.array(dht.string, usyms)
    last_price = {s: round(np.abs(np.random.normal(100, 30.0)), 2) for s in usyms}
    last_vol = {s: np.abs(np.random.normal(0.4, 0.2)) + 0.03 for s in usyms}

    # noinspection PyUnusedLocal
    def gen_sym() -> str:
        """ Generate a random symbol """
        return usyms[np.random.randint(0, len(usyms))]

    # noinspection PyUnusedLocal
    def gen_price(sym: str) -> float:
        """ Generate a random price for a given symbol """
        p = last_price[sym]
        p += (np.random.random() - 0.5)
        p = abs(p)
        last_price[sym] = p
        return round(p, 2)

    # noinspection PyUnusedLocal
    def gen_vol(sym: str) -> float:
        """ Generate a random volatility for a given symbol """
        v = last_vol[sym]
        v += (np.random.random() - 0.5) * 0.01
        v = abs(v)
        last_vol[sym] = v
        return v

    _underlying_securities = empty_table(1) \
        .update(["Type=`STOCK`", "USym = usyms_array"]) \
        .ungroup() \
        .update([
            "Strike = NULL_DOUBLE",
            "Expiry = (Instant) null",
            "Parity = (String) null",
        ])

    _underlying_prices = time_table("PT00:00:00.1") \
        .update([
            "Type = `STOCK`",
            "USym = gen_sym()",
            "Strike = NULL_DOUBLE",
            "Expiry = (Instant) null",
            "Parity = (String) null",
            "UBid = gen_price(USym)",
            "UAsk = UBid + randomInt(1, 10)*0.01",
            "VolBid = gen_vol(USym)",
            "VolAsk = VolBid + randomInt(1, 10)*0.01",
            "Bid = UBid",
            "Ask = UAsk",
        ])

    ############################################################################################################
    # Option price simulation
    #
    # Simulate the price of a set of options based on the underlying securities
    ############################################################################################################

    def compute_strikes(spot: float) -> npt.NDArray[np.float64]:
        """ Compute the option strikes from a given underlying opening price """
        ref = round(spot, 0)
        start = ref - 5
        stop = ref + 5
        return np.arange(start, stop, step=1)

    strikes = {s: compute_strikes(p) for s, p in last_price.items()}

    # noinspection PyUnusedLocal
    def get_strikes(sym: str) -> npt.NDArray[np.float64]:
        """ Get the strikes for a given symbol """
        return strikes[sym]

    # noinspection PyUnusedLocal
    expiry_array = dht.array(dht.Instant, [
        datetime.combine(date.today() + timedelta(days=30), datetime.min.time()),
        datetime.combine(date.today() + timedelta(days=60), datetime.min.time()),
    ])

    _option_securities = empty_table(1) \
        .update(["Type=`OPTION`", "USym = usyms_array"]) \
        .ungroup() \
        .update(["Strike = get_strikes(USym)"]) \
        .ungroup() \
        .update(["Expiry = expiry_array"]) \
        .ungroup() \
        .update(["Parity = new String[] {`CALL`, `PUT`}"]) \
        .ungroup() \
        .view(["Type", "USym", "Strike", "Expiry", "Parity"])

    _option_prices = _underlying_prices \
        .view(["Timestamp", "USym", "UBid", "UAsk", "VolBid", "VolAsk"]) \
        .join(_option_securities, "USym") \
        .view(["Timestamp", "Type", "USym", "Strike", "Expiry", "Parity", "UBid", "UAsk", "VolBid", "VolAsk"]) \
        .update([
            "DT = diffYearsAvg(Timestamp, Expiry)",
            "IsStock = Type == `STOCK`",
            "IsCall = Parity == `CALL`",
            "Bid = black_scholes_price(UBid, Strike, rate_risk_free, DT, VolBid, IsCall, IsStock)",
            "Ask = black_scholes_price(UAsk, Strike, rate_risk_free, DT, VolAsk, IsCall, IsStock)",
        ]) \
        .drop_columns(["DT", "IsStock", "IsCall"])

    ############################################################################################################
    # Securities
    #
    # Combine the underlying and option securities into a single table
    ############################################################################################################

    securities = merge([_underlying_securities, _option_securities])

    _underlying_securities = None
    _option_securities = None

    ############################################################################################################
    # Prices
    #
    # Combine the underlying and option prices into a single table
    ############################################################################################################

    price_history = merge([_underlying_prices, _option_prices])

    ############################################################################################################
    # Trade simulation
    #
    # Simulate a series of trades
    ############################################################################################################

    # noinspection PyUnusedLocal
    def get_random_strike(sym: str) -> float:
        """ Get a random strike for a given underlying symbol """
        return np.random.choice(strikes[sym])

    trade_history = time_table("PT00:00:01") \
        .update([
            "Type = random() < 0.3 ? `STOCK` : `OPTION`",
            "USym = usyms_array[randomInt(0, usyms_array.length)]",
            "Strike = Type == `STOCK` ? NULL_DOUBLE : get_random_strike(USym)",
            "Expiry = Type == `STOCK` ? null : _expiry_array[randomInt(0, _expiry_array.length)]",
            "Parity = Type == `STOCK` ? null : random() < 0.5 ? `CALL` : `PUT`",
            "TradeSize = randomInt(-1000, 1000)",
        ]) \
        .aj(price_history, ["USym", "Strike", "Expiry", "Parity", "Timestamp"], ["Bid", "Ask"]) \
        .update(["TradePrice = random() < 0.5 ? Bid : Ask"])

    ############################################################################################################
    # Risk betas
    ############################################################################################################

    betas = empty_table(1) \
        .update(["USym = usyms_array"]) \
        .ungroup() \
        .update(["Beta = random() * 2 - 0.5"])

    return securities, price_history, trade_history, betas

Next run this code:


# from deephaven_server import Server
# _s = Server(port=10000, jvm_args=["-Xmx16g"])
# _s.start()

from deephaven import time_table, updateby as uby
# from setup_risk_management import simulate_market_data, black_scholes_price, black_scholes_delta, black_scholes_gamma, black_scholes_theta, black_scholes_vega, black_scholes_rho

usyms = ["AAPL", "GOOG", "MSFT", "AMZN", "FB", "TSLA", "NVDA", "INTC", "CSCO", "ADBE", "SPY", "QQQ", "DIA", "IWM", "GLD", "SLV", "USO", "UNG", "TLT", "IEF", "LQD", "HYG", "JNK"]
rate_risk_free = 0.05

securities, price_history, trade_history, betas = simulate_market_data(usyms)

price_current = price_history.last_by(["USym", "Strike", "Expiry", "Parity"])

############################################################################################################
# Greeks
#
# Calculate the greeks for the securites
############################################################################################################

greek_history = price_history \
    .snapshot_when(time_table("PT00:00:05").drop_columns("Timestamp")) \
    .update([
        "UMid = (UBid + UAsk) / 2",
        "VolMid = (VolBid + VolAsk) / 2",
        "DT = diffYearsAvg(Timestamp, Expiry)",
        "IsStock = Type == `STOCK`",
        "IsCall = Parity == `CALL`",
        "Theo = black_scholes_price(UMid, Strike, rate_risk_free, DT, VolMid, IsCall, IsStock)",
        "Delta = black_scholes_delta(UBid, Strike, rate_risk_free, DT, VolBid, IsCall, IsStock)",
        "Gamma = black_scholes_gamma(UBid, Strike, rate_risk_free, DT, VolBid, IsStock)",
        "Theta = black_scholes_theta(UBid, Strike, rate_risk_free, DT, VolBid, IsCall, IsStock)",
        "Vega = black_scholes_vega(UBid, Strike, rate_risk_free, DT, VolBid, IsStock)",
        "Rho = black_scholes_rho(UBid, Strike, rate_risk_free, DT, VolBid, IsCall, IsStock)",
        "UMidUp10 = UMid * 1.1",
        "UMidDown10 = UMid * 0.9",
        "Up10 = black_scholes_price(UMidUp10, Strike, rate_risk_free, DT, VolMid, IsCall, IsStock)",
        "Down10 = black_scholes_price(UMidDown10, Strike, rate_risk_free, DT, VolMid, IsCall, IsStock)",
        "JumpUp10 = Up10 - Theo",
        "JumpDown10 = Down10 - Theo",
    ]) \
    .drop_columns(["UMidUp10", "UMidDown10", "Up10", "Down10"])

greek_current = greek_history.last_by(["USym", "Strike", "Expiry", "Parity"])

############################################################################################################
# Portfolio
#
# Calculate the current portfolio and history
############################################################################################################

portfolio_history = trade_history \
    .update_by([uby.cum_sum("Position=TradeSize")], ["USym", "Strike", "Expiry", "Parity"])

portfolio_current = portfolio_history \
    .last_by(["USym", "Strike", "Expiry", "Parity"]) \
    .view(["USym", "Strike", "Expiry", "Parity", "Position"])

############################################################################################################
# Risk
#
# Calculate the risk for the portfolio in different ways
############################################################################################################

risk_all = greek_current \
    .natural_join(portfolio_current, ["USym", "Strike", "Expiry", "Parity"]) \
    .natural_join(betas, "USym") \
    .update([
        "Theo = Theo * Position",
        "DollarDelta = UMid * Delta * Position",
        "BetaDollarDelta = Beta * DollarDelta",
        "GammaPercent = UMid * Gamma * Position",
        "Theta = Theta * Position",
        "VegaPercent = VolMid * Vega * Position",
        "Rho = Rho * Position",
        "JumpUp10 = JumpUp10 * Position",
        "JumpDown10 = JumpDown10 * Position",
    ]) \
    .view([
        "USym", "Strike", "Expiry", "Parity",
        "Theo", "DollarDelta", "BetaDollarDelta", "GammaPercent", "VegaPercent", "Theta", "Rho", "JumpUp10", "JumpDown10"])

risk_ue = risk_all.drop_columns(["Strike", "Parity"]).sum_by(["USym", "Expiry"])

risk_u = risk_ue.drop_columns("Expiry").sum_by("USym")

risk_e = risk_ue.drop_columns("USym").sum_by("Expiry")

risk_net = risk_ue.drop_columns(["USym", "Expiry"]).sum_by()

############################################################################################################
# Trade analysis
#
# Calculate the PnL for the trades with a 10 minute holding period
############################################################################################################

trade_pnl = trade_history \
    .view(["Timestamp", "USym", "Strike", "Expiry", "Parity", "TradeSize", "TradePrice"]) \
    .aj(price_history.update("Timestamp=Timestamp-'PT10m'"), ["USym", "Strike", "Expiry", "Parity", "Timestamp"], ["FutureBid=Bid", "FutureAsk=Ask"]) \
    .update([
        "FutureMid = (FutureBid + FutureAsk) / 2",
        "PriceChange = FutureMid - TradePrice",
        "PnL = TradeSize * PriceChange",
    ])

trade_pnl_by_sym = trade_pnl \
    .view(["USym", "PnL"]) \
    .sum_by("USym")

Error:

ype: <class 'deephaven.dherror.DHError'>
Value: table update operation failed. : black_scholes_price
Traceback (most recent call last):
  File "/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/table.py", line 764, in update
    return Table(j_table=self.j_table.update(*formulas))
RuntimeError: io.deephaven.engine.table.impl.select.FormulaCompilationException: Formula compilation error for: black_scholes_price(UBid, Strike, rate_risk_free, DT, VolBid, IsCall, IsStock)
    at io.deephaven.engine.table.impl.select.DhFormulaColumn.initDef(DhFormulaColumn.java:216)
    at io.deephaven.engine.table.impl.select.SwitchColumn.initDef(SwitchColumn.java:64)
    at io.deephaven.engine.table.impl.select.analyzers.SelectAndViewAnalyzer.create(SelectAndViewAnalyzer.java:124)
    at io.deephaven.engine.table.impl.select.analyzers.SelectAndViewAnalyzer.create(SelectAndViewAnalyzer.java:73)
    at io.deephaven.engine.table.impl.QueryTable.lambda$selectOrUpdate$32(QueryTable.java:1518)
    at io.deephaven.engine.table.impl.perf.QueryPerformanceRecorder.withNugget(QueryPerformanceRecorder.java:369)
    at io.deephaven.engine.table.impl.QueryTable.lambda$selectOrUpdate$33(QueryTable.java:1500)
    at io.deephaven.engine.table.impl.QueryTable.memoizeResult(QueryTable.java:3639)
    at io.deephaven.engine.table.impl.QueryTable.selectOrUpdate(QueryTable.java:1499)
    at io.deephaven.engine.table.impl.QueryTable.update(QueryTable.java:1477)
    at io.deephaven.engine.table.impl.QueryTable.update(QueryTable.java:100)
    at io.deephaven.api.TableOperationsDefaults.update(TableOperationsDefaults.java:94)
    at org.jpy.PyLib.executeCode(Native Method)
    at org.jpy.PyObject.executeCode(PyObject.java:138)
    at io.deephaven.engine.util.PythonEvaluatorJpy.evalScript(PythonEvaluatorJpy.java:73)
    at io.deephaven.integrations.python.PythonDeephavenSession.lambda$evaluate$1(PythonDeephavenSession.java:205)
    at io.deephaven.util.locks.FunctionalLock.doLockedInterruptibly(FunctionalLock.java:51)
    at io.deephaven.integrations.python.PythonDeephavenSession.evaluate(PythonDeephavenSession.java:205)
    at io.deephaven.engine.util.AbstractScriptSession.lambda$evaluateScript$0(AbstractScriptSession.java:163)
    at io.deephaven.engine.context.ExecutionContext.lambda$apply$0(ExecutionContext.java:196)
    at io.deephaven.engine.context.ExecutionContext.apply(ExecutionContext.java:207)
    at io.deephaven.engine.context.ExecutionContext.apply(ExecutionContext.java:195)
    at io.deephaven.engine.util.AbstractScriptSession.evaluateScript(AbstractScriptSession.java:163)
    at io.deephaven.engine.util.DelegatingScriptSession.evaluateScript(DelegatingScriptSession.java:72)
    at io.deephaven.engine.util.ScriptSession.evaluateScript(ScriptSession.java:75)
    at io.deephaven.server.console.ConsoleServiceGrpcImpl.lambda$executeCommand$4(ConsoleServiceGrpcImpl.java:191)
    at io.deephaven.server.session.SessionState$ExportBuilder.lambda$submit$2(SessionState.java:1537)
    at io.deephaven.server.session.SessionState$ExportObject.doExport(SessionState.java:995)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at io.deephaven.server.runner.scheduler.SchedulerModule$ThreadFactory.lambda$newThread$0(SchedulerModule.java:100)
    at java.base/java.lang.Thread.run(Thread.java:1583)
caused by io.deephaven.engine.table.impl.lang.QueryLanguageParser$QueryLanguageParseException: 

Having trouble with the following expression:
Full expression           : black_scholes_price(UBid, Strike, rate_risk_free, DT, VolBid, IsCall, IsStock)
Expression having trouble : black_scholes_price.call(UBid, Strike, rate_risk_free, DT, VolBid, IsCall, IsStock)
Exception type            : java.lang.IllegalArgumentException
Exception message         : black_scholes_price: Expected argument (3) to be either one of [double] or their compatible ones, got class java.lang.Double

    at io.deephaven.engine.util.PyCallableWrapperJpyImpl.verifyArguments(PyCallableWrapperJpyImpl.java:331)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.verifyPyCallableArguments(QueryLanguageParser.java:2506)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:2478)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:133)
    at com.github.javaparser.ast.expr.MethodCallExpr.accept(MethodCallExpr.java:116)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:2387)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:133)
    at com.github.javaparser.ast.expr.MethodCallExpr.accept(MethodCallExpr.java:116)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.<init>(QueryLanguageParser.java:293)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.<init>(QueryLanguageParser.java:209)
    at io.deephaven.engine.table.impl.select.codegen.FormulaAnalyzer.parseFormula(FormulaAnalyzer.java:209)
    at io.deephaven.engine.table.impl.select.codegen.FormulaAnalyzer.parseFormula(FormulaAnalyzer.java:88)
    at io.deephaven.engine.table.impl.select.DhFormulaColumn.initDef(DhFormulaColumn.java:196)
    ... 33 more

Line: 766
Namespace: update
File: /opt/deephaven/venv/lib/python3.10/site-packages/deephaven/table.py
Traceback (most recent call last):
  File "<string>", line 12, in <module>
  File "<string>", line 286, in simulate_market_data
  File "/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/table.py", line 766, in update

    at org.jpy.PyLib.executeCode(Native Method)
    at org.jpy.PyObject.executeCode(PyObject.java:138)
    at io.deephaven.engine.util.PythonEvaluatorJpy.evalScript(PythonEvaluatorJpy.java:73)
    at io.deephaven.integrations.python.PythonDeephavenSession.lambda$evaluate$1(PythonDeephavenSession.java:205)
    at io.deephaven.util.locks.FunctionalLock.doLockedInterruptibly(FunctionalLock.java:51)
    at io.deephaven.integrations.python.PythonDeephavenSession.evaluate(PythonDeephavenSession.java:205)
    at io.deephaven.engine.util.AbstractScriptSession.lambda$evaluateScript$0(AbstractScriptSession.java:163)
    at io.deephaven.engine.context.ExecutionContext.lambda$apply$0(ExecutionContext.java:196)
    at io.deephaven.engine.context.ExecutionContext.apply(ExecutionContext.java:207)
    at io.deephaven.engine.context.ExecutionContext.apply(ExecutionContext.java:195)
    at io.deephaven.engine.util.AbstractScriptSession.evaluateScript(AbstractScriptSession.java:163)
    at io.deephaven.engine.util.DelegatingScriptSession.evaluateScript(DelegatingScriptSession.java:72)
    at io.deephaven.engine.util.ScriptSession.evaluateScript(ScriptSession.java:75)
    at io.deephaven.server.console.ConsoleServiceGrpcImpl.lambda$executeCommand$4(ConsoleServiceGrpcImpl.java:191)
    at io.deephaven.server.session.SessionState$ExportBuilder.lambda$submit$2(SessionState.java:1537)
    at io.deephaven.server.session.SessionState$ExportObject.doExport(SessionState.java:995)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at io.deephaven.server.runner.scheduler.SchedulerModule$ThreadFactory.lambda$newThread$0(SchedulerModule.java:100)
    at java.base/java.lang.Thread.run(Thread.java:1583)
chipkent commented 1 month ago

If the first block of code is changed to fix a typo and add casts:

""" Setup the risk management example. """

import math
import numpy as np
import numpy.typing as npt
import numba
from datetime import date, datetime, timedelta
from deephaven import time_table, empty_table, merge, dtypes as dht
from deephaven.table import Table

############################################################################################################
# Black-Scholes
#
# Write a Black-Scholes option pricing model in Python using Numba for vectorization.
############################################################################################################

@numba.vectorize(['float64(float64)'])
def norm_cdf(x):
    """ Cumulative distribution function for the standard normal distribution """
    return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0

@numba.vectorize(['float64(float64)'])
def norm_pdf(x):
    """ Probability density function for the standard normal distribution """
    return math.exp(-x**2 / 2.0) / math.sqrt(2.0 * math.pi)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_price(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option price for a call/put """

    if is_stock:
        return s

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    d2 = d1 - vol * np.sqrt(t)

    if is_call:
        return s * norm_cdf(d1) - k * np.exp(-r * t) * norm_cdf(d2)
    else:
        return k * np.exp(-r * t) * norm_cdf(-d2) - s * norm_cdf(-d1)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_delta(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option delta for a call/put """

    if is_stock:
        return 1.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    # d2 = d1 - vol * np.sqrt(T)

    if is_call:
        return norm_cdf(d1)
    else:
        return -norm_cdf(-d1)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean)'])
def black_scholes_gamma(s, k, r, t, vol, is_stock):
    """ Calculates the Black-Scholes option gamma """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    # d2 = d1 - vol * np.sqrt(T)

    return norm_pdf(d1) / (s * vol * np.sqrt(t))

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_theta(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option theta """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    d2 = d1 - vol * np.sqrt(t)

    if is_call:
        return - ((s * norm_pdf(d1) * vol) / (2 * np.sqrt(t))) - r * k * np.exp(-r * t) * norm_cdf(d2)
    else:
        return - ((s * norm_pdf(d1) * vol) / (2 * np.sqrt(t))) + r * k * np.exp(-r * t) * norm_cdf(-d2)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean)'])
def black_scholes_vega(s, k, r, t, vol, is_stock):
    """ Calculates the Black-Scholes option vega """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    return s * np.sqrt(t) * norm_pdf(d1)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_rho(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option rho """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    d2 = d1 - vol * np.sqrt(t)

    if is_call:
        return 0.01 * k * t * np.exp(-r * t) * norm_cdf(d2)
    else:
        return 0.01 * -k * t * np.exp(-r * t) * norm_cdf(-d2)

############################################################################################################
# Simulate market and trading data
############################################################################################################

# noinspection PyUnusedLocal
def simulate_market_data(usyms: list[str], rate_risk_free: float) -> tuple[Table, Table, Table, Table]:
    """ Simulate market data for a set of underlying securities and options.

    Args:
        usyms: List of underlying symbols
        rate_risk_free: The risk-free rate

    Returns:
        Tuple of tables containing the simulated securities, price history, trade history, betas
    """

    ############################################################################################################
    # Underlying price simulation
    #
    # Simulate the price and volatility of a set of underlying securities
    ############################################################################################################

    # noinspection PyUnusedLocal
    usyms_array = dht.array(dht.string, usyms)
    last_price = {s: round(np.abs(np.random.normal(100, 30.0)), 2) for s in usyms}
    last_vol = {s: np.abs(np.random.normal(0.4, 0.2)) + 0.03 for s in usyms}

    # noinspection PyUnusedLocal
    def gen_sym() -> str:
        """ Generate a random symbol """
        return usyms[np.random.randint(0, len(usyms))]

    # noinspection PyUnusedLocal
    def gen_price(sym: str) -> float:
        """ Generate a random price for a given symbol """
        p = last_price[sym]
        p += (np.random.random() - 0.5)
        p = abs(p)
        last_price[sym] = p
        return round(p, 2)

    # noinspection PyUnusedLocal
    def gen_vol(sym: str) -> float:
        """ Generate a random volatility for a given symbol """
        v = last_vol[sym]
        v += (np.random.random() - 0.5) * 0.01
        v = abs(v)
        last_vol[sym] = v
        return v

    _underlying_securities = empty_table(1) \
        .update(["Type=`STOCK`", "USym = usyms_array"]) \
        .ungroup() \
        .update([
            "Strike = NULL_DOUBLE",
            "Expiry = (Instant) null",
            "Parity = (String) null",
        ])

    _underlying_prices = time_table("PT00:00:00.1") \
        .update([
            "Type = `STOCK`",
            "USym = gen_sym()",
            "Strike = NULL_DOUBLE",
            "Expiry = (Instant) null",
            "Parity = (String) null",
            "UBid = gen_price(USym)",
            "UAsk = UBid + randomInt(1, 10)*0.01",
            "VolBid = gen_vol(USym)",
            "VolAsk = VolBid + randomInt(1, 10)*0.01",
            "Bid = UBid",
            "Ask = UAsk",
        ])

    ############################################################################################################
    # Option price simulation
    #
    # Simulate the price of a set of options based on the underlying securities
    ############################################################################################################

    def compute_strikes(spot: float) -> npt.NDArray[np.float64]:
        """ Compute the option strikes from a given underlying opening price """
        ref = round(spot, 0)
        start = ref - 5
        stop = ref + 5
        return np.arange(start, stop, step=1)

    strikes = {s: compute_strikes(p) for s, p in last_price.items()}

    # noinspection PyUnusedLocal
    def get_strikes(sym: str) -> npt.NDArray[np.float64]:
        """ Get the strikes for a given symbol """
        return strikes[sym]

    # noinspection PyUnusedLocal
    expiry_array = dht.array(dht.Instant, [
        datetime.combine(date.today() + timedelta(days=30), datetime.min.time()),
        datetime.combine(date.today() + timedelta(days=60), datetime.min.time()),
    ])

    _option_securities = empty_table(1) \
        .update(["Type=`OPTION`", "USym = usyms_array"]) \
        .ungroup() \
        .update(["Strike = get_strikes(USym)"]) \
        .ungroup() \
        .update(["Expiry = expiry_array"]) \
        .ungroup() \
        .update(["Parity = new String[] {`CALL`, `PUT`}"]) \
        .ungroup() \
        .view(["Type", "USym", "Strike", "Expiry", "Parity"])

    _option_prices = _underlying_prices \
        .view(["Timestamp", "USym", "UBid", "UAsk", "VolBid", "VolAsk"]) \
        .join(_option_securities, "USym") \
        .view(["Timestamp", "Type", "USym", "Strike", "Expiry", "Parity", "UBid", "UAsk", "VolBid", "VolAsk"]) \
        .update([
            "DT = diffYearsAvg(Timestamp, Expiry)",
            "IsStock = Type == `STOCK`",
            "IsCall = Parity == `CALL`",
            "Bid = black_scholes_price(UBid, Strike, (double) rate_risk_free, DT, VolBid, IsCall, IsStock)",
            "Ask = black_scholes_price(UAsk, Strike, (double) rate_risk_free, DT, VolAsk, IsCall, IsStock)",
        ]) \
        .drop_columns(["DT", "IsStock", "IsCall"])

    ############################################################################################################
    # Securities
    #
    # Combine the underlying and option securities into a single table
    ############################################################################################################

    securities = merge([_underlying_securities, _option_securities])

    _underlying_securities = None
    _option_securities = None

    ############################################################################################################
    # Prices
    #
    # Combine the underlying and option prices into a single table
    ############################################################################################################

    price_history = merge([_underlying_prices, _option_prices])

    ############################################################################################################
    # Trade simulation
    #
    # Simulate a series of trades
    ############################################################################################################

    # noinspection PyUnusedLocal
    def get_random_strike(sym: str) -> float:
        """ Get a random strike for a given underlying symbol """
        return np.random.choice(strikes[sym])

    trade_history = time_table("PT00:00:01") \
        .update([
            "Type = random() < 0.3 ? `STOCK` : `OPTION`",
            "USym = usyms_array[randomInt(0, usyms_array.length)]",
            "Strike = Type == `STOCK` ? NULL_DOUBLE : get_random_strike(USym)",
            "Expiry = Type == `STOCK` ? null : _expiry_array[randomInt(0, _expiry_array.length)]",
            "Parity = Type == `STOCK` ? null : random() < 0.5 ? `CALL` : `PUT`",
            "TradeSize = randomInt(-1000, 1000)",
        ]) \
        .aj(price_history, ["USym", "Strike", "Expiry", "Parity", "Timestamp"], ["Bid", "Ask"]) \
        .update(["TradePrice = random() < 0.5 ? Bid : Ask"])

    ############################################################################################################
    # Risk betas
    ############################################################################################################

    betas = empty_table(1) \
        .update(["USym = usyms_array"]) \
        .ungroup() \
        .update(["Beta = random() * 2 - 0.5"])

    return securities, price_history, trade_history, betas

Then these warnings get spewed. I think they are incorrect for numba.


/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/_udf.py:114: UserWarning:
numpy scalar type <class 'numpy.float64'> is used to annotate parameter '1'. Note that conversion of arguments to numpy scalar types is significantly slower than to Python built-in scalar types such as int, float, bool, etc. If possible, consider using Python built-in scalar types instead.
/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/_udf.py:114: UserWarning:
numpy scalar type <class 'numpy.float64'> is used to annotate parameter '2'. Note that conversion of arguments to numpy scalar types is significantly slower than to Python built-in scalar types such as int, float, bool, etc. If possible, consider using Python built-in scalar types instead.
/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/_udf.py:114: UserWarning:
numpy scalar type <class 'numpy.float64'> is used to annotate parameter '3'. Note that conversion of arguments to numpy scalar types is significantly slower than to Python built-in scalar types such as int, float, bool, etc. If possible, consider using Python built-in scalar types instead.
/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/_udf.py:114: UserWarning:
numpy scalar type <class 'numpy.float64'> is used to annotate parameter '4'. Note that conversion of arguments to numpy scalar types is significantly slower than to Python built-in scalar types such as int, float, bool, etc. If possible, consider using Python built-in scalar types instead.
/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/_udf.py:114: UserWarning:
numpy scalar type <class 'numpy.float64'> is used to annotate parameter '5'. Note that conversion of arguments to numpy scalar types is significantly slower than to Python built-in scalar types such as int, float, bool, etc. If possible, consider using Python built-in scalar types instead.
/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/_udf.py:114: UserWarning:
numpy scalar type <class 'numpy.bool_'> is used to annotate parameter '6'. Note that conversion of arguments to numpy scalar types is significantly slower than to Python built-in scalar types such as int, float, bool, etc. If possible, consider using Python built-in scalar types instead.
/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/_udf.py:114: UserWarning:
numpy scalar type <class 'numpy.bool_'> is used to annotate parameter '7'. Note that conversion of arguments to numpy scalar types is significantly slower than to Python built-in scalar types such as int, float, bool, etc. If possible, consider using Python built-in scalar types instead.
r-Scheduler-Serial-1 | .c.ConsoleServiceGrpcImpl | Error running script: java.lang.RuntimeException: Error in Python interpreter:
chipkent commented 1 month ago

Adding casts just triggers anti-vectorization logic:

Having trouble with the following expression:
Full expression           : black_scholes_price(UBid, Strike, (double) rate_risk_free, DT, VolBid, IsCall, IsStock)
Expression having trouble : black_scholes_price.call(UBid, Strike, doubleCast(rate_risk_free), DT, VolBid, IsCall, IsStock)
Exception type            : io.deephaven.engine.table.impl.lang.QueryLanguageParser$PythonCallVectorizationFailure
Exception message         : Invalid argument at index 2: Python vectorized function arguments can only be columns, variables, and constants: doubleCast(rate_risk_free)

    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.checkVectorizability(QueryLanguageParser.java:2725)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.prepareVectorization(QueryLanguageParser.java:2660)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.tryVectorizePythonCallable(QueryLanguageParser.java:2651)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:2479)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:133)
    at com.github.javaparser.ast.expr.MethodCallExpr.accept(MethodCallExpr.java:116)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:2387)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:133)
    at com.github.javaparser.ast.expr.MethodCallExpr.accept(MethodCallExpr.java:116)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.<init>(QueryLanguageParser.java:293)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.<init>(QueryLanguageParser.java:209)
    at io.deephaven.engine.table.impl.select.codegen.FormulaAnalyzer.parseFormula(FormulaAnalyzer.java:209)
    at io.deephaven.engine.table.impl.select.codegen.FormulaAnalyzer.parseFormula(FormulaAnalyzer.java:88)
    at io.deephaven.engine.table.impl.select.DhFormulaColumn.initDef(DhFormulaColumn.java:196)
    ... 33 more

To make it function, a new column needs to be added.

chipkent commented 1 month ago

There is another weird part to this problem. Here is the example code:

Run this first:

""" Setup the risk management example. """

import math
import numpy as np
import numpy.typing as npt
import numba
from datetime import date, datetime, timedelta
from deephaven import time_table, empty_table, merge, dtypes as dht
from deephaven.table import Table

############################################################################################################
# Black-Scholes
#
# Write a Black-Scholes option pricing model in Python using Numba for vectorization.
############################################################################################################

@numba.vectorize(['float64(float64)'])
def norm_cdf(x):
    """ Cumulative distribution function for the standard normal distribution """
    return (1.0 + math.erf(x / math.sqrt(2.0))) / 2.0

@numba.vectorize(['float64(float64)'])
def norm_pdf(x):
    """ Probability density function for the standard normal distribution """
    return math.exp(-x**2 / 2.0) / math.sqrt(2.0 * math.pi)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_price(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option price for a call/put """

    if is_stock:
        return s

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    d2 = d1 - vol * np.sqrt(t)

    if is_call:
        return s * norm_cdf(d1) - k * np.exp(-r * t) * norm_cdf(d2)
    else:
        return k * np.exp(-r * t) * norm_cdf(-d2) - s * norm_cdf(-d1)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_delta(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option delta for a call/put """

    if is_stock:
        return 1.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    # d2 = d1 - vol * np.sqrt(T)

    if is_call:
        return norm_cdf(d1)
    else:
        return -norm_cdf(-d1)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean)'])
def black_scholes_gamma(s, k, r, t, vol, is_stock):
    """ Calculates the Black-Scholes option gamma """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    # d2 = d1 - vol * np.sqrt(T)

    return norm_pdf(d1) / (s * vol * np.sqrt(t))

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_theta(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option theta """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    d2 = d1 - vol * np.sqrt(t)

    if is_call:
        return - ((s * norm_pdf(d1) * vol) / (2 * np.sqrt(t))) - r * k * np.exp(-r * t) * norm_cdf(d2)
    else:
        return - ((s * norm_pdf(d1) * vol) / (2 * np.sqrt(t))) + r * k * np.exp(-r * t) * norm_cdf(-d2)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean)'])
def black_scholes_vega(s, k, r, t, vol, is_stock):
    """ Calculates the Black-Scholes option vega """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    return s * np.sqrt(t) * norm_pdf(d1)

@numba.vectorize(['float64(float64, float64, float64, float64, float64, boolean, boolean)'])
def black_scholes_rho(s, k, r, t, vol, is_call, is_stock):
    """ Calculates the Black-Scholes option rho """

    if is_stock:
        return 0.0

    d1 = (np.log(s / k) + (r + vol ** 2 / 2) * t) / (vol * np.sqrt(t))
    d2 = d1 - vol * np.sqrt(t)

    if is_call:
        return 0.01 * k * t * np.exp(-r * t) * norm_cdf(d2)
    else:
        return 0.01 * -k * t * np.exp(-r * t) * norm_cdf(-d2)

############################################################################################################
# Simulate market and trading data
############################################################################################################

# noinspection PyUnusedLocal
def simulate_market_data(usyms: list[str], rate_risk_free: float, n_accounts: int = 5) -> tuple[Table, Table, Table, Table]:
    """ Simulate market data for a set of underlying securities and options.

    Args:
        usyms: List of underlying symbols
        rate_risk_free: The risk-free rate
        n_accounts: The number of trading accounts to simulate

    Returns:
        Tuple of tables containing the simulated securities, price history, trade history, betas
    """

    ############################################################################################################
    # Underlying price simulation
    #
    # Simulate the price and volatility of a set of underlying securities
    ############################################################################################################

    # noinspection PyUnusedLocal
    usyms_array = dht.array(dht.string, usyms)
    last_price = {s: round(np.abs(np.random.normal(100, 30.0)), 2) for s in usyms}
    last_vol = {s: np.abs(np.random.normal(0.4, 0.2)) + 0.03 for s in usyms}

    # noinspection PyUnusedLocal
    def gen_sym() -> str:
        """ Generate a random symbol """
        return usyms[np.random.randint(0, len(usyms))]

    # noinspection PyUnusedLocal
    def gen_price(sym: str) -> float:
        """ Generate a random price for a given symbol """
        p = last_price[sym]
        p += (np.random.random() - 0.5)
        p = abs(p)
        last_price[sym] = p
        return round(p, 2)

    # noinspection PyUnusedLocal
    def gen_vol(sym: str) -> float:
        """ Generate a random volatility for a given symbol """
        v = last_vol[sym]
        v += (np.random.random() - 0.5) * 0.01
        v = abs(v)
        last_vol[sym] = v
        return v

    _underlying_securities = empty_table(1) \
        .update(["Type=`STOCK`", "USym = usyms_array"]) \
        .ungroup() \
        .update([
            "Strike = NULL_DOUBLE",
            "Expiry = (Instant) null",
            "Parity = (String) null",
        ])

    _underlying_prices = time_table("PT00:00:00.1") \
        .update([
            "Type = `STOCK`",
            "USym = gen_sym()",
            "Strike = NULL_DOUBLE",
            "Expiry = (Instant) null",
            "Parity = (String) null",
            "UBid = gen_price(USym)",
            "UAsk = UBid + randomInt(1, 10)*0.01",
            "VolBid = gen_vol(USym)",
            "VolAsk = VolBid + randomInt(1, 10)*0.01",
            "Bid = UBid",
            "Ask = UAsk",
        ])

    ############################################################################################################
    # Option price simulation
    #
    # Simulate the price of a set of options based on the underlying securities
    ############################################################################################################

    def compute_strikes(spot: float) -> npt.NDArray[np.float64]:
        """ Compute the option strikes from a given underlying opening price """
        ref = round(spot, 0)
        start = ref - 5
        stop = ref + 5
        return np.arange(start, stop, step=1)

    strikes = {s: compute_strikes(p) for s, p in last_price.items()}

    # noinspection PyUnusedLocal
    def get_strikes(sym: str) -> npt.NDArray[np.float64]:
        """ Get the strikes for a given symbol """
        return strikes[sym]

    # noinspection PyUnusedLocal
    expiry_array = dht.array(dht.Instant, [
        datetime.combine(date.today() + timedelta(days=30), datetime.min.time()),
        datetime.combine(date.today() + timedelta(days=60), datetime.min.time()),
    ])

    _option_securities = empty_table(1) \
        .update(["Type=`OPTION`", "USym = usyms_array"]) \
        .ungroup() \
        .update(["Strike = get_strikes(USym)"]) \
        .ungroup() \
        .update(["Expiry = expiry_array"]) \
        .ungroup() \
        .update(["Parity = new String[] {`CALL`, `PUT`}"]) \
        .ungroup() \
        .view(["Type", "USym", "Strike", "Expiry", "Parity"])

    _option_prices = _underlying_prices \
        .view(["Timestamp", "USym", "UBid", "UAsk", "VolBid", "VolAsk"]) \
        .join(_option_securities, "USym") \
        .view(["Timestamp", "Type", "USym", "Strike", "Expiry", "Parity", "UBid", "UAsk", "VolBid", "VolAsk"]) \
        .update([
            "DT = diffYearsAvg(Timestamp, Expiry)",
            "IsStock = Type == `STOCK`",
            "IsCall = Parity == `CALL`",
            "Rf = (double) rate_risk_free",
            "Bid = black_scholes_price(UBid, Strike, Rf, DT, VolBid, IsCall, IsStock)",
            "Ask = black_scholes_price(UAsk, Strike, Rf, DT, VolAsk, IsCall, IsStock)",
        ]) \
        .drop_columns(["Rf", "DT", "IsStock", "IsCall"])

    ############################################################################################################
    # Securities
    #
    # Combine the underlying and option securities into a single table
    ############################################################################################################

    securities = merge([_underlying_securities, _option_securities])

    _underlying_securities = None
    _option_securities = None

    ############################################################################################################
    # Prices
    #
    # Combine the underlying and option prices into a single table
    ############################################################################################################

    price_history = merge([_underlying_prices, _option_prices])

    ############################################################################################################
    # Trade simulation
    #
    # Simulate a series of trades
    ############################################################################################################

    # noinspection PyUnusedLocal
    def get_random_strike(sym: str) -> float:
        """ Get a random strike for a given underlying symbol """
        return np.random.choice(strikes[sym])

    trade_history = time_table("PT00:00:01") \
        .update([
            "Account = randomInt(0, n_accounts)",
            "Type = random() < 0.3 ? `STOCK` : `OPTION`",
            "USym = usyms_array[randomInt(0, usyms_array.length)]",
            "Strike = Type == `STOCK` ? NULL_DOUBLE : get_random_strike(USym)",
            "Expiry = Type == `STOCK` ? null : expiry_array[randomInt(0, expiry_array.length)]",
            "Parity = Type == `STOCK` ? null : random() < 0.5 ? `CALL` : `PUT`",
            "TradeSize = randomInt(-1000, 1000)",
        ]) \
        .aj(price_history, ["USym", "Strike", "Expiry", "Parity", "Timestamp"], ["Bid", "Ask"]) \
        .update(["TradePrice = random() < 0.5 ? Bid : Ask"])

    ############################################################################################################
    # Risk betas
    ############################################################################################################

    betas = empty_table(1) \
        .update(["USym = usyms_array"]) \
        .ungroup() \
        .update(["Beta = random() * 2 - 0.5"])

    return securities, price_history, trade_history, betas

Run this next:


# from deephaven_server import Server
# _s = Server(port=10000, jvm_args=["-Xmx16g"])
# _s.start()

from deephaven import time_table, updateby as uby
# from setup_risk_management import simulate_market_data, black_scholes_price, black_scholes_delta, black_scholes_gamma, black_scholes_theta, black_scholes_vega, black_scholes_rho

usyms = ["AAPL", "GOOG", "MSFT", "AMZN", "FB", "TSLA", "NVDA", "INTC", "CSCO", "ADBE", "SPY", "QQQ", "DIA", "IWM", "GLD", "SLV", "USO", "UNG", "TLT", "IEF", "LQD", "HYG", "JNK"]
rate_risk_free = 0.05

securities, price_history, trade_history, betas = simulate_market_data(usyms, rate_risk_free)

price_current = price_history.last_by(["USym", "Strike", "Expiry", "Parity"])

############################################################################################################
# Greeks
#
# Calculate the greeks for the securites
############################################################################################################

greek_history = price_history \
    .snapshot_when(time_table("PT00:00:05").drop_columns("Timestamp")) \
    .update([
        "UMid = (UBid + UAsk) / 2",
        "VolMid = (VolBid + VolAsk) / 2",
        "DT = diffYearsAvg(Timestamp, Expiry)",
        "Rf = (double) rate_risk_free",
        "IsStock = Type == `STOCK`",
        "IsCall = Parity == `CALL`",
        "Theo = black_scholes_price(UMid, Strike, Rf, DT, VolMid, IsCall, IsStock)",
        "Delta = black_scholes_delta(UBid, Strike, Rf, DT, VolBid, IsCall, IsStock)",
        "Gamma = black_scholes_gamma(UBid, Strike, Rf, DT, VolBid, IsStock)",
        "Theta = black_scholes_theta(UBid, Strike, Rf, DT, VolBid, IsCall, IsStock)",
        "Vega = black_scholes_vega(UBid, Strike, Rf, DT, VolBid, IsStock)",
        "Rho = black_scholes_rho(UBid, Strike, Rf, DT, VolBid, IsCall, IsStock)",
        "UMidUp10 = UMid * 1.1",
        "UMidDown10 = UMid * 0.9",
        "Up10 = black_scholes_price(UMidUp10, Strike, Rf, DT, VolMid, IsCall, IsStock)",
        "Down10 = black_scholes_price(UMidDown10, Strike, Rf, DT, VolMid, IsCall, IsStock)",
        "JumpUp10 = Up10 - Theo",
        "JumpDown10 = Down10 - Theo",
    ]) \
    .drop_columns(["UMidUp10", "UMidDown10", "Up10", "Down10"])

greek_current = greek_history.last_by(["USym", "Strike", "Expiry", "Parity"])

############################################################################################################
# Portfolio
#
# Calculate the current portfolio and history
############################################################################################################

portfolio_history = trade_history \
    .update_by([uby.cum_sum("Position=TradeSize")], ["Account", "USym", "Strike", "Expiry", "Parity"])

portfolio_current = portfolio_history \
    .last_by(["Account", "USym", "Strike", "Expiry", "Parity"]) \
    .view(["Account", "USym", "Strike", "Expiry", "Parity", "Position"])

############################################################################################################
# Risk
#
# Calculate the risk for the portfolio in different ways
############################################################################################################

risk_all = greek_current \
    .natural_join(portfolio_current, ["USym", "Strike", "Expiry", "Parity"]) \
    .natural_join(betas, "USym") \
    .update([
        "Theo = Theo * Position",
        "DollarDelta = UMid * Delta * Position",
        "BetaDollarDelta = Beta * DollarDelta",
        "GammaPercent = UMid * Gamma * Position",
        "Theta = Theta * Position",
        "VegaPercent = VolMid * Vega * Position",
        "Rho = Rho * Position",
        "JumpUp10 = JumpUp10 * Position",
        "JumpDown10 = JumpDown10 * Position",
    ]) \
    .view([
        "Account",
        "USym",
        "Strike",
        "Expiry",
        "Parity",
        "Theo",
        "DollarDelta",
        "BetaDollarDelta",
        "GammaPercent",
        "VegaPercent",
        "Theta",
        "Rho",
        "JumpUp10",
        "JumpDown10",
    ])

risk_ue = risk_all.drop_columns(["Strike", "Parity"]).sum_by(["Account", "USym", "Expiry"])

risk_u = risk_ue.drop_columns("Expiry").sum_by(["Account", "USym"])

risk_e = risk_ue.drop_columns("USym").sum_by(["Account", "Expiry"])

risk_net = risk_ue.drop_columns(["USym", "Expiry"]).sum_by("Account")

risk_firm = risk_net.drop_columns("Account").sum_by()

############################################################################################################
# Trade analysis
#
# Calculate the PnL for the trades with a 10 minute holding period
############################################################################################################

trade_pnl = trade_history \
    .view(["Timestamp", "USym", "Strike", "Expiry", "Parity", "TradeSize", "TradePrice"]) \
    .aj(price_history.update("Timestamp=Timestamp-'PT10m'"),
        ["USym", "Strike", "Expiry", "Parity", "Timestamp"],
        ["FutureBid=Bid", "FutureAsk=Ask"]) \
    .update([
        "FutureMid = (FutureBid + FutureAsk) / 2",
        "PriceChange = FutureMid - TradePrice",
        "PnL = TradeSize * PriceChange",
    ])

trade_pnl_by_sym = trade_pnl \
    .view(["USym", "PnL"]) \
    .sum_by("USym")

That should run fine. Now if the double cast in Rf = (double) rate_risk_free is removed, it fails.

Value: table update operation failed. : black_scholes_price
Traceback (most recent call last):
  File "/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/table.py", line 764, in update
    return Table(j_table=self.j_table.update(*formulas))
RuntimeError: io.deephaven.engine.table.impl.select.FormulaCompilationException: Formula compilation error for: black_scholes_price(UMid, Strike, Rf, DT, VolMid, IsCall, IsStock)
    at io.deephaven.engine.table.impl.select.DhFormulaColumn.initDef(DhFormulaColumn.java:216)
    at io.deephaven.engine.table.impl.select.SwitchColumn.initDef(SwitchColumn.java:64)
    at io.deephaven.engine.table.impl.select.analyzers.SelectAndViewAnalyzer.create(SelectAndViewAnalyzer.java:124)
    at io.deephaven.engine.table.impl.select.analyzers.SelectAndViewAnalyzer.create(SelectAndViewAnalyzer.java:73)
    at io.deephaven.engine.table.impl.QueryTable.lambda$selectOrUpdate$32(QueryTable.java:1518)
    at io.deephaven.engine.table.impl.perf.QueryPerformanceRecorder.withNugget(QueryPerformanceRecorder.java:369)
    at io.deephaven.engine.table.impl.QueryTable.lambda$selectOrUpdate$33(QueryTable.java:1500)
    at io.deephaven.engine.table.impl.QueryTable.memoizeResult(QueryTable.java:3639)
    at io.deephaven.engine.table.impl.QueryTable.selectOrUpdate(QueryTable.java:1499)
    at io.deephaven.engine.table.impl.QueryTable.update(QueryTable.java:1477)
    at io.deephaven.engine.table.impl.QueryTable.update(QueryTable.java:100)
    at io.deephaven.api.TableOperationsDefaults.update(TableOperationsDefaults.java:94)
    at org.jpy.PyLib.executeCode(Native Method)
    at org.jpy.PyObject.executeCode(PyObject.java:138)
    at io.deephaven.engine.util.PythonEvaluatorJpy.evalScript(PythonEvaluatorJpy.java:73)
    at io.deephaven.integrations.python.PythonDeephavenSession.lambda$evaluate$1(PythonDeephavenSession.java:205)
    at io.deephaven.util.locks.FunctionalLock.doLockedInterruptibly(FunctionalLock.java:51)
    at io.deephaven.integrations.python.PythonDeephavenSession.evaluate(PythonDeephavenSession.java:205)
    at io.deephaven.engine.util.AbstractScriptSession.lambda$evaluateScript$0(AbstractScriptSession.java:163)
    at io.deephaven.engine.context.ExecutionContext.lambda$apply$0(ExecutionContext.java:196)
    at io.deephaven.engine.context.ExecutionContext.apply(ExecutionContext.java:207)
    at io.deephaven.engine.context.ExecutionContext.apply(ExecutionContext.java:195)
    at io.deephaven.engine.util.AbstractScriptSession.evaluateScript(AbstractScriptSession.java:163)
    at io.deephaven.engine.util.DelegatingScriptSession.evaluateScript(DelegatingScriptSession.java:72)
    at io.deephaven.engine.util.ScriptSession.evaluateScript(ScriptSession.java:75)
    at io.deephaven.server.console.ConsoleServiceGrpcImpl.lambda$executeCommand$4(ConsoleServiceGrpcImpl.java:191)
    at io.deephaven.server.session.SessionState$ExportBuilder.lambda$submit$2(SessionState.java:1537)
    at io.deephaven.server.session.SessionState$ExportObject.doExport(SessionState.java:995)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at io.deephaven.server.runner.scheduler.SchedulerModule$ThreadFactory.lambda$newThread$0(SchedulerModule.java:100)
    at java.base/java.lang.Thread.run(Thread.java:1583)
caused by io.deephaven.engine.table.impl.lang.QueryLanguageParser$QueryLanguageParseException: 

Having trouble with the following expression:
Full expression           : black_scholes_price(UMid, Strike, Rf, DT, VolMid, IsCall, IsStock)
Expression having trouble : black_scholes_price.call(UMid, Strike, Rf, DT, VolMid, IsCall, IsStock)
Exception type            : java.lang.IllegalArgumentException
Exception message         : black_scholes_price: Expected argument (3) to be either one of [double] or their compatible ones, got class java.lang.Double

    at io.deephaven.engine.util.PyCallableWrapperJpyImpl.verifyArguments(PyCallableWrapperJpyImpl.java:331)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.verifyPyCallableArguments(QueryLanguageParser.java:2506)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:2478)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:133)
    at com.github.javaparser.ast.expr.MethodCallExpr.accept(MethodCallExpr.java:116)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:2387)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.visit(QueryLanguageParser.java:133)
    at com.github.javaparser.ast.expr.MethodCallExpr.accept(MethodCallExpr.java:116)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.<init>(QueryLanguageParser.java:293)
    at io.deephaven.engine.table.impl.lang.QueryLanguageParser.<init>(QueryLanguageParser.java:209)
    at io.deephaven.engine.table.impl.select.codegen.FormulaAnalyzer.parseFormula(FormulaAnalyzer.java:209)
    at io.deephaven.engine.table.impl.select.codegen.FormulaAnalyzer.parseFormula(FormulaAnalyzer.java:88)
    at io.deephaven.engine.table.impl.select.DhFormulaColumn.initDef(DhFormulaColumn.java:196)
    ... 33 more

Line: 766
Namespace: update
File: /opt/deephaven/venv/lib/python3.10/site-packages/deephaven/table.py
Traceback (most recent call last):
  File "<string>", line 25, in <module>
  File "/opt/deephaven/venv/lib/python3.10/site-packages/deephaven/table.py", line 766, in update

    at org.jpy.PyLib.executeCode(Native Method)
    at org.jpy.PyObject.executeCode(PyObject.java:138)
    at io.deephaven.engine.util.PythonEvaluatorJpy.evalScript(PythonEvaluatorJpy.java:73)
    at io.deephaven.integrations.python.PythonDeephavenSession.lambda$evaluate$1(PythonDeephavenSession.java:205)
    at io.deephaven.util.locks.FunctionalLock.doLockedInterruptibly(FunctionalLock.java:51)
    at io.deephaven.integrations.python.PythonDeephavenSession.evaluate(PythonDeephavenSession.java:205)
    at io.deephaven.engine.util.AbstractScriptSession.lambda$evaluateScript$0(AbstractScriptSession.java:163)
    at io.deephaven.engine.context.ExecutionContext.lambda$apply$0(ExecutionContext.java:196)
    at io.deephaven.engine.context.ExecutionContext.apply(ExecutionContext.java:207)
    at io.deephaven.engine.context.ExecutionContext.apply(ExecutionContext.java:195)
    at io.deephaven.engine.util.AbstractScriptSession.evaluateScript(AbstractScriptSession.java:163)
    at io.deephaven.engine.util.DelegatingScriptSession.evaluateScript(DelegatingScriptSession.java:72)
    at io.deephaven.engine.util.ScriptSession.evaluateScript(ScriptSession.java:75)
    at io.deephaven.server.console.ConsoleServiceGrpcImpl.lambda$executeCommand$4(ConsoleServiceGrpcImpl.java:191)
    at io.deephaven.server.session.SessionState$ExportBuilder.lambda$submit$2(SessionState.java:1537)
    at io.deephaven.server.session.SessionState$ExportObject.doExport(SessionState.java:995)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
    at io.deephaven.server.runner.scheduler.SchedulerModule$ThreadFactory.lambda$newThread$0(SchedulerModule.java:100)
    at java.base/java.lang.Thread.run(Thread.java:1583)