nautilus_model/ffi/instruments/
synthetic.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{
17    ffi::c_char,
18    ops::{Deref, DerefMut},
19};
20
21use nautilus_core::{
22    UnixNanos,
23    ffi::{
24        cvec::CVec,
25        parsing::{bytes_to_string_vec, string_vec_to_bytes},
26        string::{cstr_as_str, str_to_cstr},
27    },
28};
29
30use crate::{
31    identifiers::{InstrumentId, Symbol},
32    instruments::synthetic::SyntheticInstrument,
33    types::{ERROR_PRICE, Price},
34};
35
36/// C compatible Foreign Function Interface (FFI) for an underlying
37/// [`SyntheticInstrument`].
38///
39/// This struct wraps `SyntheticInstrument` in a way that makes it compatible with C function
40/// calls, enabling interaction with `SyntheticInstrument` in a C environment.
41///
42/// It implements the `Deref` trait, allowing instances of `SyntheticInstrument_API` to be
43/// dereferenced to `SyntheticInstrument`, providing access to `SyntheticInstruments`'s methods without
44/// having to manually access the underlying instance.
45#[repr(C)]
46#[derive(Debug)]
47#[allow(non_camel_case_types)]
48pub struct SyntheticInstrument_API(Box<SyntheticInstrument>);
49
50impl Deref for SyntheticInstrument_API {
51    type Target = SyntheticInstrument;
52
53    fn deref(&self) -> &Self::Target {
54        &self.0
55    }
56}
57
58impl DerefMut for SyntheticInstrument_API {
59    fn deref_mut(&mut self) -> &mut Self::Target {
60        &mut self.0
61    }
62}
63
64/// Changes the formula of the synthetic instrument.
65///
66/// # Panics
67///
68/// Panics if the formula update operation fails (`unwrap`).
69///
70/// # Safety
71///
72/// This function assumes:
73/// - `components_ptr` is a valid C string pointer of a JSON format list of strings.
74/// - `formula_ptr` is a valid C string pointer.
75#[unsafe(no_mangle)]
76pub unsafe extern "C" fn synthetic_instrument_new(
77    symbol: Symbol,
78    price_precision: u8,
79    components_ptr: *const c_char,
80    formula_ptr: *const c_char,
81    ts_event: u64,
82    ts_init: u64,
83) -> SyntheticInstrument_API {
84    // TODO: There is absolutely no error handling here yet
85    let components = unsafe { bytes_to_string_vec(components_ptr) }
86        .into_iter()
87        .map(|s| InstrumentId::from(s.as_str()))
88        .collect::<Vec<InstrumentId>>();
89    let formula = unsafe { cstr_as_str(formula_ptr).to_string() };
90    let synth = SyntheticInstrument::new(
91        symbol,
92        price_precision,
93        components,
94        formula,
95        ts_event.into(),
96        ts_init.into(),
97    );
98
99    SyntheticInstrument_API(Box::new(synth))
100}
101
102#[unsafe(no_mangle)]
103pub extern "C" fn synthetic_instrument_drop(synth: SyntheticInstrument_API) {
104    drop(synth); // Memory freed here
105}
106
107#[unsafe(no_mangle)]
108pub extern "C" fn synthetic_instrument_id(synth: &SyntheticInstrument_API) -> InstrumentId {
109    synth.id
110}
111
112#[unsafe(no_mangle)]
113pub extern "C" fn synthetic_instrument_price_precision(synth: &SyntheticInstrument_API) -> u8 {
114    synth.price_precision
115}
116
117#[unsafe(no_mangle)]
118#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
119pub extern "C" fn synthetic_instrument_price_increment(synth: &SyntheticInstrument_API) -> Price {
120    synth.price_increment
121}
122
123#[unsafe(no_mangle)]
124pub extern "C" fn synthetic_instrument_formula_to_cstr(
125    synth: &SyntheticInstrument_API,
126) -> *const c_char {
127    str_to_cstr(&synth.formula)
128}
129
130#[unsafe(no_mangle)]
131pub extern "C" fn synthetic_instrument_components_to_cstr(
132    synth: &SyntheticInstrument_API,
133) -> *const c_char {
134    let components_vec = synth
135        .components
136        .iter()
137        .map(ToString::to_string)
138        .collect::<Vec<String>>();
139
140    string_vec_to_bytes(&components_vec)
141}
142
143#[unsafe(no_mangle)]
144pub extern "C" fn synthetic_instrument_components_count(synth: &SyntheticInstrument_API) -> usize {
145    synth.components.len()
146}
147
148#[unsafe(no_mangle)]
149pub extern "C" fn synthetic_instrument_ts_event(synth: &SyntheticInstrument_API) -> UnixNanos {
150    synth.ts_event
151}
152
153#[unsafe(no_mangle)]
154pub extern "C" fn synthetic_instrument_ts_init(synth: &SyntheticInstrument_API) -> UnixNanos {
155    synth.ts_init
156}
157
158/// # Safety
159///
160/// Assumes `formula_ptr` is a valid C string pointer.
161#[unsafe(no_mangle)]
162pub unsafe extern "C" fn synthetic_instrument_is_valid_formula(
163    synth: &SyntheticInstrument_API,
164    formula_ptr: *const c_char,
165) -> u8 {
166    if formula_ptr.is_null() {
167        return u8::from(false);
168    }
169    let formula = unsafe { cstr_as_str(formula_ptr) };
170    u8::from(synth.is_valid_formula(formula))
171}
172
173/// # Safety
174///
175/// Assumes `formula_ptr` is a valid C string pointer.
176///
177/// # Panics
178///
179/// Panics if changing the formula fails (i.e., `unwrap()` in `change_formula`).
180#[unsafe(no_mangle)]
181pub unsafe extern "C" fn synthetic_instrument_change_formula(
182    synth: &mut SyntheticInstrument_API,
183    formula_ptr: *const c_char,
184) {
185    let formula = unsafe { cstr_as_str(formula_ptr) };
186    synth.change_formula(formula.to_string()).unwrap();
187}
188
189#[unsafe(no_mangle)]
190#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
191pub extern "C" fn synthetic_instrument_calculate(
192    synth: &mut SyntheticInstrument_API,
193    inputs_ptr: &CVec,
194) -> Price {
195    let CVec { ptr, len, .. } = inputs_ptr;
196    let inputs: &[f64] = unsafe { std::slice::from_raw_parts((*ptr).cast::<f64>(), *len) };
197
198    match synth.calculate(inputs) {
199        Ok(price) => price,
200        Err(_) => ERROR_PRICE,
201    }
202}