Skip to content

Commit

Permalink
fixed tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruida Zeng authored and Ruida Zeng committed Oct 18, 2024
1 parent dc6545e commit 586b350
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 45 deletions.
2 changes: 1 addition & 1 deletion main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import octobot_commons.symbols as symbols
import octobot_commons.os_util as os_util

import arbitrage_opportunity.detector as detector
import triangular_arbitrage.detector as detector

if __name__ == "__main__":
benchmark = os_util.parse_boolean_environment_var("IS_BENCHMARKING", "False")
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from setuptools import find_packages
from setuptools import setup

from arbitrage_opportunity import PROJECT_NAME, VERSION
from triangular_arbitrage import PROJECT_NAME, VERSION

PACKAGES = find_packages(
exclude=[
Expand Down
47 changes: 44 additions & 3 deletions tests/test_detector.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest
import octobot_commons.symbols as symbols
from arbitrage_opportunity.detector import ShortTicker, get_best_opportunity
from triangular_arbitrage.detector import ShortTicker, get_best_triangular_opportunity, get_best_opportunity


@pytest.fixture
Expand All @@ -14,19 +14,60 @@ def sample_tickers():
]


def test_get_best_triangular_opportunity_handles_empty_tickers():
best_opportunity, best_profit = get_best_triangular_opportunity([])
assert best_profit == 0
assert best_opportunity is None


def test_get_best_triangular_opportunity_handles_no_cycle_opportunity(sample_tickers):
sample_tickers.append(ShortTicker(symbol=symbols.Symbol('DOT/USDT'), last_price=0.05))
best_opportunity, best_profit = get_best_triangular_opportunity(sample_tickers)
assert best_profit == 0
assert best_opportunity is None

def test_get_best_opportunity_handles_empty_tickers():
best_opportunity, best_profit = get_best_opportunity([])
assert best_profit == 0
assert best_opportunity is None


def test_get_best_opportunity_handles_no_cycle_opportunity(sample_tickers):
def test_get_best_opportunity_handles_no_triplet_opportunity(sample_tickers):
sample_tickers.append(ShortTicker(symbol=symbols.Symbol('DOT/USDT'), last_price=0.05))
best_opportunity, best_profit = get_best_opportunity(sample_tickers)
assert best_profit == 0
assert best_opportunity is None


def test_get_best_opportunity_returns_correct_triplet_with_correct_tickers():
tickers = [
ShortTicker(symbol=symbols.Symbol('BTC/USDT'), last_price=30000),
ShortTicker(symbol=symbols.Symbol('ETH/BTC'), last_price=0.3),
ShortTicker(symbol=symbols.Symbol('ETH/USDT'), last_price=2000),
]
best_opportunity, best_profit = get_best_triangular_opportunity(tickers)
assert len(best_opportunity) == 3
assert best_profit == 4.5
assert all(isinstance(ticker, ShortTicker) for ticker in best_opportunity)


def test_get_best_opportunity_returns_correct_triplet_with_multiple_tickers():
tickers = [
ShortTicker(symbol=symbols.Symbol('BTC/USDT'), last_price=30000),
ShortTicker(symbol=symbols.Symbol('ETH/BTC'), last_price=0.3),
ShortTicker(symbol=symbols.Symbol('ETH/USDT'), last_price=2000),
ShortTicker(symbol=symbols.Symbol('ETH/USDC'), last_price=1900),
ShortTicker(symbol=symbols.Symbol('BTC/USDC'), last_price=35000),
ShortTicker(symbol=symbols.Symbol('USDC/USDT'), last_price=1.1),
ShortTicker(symbol=symbols.Symbol('USDC/TUSD'), last_price=0.95),
ShortTicker(symbol=symbols.Symbol('ETH/TUSD'), last_price=1950),
ShortTicker(symbol=symbols.Symbol('BTC/TUSD'), last_price=32500),
]
best_opportunity, best_profit = get_best_triangular_opportunity(tickers)
assert len(best_opportunity) == 3
assert best_profit == 5.526315789473684
assert all(isinstance(ticker, ShortTicker) for ticker in best_opportunity)

def test_get_best_opportunity_returns_correct_cycle_with_correct_tickers():
tickers = [
ShortTicker(symbol=symbols.Symbol('BTC/USDT'), last_price=30000),
Expand All @@ -53,5 +94,5 @@ def test_get_best_opportunity_returns_correct_cycle_with_multiple_tickers():
]
best_opportunity, best_profit = get_best_opportunity(tickers)
assert len(best_opportunity) >= 3 # Handling cycles with more than 3 tickers
assert best_profit == 4.775
assert best_profit == 5.775
assert all(isinstance(ticker, ShortTicker) for ticker in best_opportunity)
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -47,45 +47,9 @@ def get_last_prices(exchange_time, tickers, ignored_symbols, whitelisted_symbols

def get_best_triangular_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker], float]:
# Build a directed graph of currencies
graph = nx.DiGraph()

for ticker in tickers:
if ticker.symbol is not None:
graph.add_edge(ticker.symbol.base, ticker.symbol.quote, ticker=ticker)
graph.add_edge(ticker.symbol.quote, ticker.symbol.base,
ticker=ShortTicker(symbols.Symbol(f"{ticker.symbol.quote}/{ticker.symbol.base}"),
1 / ticker.last_price, reversed=True))

best_profit = 0
best_triplet = None

for cycle in nx.simple_cycles(graph):
if len(cycle) != 3:
continue

a, b, c = cycle
a_to_b = graph[a][b]['ticker']
b_to_c = graph[b][c]['ticker']
c_to_a = graph[c][a]['ticker']

profit = a_to_b.last_price * b_to_c.last_price * c_to_a.last_price
return get_best_opportunity(tickers, 3)

if profit > best_profit:
best_profit = profit
best_triplet = [a_to_b, b_to_c, c_to_a]

if best_triplet is not None:
# restore original symbols for reversed pairs
best_triplet = [
ShortTicker(symbols.Symbol(f"{triplet.symbol.quote}/{triplet.symbol.base}"), triplet.last_price,
reversed=True)
if triplet.reversed else triplet
for triplet in best_triplet
]

return best_triplet, best_profit

def get_best_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker], float]:
def get_best_opportunity(tickers: List[ShortTicker], max_cycle: int = 50) -> Tuple[List[ShortTicker], float]:
# Build a directed graph of currencies
graph = nx.DiGraph()

Expand All @@ -99,8 +63,11 @@ def get_best_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker],
best_profit = 0
best_cycle = None

# Find all cycles in the graph (not just len = 3)
# Find all cycles in the graph with a length <= max_cycle
for cycle in nx.simple_cycles(graph):
if len(cycle) > max_cycle:
continue # Skip cycles longer than max_cycle

profit = 1
tickers_in_cycle = []

Expand All @@ -111,7 +78,6 @@ def get_best_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker],
tickers_in_cycle.append(ticker)
profit *= ticker.last_price


if profit > best_profit:
best_profit = profit
best_cycle = tickers_in_cycle
Expand All @@ -126,6 +92,7 @@ def get_best_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker],
return best_cycle, best_profit



async def get_exchange_data(exchange_name):
exchange_class = getattr(ccxt, exchange_name)
exchange = exchange_class()
Expand Down

0 comments on commit 586b350

Please sign in to comment.