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

[example project] LP Repositioning Bot #558

Merged
merged 19 commits into from
Dec 7, 2024
Merged

Conversation

calintje
Copy link
Contributor

@calintje calintje commented Nov 30, 2024

Title
[example project] LP Repositioning Bot

Details
This pull request introduces a Rust-based CLI bot designed to monitor and rebalance liquidity positions on Orca Whirlpools. The bot automates the process of tracking price deviations, closing out-of-range positions, and reopening new ones at adjusted ranges. It also includes prioritization fee management and customizable options to meet various user needs.

The bot calculates the center price of a position, checks its deviation against the whirlpool price and a user-defined threshold, and adjusts liquidity around the current price using the same width as the initial range. Users can configure parameters such as slippage tolerance, time intervals, and prioritization fees to optimize rebalancing according to their preferences.

Key features:

  • Automated position monitoring and rebalancing.
  • A dynamic priority fee calculation mechanism that simulates transactions to determine compute-unit consumption and applies user-defined caps on priority fees.
  • A retry mechanism for handling transient RPC or transaction failures.
  • User-friendly CLI output with clear details on configurations, token balances, and transaction statistics.

For a full explanation of the program's functionality and configuration options, please refer to README.

Copy link
Collaborator

@yugure-orca yugure-orca left a comment

Choose a reason for hiding this comment

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

How about adding README.md

  • to explain what this bot does.
  • to explain how to invoke (1 example lp-bot --position-mint-address xxxxx ...
  • to explain what preparation is required (need to have 1 position, wallet.json file)

How about putting RPC URL in main (reader will notice that it is defined as constant)

How about add "rust-sdk" / "ts-sdk" directory in examples ? (/examples/rust-sdk/lp-bod)

How about executing close and open in atomic because once open position fails, user need to open position manually

I think utility should have the following 2 functions:

  • add priority fee (jito tip may be overkill as an example, so ComputeBudget program is sufficient)
  • client-side retry

client-side retry is not scope of SDK, but dev often faces "transaction expired" error.
https://github.com/orca-so/whirlpools/blob/main/legacy-sdk/cli/src/utils/transaction_sender.ts#L110

How about displaying wallet balance for tokenA and tokenB (The balance will be rebalanced without swaps, so there may be a shortfall.)

…dk. Make atomic transaction for close and open position instructions. Add utility functions for priority fee and client-side retry. Work in progress: dislapying wallet balances.
@calintje calintje marked this pull request as ready for review December 4, 2024 12:38
@@ -0,0 +1,16 @@
[package]
name = "lp-bot"
Copy link
Member

Choose a reason for hiding this comment

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

Maybe something like:orca_whirlpools_repositioning_cli_example?

Copy link
Member

Choose a reason for hiding this comment

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

Also same goes for the folder examples/lp-bot is not very descriptive.


[dependencies]
clap = { version = "4.5.21", features = ["derive"] }
colored = "2.0"
Copy link
Member

Choose a reason for hiding this comment

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

Nit: format these to all use expanded object with explicit caret (^)

@@ -6,5 +6,6 @@ members = [
exclude = [
"rust-sdk",
"ts-sdk",
"docs"
"docs",
"examples"
Copy link
Member

Choose a reason for hiding this comment

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

Useful to add a readme to the examples folder with a little more description on each example?

The bot connects to Solana Mainnet Beta by default using:

```rust
const RPC_URL: &str = "https://api.mainnet-beta.solana.com";
Copy link
Member

Choose a reason for hiding this comment

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

Mainnet beta rpc url doesnt work for most things

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 noticed it barely worked with this project. I will add a recommendation to update the URL.

- `--position-mint-address` (required): The mint address of the position to monitor and rebalance.
- `--threshold` (optional): The percentage deviation from the center price at which rebalancing is triggered. Default: 1.0.
- `--interval` (optional): The time interval (in seconds) between checks. Default: 60.
- `--priority-fee-tier` (optional): The priority fee tier for transaction processing. Options:
Copy link
Member

Choose a reason for hiding this comment

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

Why not just let the user specify the percentile instead of these arbitrary levels?

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 copied this from Helius' documentation on their priority fee API: https://docs.helius.dev/solana-apis/priority-fee-api

I feel it's more user friendly this way and abstracts away how priority fees are calculated, but I don't have a very strong opinion about it. Happy to adjust.

max_priority_fee_lamports: u64,
) -> Result<Option<solana_sdk::instruction::Instruction>, Box<dyn std::error::Error>> {
if let Some(priority_fee_micro_lamports) = calculate_priority_fee(rpc, tier).await? {
let recent_blockhash = rpc.get_latest_blockhash().await?;
Copy link
Member

Choose a reason for hiding this comment

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

You can ask sim_tx to give you a recent blockhash (saved one rpc call)

if non_zero_fees.is_empty() {
return Ok(Some(0));
}
non_zero_fees.sort_unstable();
Copy link
Member

Choose a reason for hiding this comment

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

Why sort unstable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fromt the docs I understand that sort_unstable() is usually faster than sort(), because it doesn't try to preserve the order of equal elements. Since we're dealing with u64s, I think that's fine. Also, it requires less memory and performs the sort in-place.

Copy link
Member

Choose a reason for hiding this comment

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

Fair enough. I believe normal sort is also in-place tho

let mut non_zero_fees: Vec<u64> = prioritization_fees
.iter()
.map(|fee| fee.prioritization_fee)
.filter(|&fee| fee > 0) // Keep only non-zero fees
Copy link
Member

Choose a reason for hiding this comment

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

Why only non-zero?

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 remember implementing this myself a while back and used an article from Chainstack as reference: https://docs.chainstack.com/docs/solana-estimate-priority-fees-getrecentprioritizationfees#add-the-code

If this doesn't make a lot of sense, I'll remove this part.

}

fn create_priority_fee_instruction(unit_price: u64) -> solana_sdk::instruction::Instruction {
ComputeBudgetInstruction::set_compute_unit_price(unit_price)
Copy link
Member

Choose a reason for hiding this comment

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

You also need to set compute budget units instruction

ComputeBudgetInstruction::set_compute_unit_price(unit_price)
}

async fn retry_async<'a, F, Fut, T, E>(
Copy link
Member

Choose a reason for hiding this comment

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

Isn't there a package we can use for this?

Copy link
Member

@wjthieme wjthieme left a comment

Choose a reason for hiding this comment

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

🚢🚢🚢

"scripts": {
"build": "cargo build",
"format": "cargo clippy --fix --allow-dirty --allow-staged && cargo fmt",
"lint": "cargo clippy",
Copy link
Member

Choose a reason for hiding this comment

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

I changed this command in #522 so you might want to do that here too

@calintje calintje enabled auto-merge (squash) December 7, 2024 20:04
@calintje calintje merged commit 89ed689 into main Dec 7, 2024
5 checks passed
@calintje calintje deleted the calintje/example-lp-bot branch December 7, 2024 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants