-
Notifications
You must be signed in to change notification settings - Fork 26
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
Conversation
There was a problem hiding this 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
There was a problem hiding this 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.
tests/test_detector.py
Outdated
assert len(best_opportunity) >= 3 # Handling cycles with more than 3 tickers | ||
assert best_profit == 4.775 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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}") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great example!
arbitrage_opportunity/detector.py
Outdated
@@ -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]: |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Co-authored-by: Herklos <[email protected]>
@@ -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 |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
exchanges = ['binance', 'coinbase', 'kucoin', 'binanceus'] | ||
exchange_name = exchanges[3] # allow pickable exchange_id from https://github.com/ccxt/ccxt/wiki/manual#exchanges |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
def get_best_triangular_opportunity(tickers: List[ShortTicker]) -> Tuple[List[ShortTicker], float]: | ||
# Build a directed graph of currencies | ||
return get_best_opportunity(tickers, 3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's great!
Hmm @Herklos not sure how to solve this... The profit is supposed to be However, sometimes it returns |
Is |
Yes, that works for me, let's do it that way |
@Herklos all checks have passed |
There was a problem hiding this 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
Implemented cycle detection, adapted tests, improved README, added comments.
This works on live data (example below):