nautilus_model/ffi/instruments/
synthetic.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.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    ffi::{
23        cvec::CVec,
24        parsing::{bytes_to_string_vec, string_vec_to_bytes},
25        string::{cstr_as_str, str_to_cstr},
26    },
27    UnixNanos,
28};
29
30use crate::{
31    identifiers::{InstrumentId, Symbol},
32    instruments::synthetic::SyntheticInstrument,
33    types::{Price, ERROR_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#[allow(non_camel_case_types)]
47pub struct SyntheticInstrument_API(Box<SyntheticInstrument>);
48
49impl Deref for SyntheticInstrument_API {
50    type Target = SyntheticInstrument;
51
52    fn deref(&self) -> &Self::Target {
53        &self.0
54    }
55}
56
57impl DerefMut for SyntheticInstrument_API {
58    fn deref_mut(&mut self) -> &mut Self::Target {
59        &mut self.0
60    }
61}
62
63/// # Safety
64///
65/// - Assumes `components_ptr` is a valid C string pointer of a JSON format list of strings.
66/// - Assumes `formula_ptr` is a valid C string pointer.
67#[no_mangle]
68pub unsafe extern "C" fn synthetic_instrument_new(
69    symbol: Symbol,
70    price_precision: u8,
71    components_ptr: *const c_char,
72    formula_ptr: *const c_char,
73    ts_event: u64,
74    ts_init: u64,
75) -> SyntheticInstrument_API {
76    // TODO: There is absolutely no error handling here yet
77    let components = bytes_to_string_vec(components_ptr)
78        .into_iter()
79        .map(|s| InstrumentId::from(s.as_str()))
80        .collect::<Vec<InstrumentId>>();
81    let formula = cstr_as_str(formula_ptr).to_string();
82    let synth = SyntheticInstrument::new(
83        symbol,
84        price_precision,
85        components,
86        formula,
87        ts_event.into(),
88        ts_init.into(),
89    );
90
91    SyntheticInstrument_API(Box::new(synth))
92}
93
94#[no_mangle]
95pub extern "C" fn synthetic_instrument_drop(synth: SyntheticInstrument_API) {
96    drop(synth); // Memory freed here
97}
98
99#[no_mangle]
100pub extern "C" fn synthetic_instrument_id(synth: &SyntheticInstrument_API) -> InstrumentId {
101    synth.id
102}
103
104#[no_mangle]
105pub extern "C" fn synthetic_instrument_price_precision(synth: &SyntheticInstrument_API) -> u8 {
106    synth.price_precision
107}
108
109#[no_mangle]
110#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
111pub extern "C" fn synthetic_instrument_price_increment(synth: &SyntheticInstrument_API) -> Price {
112    synth.price_increment
113}
114
115#[no_mangle]
116pub extern "C" fn synthetic_instrument_formula_to_cstr(
117    synth: &SyntheticInstrument_API,
118) -> *const c_char {
119    str_to_cstr(&synth.formula)
120}
121
122#[no_mangle]
123pub extern "C" fn synthetic_instrument_components_to_cstr(
124    synth: &SyntheticInstrument_API,
125) -> *const c_char {
126    let components_vec = synth
127        .components
128        .iter()
129        .map(std::string::ToString::to_string)
130        .collect::<Vec<String>>();
131
132    string_vec_to_bytes(components_vec)
133}
134
135#[no_mangle]
136pub extern "C" fn synthetic_instrument_components_count(synth: &SyntheticInstrument_API) -> usize {
137    synth.components.len()
138}
139
140#[no_mangle]
141pub extern "C" fn synthetic_instrument_ts_event(synth: &SyntheticInstrument_API) -> UnixNanos {
142    synth.ts_event
143}
144
145#[no_mangle]
146pub extern "C" fn synthetic_instrument_ts_init(synth: &SyntheticInstrument_API) -> UnixNanos {
147    synth.ts_init
148}
149
150/// # Safety
151///
152/// - Assumes `formula_ptr` is a valid C string pointer.
153#[no_mangle]
154pub unsafe extern "C" fn synthetic_instrument_is_valid_formula(
155    synth: &SyntheticInstrument_API,
156    formula_ptr: *const c_char,
157) -> u8 {
158    if formula_ptr.is_null() {
159        return u8::from(false);
160    }
161    let formula = cstr_as_str(formula_ptr);
162    u8::from(synth.is_valid_formula(formula))
163}
164
165/// # Safety
166///
167/// - Assumes `formula_ptr` is a valid C string pointer.
168#[no_mangle]
169pub unsafe extern "C" fn synthetic_instrument_change_formula(
170    synth: &mut SyntheticInstrument_API,
171    formula_ptr: *const c_char,
172) {
173    let formula = cstr_as_str(formula_ptr);
174    synth.change_formula(formula.to_string()).unwrap();
175}
176
177#[no_mangle]
178#[cfg_attr(feature = "high-precision", allow(improper_ctypes_definitions))]
179pub extern "C" fn synthetic_instrument_calculate(
180    synth: &mut SyntheticInstrument_API,
181    inputs_ptr: &CVec,
182) -> Price {
183    let CVec { ptr, len, .. } = inputs_ptr;
184    let inputs: &[f64] = unsafe { std::slice::from_raw_parts((*ptr).cast::<f64>(), *len) };
185
186    match synth.calculate(inputs) {
187        Ok(price) => price,
188        Err(_) => ERROR_PRICE,
189    }
190}