[Ethereum] 아비트라지를 위한 여정 9편 - 언제 스왑할 것인가?

0xDave·2022년 10월 17일
0

Ethereum

목록 보기
48/112

Set Up 1 - Global Variables


def main():

    global spell_contract
    global sspell_contract
    global traderjoe_router_contract
    global spell
    global sspell

    try:
        network.connect("avax-main")
        
        # Avalanche supports EIP-1559 transactions, so we set the priority fee
        # and allow the base fee to change as needed
        network.priority_fee('5 gwei')
        # Can set a limit on maximum fee, if desired
        #network.max_fee('200 gwei')
    except:
        sys.exit(
            "Could not connect to Avalanche! Verify that brownie lists the Avalanche Mainnet using 'brownie networks list'"
        )

    try:
        global user
        user = accounts.load("test_account")
    except:
        sys.exit(
            "Could not load account! Verify that your account is listed using 'brownie accounts list' and that you are using the correct password. If you have not added an account, run 'brownie accounts' now."
        )

[... continued]

global


Set Up 2 - Contracts


print("\nContracts loaded:")
spell_contract = contract_load(SPELL_CONTRACT_ADDRESS, "Avalanche Token: SPELL")
sspell_contract = contract_load(SSPELL_CONTRACT_ADDRESS, "Avalanche Token: sSPELL")
router_contract = contract_load(
    TRADERJOE_ROUTER_CONTRACT_ADDRESS, "TraderJoe: Router"
)

spell = {
    "address": SPELL_CONTRACT_ADDRESS,
    "contract": spell_contract,
    "name": None,
    "symbol": None,
    "balance": None,
    "decimals": None,
}

sspell = {
    "address": SSPELL_CONTRACT_ADDRESS,
    "contract": sspell_contract,
    "name": None,
    "symbol": None,
    "balance": None,
    "decimals": None,
}

spell["symbol"] = get_token_symbol(spell["contract"])
spell["name"] = get_token_name(spell["contract"])
spell["balance"] = get_token_balance(spell_contract, user)
spell["decimals"] = get_token_decimals(spell_contract)

sspell["symbol"] = get_token_symbol(sspell["contract"])
sspell["name"] = get_token_name(sspell["contract"])
sspell["balance"] = get_token_balance(sspell_contract, user)
sspell["decimals"] = get_token_decimals(sspell_contract)

if (spell["balance"] == 0) and (sspell["balance"] == 0):
    sys.exit("No tokens found!")

[... continued]

지난 번에 만들었던 helper function을 활용했다.


Set Up 3 - Approval


# Confirm approvals for tokens
print("\nChecking Approvals:")

if get_approval(spell["contract"], router_contract, user):
    print(f"• {spell['symbol']} OK")
else:
    token_approve(spell["contract"], router_contract)

if get_approval(sspell["contract"], router_contract, user):
    print(f"• {sspell['symbol']} OK")
else:
    token_approve(sspell["contract"], router_contract)
  
try:
    with open(STAKING_RATE_FILENAME, "r") as file:
        base_staking_rate = float(file.read().strip())
        print(f"\nEthereum L1 Staking Rate: {base_staking_rate}")
except FileNotFoundError:
    sys.exit(
       "Cannot load the base Abracadabra SPELL/sSPELL staking rate. Run `python3 abra_rate.py` and try again."
    )

balance_refresh = True

[... continued]

Main Loop


while True:

    loop_start = time.time()

    try:
        with open(STAKING_RATE_FILENAME, "r") as file:
        if (result := float(file.read().strip())) != base_staking_rate:
            base_staking_rate = result
            print(f"Updated staking rate: {base_staking_rate}")
    except FileNotFoundError:
        sys.exit(
            "Cannot load the base Abracadabra SPELL/sSPELL staking rate. Run `python3 abra_rate.py` and try again."
        )

    if balance_refresh:
        time.sleep(10)
        spell["balance"] = get_token_balance(spell_contract, user)
        sspell["balance"] = get_token_balance(sspell_contract, user)
        print("\nAccount Balance:")
        print(
            f"• Token #1: {int(spell['balance']/(10**spell['decimals']))} {spell['symbol']} ({spell['name']})"
        )
        print(
            f"• Token #2: {int(sspell['balance']/(10**sspell['decimals']))} {sspell['symbol']} ({sspell['name']})"
        )
        print()
        balance_refresh = False
        last_ratio_spell_to_sspell = 0
        last_ratio_sspell_to_spell = 0

[... continued]

Main Loop2


# get quotes and execute SPELL -> sSPELL swaps only if we have a balance of SPELL
if spell["balance"]:

	#get_swap_rate을 실행해서 result에 할당. 성공적으로 실행했다면 if문을 통과할 것이다.
    if result := get_swap_rate(
        token_in_quantity=spell["balance"],
        token_in_address=spell["address"],
        token_out_address=sspell["address"],
        router=router_contract,
    ):
        spell_in, sspell_out = result
        ratio_spell_to_sspell = round(sspell_out / spell_in, 4)

        # print and save any updated swap values since last loop
        if ratio_spell_to_sspell != last_ratio_spell_to_sspell:
            print(
                f"{datetime.datetime.now().strftime('[%I:%M:%S %p]')} {spell['symbol']}{sspell['symbol']}: ({ratio_spell_to_sspell:.4f}/{1 / (base_staking_rate * (1 + THRESHOLD_SPELL_TO_SSPELL)):.4f})"
            )
            last_ratio_spell_to_sspell = ratio_spell_to_sspell
    else:
        # abandon the for loop to avoid re-using stale data
        break

    # execute SPELL -> sSPELL arb if trigger is satisfied
    if ratio_spell_to_sspell >= 1 / (
        base_staking_rate * (1 + THRESHOLD_SPELL_TO_SSPELL)
    ):
        print(
            f"*** EXECUTING SWAP OF {int(spell_in / (10**spell['decimals']))} {spell['symbol']} AT BLOCK {chain.height} ***"
        )
        if token_swap(
            token_in_quantity=spell_in,
            token_in_address=spell["address"],
            token_out_quantity=sspell_out,
            token_out_address=sspell["address"],
            router=router_contract,
        ):
            balance_refresh = True
            if ONE_SHOT:
                sys.exit("single shot complete!")

# get quotes and execute sSPELL -> SPELL swaps only if we have a balance of sSPELL
if sspell["balance"]:

    if result := get_swap_rate(
        token_in_quantity=sspell["balance"],
        token_in_address=sspell["address"],
        token_out_address=spell["address"],
        router=router_contract,
    ):
        sspell_in, spell_out = result
        ratio_sspell_to_spell = round(spell_out / sspell_in, 4)

        # print and save any updated swap values since last loop
        if ratio_sspell_to_spell != last_ratio_sspell_to_spell:
            print(
                f"{datetime.datetime.now().strftime('[%I:%M:%S %p]')} {sspell['symbol']}{spell['symbol']}: ({ratio_sspell_to_spell:.4f}/{base_staking_rate * (1 + THRESHOLD_SSPELL_TO_SPELL):.4f})"
            )
            last_ratio_sspell_to_spell = ratio_sspell_to_spell
    else:
        # abandon the for loop to avoid re-using stale data
        break

    # execute sSPELL -> SPELL arb if trigger is satisfied
    if ratio_sspell_to_spell >= base_staking_rate * (
        1 + THRESHOLD_SSPELL_TO_SPELL
    ):
        print(
            f"*** EXECUTING SWAP OF {int(sspell_in/(10**sspell['decimals']))} {sspell['symbol']} AT BLOCK {chain.height} ***"
        )
        if token_swap(
            token_in_quantity=sspell_in,
            token_in_address=sspell["address"],
            token_out_quantity=spell_out,
            token_out_address=spell["address"],
            router=router_contract,
        ):
            balance_refresh = True
            if ONE_SHOT:
                sys.exit("single shot complete!")

loop_end = time.time()

# Control the loop timing more precisely by measuring start and end time and sleeping as needed
if (loop_end - loop_start) >= LOOP_TIME:
    continue
else:
    time.sleep(LOOP_TIME - (loop_end - loop_start))
    continue

[... continued]

위 예제에서 바다코끼리 연산자(:=)가 사용됐다. 함수 리턴값을 변수에 할당해서 재사용할 수 있다!

sSPELL과 SPELL 토큰을 교환할 때 계산식을 살펴볼 필요가 있다.

# execute sSPELL -> SPELL arb if trigger is satisfied
if ratio_sspell_to_spell >= base_staking_rate * (
    1 + THRESHOLD_SSPELL_TO_SPELL
):

# execute SPELL -> sSPELL arb if trigger is satisfied
if ratio_spell_to_sspell >= 1 / (
    base_staking_rate * (1 + THRESHOLD_SPELL_TO_SSPELL)
):

base_staking_rate은 고정값이라고 생각하고 정해놓은 Threshold 퍼센트 이상으로 비율이 올라가거나 내려갈 때 스왑을 진행한다. 예를 들어서 base_staking_rate이 1.1이라고 하자. 1sSPELL = 1.1SPELL

그럼 sSPELL토큰을 언제 SPELL토큰으로 바꾸는 것이 이득일까? 당연히 1.1 이상일 때 바꾸는 것이 이득이다. 극단적으로 생각했을 때 1sSPELL = 2SPELL이면 돈복사가 가능하다. 그렇다면 반대로 SPELL 토큰을 언제 sSPELL 토큰으로 바꾸는 것이 이득일까? 1.1 이하일 때 바꾸는 것이 이득이다. 다시 한 번 극단적으로 생각했을 때 1sSPELL = 0.5SPELL이면 돈복사가 가능하다.

실제 시나리오를 생각해보자. 우리는 SPELL 토큰 갯수를 늘리는 것이 목표다. 처음 11개의 SPELL 토큰을 갖고 있다고 가정했을 때 평소에는 10개의 sSPELL로 교환할 수 있다. 극한의 효율을 원하는 우리는 1sSPELL = 0.5SPELL일 때 교환할 것이다. 우리의 바람대로 비율이 낮아졌을 때 스왑을 진행하면 22개의 sSPELL을 얻을 수 있다. 시간이 지나고 갑자기 1sSPELL = 2SPELL 비율이 됐을 때 다시 교환한다면 44개의 SPELL 토큰을 얻을 수 있다. 교환비율이 달라졌을 뿐인데 우리는 두 번의 스왑으로 300% 수익을 얻었다. 다소 극단적인 상황을 예시로 들었지만 이렇게 하는게 훨씬 이해하기 쉽다. 따라서 결론은 base_staking_rate을 두고 일정 비율을 위 아래로 둬서 그 이상으로 올라가거나 그 아래로 내려갈 때 서로 다른 방향으로 스왑을 진행한다는 것이다.


출처 및 참고자료


  1. 파이썬의 global과 nonlocal 키워드 사용법
  2. Bot Building — Setup, Main Loop and Swapping Logic
profile
Just BUIDL :)

0개의 댓글