[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-14-buscador-ciclos-arbitraje-dfs-grafo-pools":3},{"article":4,"author":57},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":35,"related_articles":36},"d8000000-0000-0000-0000-000000000114","a0000000-0000-0000-0000-000000000082","Deep EVM #14: Construyendo un Buscador de Ciclos de Arbitraje — DFS en un Grafo de Pools","deep-evm-14-buscador-ciclos-arbitraje-dfs-grafo-pools","Implementa un buscador de ciclos de arbitraje usando búsqueda en profundidad sobre un grafo de pools de liquidez: modelado del grafo, detección de ciclos, cálculo de rentabilidad y poda eficiente.","## El arbitraje como problema de grafos\n\nEl arbitraje en DeFi se modela naturalmente como un problema de grafos. Cada token es un nodo, y cada pool de liquidez (Uniswap, Sushiswap, Curve, etc.) es una arista dirigida con un peso que representa el tipo de cambio.\n\nUn ciclo de arbitraje rentable es un ciclo en este grafo donde el producto de los tipos de cambio a lo largo del camino es mayor que 1:\n\n```\nETH -> USDC -> DAI -> ETH\nSi: rate(ETH->USDC) * rate(USDC->DAI) * rate(DAI->ETH) > 1.0\nEntonces: el ciclo es rentable\n```\n\n## Modelado del grafo\n\nEn Rust, representamos el grafo con listas de adyacencia:\n\n```rust\nuse std::collections::HashMap;\n\nstruct Pool {\n    address: Address,\n    token0: Address,\n    token1: Address,\n    reserve0: U256,\n    reserve1: U256,\n    fee: u32, \u002F\u002F 3000 = 0.3%\n}\n\nstruct Graph {\n    \u002F\u002F token -> [(pool_index, next_token, direction)]\n    adjacency: HashMap\u003CAddress, Vec\u003C(usize, Address, bool)>>,\n    pools: Vec\u003CPool>,\n}\n```\n\n## Detección de ciclos con DFS\n\nUsamos búsqueda en profundidad (DFS) limitada para encontrar ciclos que comienzan y terminan en un token dado:\n\n```rust\nimpl Graph {\n    fn find_cycles(\n        &self,\n        start: Address,\n        max_hops: usize,\n    ) -> Vec\u003CVec\u003Cusize>> {\n        let mut result = Vec::new();\n        let mut path = Vec::new();\n        let mut visited_pools = HashSet::new();\n        \n        self.dfs(\n            start, start, &mut path,\n            &mut visited_pools, &mut result,\n            max_hops, 0,\n        );\n        \n        result\n    }\n    \n    fn dfs(\n        &self,\n        current: Address,\n        target: Address,\n        path: &mut Vec\u003Cusize>,\n        visited_pools: &mut HashSet\u003Cusize>,\n        result: &mut Vec\u003CVec\u003Cusize>>,\n        max_hops: usize,\n        depth: usize,\n    ) {\n        if depth > 0 && current == target {\n            result.push(path.clone());\n            return;\n        }\n        if depth >= max_hops {\n            return;\n        }\n        \n        if let Some(neighbors) = self.adjacency.get(&current) {\n            for &(pool_idx, next_token, _dir) in neighbors {\n                if visited_pools.contains(&pool_idx) {\n                    continue;\n                }\n                \n                visited_pools.insert(pool_idx);\n                path.push(pool_idx);\n                \n                self.dfs(\n                    next_token, target, path,\n                    visited_pools, result,\n                    max_hops, depth + 1,\n                );\n                \n                path.pop();\n                visited_pools.remove(&pool_idx);\n            }\n        }\n    }\n}\n```\n\n## Cálculo de rentabilidad\n\nPara cada ciclo encontrado, calculamos si es rentable simulando los swaps secuencialmente:\n\n```rust\nfn calculate_profit(\n    &self,\n    cycle: &[usize],\n    initial_amount: U256,\n) -> Option\u003CU256> {\n    let mut amount = initial_amount;\n    \n    for &pool_idx in cycle {\n        let pool = &self.pools[pool_idx];\n        amount = self.get_amount_out(\n            amount, pool.reserve0, pool.reserve1, pool.fee\n        );\n    }\n    \n    if amount > initial_amount {\n        Some(amount - initial_amount)\n    } else {\n        None\n    }\n}\n\nfn get_amount_out(\n    &self,\n    amount_in: U256,\n    reserve_in: U256,\n    reserve_out: U256,\n    fee: u32, \u002F\u002F base 10000\n) -> U256 {\n    let fee_factor = 10000 - fee; \u002F\u002F e.g., 9970 para 0.3%\n    let numerator = amount_in * fee_factor * reserve_out;\n    let denominator = reserve_in * 10000 + amount_in * fee_factor;\n    numerator \u002F denominator\n}\n```\n\n## Poda eficiente\n\nCon miles de pools, el espacio de búsqueda es enorme. Técnicas de poda:\n\n1. **Limitar profundidad** — Ciclos de 2-4 hops son los más rentables; 5+ rara vez lo son\n2. **Poda por liquidez** — Ignorar pools con reservas menores a un umbral\n3. **Poda por token** — Solo considerar tokens con suficiente liquidez y volumen\n4. **Poda temprana por precio** — Si el producto parcial de tipos de cambio ya es \u003C 0.99, podar\n5. **Paralelización** — Buscar ciclos desde múltiples tokens iniciales en paralelo usando rayon\n\n## Optimización del monto inicial\n\nUna vez encontrado un ciclo rentable, necesitamos optimizar cuánto capital invertir. Demasiado poco y el beneficio no cubre el gas; demasiado y el impacto de precio reduce las ganancias.\n\nUsamos búsqueda binaria para encontrar el óptimo:\n\n```rust\nfn optimize_amount(\n    &self,\n    cycle: &[usize],\n    min: U256,\n    max: U256,\n) -> (U256, U256) {\n    let mut lo = min;\n    let mut hi = max;\n    let mut best_amount = lo;\n    let mut best_profit = U256::zero();\n    \n    for _ in 0..64 { \u002F\u002F 64 iteraciones = precisión de ~1 wei\n        let mid = (lo + hi) \u002F 2;\n        if let Some(profit) = self.calculate_profit(cycle, mid) {\n            if profit > best_profit {\n                best_profit = profit;\n                best_amount = mid;\n            }\n            lo = mid;\n        } else {\n            hi = mid;\n        }\n    }\n    \n    (best_amount, best_profit)\n}\n```\n\n## Conclusión\n\nEncontrar ciclos de arbitraje es un problema combinatorio que requiere un balance entre exhaustividad y velocidad. La búsqueda DFS con poda agresiva y optimización del monto inicial proporciona un framework sólido para construir un searcher de arbitraje competitivo. En el próximo artículo, cubriremos la simulación de transacciones con state forks y la optimización bajo la presión del deadline de 12 segundos.","\u003Ch2 id=\"el-arbitraje-como-problema-de-grafos\">El arbitraje como problema de grafos\u003C\u002Fh2>\n\u003Cp>El arbitraje en DeFi se modela naturalmente como un problema de grafos. Cada token es un nodo, y cada pool de liquidez (Uniswap, Sushiswap, Curve, etc.) es una arista dirigida con un peso que representa el tipo de cambio.\u003C\u002Fp>\n\u003Cp>Un ciclo de arbitraje rentable es un ciclo en este grafo donde el producto de los tipos de cambio a lo largo del camino es mayor que 1:\u003C\u002Fp>\n\u003Cpre>\u003Ccode>ETH -&gt; USDC -&gt; DAI -&gt; ETH\nSi: rate(ETH-&gt;USDC) * rate(USDC-&gt;DAI) * rate(DAI-&gt;ETH) &gt; 1.0\nEntonces: el ciclo es rentable\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"modelado-del-grafo\">Modelado del grafo\u003C\u002Fh2>\n\u003Cp>En Rust, representamos el grafo con listas de adyacencia:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use std::collections::HashMap;\n\nstruct Pool {\n    address: Address,\n    token0: Address,\n    token1: Address,\n    reserve0: U256,\n    reserve1: U256,\n    fee: u32, \u002F\u002F 3000 = 0.3%\n}\n\nstruct Graph {\n    \u002F\u002F token -&gt; [(pool_index, next_token, direction)]\n    adjacency: HashMap&lt;Address, Vec&lt;(usize, Address, bool)&gt;&gt;,\n    pools: Vec&lt;Pool&gt;,\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"detecci-n-de-ciclos-con-dfs\">Detección de ciclos con DFS\u003C\u002Fh2>\n\u003Cp>Usamos búsqueda en profundidad (DFS) limitada para encontrar ciclos que comienzan y terminan en un token dado:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">impl Graph {\n    fn find_cycles(\n        &amp;self,\n        start: Address,\n        max_hops: usize,\n    ) -&gt; Vec&lt;Vec&lt;usize&gt;&gt; {\n        let mut result = Vec::new();\n        let mut path = Vec::new();\n        let mut visited_pools = HashSet::new();\n        \n        self.dfs(\n            start, start, &amp;mut path,\n            &amp;mut visited_pools, &amp;mut result,\n            max_hops, 0,\n        );\n        \n        result\n    }\n    \n    fn dfs(\n        &amp;self,\n        current: Address,\n        target: Address,\n        path: &amp;mut Vec&lt;usize&gt;,\n        visited_pools: &amp;mut HashSet&lt;usize&gt;,\n        result: &amp;mut Vec&lt;Vec&lt;usize&gt;&gt;,\n        max_hops: usize,\n        depth: usize,\n    ) {\n        if depth &gt; 0 &amp;&amp; current == target {\n            result.push(path.clone());\n            return;\n        }\n        if depth &gt;= max_hops {\n            return;\n        }\n        \n        if let Some(neighbors) = self.adjacency.get(&amp;current) {\n            for &amp;(pool_idx, next_token, _dir) in neighbors {\n                if visited_pools.contains(&amp;pool_idx) {\n                    continue;\n                }\n                \n                visited_pools.insert(pool_idx);\n                path.push(pool_idx);\n                \n                self.dfs(\n                    next_token, target, path,\n                    visited_pools, result,\n                    max_hops, depth + 1,\n                );\n                \n                path.pop();\n                visited_pools.remove(&amp;pool_idx);\n            }\n        }\n    }\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"c-lculo-de-rentabilidad\">Cálculo de rentabilidad\u003C\u002Fh2>\n\u003Cp>Para cada ciclo encontrado, calculamos si es rentable simulando los swaps secuencialmente:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">fn calculate_profit(\n    &amp;self,\n    cycle: &amp;[usize],\n    initial_amount: U256,\n) -&gt; Option&lt;U256&gt; {\n    let mut amount = initial_amount;\n    \n    for &amp;pool_idx in cycle {\n        let pool = &amp;self.pools[pool_idx];\n        amount = self.get_amount_out(\n            amount, pool.reserve0, pool.reserve1, pool.fee\n        );\n    }\n    \n    if amount &gt; initial_amount {\n        Some(amount - initial_amount)\n    } else {\n        None\n    }\n}\n\nfn get_amount_out(\n    &amp;self,\n    amount_in: U256,\n    reserve_in: U256,\n    reserve_out: U256,\n    fee: u32, \u002F\u002F base 10000\n) -&gt; U256 {\n    let fee_factor = 10000 - fee; \u002F\u002F e.g., 9970 para 0.3%\n    let numerator = amount_in * fee_factor * reserve_out;\n    let denominator = reserve_in * 10000 + amount_in * fee_factor;\n    numerator \u002F denominator\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"poda-eficiente\">Poda eficiente\u003C\u002Fh2>\n\u003Cp>Con miles de pools, el espacio de búsqueda es enorme. Técnicas de poda:\u003C\u002Fp>\n\u003Col>\n\u003Cli>\u003Cstrong>Limitar profundidad\u003C\u002Fstrong> — Ciclos de 2-4 hops son los más rentables; 5+ rara vez lo son\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Poda por liquidez\u003C\u002Fstrong> — Ignorar pools con reservas menores a un umbral\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Poda por token\u003C\u002Fstrong> — Solo considerar tokens con suficiente liquidez y volumen\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Poda temprana por precio\u003C\u002Fstrong> — Si el producto parcial de tipos de cambio ya es &lt; 0.99, podar\u003C\u002Fli>\n\u003Cli>\u003Cstrong>Paralelización\u003C\u002Fstrong> — Buscar ciclos desde múltiples tokens iniciales en paralelo usando rayon\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Ch2 id=\"optimizaci-n-del-monto-inicial\">Optimización del monto inicial\u003C\u002Fh2>\n\u003Cp>Una vez encontrado un ciclo rentable, necesitamos optimizar cuánto capital invertir. Demasiado poco y el beneficio no cubre el gas; demasiado y el impacto de precio reduce las ganancias.\u003C\u002Fp>\n\u003Cp>Usamos búsqueda binaria para encontrar el óptimo:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">fn optimize_amount(\n    &amp;self,\n    cycle: &amp;[usize],\n    min: U256,\n    max: U256,\n) -&gt; (U256, U256) {\n    let mut lo = min;\n    let mut hi = max;\n    let mut best_amount = lo;\n    let mut best_profit = U256::zero();\n    \n    for _ in 0..64 { \u002F\u002F 64 iteraciones = precisión de ~1 wei\n        let mid = (lo + hi) \u002F 2;\n        if let Some(profit) = self.calculate_profit(cycle, mid) {\n            if profit &gt; best_profit {\n                best_profit = profit;\n                best_amount = mid;\n            }\n            lo = mid;\n        } else {\n            hi = mid;\n        }\n    }\n    \n    (best_amount, best_profit)\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"conclusi-n\">Conclusión\u003C\u002Fh2>\n\u003Cp>Encontrar ciclos de arbitraje es un problema combinatorio que requiere un balance entre exhaustividad y velocidad. La búsqueda DFS con poda agresiva y optimización del monto inicial proporciona un framework sólido para construir un searcher de arbitraje competitivo. En el próximo artículo, cubriremos la simulación de transacciones con state forks y la optimización bajo la presión del deadline de 12 segundos.\u003C\u002Fp>\n","es","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:31.396337Z","Deep EVM #14: Buscador de Ciclos de Arbitraje — DFS en un Grafo de Pools","Implementa un buscador de arbitraje con DFS: modelado de grafos, detección de ciclos, cálculo de rentabilidad y poda eficiente.","MEV arbitraje ciclos DFS",null,"index, follow",[22,27,31],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000016","EVM","evm","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000019","MEV","mev",{"id":32,"name":33,"slug":34,"created_at":26},"c0000000-0000-0000-0000-000000000001","Rust","rust","Blockchain",[37,44,51],{"id":38,"title":39,"slug":40,"excerpt":41,"locale":12,"category_name":42,"published_at":43},"d0000000-0000-0000-0000-000000000660","WASI 0.3 y la muerte de los arranques en frío: Wasm del lado del servidor en producción","wasi-0-3-muerte-arranques-frio-wasm-servidor-produccion","WASI 0.3 se lanzó en febrero de 2026 con async I\u002FO nativo, tipos stream y soporte completo de sockets. WebAssembly del lado del servidor ahora ofrece arranques en frío en microsegundos, y cada proveedor cloud importante ofrece Wasm serverless.","DevOps","2026-03-28T10:44:48.518132Z",{"id":45,"title":46,"slug":47,"excerpt":48,"locale":12,"category_name":49,"published_at":50},"d0000000-0000-0000-0000-000000000638","El stack backend moderno 2026: Rust + PostgreSQL 18 + Wasm + eBPF","stack-backend-moderno-2026-rust-postgresql-wasm-ebpf","Cuatro tecnologias convergen para redefinir la infraestructura backend en 2026: Rust elimina la sobrecarga del garbage collection y reduce los contenedores en 3x, PostgreSQL 18 reemplaza bases de datos especializadas, WASI 0.3 ofrece arranques en frio de microsegundos para funciones serverless, y eBPF permite la observabilidad sin instrumentacion a una fraccion del costo del monitoreo tradicional.","Engineering","2026-03-28T10:44:47.080722Z",{"id":52,"title":53,"slug":54,"excerpt":55,"locale":12,"category_name":35,"published_at":56},"d0000000-0000-0000-0000-000000000614","La capa de interoperabilidad de Ethereum: Como 55+ L2s se convierten en una sola cadena","capa-interoperabilidad-ethereum-55-l2s-una-sola-cadena","Ethereum tiene 55+ rollups Layer 2, fragmentando la liquidez y la experiencia del usuario. La capa de interoperabilidad de Ethereum — combinando mensajeria cross-rollup, secuenciadores compartidos y based rollups — busca unificarlos en una red componible unica.","2026-03-28T10:44:45.451917Z",{"id":13,"name":58,"slug":59,"bio":60,"photo_url":19,"linkedin":19,"role":61,"created_at":62,"updated_at":62},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]