Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented Cycle Detection #19

Merged
merged 16 commits into from
Oct 24, 2024
Merged

Implemented Cycle Detection #19

merged 16 commits into from
Oct 24, 2024

Conversation

ruidazeng
Copy link
Contributor

Implemented cycle detection, adapted tests, improved README, added comments.

This works on live data (example below):

New 2.33873% binanceus opportunity:
# 1. buy DOGE to BTC at 552486.18785
# 2. sell DOGE to USDT at 0.12232
# 3. buy ETH to USDT at 0.00038
# 4. buy ADA to ETH at 7570.02271
# 5. sell ADA to USDC at 0.35000
# 6. buy SOL to USDC at 0.00662
# 7. sell SOL to BTC at 0.00226

Copy link
Contributor Author

@ruidazeng ruidazeng left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made changes to appropriate places. Also recommend renaming repository since it is no longer just triangular.

This code uses the best cycle detection algorithm developed by Johnson in 1975 with optimization

Copy link
Member

@Herklos Herklos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job! Thank you for this very useful contribution.

Comment on lines 55 to 56
assert len(best_opportunity) >= 3 # Handling cycles with more than 3 tickers
assert best_profit == 4.775
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please keep the old test to ensure nothing is broken with the new code and add a new one with more than 3 cycles?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will write separate tests for get best opportunities (new method) vs get best triangular opportunities (old method) then.

setup.py Outdated
@@ -1,7 +1,7 @@
from setuptools import find_packages
from setuptools import setup

from triangular_arbitrage import PROJECT_NAME, VERSION
from arbitrage_opportunity import PROJECT_NAME, VERSION
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Although the library now offers more than just triangular arbitrage, could you please keep the old package name?

This will break the imports for people (including us) who already use the library.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Will revert the renames for now. Maybe something in the future if big updates/overhaul are required to avoid confusions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I totally agree on that.

Comment on lines +35 to +53
print(f"New {total_profit_percentage}% {exchange_name} opportunity:")
for i, opportunity in enumerate(best_opportunities):
# Get the base and quote currencies
base_currency = opportunity.symbol.base
quote_currency = opportunity.symbol.quote

# Format the output as below (real live example):
# -------------------------------------------
# New 2.33873% binanceus opportunity:
# 1. buy DOGE to BTC at 552486.18785
# 2. sell DOGE to USDT at 0.12232
# 3. buy ETH to USDT at 0.00038
# 4. buy ADA to ETH at 7570.02271
# 5. sell ADA to USDC at 0.35000
# 6. buy SOL to USDC at 0.00662
# 7. sell SOL to BTC at 0.00226
# -------------------------------------------
order_side = get_order_side(opportunity)
print(f"{i+1}. {order_side} {base_currency} to {quote_currency} at {opportunity.last_price:.5f}")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great example!

@@ -85,6 +85,46 @@ def get_best_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker],

return best_triplet, best_profit

def get_best_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker], float]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a max cycle param here? Then we would be able to call this method inside get_best_triangular_opportunity with a max_cycle=3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Herklos what's a good default value? I am thinking 50?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have many ideas.
I was thinking of a value much smaller than 50. For example, 5.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just changed it to 10, since our example yielded a great result where the cycle length is below 10 but greater than 5

@Herklos Herklos linked an issue Oct 18, 2024 that may be closed by this pull request
@Herklos Herklos assigned Herklos and ruidazeng and unassigned Herklos Oct 18, 2024
@Herklos Herklos added the enhancement New feature or request label Oct 18, 2024
ruidazeng and others added 2 commits October 18, 2024 08:19
@ruidazeng ruidazeng requested a review from Herklos October 18, 2024 14:08
@@ -56,34 +60,37 @@ def get_best_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker],
ticker=ShortTicker(symbols.Symbol(f"{ticker.symbol.quote}/{ticker.symbol.base}"),
1 / ticker.last_price, reversed=True))

best_profit = 0
best_triplet = None
best_profit = 1
Copy link
Contributor Author

@ruidazeng ruidazeng Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

base case must be = 1, since 100%-100% means no profit, and 1 is the multiplicative identity (we are multiplying the exchange rates, and not adding, the original best_profit = 0 does not make sense)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree

@@ -14,16 +14,28 @@ def sample_tickers():
]


def test_get_best_triangular_opportunity_handles_empty_tickers():
best_opportunity, best_profit = get_best_triangular_opportunity([])
assert best_profit == 1
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is necessary since the we need to multiply all the stock prices by the number.

The reason why the old test (with best_profit = 0) worked for the old code is, if no opportunity or empty, the multiplication never occurred as it was hardcoded to only accept minimum a trio of tickers.

However, for our new code, even if there is 0, 1, or 2 tickers, the multiplication occurs, and the base line for profit will be 1 since we are multiplying 1 by the exchange rates to maintain identity.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, you're right it's better without best_profit = 0

main.py Outdated
Comment on lines 17 to 18
exchanges = ['binance', 'coinbase', 'kucoin', 'binanceus']
exchange_name = exchanges[3] # allow pickable exchange_id from https://github.com/ccxt/ccxt/wiki/manual#exchanges
Copy link
Member

@Herklos Herklos Oct 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain the purpose of the exchanges list? Why not only reference the exchange_id URL instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just testing different exchanges and didn't want to rewrite the entire exchange_id every time... I will get rid of this.

Comment on lines +48 to +50
def get_best_triangular_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker], float]:
# Build a directed graph of currencies
return get_best_opportunity(tickers, 3)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's great!

@ruidazeng
Copy link
Contributor Author

ruidazeng commented Oct 23, 2024

Hmm @Herklos not sure how to solve this... The profit is supposed to be 5.775.

However, sometimes it returns 5.775000000000001 and sometimes it returns 5.7749999999999995 and I am not sure why...

@ruidazeng
Copy link
Contributor Author

Hmm @Herklos not sure how to solve this... The profit is supposed to be 5.775.

However, sometimes it returns 5.775000000000001 and sometimes it returns 5.7749999999999995 and I am not sure why...

Is assert round(best_profit, 3) == 5.775 the best approach? I am skeptical.

@Herklos
Copy link
Member

Herklos commented Oct 23, 2024

Hmm @Herklos not sure how to solve this... The profit is supposed to be 5.775.
However, sometimes it returns 5.775000000000001 and sometimes it returns 5.7749999999999995 and I am not sure why...

Is assert round(best_profit, 3) == 5.775 the best approach? I am skeptical.

Yes, that works for me, let's do it that way

@ruidazeng
Copy link
Contributor Author

@Herklos all checks have passed

Copy link
Member

@Herklos Herklos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Thanks for your contribution @ruidazeng

@Herklos Herklos merged commit 368a785 into Drakkar-Software:master Oct 24, 2024
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Higher order cycles
2 participants