fynd_core/graph/mod.rs
1//! Graph management for algorithms.
2//!
3//! This module provides the GraphManager trait which solvers use to manage their market graph
4//! representation. GraphManager handles both building graphs from market data and updating them
5//! based on market events.
6
7pub mod petgraph;
8
9use std::collections::HashMap;
10
11pub use petgraph::{EdgeData, PetgraphStableDiGraphManager, StableDiGraph};
12use thiserror::Error;
13use tycho_simulation::{
14 tycho_common::{models::Address, simulation::protocol_sim::ProtocolSim},
15 tycho_core::models::token::Token,
16};
17
18use crate::types::ComponentId;
19
20/// A path through the graph as a sequence of edge indices.
21///
22/// Each edge index points to an edge in the graph containing the component ID and weight.
23/// This representation allows O(1) access to edge data during scoring and simulation.
24#[derive(Clone, Default)]
25pub struct Path<'a, D> {
26 /// Sequence of token addresses in the path.
27 pub tokens: Vec<&'a Address>,
28 /// Sequence of edge indices representing the path. Length is tokens.len() - 1.
29 pub edge_data: Vec<&'a EdgeData<D>>,
30}
31
32impl<'a, D> Path<'a, D> {
33 /// Creates a new empty Path.
34 pub fn new() -> Self {
35 Self { tokens: Vec::new(), edge_data: Vec::new() }
36 }
37
38 /// Adds a hop to the path.
39 ///
40 /// Arguments:
41 /// - from: The starting token address of the hop.
42 /// - edge_data: The edge data for the hop.
43 /// - to: The ending token address of the hop.
44 pub fn add_hop(&mut self, from: &'a Address, edge_data: &'a EdgeData<D>, to: &'a Address) {
45 if self.tokens.is_empty() {
46 self.tokens.push(from);
47 }
48 self.tokens.push(to);
49 self.edge_data.push(edge_data);
50 }
51
52 /// Returns the number of hops in the path.
53 pub fn len(&self) -> usize {
54 self.edge_data.len()
55 }
56
57 /// Returns true if the path has no hops.
58 pub fn is_empty(&self) -> bool {
59 self.edge_data.is_empty()
60 }
61
62 /// Returns an iterator over the edges in the path.
63 pub fn edge_iter(&self) -> &[&'a EdgeData<D>] {
64 &self.edge_data
65 }
66
67 /// Returns an iterator over hops in the path (from_token, edge_data, to_token).
68 pub fn iter(&self) -> impl Iterator<Item = (&'a Address, &'a EdgeData<D>, &'a Address)> + '_ {
69 self.tokens
70 .windows(2)
71 .zip(self.edge_data.iter())
72 .map(|(tokens, edge)| (tokens[0], *edge, tokens[1]))
73 }
74
75 /// Creates a new reversed Path from the current one.
76 pub fn reversed(self) -> Self {
77 let reversed_tokens = self.tokens.into_iter().rev().collect();
78 let reversed_edge_data = self
79 .edge_data
80 .into_iter()
81 .rev()
82 .collect();
83 Self { tokens: reversed_tokens, edge_data: reversed_edge_data }
84 }
85}
86
87/// Errors that can occur during graph operations.
88#[derive(Error, Debug)]
89pub enum GraphError {
90 /// Token address not found as a node in the graph.
91 #[error("Token not found in graph: {0:?}")]
92 TokenNotFound(Address),
93 /// One or more components were not found in the graph.
94 #[error("Components not found in graph: {0:?}")]
95 ComponentsNotFound(Vec<ComponentId>),
96 /// Components with fewer than 2 tokens cannot form edges.
97 #[error("Components with less then 2 tokens cannot be added: {0:?}")]
98 InvalidComponents(Vec<ComponentId>),
99 /// No edge exists between the given tokens for this component (test-only).
100 #[cfg(test)]
101 #[error("No edge found between tokens {0:?} and {1:?} for component {2}")]
102 MissingComponentBetweenTokens(Address, Address, ComponentId),
103}
104
105/// Trait for managing graph representations.
106///
107/// Graph managers are stateful - they maintain the graph internally and update it based on market
108/// events.
109pub trait GraphManager<G>: Send + Sync
110where
111 G: Send + Sync,
112{
113 /// Initializes the graph from the market topology.
114 ///
115 /// Arguments:
116 /// - components: A map of component IDs to their tokens addresses.
117 fn initialize_graph(&mut self, components: &HashMap<ComponentId, Vec<Address>>);
118
119 /// Returns a reference to the managed graph.
120 fn graph(&self) -> &G;
121}
122
123use crate::{derived::DerivedData, feed::market_data::SharedMarketData};
124
125/// Trait for edge weight types that can be computed from a ProtocolSim and DerivedData.
126///
127/// Implement this trait for edge data types that should use pre-computed derived data
128/// (pool depths, spot prices, etc.) instead of computing them from scratch.
129pub trait EdgeWeightFromSimAndDerived: Sized {
130 /// Computes edge weight data using ProtocolSim and pre-computed DerivedData.
131 ///
132 /// # Arguments
133 ///
134 /// * `sim` - The protocol simulation state
135 /// * `component_id` - The component ID for derived data lookup
136 /// * `token_in` - The input token
137 /// * `token_out` - The output token
138 /// * `derived` - Pre-computed derived data (pool depths, spot prices, etc.)
139 ///
140 /// # Returns
141 ///
142 /// The computed edge weight, or `None` if it cannot be computed.
143 fn from_sim_and_derived(
144 sim: &dyn ProtocolSim,
145 component_id: &ComponentId,
146 token_in: &Token,
147 token_out: &Token,
148 derived: &DerivedData,
149 ) -> Option<Self>;
150}
151
152/// Trivial implementation for algorithms that don't use edge weights (e.g., Bellman-Ford).
153impl EdgeWeightFromSimAndDerived for () {
154 fn from_sim_and_derived(
155 _sim: &dyn ProtocolSim,
156 _component_id: &ComponentId,
157 _token_in: &Token,
158 _token_out: &Token,
159 _derived: &DerivedData,
160 ) -> Option<Self> {
161 Some(())
162 }
163}
164
165/// Trait for graph managers that support edge weight updates with derived data.
166pub trait EdgeWeightUpdaterWithDerived {
167 /// Updates edge weights using simulation states and pre-computed derived data.
168 ///
169 /// Returns the number of edges successfully updated.
170 fn update_edge_weights_with_derived(
171 &mut self,
172 market: &SharedMarketData,
173 derived: &DerivedData,
174 ) -> usize;
175}