-
Notifications
You must be signed in to change notification settings - Fork 1
Albion npv strat #71
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
base: 2025-04-22-folio
Are you sure you want to change the base?
Albion npv strat #71
Conversation
WalkthroughUpdates registry URLs for five existing strategies to a new commit hash and adds folio. Introduces base2 network support across settings and multiple strategy files. Adds four new strategy implementations (fixed-spread, folio, npv-dca, npv-dca-approx) with supporting documentation. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes
Areas requiring extra attention:
Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
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.
Actionable comments posted: 13
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/auction-dca.rain (1)
321-1582: Deployments and GUI wiring are complete; consider aligning doc URL SHAAll new deployments (
arbitrum2,base2, Pyth variants, and folio-style updates) have matching entries indeploymentsandgui.deployments, and the Pyth GUI flows correctly introduce thepyth-pairbinding plus the familiar budget/auction controls and token selectors. Functionally this looks sound.The GUI description URL is pinned to
e25bc4876b5ffb8bb28097b0ca66de291c75ff56, while the registry points strategies atb051cdb7b9ef5e65ab58a16a5b7a1c4eb522039d. If the markdown has changed alongside the strategy code, consider updating the GUI description URL to the same SHA to keep docs and implementation in sync.Based on learnings about SHA pinning in the registry, keeping docs pinned to the same commit as the
.rainfile would preserve a consistent snapshot.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (15)
registry(1 hunks)settings.yaml(5 hunks)src/auction-dca.rain(16 hunks)src/canary.rain(2 hunks)src/dynamic-spread.rain(2 hunks)src/fixed-limit.rain(10 hunks)src/fixed-spread.md(1 hunks)src/fixed-spread.rain(1 hunks)src/folio.md(1 hunks)src/folio.rain(1 hunks)src/grid.rain(1 hunks)src/npv-dca-approx.md(1 hunks)src/npv-dca-approx.rain(1 hunks)src/npv-dca.md(1 hunks)src/npv-dca.rain(1 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: 0xgleb
Repo: rainlanguage/rain.strategies PR: 45
File: registry:0-0
Timestamp: 2025-07-26T15:31:37.916Z
Learning: In the rain.strategies repository, the registry file uses full commit SHAs to pin strategy URLs as the current established pattern, rather than using tags or branches, as confirmed by project maintainer 0xgleb.
Learnt from: findolor
Repo: rainlanguage/rain.strategies PR: 59
File: src/fixed-limit.rain:68-68
Timestamp: 2025-09-01T12:19:42.343Z
Learning: In the rain.strategies repository, the team plans to replace contract addresses in the configuration files, and deployment-block values will be updated as part of that broader effort, so placeholder values like 1 are intentionally temporary.
📚 Learning: 2025-09-01T12:19:42.343Z
Learnt from: findolor
Repo: rainlanguage/rain.strategies PR: 59
File: src/fixed-limit.rain:68-68
Timestamp: 2025-09-01T12:19:42.343Z
Learning: In the rain.strategies repository, the team plans to replace contract addresses in the configuration files, and deployment-block values will be updated as part of that broader effort, so placeholder values like 1 are intentionally temporary.
Applied to files:
src/npv-dca-approx.rainsrc/npv-dca.rainsrc/fixed-limit.rainsrc/fixed-spread.rainregistrysrc/auction-dca.rainsrc/folio.rain
📚 Learning: 2025-09-04T12:59:34.219Z
Learnt from: thedavidmeister
Repo: rainlanguage/rain.strategies PR: 60
File: src/dynamic-spread.rain:924-929
Timestamp: 2025-09-04T12:59:34.219Z
Learning: In the Rain Strategies dynamic spread system, the io values cannot be zero due to business logic constraints where baseline uses any(cost-basis-io last-io) and cost-basis-multiplier is positive, making division-by-zero in inv(stored-last-io) mathematically impossible under normal operation.
Applied to files:
src/npv-dca-approx.rainsrc/npv-dca.rainsrc/fixed-spread.rainregistrysrc/folio.rain
📚 Learning: 2025-06-19T03:59:46.063Z
Learnt from: Siddharth2207
Repo: rainlanguage/rain.strategies PR: 42
File: src/fixed-limit.rain:19-23
Timestamp: 2025-06-19T03:59:46.063Z
Learning: In rain.strategies configuration files, `arbitrum2` is an intentional alias for the Arbitrum network (same chain-id 42161) but with updated contract addresses. It serves as a deployment target for newer contract versions while maintaining the existing `arbitrum` configuration during transition periods.
Applied to files:
src/dynamic-spread.rainsrc/fixed-limit.rainsrc/canary.rainregistrysrc/auction-dca.rainsettings.yamlsrc/folio.rain
📚 Learning: 2025-04-30T06:32:43.860Z
Learnt from: findolor
Repo: rainlanguage/rain.strategies PR: 29
File: settings.yaml:2-6
Timestamp: 2025-04-30T06:32:43.860Z
Learning: The `network-id` field is intentionally omitted for the flare network in the `settings.yaml` configuration file, unlike other networks which include both `chain-id` and `network-id` fields.
Applied to files:
src/fixed-limit.rain
📚 Learning: 2025-07-03T09:47:48.019Z
Learnt from: thedavidmeister
Repo: rainlanguage/rain.strategies PR: 28
File: src/folio.rain:28-42
Timestamp: 2025-07-03T09:47:48.019Z
Learning: In Rain language strategy files, thedavidmeister prefers intentionally oversimplified setups over complex validation logic, especially in initial implementations like the folio strategy configuration.
Applied to files:
src/fixed-spread.rainregistrysrc/folio.rain
📚 Learning: 2025-07-03T09:47:14.894Z
Learnt from: thedavidmeister
Repo: rainlanguage/rain.strategies PR: 28
File: src/folio.rain:115-122
Timestamp: 2025-07-03T09:47:14.894Z
Learning: In Rain language folio strategy, the zero balance check using `every(input-vault-before() output-vault-before())` is specifically implemented to prevent trades from executing while users are depositing or withdrawing from vaults, serving as a safety mechanism against race conditions during vault operations.
Applied to files:
src/fixed-spread.rainregistrysrc/folio.rain
📚 Learning: 2025-07-26T15:31:37.916Z
Learnt from: 0xgleb
Repo: rainlanguage/rain.strategies PR: 45
File: registry:0-0
Timestamp: 2025-07-26T15:31:37.916Z
Learning: In the rain.strategies repository, the registry file uses full commit SHAs to pin strategy URLs as the current established pattern, rather than using tags or branches, as confirmed by project maintainer 0xgleb.
Applied to files:
registry
📚 Learning: 2025-04-30T06:32:49.466Z
Learnt from: findolor
Repo: rainlanguage/rain.strategies PR: 29
File: settings.yaml:1-35
Timestamp: 2025-04-30T06:32:49.466Z
Learning: In the Rain Strategies project, the `matchain` network referenced in `orderbooks` and `deployers` sections of the configuration YAML doesn't require an explicit definition in the `networks` section.
Applied to files:
src/auction-dca.rainsrc/folio.rain
🪛 markdownlint-cli2 (0.18.1)
src/npv-dca-approx.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
src/npv-dca.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
4-4: Files should end with a single newline character
(MD047, single-trailing-newline)
src/fixed-spread.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
1-1: Files should end with a single newline character
(MD047, single-trailing-newline)
🔇 Additional comments (15)
settings.yaml (3)
14-19: LGTM: base2 network alias follows established pattern.The addition of
base2as an alias for the Base network (chain-id 8453) with updated contract addresses follows the established pattern used forarbitrum2, which serves as a deployment target for newer contract versions during transition periods.Based on learnings, this is an intentional configuration pattern in the rain.strategies repository.
59-72: Updated subgraph and metaboard endpoints for base2 and arbitrum2.The additions introduce newer subgraph endpoints (dated 2025-08-18) for
base2andarbitrum2, while their respective base/arbitrum counterparts retain older endpoints. The metaboard endpoints are consistently shared between each pair.
120-122: Remove base2 deployer address inconsistency concern.Address differences between configuration files may reflect intentional temporary values during the deployment phase. Per your team's documented practices, placeholder values in configuration are expected to be updated as part of the broader deployment effort. Verify that both addresses are intentionally temporary or clarify the correct canonical address before flagging this as an issue.
src/dynamic-spread.rain (1)
1-1: LGTM: Version bump and metaboard URL update.The version increment to 2 and the arbitrum metaboard URL update to the newer
metadata-arbitrum-oneendpoint are consistent with the broader infrastructure updates in this PR. No functional logic changes.Also applies to: 122-122
src/canary.rain (1)
1-1: LGTM: Version bump and metaboard URL update.The version increment to 2 and the arbitrum metaboard URL update align with the infrastructure modernization across all strategy files in this PR. No functional changes to the canary strategy logic.
Also applies to: 44-44
src/grid.rain (1)
1-1: LGTM: Version bump.The version increment to 2 maintains consistency with other strategy files in this PR. No functional changes.
src/fixed-spread.rain (2)
11-12: Verify the placeholder subgraph URL.The subgraph URL
https://example.com/subgraphat lines 11-12 requires verification to confirm it is a placeholder rather than intentional configuration, and to identify the correct production URL.
204-208: Verify Pyth oracle confidence parameter format and value.The confidence parameter
0.0000000000000036uses a decimal format, but Pyth documentation specifies that confidence should be an integer mantissa (actual_confidence = conf × 10^exponent) applied with the same exponent as the price. Confirm this value aligns with Pyth's specification and that the Rain language implementation correctly interprets this format.src/folio.rain (1)
22-23: Placeholder subgraph URLs detected.Both subgraph entries use
https://example.com/subgraph. Verify these are intentionally placeholder values or update to actual subgraph endpoints before production deployment.src/fixed-limit.rain (2)
16-21: LGTM!The
base2network definition follows the established pattern for network aliases with updated contract addresses. The configuration is consistent with existing networks.
78-79: Verify address consistency between base and base2.
base2uses the same deployer address asbase(line 95 vs 97:0xC1A14cE2fd58A3A2f99deCb8eDd866204eE07f8D) but a different orderbook address (0x881cf4c0764e733d9C387f3858eE87CcA04AFFe0vs0xd2938e7c9fe3597f78832ce780feb61945c377d7). Confirm this is intentional for the updated orderbook deployment.Also applies to: 96-97
registry (1)
1-6: Registry pinning and folio addition look consistentAll strategy entries (including the new
folio) are pinned to the same full commit SHA and follow the establishedraw.githubusercontent.com/.../src/*.rainpattern, matching the registry convention of SHA pinning.Based on learnings, this aligns with the existing registry style of using full commit SHAs.
src/auction-dca.rain (3)
1-108: Alias network wiring forarbitrum2/base2is coherent
arbitrum2andbase2reuse the same RPCs, chain/network IDs, and currency asarbitrum/base, and have correspondingsubgraphs,metaboards,orderbooks, anddeployersentries wired up consistently. This matches the documented pattern of alias networks with updated contract addresses.Based on learnings, the alias-network setup for
arbitrum2is intentional and this extension tobase2follows the same approach.
119-319: Orders and scenarios (including Pyth variants) are wired consistently
ordersandscenariosforarbitrum2andbase2mirror the existing networks, and the new Pyth scenarios correctly reference the alias orderbooks/deployers and thepyth-price-baseline/pyth-price-baseline-invfunctions. No mismatched names or missing backing definitions stand out.
1983-2103: Pyth helper words and binding comment integrate cleanlyThe new
#pyth-paircomment pluspyth-price-baseline/pyth-price-baseline-invhelpers are consistent with how other baseline helpers are defined and are correctly referenced by the new Pyth scenarios and GUI bindings. No structural or naming issues spotted.
| @@ -0,0 +1 @@ | |||
| This strategy has the ratio track a benchmark price (e.g. pyth) with a fixed multiplier. As long as that multiplier is >1, the strategy will always offer to trade at a premium to the benchmark, thereby increasing the likelihood that the deployer can "buy low sell high". No newline at end of file | |||
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.
🧹 Nitpick | 🔵 Trivial
Fix documentation formatting issues.
The documentation file has minor formatting issues flagged by static analysis:
- Missing top-level heading (MD041)
- Missing trailing newline (MD047)
Apply this diff to improve formatting:
+# Fixed Spread Strategy
+
This strategy has the ratio track a benchmark price (e.g. pyth) with a fixed multiplier. As long as that multiplier is >1, the strategy will always offer to trade at a premium to the benchmark, thereby increasing the likelihood that the deployer can "buy low sell high".
+📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| This strategy has the ratio track a benchmark price (e.g. pyth) with a fixed multiplier. As long as that multiplier is >1, the strategy will always offer to trade at a premium to the benchmark, thereby increasing the likelihood that the deployer can "buy low sell high". | |
| # Fixed Spread Strategy | |
| This strategy has the ratio track a benchmark price (e.g. pyth) with a fixed multiplier. As long as that multiplier is >1, the strategy will always offer to trade at a premium to the benchmark, thereby increasing the likelihood that the deployer can "buy low sell high". | |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
1-1: Files should end with a single newline character
(MD047, single-trailing-newline)
🤖 Prompt for AI Agents
In src/fixed-spread.md around lines 1 to 1, the markdown is missing a top-level
heading and a trailing newline; add a descriptive H1 heading at the top (e.g.,
"# Fixed Spread Strategy") and ensure the file ends with a single trailing
newline character so the file conforms to MD041 and MD047.
| tokens: | ||
| flare-wflr: | ||
| network: flare | ||
| address: 0x1D80c49BbBCd1C0911346656B529DF9E5c2F783d | ||
| decimals: 18 | ||
| flare-sflr: | ||
| network: flare | ||
| address: 0x12e605bc104e93B45e1aD99F9e555f659051c2BB | ||
| decimals: 18 |
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.
🛠️ Refactor suggestion | 🟠 Major
Remove unused Flare token definitions.
The flare-wflr and flare-sflr token definitions appear to be unused in this configuration, which focuses exclusively on the base2 network. These may be copy-paste residue from another strategy file.
Apply this diff to remove the unused tokens:
-tokens:
- flare-wflr:
- network: flare
- address: 0x1D80c49BbBCd1C0911346656B529DF9E5c2F783d
- decimals: 18
- flare-sflr:
- network: flare
- address: 0x12e605bc104e93B45e1aD99F9e555f659051c2BB
- decimals: 18
-📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| tokens: | |
| flare-wflr: | |
| network: flare | |
| address: 0x1D80c49BbBCd1C0911346656B529DF9E5c2F783d | |
| decimals: 18 | |
| flare-sflr: | |
| network: flare | |
| address: 0x12e605bc104e93B45e1aD99F9e555f659051c2BB | |
| decimals: 18 |
🤖 Prompt for AI Agents
In src/fixed-spread.rain around lines 25 to 33, remove the unused flare-wflr and
flare-sflr token entries (their network/address/decimals mappings) which appear
to be copy-paste residue; after deleting those entries, ensure the tokens: map
remains valid YAML (no dangling commas or incorrect indentation) and run a quick
grep/lookup in the repo to confirm no other references to these token keys exist
before committing.
| description: Select the token you want to purchase | ||
| base-pyth-inv: | ||
| name: Base pyth buy shares | ||
| description: Deploy an fixed-spread trategy on Base using Pyth price feeds to buy shares. |
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.
Fix typo in description.
Line 135 contains "trategy" which should be "strategy".
Apply this diff:
- description: Deploy an fixed-spread trategy on Base using Pyth price feeds to buy shares.
+ description: Deploy an fixed-spread strategy on Base using Pyth price feeds to buy shares.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| description: Deploy an fixed-spread trategy on Base using Pyth price feeds to buy shares. | |
| description: Deploy an fixed-spread strategy on Base using Pyth price feeds to buy shares. |
🤖 Prompt for AI Agents
In src/fixed-spread.rain at line 135, the description contains a typo: "trategy"
should be "strategy"; update the description text to read "Deploy a fixed-spread
strategy on Base using Pyth price feeds to buy shares." ensuring grammar (change
"an" to "a") and correct spelling.
| - binding: multiplier | ||
| name: Benchmark price multiplier | ||
| description: | | ||
| The multiplier to be applied to the benchmark when calculating the price the strategy will offer at | ||
| default: 1.001 |
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.
Inconsistent binding name between deployments.
The base-pyth deployment uses baseline-multiplier (line 121) while base-pyth-inv uses multiplier (line 179). This inconsistency could cause configuration errors or unexpected behavior.
Apply this diff to maintain consistency:
- - binding: multiplier
+ - binding: baseline-multiplier
name: Benchmark price multiplier
description: |
The multiplier to be applied to the benchmark when calculating the price the strategy will offer at
default: 1.001📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - binding: multiplier | |
| name: Benchmark price multiplier | |
| description: | | |
| The multiplier to be applied to the benchmark when calculating the price the strategy will offer at | |
| default: 1.001 | |
| - binding: baseline-multiplier | |
| name: Benchmark price multiplier | |
| description: | | |
| The multiplier to be applied to the benchmark when calculating the price the strategy will offer at | |
| default: 1.001 |
🤖 Prompt for AI Agents
In src/fixed-spread.rain around lines 179 to 183, the binding key is named
"multiplier" while the other deployment uses "baseline-multiplier" (line 121);
rename the binding from "multiplier" to "baseline-multiplier" and keep the same
name, description and default (1.001) so both deployments use the identical
binding name, and update any local references in this file that read the old
"multiplier" key to the new "baseline-multiplier".
| # Folio Rebalancer | ||
|
|
||
| Folio rebalances a portfolio of tokens to maintain an equal weight distribution across all assets. |
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.
🧹 Nitpick | 🔵 Trivial
Minimal but clear documentation.
The Folio Rebalancer documentation is brief but provides a clear high-level description. Consider expanding this in future iterations to include usage examples, configuration parameters, or rebalancing logic details.
🤖 Prompt for AI Agents
In src/folio.md around lines 1 to 3, the documentation is minimal; expand the
README to include a short usage section, configuration parameters, and a brief
summary of the rebalancing algorithm: add example CLI/SDK usage (commands or
code snippets), list configurable options (rebalance interval, tolerance
thresholds, allowed tokens, fee settings), and outline the rebalancing
steps/logic (how weights are computed, trade execution order, and edge-case
handling). Keep each addition concise with headings and a one-paragraph
explanation for each item to improve clarity for future readers.
| #io-for-epoch | ||
| epoch:, | ||
| last-io: call<'get-last-trade>(), | ||
| max-next-trade: any(mul(last-io next-trade-multiplier) call<'initial-io-fn>()), | ||
| baseline-next-trade: mul(last-io next-trade-baseline-multiplier), | ||
| real-baseline: max(baseline-next-trade call<'baseline-fn>()), | ||
| variable-component: saturating-sub(max-next-trade real-baseline), | ||
| above-baseline: mul(variable-component call<'halflife>(epoch)), | ||
| _: add(real-baseline above-baseline); |
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.
io-for-epoch only captures one value from get-last-trade which returns two.
get-last-trade returns (last-time, last-io) but line 512 only captures last-io. The last-time value is silently dropped. If this is intentional, consider using _ to explicitly discard the first return value for clarity.
Apply this diff:
#io-for-epoch
epoch:,
-last-io: call<'get-last-trade>(),
+_ last-io: call<'get-last-trade>(),Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/npv-dca-approx.rain around lines 510 to 518, the call to get-last-trade
only binds last-io but get-last-trade returns (last-time, last-io), so last-time
is being silently dropped; update the binding to capture both return values
(e.g., last-time last-io) or explicitly discard the first value with _ last-io
to make intent clear, then use last-io as before (no other logic changes).
| This is a variant of the auction-dca strategy. In this strategy, the floor price for the auction is calculated as the "fair value" of the token based on present value of future cash flows. The production profiles and discount rates are hardcoded. The fair value will vary based on: | ||
| - Benchmark oil prices, which are fetched from pyth oracle | ||
| - The current time (as time progresses more of the production gets discarded as already historical) | ||
| - Token supply, if new mints occur No newline at end of file |
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.
🧹 Nitpick | 🔵 Trivial
Fix documentation formatting issues.
The documentation file has minor formatting issues flagged by static analysis:
- Missing top-level heading (MD041)
- Missing trailing newline (MD047)
Apply this diff to improve formatting:
+# NPV-based DCA Strategy
+
This is a variant of the auction-dca strategy. In this strategy, the floor price for the auction is calculated as the "fair value" of the token based on present value of future cash flows. The production profiles and discount rates are hardcoded. The fair value will vary based on:
- Benchmark oil prices, which are fetched from pyth oracle
- The current time (as time progresses more of the production gets discarded as already historical)
- Token supply, if new mints occur
+📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| This is a variant of the auction-dca strategy. In this strategy, the floor price for the auction is calculated as the "fair value" of the token based on present value of future cash flows. The production profiles and discount rates are hardcoded. The fair value will vary based on: | |
| - Benchmark oil prices, which are fetched from pyth oracle | |
| - The current time (as time progresses more of the production gets discarded as already historical) | |
| - Token supply, if new mints occur | |
| # NPV-based DCA Strategy | |
| This is a variant of the auction-dca strategy. In this strategy, the floor price for the auction is calculated as the "fair value" of the token based on present value of future cash flows. The production profiles and discount rates are hardcoded. The fair value will vary based on: | |
| - Benchmark oil prices, which are fetched from pyth oracle | |
| - The current time (as time progresses more of the production gets discarded as already historical) | |
| - Token supply, if new mints occur | |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
4-4: Files should end with a single newline character
(MD047, single-trailing-newline)
🤖 Prompt for AI Agents
In src/npv-dca.md around lines 1 to 4, the Markdown file is missing a top-level
heading and a trailing newline; add a clear H1 title (e.g., "# NPV DCA Strategy"
or an appropriate descriptive top-level heading) at the top of the file and
ensure the file ends with a newline character so the document passes MD041 and
MD047 checks.
| version: 2 | ||
|
|
||
| networks: | ||
| base2: | ||
| rpcs: | ||
| - https://base-rpc.publicnode.com | ||
| chain-id: 8453 | ||
| network-id: 8453 | ||
| currency: ETH | ||
|
|
||
| subgraphs: | ||
| base2: https://example.com/subgraph | ||
|
|
||
| metaboards: | ||
| base2: https://api.goldsky.com/api/public/project_clv14x04y9kzi01saerx7bxpg/subgraphs/metadata-base/2025-07-06-594f/gn | ||
|
|
||
| orderbooks: | ||
| base2: | ||
| address: 0x881cf4c0764e733d9C387f3858eE87CcA04AFFe0 | ||
|
|
||
| deployers: | ||
| base2: | ||
| address: 0xC1A14cE2fd58A3A2f99deCb8eDd866204eE07f8D | ||
|
|
||
| tokens: | ||
| base-usdc: | ||
| network: base2 | ||
| address: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | ||
| decimals: 6 | ||
|
|
||
| orders: | ||
| base2: | ||
| orderbook: base2 | ||
| inputs: | ||
| - token: input | ||
| outputs: | ||
| - token: output | ||
|
|
||
| scenarios: | ||
| base2: | ||
| orderbook: base2 | ||
| deployer: base2 | ||
| runs: 1 | ||
| bindings: | ||
| raindex-subparser: 0xb05BC61963B6bEBAe9C2FEd827de600E99FB83fD | ||
| pyth-subparser: 0x2fbBe5715c3b40aD455992dD75129e7e0733ec7a | ||
| baseline-fn: '''npv-baseline' | ||
| initial-io-fn: '''npv-baseline' | ||
| next-trade-baseline-multiplier: 0 | ||
| shy-epoch: 0.05 | ||
|
|
||
| deployments: | ||
| base2: | ||
| order: base2 | ||
| scenario: base2 | ||
|
|
||
| gui: | ||
| name: NPV-based DCA Strategy | ||
| description: A strategy that sells tokens at or above their calculated NPV floor price. | ||
| short-description: Sells tokens using NPV of future cashflows as the floor price. | ||
| deployments: | ||
| base2: | ||
| name: Base NPV DCA | ||
| description: Deploy an NPV-based DCA strategy on Base. | ||
| deposits: | ||
| - token: output | ||
| fields: | ||
| - binding: pyth-feed | ||
| name: Pyth price feed ID | ||
| description: The Pyth feed ID for the commodity price (e.g., natural gas). | ||
| - binding: price-adjustment | ||
| name: Price adjustment | ||
| description: Amount to subtract from Pyth price (e.g., 1.3 for transport/basis differential). | ||
| default: 1.3 | ||
| - binding: disc-month | ||
| name: Monthly discount rate | ||
| description: Monthly discount rate as decimal (e.g., 0.008 for 0.8%). | ||
| default: 0.008 | ||
| - binding: time-per-amount-epoch | ||
| name: Budget period (seconds) | ||
| description: The budget period for amount streaming. | ||
| show-custom-field: true | ||
| default: 86400 | ||
| presets: | ||
| - name: Per day (86400) | ||
| value: 86400 | ||
| - name: Per week (604800) | ||
| value: 604800 | ||
| - binding: amount-per-epoch | ||
| name: Budget per period | ||
| description: Amount of tokens to sell per budget period. | ||
| - binding: max-trade-amount | ||
| name: Maximum trade size | ||
| description: Maximum tokens to sell in a single trade. | ||
| - binding: min-trade-amount | ||
| name: Minimum trade size | ||
| description: Minimum tokens to sell in a single trade. | ||
| - binding: time-per-trade-epoch | ||
| name: Auction period (seconds) | ||
| description: Time between auction price halvenings. | ||
| show-custom-field: true | ||
| default: 3600 | ||
| presets: | ||
| - name: Every hour (3600) | ||
| value: 3600 | ||
| - name: Every 2 hours (7200) | ||
| value: 7200 | ||
| - binding: next-trade-multiplier | ||
| name: Auction start multiplier | ||
| description: Multiplier above last trade to start next auction. | ||
| show-custom-field: true | ||
| default: 1.01 | ||
| presets: | ||
| - name: 1.01x | ||
| value: 1.01 | ||
| - name: 1.02x | ||
| value: 1.02 | ||
| - name: 1.05x | ||
| value: 1.05 | ||
| select-tokens: | ||
| - key: output | ||
| name: Token to Sell | ||
| description: Select the token you want to sell (represents cashflow ownership) | ||
| - key: input | ||
| name: Token to Buy | ||
| description: Select the token you want to receive (e.g., USDC) | ||
|
|
||
| --- | ||
| #raindex-subparser !Raindex subparser. | ||
| #pyth-subparser !Pyth oracle subparser. | ||
|
|
||
| #time-per-amount-epoch !Duration of one unit of streaming amount halflife. | ||
| #amount-per-epoch !Amount of output token to approve for selling per epoch. | ||
| #min-trade-amount !Each trade must be at least this many output tokens. | ||
| #max-trade-amount !Each trade will be capped at this many tokens. | ||
|
|
||
| #time-per-trade-epoch !Duration of one unit of io ratio halflife. | ||
| #shy-epoch !Epoch below which only the minimum amount is offered. | ||
|
|
||
| #baseline-fn !Function to calculate the baseline (floor) price. | ||
| #initial-io-fn !Function to calculate the initial io ratio. | ||
|
|
||
| #next-trade-multiplier !Start next auction at this x the last trade. | ||
| #next-trade-baseline-multiplier !Lifts baseline relative to previous trade. | ||
|
|
||
| #pyth-feed !The Pyth feed ID for the commodity price. | ||
| #price-adjustment !Amount to subtract from Pyth price for basis/transport. | ||
| #disc-month !Monthly discount rate (e.g., 0.008 = 0.8%). | ||
| #avg-month-seconds 2629746 | ||
|
|
||
| /* ============ PRODUCTION SCHEDULE (EOM timestamps and production amounts) ============ */ | ||
| /* Each row: prev_eom timestamp, this_eom timestamp, production amount */ | ||
|
|
||
| #row0-prev 1746057599 | ||
| #row0-eom 1748735999 | ||
| #row0-prod 347.76 | ||
|
|
||
| #row1-prev 1748735999 | ||
| #row1-eom 1751327999 | ||
| #row1-prod 330.885 | ||
|
|
||
| #row2-prev 1751327999 | ||
| #row2-eom 1754006399 | ||
| #row2-prod 336.24 | ||
|
|
||
| #row3-prev 1754006399 | ||
| #row3-eom 1756684799 | ||
| #row3-prod 330.615 | ||
|
|
||
| #row4-prev 1756684799 | ||
| #row4-eom 1759276799 | ||
| #row4-prod 314.64 | ||
|
|
||
| #row5-prev 1759276799 | ||
| #row5-eom 1761955199 | ||
| #row5-prod 319.725 | ||
|
|
||
| #row6-prev 1761955199 | ||
| #row6-eom 1764547199 | ||
| #row6-prod 304.245 | ||
|
|
||
| #row7-prev 1764547199 | ||
| #row7-eom 1767225599 | ||
| #row7-prod 302.85 | ||
|
|
||
| #row8-prev 1767225599 | ||
| #row8-eom 1769903999 | ||
| #row8-prod 297.72 | ||
|
|
||
| #row9-prev 1769903999 | ||
| #row9-eom 1772323199 | ||
| #row9-prod 252.675 | ||
|
|
||
| #row10-prev 1772323199 | ||
| #row10-eom 1775001599 | ||
| #row10-prod 280.08 | ||
|
|
||
| #row11-prev 1775001599 | ||
| #row11-eom 1777593599 | ||
| #row11-prod 136.26 | ||
|
|
||
| #row12-prev 1777593599 | ||
| #row12-eom 1780271999 | ||
| #row12-prod 397.125 | ||
|
|
||
| #row13-prev 1780271999 | ||
| #row13-eom 1782863999 | ||
| #row13-prod 339.75 | ||
|
|
||
| #row14-prev 1782863999 | ||
| #row14-eom 1785542399 | ||
| #row14-prod 328.095 | ||
|
|
||
| #row15-prev 1785542399 | ||
| #row15-eom 1788220799 | ||
| #row15-prod 305.1 | ||
|
|
||
| #row16-prev 1788220799 | ||
| #row16-eom 1790812799 | ||
| #row16-prod 301.32 | ||
|
|
||
| #row17-prev 1790812799 | ||
| #row17-eom 1793491199 | ||
| #row17-prod 317.025 | ||
|
|
||
| #row18-prev 1793491199 | ||
| #row18-eom 1796083199 | ||
| #row18-prod 307.665 | ||
|
|
||
| #row19-prev 1796083199 | ||
| #row19-eom 1798761599 | ||
| #row19-prod 311.355 | ||
|
|
||
| #row20-prev 1798761599 | ||
| #row20-eom 1801439999 | ||
| #row20-prod 273.96 | ||
|
|
||
| #row21-prev 1801439999 | ||
| #row21-eom 1803859199 | ||
| #row21-prod 221.58 | ||
|
|
||
| #row22-prev 1803859199 | ||
| #row22-eom 1806537599 | ||
| #row22-prod 229.14 | ||
|
|
||
| #row23-prev 1806537599 | ||
| #row23-eom 1809129599 | ||
| #row23-prod 219.375 | ||
|
|
||
| #row24-prev 1809129599 | ||
| #row24-eom 1811807999 | ||
| #row24-prod 227.655 | ||
|
|
||
| #row25-prev 1811807999 | ||
| #row25-eom 1814399999 | ||
| #row25-prod 217.08 | ||
|
|
||
| #row26-prev 1814399999 | ||
| #row26-eom 1817078399 | ||
| #row26-prod 217.755 | ||
|
|
||
| #row27-prev 1817078399 | ||
| #row27-eom 1819756799 | ||
| #row27-prod 203.85 | ||
|
|
||
| #row28-prev 1819756799 | ||
| #row28-eom 1822348799 | ||
| #row28-prod 184.455 | ||
|
|
||
| #row29-prev 1822348799 | ||
| #row29-eom 1825027199 | ||
| #row29-prod 177.885 | ||
|
|
||
| #row30-prev 1825027199 | ||
| #row30-eom 1827619199 | ||
| #row30-prod 159.84 | ||
|
|
||
| #row31-prev 1827619199 | ||
| #row31-eom 1830297599 | ||
| #row31-prod 151.02 | ||
|
|
||
| /* ============ STATE KEYS ============ */ | ||
| #last-trade-time-key "last-trade-time" | ||
| #last-trade-io-key "last-trade-io" | ||
| #initial-time-key "initial-time" | ||
| #amount-used-key "amount-used" | ||
|
|
||
| /* ============ DISCOUNT FACTOR HELPER ============ */ | ||
| /* Returns discount factor from now to payment time */ | ||
| /* df = (1 + r)^(-t) where t is time in months */ | ||
| #discount-factor | ||
| paytime:, | ||
| time-to-payment: saturating-sub(paytime now()), | ||
| months-to-payment: div(time-to-payment avg-month-seconds), | ||
| discount-base: inv(add(1 disc-month)), | ||
| _: power(discount-base months-to-payment); | ||
|
|
||
| /* ============ STEP FUNCTION HELPER ============ */ | ||
| /* Returns 1 if prev < t <= eom, else 0 */ | ||
| #step-eom | ||
| t prev-eom this-eom:, | ||
| after-prev: greater-than(t prev-eom), | ||
| at-or-before-eom: greater-than-or-equal-to(this-eom t), | ||
| _: mul(after-prev at-or-before-eom); | ||
|
|
||
| /* ============ GET ADJUSTED PRICE ============ */ | ||
| #get-adjusted-price | ||
| raw-price: pyth-price(pyth-feed 0.0000000000000036), | ||
| _: saturating-sub(raw-price price-adjustment); | ||
|
|
||
| /* ============ NPV CALCULATION ============ */ | ||
| #npv-baseline | ||
| t: now(), | ||
| adj-price: call<'get-adjusted-price>(), | ||
|
|
||
| /* Determine which month we're in using step indicators */ | ||
| i0: call<'step-eom>(t row0-prev row0-eom), | ||
| i1: call<'step-eom>(t row1-prev row1-eom), | ||
| i2: call<'step-eom>(t row2-prev row2-eom), | ||
| i3: call<'step-eom>(t row3-prev row3-eom), | ||
| i4: call<'step-eom>(t row4-prev row4-eom), | ||
| i5: call<'step-eom>(t row5-prev row5-eom), | ||
| i6: call<'step-eom>(t row6-prev row6-eom), | ||
| i7: call<'step-eom>(t row7-prev row7-eom), | ||
| i8: call<'step-eom>(t row8-prev row8-eom), | ||
| i9: call<'step-eom>(t row9-prev row9-eom), | ||
| i10: call<'step-eom>(t row10-prev row10-eom), | ||
| i11: call<'step-eom>(t row11-prev row11-eom), | ||
| i12: call<'step-eom>(t row12-prev row12-eom), | ||
| i13: call<'step-eom>(t row13-prev row13-eom), | ||
| i14: call<'step-eom>(t row14-prev row14-eom), | ||
| i15: call<'step-eom>(t row15-prev row15-eom), | ||
| i16: call<'step-eom>(t row16-prev row16-eom), | ||
| i17: call<'step-eom>(t row17-prev row17-eom), | ||
| i18: call<'step-eom>(t row18-prev row18-eom), | ||
| i19: call<'step-eom>(t row19-prev row19-eom), | ||
| i20: call<'step-eom>(t row20-prev row20-eom), | ||
| i21: call<'step-eom>(t row21-prev row21-eom), | ||
| i22: call<'step-eom>(t row22-prev row22-eom), | ||
| i23: call<'step-eom>(t row23-prev row23-eom), | ||
| i24: call<'step-eom>(t row24-prev row24-eom), | ||
| i25: call<'step-eom>(t row25-prev row25-eom), | ||
| i26: call<'step-eom>(t row26-prev row26-eom), | ||
| i27: call<'step-eom>(t row27-prev row27-eom), | ||
| i28: call<'step-eom>(t row28-prev row28-eom), | ||
| i29: call<'step-eom>(t row29-prev row29-eom), | ||
| i30: call<'step-eom>(t row30-prev row30-eom), | ||
| i31: call<'step-eom>(t row31-prev row31-eom), | ||
|
|
||
| /* Recover the current month's boundaries and production */ | ||
| next-eom: add( | ||
| mul(i0 row0-eom) mul(i1 row1-eom) mul(i2 row2-eom) mul(i3 row3-eom) | ||
| mul(i4 row4-eom) mul(i5 row5-eom) mul(i6 row6-eom) mul(i7 row7-eom) | ||
| mul(i8 row8-eom) mul(i9 row9-eom) mul(i10 row10-eom) mul(i11 row11-eom) | ||
| mul(i12 row12-eom) mul(i13 row13-eom) mul(i14 row14-eom) mul(i15 row15-eom) | ||
| mul(i16 row16-eom) mul(i17 row17-eom) mul(i18 row18-eom) mul(i19 row19-eom) | ||
| mul(i20 row20-eom) mul(i21 row21-eom) mul(i22 row22-eom) mul(i23 row23-eom) | ||
| mul(i24 row24-eom) mul(i25 row25-eom) mul(i26 row26-eom) mul(i27 row27-eom) | ||
| mul(i28 row28-eom) mul(i29 row29-eom) mul(i30 row30-eom) mul(i31 row31-eom) | ||
| ), | ||
| prev-eom: add( | ||
| mul(i0 row0-prev) mul(i1 row1-prev) mul(i2 row2-prev) mul(i3 row3-prev) | ||
| mul(i4 row4-prev) mul(i5 row5-prev) mul(i6 row6-prev) mul(i7 row7-prev) | ||
| mul(i8 row8-prev) mul(i9 row9-prev) mul(i10 row10-prev) mul(i11 row11-prev) | ||
| mul(i12 row12-prev) mul(i13 row13-prev) mul(i14 row14-prev) mul(i15 row15-prev) | ||
| mul(i16 row16-prev) mul(i17 row17-prev) mul(i18 row18-prev) mul(i19 row19-prev) | ||
| mul(i20 row20-prev) mul(i21 row21-prev) mul(i22 row22-prev) mul(i23 row23-prev) | ||
| mul(i24 row24-prev) mul(i25 row25-prev) mul(i26 row26-prev) mul(i27 row27-prev) | ||
| mul(i28 row28-prev) mul(i29 row29-prev) mul(i30 row30-prev) mul(i31 row31-prev) | ||
| ), | ||
| next-prod: add( | ||
| mul(i0 row0-prod) mul(i1 row1-prod) mul(i2 row2-prod) mul(i3 row3-prod) | ||
| mul(i4 row4-prod) mul(i5 row5-prod) mul(i6 row6-prod) mul(i7 row7-prod) | ||
| mul(i8 row8-prod) mul(i9 row9-prod) mul(i10 row10-prod) mul(i11 row11-prod) | ||
| mul(i12 row12-prod) mul(i13 row13-prod) mul(i14 row14-prod) mul(i15 row15-prod) | ||
| mul(i16 row16-prod) mul(i17 row17-prod) mul(i18 row18-prod) mul(i19 row19-prod) | ||
| mul(i20 row20-prod) mul(i21 row21-prod) mul(i22 row22-prod) mul(i23 row23-prod) | ||
| mul(i24 row24-prod) mul(i25 row25-prod) mul(i26 row26-prod) mul(i27 row27-prod) | ||
| mul(i28 row28-prod) mul(i29 row29-prod) mul(i30 row30-prod) mul(i31 row31-prod) | ||
| ), | ||
|
|
||
| /* Prorate first partial month: (next_eom - now) / (next_eom - prev_eom) * production */ | ||
| month-length: saturating-sub(next-eom prev-eom), | ||
| remaining-in-month: saturating-sub(next-eom t), | ||
| proration: div(remaining-in-month any(month-length 1)), | ||
| first-prod: mul(next-prod proration), | ||
|
|
||
| /* PV of first partial cashflow at next_eom */ | ||
| pv-first: mul(mul(first-prod adj-price) call<'discount-factor>(next-eom)), | ||
|
|
||
| /* PV of all FULL future months (strictly after next_eom) */ | ||
| /* Each term: production * price * (eom > next_eom ? discount_factor : 0) */ | ||
| pv-rest: add( | ||
| mul(mul(row0-prod adj-price) mul(greater-than(row0-eom next-eom) call<'discount-factor>(row0-eom))) | ||
| mul(mul(row1-prod adj-price) mul(greater-than(row1-eom next-eom) call<'discount-factor>(row1-eom))) | ||
| mul(mul(row2-prod adj-price) mul(greater-than(row2-eom next-eom) call<'discount-factor>(row2-eom))) | ||
| mul(mul(row3-prod adj-price) mul(greater-than(row3-eom next-eom) call<'discount-factor>(row3-eom))) | ||
| mul(mul(row4-prod adj-price) mul(greater-than(row4-eom next-eom) call<'discount-factor>(row4-eom))) | ||
| mul(mul(row5-prod adj-price) mul(greater-than(row5-eom next-eom) call<'discount-factor>(row5-eom))) | ||
| mul(mul(row6-prod adj-price) mul(greater-than(row6-eom next-eom) call<'discount-factor>(row6-eom))) | ||
| mul(mul(row7-prod adj-price) mul(greater-than(row7-eom next-eom) call<'discount-factor>(row7-eom))) | ||
| mul(mul(row8-prod adj-price) mul(greater-than(row8-eom next-eom) call<'discount-factor>(row8-eom))) | ||
| mul(mul(row9-prod adj-price) mul(greater-than(row9-eom next-eom) call<'discount-factor>(row9-eom))) | ||
| mul(mul(row10-prod adj-price) mul(greater-than(row10-eom next-eom) call<'discount-factor>(row10-eom))) | ||
| mul(mul(row11-prod adj-price) mul(greater-than(row11-eom next-eom) call<'discount-factor>(row11-eom))) | ||
| mul(mul(row12-prod adj-price) mul(greater-than(row12-eom next-eom) call<'discount-factor>(row12-eom))) | ||
| mul(mul(row13-prod adj-price) mul(greater-than(row13-eom next-eom) call<'discount-factor>(row13-eom))) | ||
| mul(mul(row14-prod adj-price) mul(greater-than(row14-eom next-eom) call<'discount-factor>(row14-eom))) | ||
| mul(mul(row15-prod adj-price) mul(greater-than(row15-eom next-eom) call<'discount-factor>(row15-eom))) | ||
| mul(mul(row16-prod adj-price) mul(greater-than(row16-eom next-eom) call<'discount-factor>(row16-eom))) | ||
| mul(mul(row17-prod adj-price) mul(greater-than(row17-eom next-eom) call<'discount-factor>(row17-eom))) | ||
| mul(mul(row18-prod adj-price) mul(greater-than(row18-eom next-eom) call<'discount-factor>(row18-eom))) | ||
| mul(mul(row19-prod adj-price) mul(greater-than(row19-eom next-eom) call<'discount-factor>(row19-eom))) | ||
| mul(mul(row20-prod adj-price) mul(greater-than(row20-eom next-eom) call<'discount-factor>(row20-eom))) | ||
| mul(mul(row21-prod adj-price) mul(greater-than(row21-eom next-eom) call<'discount-factor>(row21-eom))) | ||
| mul(mul(row22-prod adj-price) mul(greater-than(row22-eom next-eom) call<'discount-factor>(row22-eom))) | ||
| mul(mul(row23-prod adj-price) mul(greater-than(row23-eom next-eom) call<'discount-factor>(row23-eom))) | ||
| mul(mul(row24-prod adj-price) mul(greater-than(row24-eom next-eom) call<'discount-factor>(row24-eom))) | ||
| mul(mul(row25-prod adj-price) mul(greater-than(row25-eom next-eom) call<'discount-factor>(row25-eom))) | ||
| mul(mul(row26-prod adj-price) mul(greater-than(row26-eom next-eom) call<'discount-factor>(row26-eom))) | ||
| mul(mul(row27-prod adj-price) mul(greater-than(row27-eom next-eom) call<'discount-factor>(row27-eom))) | ||
| mul(mul(row28-prod adj-price) mul(greater-than(row28-eom next-eom) call<'discount-factor>(row28-eom))) | ||
| mul(mul(row29-prod adj-price) mul(greater-than(row29-eom next-eom) call<'discount-factor>(row29-eom))) | ||
| mul(mul(row30-prod adj-price) mul(greater-than(row30-eom next-eom) call<'discount-factor>(row30-eom))) | ||
| mul(mul(row31-prod adj-price) mul(greater-than(row31-eom next-eom) call<'discount-factor>(row31-eom))) | ||
| ), | ||
|
|
||
| /* Total NPV = PV(first partial) + PV(all future full months) */ | ||
| total-npv: add(pv-first pv-rest), | ||
|
|
||
| /* Divide by total supply to get per-token NPV */ | ||
| total-supply: erc20-total-supply(output-token()), | ||
| _: div(total-npv total-supply); | ||
|
|
||
| /* ============ AUCTION DCA MECHANICS (from auction-dca.rain) ============ */ | ||
|
|
||
| #set-last-trade | ||
| last-io:, | ||
| :set(hash(order-hash() last-trade-time-key) now()), | ||
| :set(hash(order-hash() last-trade-io-key) last-io); | ||
|
|
||
| #set-initial-time | ||
| :set(hash(order-hash() initial-time-key) now()); | ||
|
|
||
| #get-initial-time | ||
| _: get(hash(order-hash() initial-time-key)); | ||
|
|
||
| #get-last-trade | ||
| last-time: get(hash(order-hash() last-trade-time-key)), | ||
| last-io: get(hash(order-hash() last-trade-io-key)); | ||
|
|
||
| #get-epoch | ||
| initial-time: call<'get-initial-time>(), | ||
| last-time _: call<'get-last-trade>(), | ||
| duration: sub(now() any(last-time initial-time)), | ||
| total-duration: sub(now() initial-time), | ||
| ratio-freeze-amount-epochs: div(min-trade-amount amount-per-epoch), | ||
| ratio-freeze-trade-epochs: mul(ratio-freeze-amount-epochs div(time-per-amount-epoch time-per-trade-epoch)), | ||
| amount-epochs: div(total-duration time-per-amount-epoch), | ||
| trade-epochs: saturating-sub(div(duration time-per-trade-epoch) ratio-freeze-trade-epochs); | ||
|
|
||
| #amount-for-epoch | ||
| amount-epochs | ||
| trade-epochs:, | ||
| total-available: linear-growth(0 amount-per-epoch amount-epochs), | ||
| used: get(hash(order-hash() amount-used-key)), | ||
| unused: sub(total-available used), | ||
| decay: call<'halflife>(trade-epochs), | ||
| shy-decay: every(greater-than(trade-epochs shy-epoch) decay), | ||
| variable-component: sub(max-trade-amount min-trade-amount), | ||
| target-amount: add(min-trade-amount mul(variable-component shy-decay)), | ||
| capped-unused: min(unused target-amount); | ||
|
|
||
| #halflife | ||
| epoch:, | ||
| multiplier: power(0.5 div(epoch 10)), | ||
| val: mul( | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| ); | ||
|
|
||
| #io-for-epoch | ||
| epoch:, | ||
| last-io: call<'get-last-trade>(), | ||
| max-next-trade: any(mul(last-io next-trade-multiplier) call<'initial-io-fn>()), | ||
| baseline-next-trade: mul(last-io next-trade-baseline-multiplier), | ||
| real-baseline: max(baseline-next-trade call<'baseline-fn>()), | ||
| variable-component: saturating-sub(max-next-trade real-baseline), | ||
| above-baseline: mul(variable-component call<'halflife>(epoch)), | ||
| _: add(real-baseline above-baseline); | ||
|
|
||
| /* ============ ENTRY POINTS ============ */ | ||
|
|
||
| #handle-add-order | ||
| using-words-from raindex-subparser pyth-subparser | ||
| :call<'set-initial-time>(); | ||
|
|
||
| #calculate-io | ||
| using-words-from raindex-subparser pyth-subparser | ||
| amount-epochs | ||
| trade-epochs: call<'get-epoch>(), | ||
| max-output: call<'amount-for-epoch>(amount-epochs trade-epochs), | ||
| io: call<'io-for-epoch>(trade-epochs), | ||
| :call<'set-last-trade>(io); | ||
|
|
||
| #handle-io | ||
| min-amount: mul(min-trade-amount 0.9), | ||
| :ensure(greater-than-or-equal-to(output-vault-decrease() min-amount) "Min trade amount."), | ||
| used: get(hash(order-hash() amount-used-key)), | ||
| :set(hash(order-hash() amount-used-key) add(used output-vault-decrease())); |
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.
🧹 Nitpick | 🔵 Trivial
Consider extracting shared code between npv-dca.rain and npv-dca-approx.rain.
Both files share significant code duplication:
- Auction DCA mechanics (
set-last-trade,get-epoch,amount-for-epoch,halflife,io-for-epoch) - Production schedule data (32 rows of identical
row*-prev,row*-eom,row*-prod) - State keys, entry points, and handle-io logic
Consider extracting shared components into a common file to improve maintainability and reduce the risk of divergent bug fixes.
🤖 Prompt for AI Agents
In src/npv-dca.rain lines 1-518, large blocks are duplicated with
npv-dca-approx.rain (auction DCA mechanics, 32-row production schedule, state
keys, and entry points). Extract the shared pieces into a new common file (e.g.,
src/npv-shared.rain) containing: the production rows and row constants,
discount/step/adjusted-price/npv helpers, auction helper functions
(set-last-trade, get-initial-time/get-last-trade/get-epoch, amount-for-epoch,
halflife, io-for-epoch), state key constants, and shared entry point wiring;
then update both npv-dca.rain and npv-dca-approx.rain to remove the duplicated
blocks and import/compose the shared file (use the Rain module/include pattern
your project uses or expose the helper words and call them via using-words-from
or equivalent), keeping public function names and keys unchanged so existing
bindings/calls continue to work.
| #halflife | ||
| epoch:, | ||
| multiplier: power(0.5 div(epoch 10)), | ||
| val: mul( | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| multiplier | ||
| ); |
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.
halflife function declares but does not return val.
Same issue as in npv-dca-approx.rain: the function computes val but doesn't return it.
Apply this diff:
#halflife
epoch:,
multiplier: power(0.5 div(epoch 10)),
-val: mul(
+_: mul(
multiplier
multiplier
multiplier
multiplier
multiplier
multiplier
multiplier
multiplier
multiplier
multiplier
);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| #halflife | |
| epoch:, | |
| multiplier: power(0.5 div(epoch 10)), | |
| val: mul( | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| ); | |
| #halflife | |
| epoch:, | |
| multiplier: power(0.5 div(epoch 10)), | |
| _: mul( | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| multiplier | |
| ); |
🤖 Prompt for AI Agents
In src/npv-dca.rain around lines 474 to 488 the halflife function computes a
local variable named val but never returns it; modify the function to return val
(e.g., add a final expression or explicit return of val as the function result)
so the computed value is exposed to callers, ensuring the function body ends
with val as the returned value.
| #io-for-epoch | ||
| epoch:, | ||
| last-io: call<'get-last-trade>(), | ||
| max-next-trade: any(mul(last-io next-trade-multiplier) call<'initial-io-fn>()), | ||
| baseline-next-trade: mul(last-io next-trade-baseline-multiplier), | ||
| real-baseline: max(baseline-next-trade call<'baseline-fn>()), | ||
| variable-component: saturating-sub(max-next-trade real-baseline), | ||
| above-baseline: mul(variable-component call<'halflife>(epoch)), | ||
| _: add(real-baseline above-baseline); |
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.
io-for-epoch only captures one value from get-last-trade which returns two.
Same issue as in npv-dca-approx.rain: get-last-trade returns (last-time, last-io) but only last-io is captured.
Apply this diff:
#io-for-epoch
epoch:,
-last-io: call<'get-last-trade>(),
+_ last-io: call<'get-last-trade>(),🤖 Prompt for AI Agents
In src/npv-dca.rain around lines 490 to 498, the io-for-epoch binding calls
get-last-trade which returns two values (last-time, last-io) but the code only
captures last-io; update the binding to destructure both returned values (e.g.,
bind last-time and last-io) and then keep all subsequent uses of last-io
unchanged (or reference last-time where appropriate if time is needed). Ensure
the call<'get-last-trade>() line is changed to capture both results and that no
other bindings shadow these names.
|
|
||
|
|
||
| #handle-io | ||
| :ensure(greater-than-or-equal-to(output-vault-decrease() min-trade-amount) "Min trade amount."), |
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.
do we actually need these min/max guards?
|
|
||
| gui: | ||
| name: Pyth fixed spread limit | ||
| name: Auction based cost averaging |
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.
still misnamed here
Motivation
Tokens like Albion are finite life, and their value changes over time, naturally decaying as the underlying asset is depleted, but also changing with oil price. This makes DeFi utility complicated. For example, if using Albion tokens as lending collateral, the lender has to account for the changing (and tending to decline) collateral value.
Solution
We provide two POC strategies for finance 101 valuation of the underlying asset, specifically using the PV approach. These valuations should be aligned with corporate finance methodology used in the O&G industry, but likely lacks the nuance and detail of dedicated trading strategies.
Both strategies rely on hardcoding the production forecasts for the asset, as these are unlikely to change more than every 3-6 months. The precomputed one precomputes various terms to reduce gas fees.
Checks
By submitting this for review, I'm confirming I've done the following:
Summary by CodeRabbit
New Features
Updates
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.