llms-full.txt for Forge API 1. 688751 bytes. One file a section: https://oathra.dev/llms/start-here.txt https://oathra.dev/llms/how-to.txt https://oathra.dev/llms/concepts.txt https://oathra.dev/llms/porting.txt https://oathra.dev/llms/data.txt https://oathra.dev/llms/examples.txt https://oathra.dev/llms/python-reference.txt https://oathra.dev/llms/built-in-indicators.txt https://oathra.dev/llms/error-codes.txt https://oathra.dev/llms/statistics.txt https://oathra.dev/llms/limits-and-costs.txt # Start here Source: https://oathra.dev/docs/get-started/draw-your-first-indicator # Draw your first indicator > **What you need.** Editing needs a screen at least 768 wide. ## 1. Open the file Press **Open in the editor** on the code below. ```python import forge as fg from forge import param, ta FORGE_API = 1 class RsiBands(fg.Indicator): name = "RSI bands" short_name = "RSIB" pane = "new" precision = 2 format = "number" period = param.Int(14, min=2, max=200) source = param.Source("close") rsi = fg.Output.line("RSI") hot = fg.Output.state("Overbought") levels = [fg.Level(70, "Overbought"), fg.Level(30, "Oversold")] warmup = lambda self: self.period def calculate(self, bars, start) -> None: r = ta.rsi(bars[self.source], self.period) self.out.rsi[start:] = r[start:] self.out.hot[start:] = r[start:] > 70 ``` `FORGE_API = 1` is required. A file without it is refused. A file holds exactly one `fg.Indicator` class, and `pane = "new"` draws it in its own pane under the price. ## 2. Read what it declares Settings are class attributes made with `param`. `param.Int(14, min=2, max=200)` is a whole number that defaults to 14 and cannot leave 2 to 200. `param.Source("close")` picks which price the indicator reads. An output is declared once and filled in `calculate`. `fg.Output.line("RSI")` holds one float for each bar, and `fg.Output.state("Overbought")` holds true or false for each bar. `levels` draws the two lines at 70 and 30. > **Trap.** An indicator cannot place orders. Calling any order, account or context function in an indicator file is refused when the file loads. > **Check.** The first 14 values of the RSI output are NaN, and the line starts on bar 15. ## 3. Change it Change the period from 14 to 7 and the levels to 80 and 20. Press **Format** to tidy the file. > **You now have** > > A file that draws an RSI with two levels and an overbought state, and a settings form that came with it. > > [Next: Write your first strategy](https://oathra.dev/docs/get-started/get-a-verdict) --- Source: https://oathra.dev/docs/get-started/get-a-verdict # Write your first strategy ## What you need 1. An Oathra account. Creating one is free. 2. Price history for one symbol, exported from MetaTrader. Oathra ships no sample prices. [Bring your price history](https://oathra.dev/docs/how-to/bring-your-price-history) 3. A first test on your own price history needs storage, and the free plan includes none. Storage starts at 40 credits a month. Credit packs are coming soon. 4. Credits for the data check and the test. Both are charged by the minute, and Forge shows the cost of a test before it starts. A standard test uses about 13 credits an hour. 5. The numbers of a test are on both plans. The verdict is on the paid plan, \$29 a month. ## 1. Write the strategy A strategy is one Python file with one `fg.Strategy` class. Its `compute` method gets the price arrays as `d` and returns the signals for every bar at once. ```python import forge as fg from forge import param, ta FORGE_API = 1 class RsiCross(fg.Strategy): bars = fg.bars.Time("H1") direction = "long" period = param.Int(14, min=2, max=50, group="RSI") buy_level = param.Int(30, min=10, max=45, step=5, group="RSI") exit_level = param.Int(60, min=50, max=80, step=5, group="RSI") def compute(self, d) -> fg.Signals: rsi = ta.rsi(d.close, self.period) return fg.Signals( long_entry=ta.crossover(rsi, self.buy_level), long_exit=rsi > self.exit_level, ) ``` The entry fires on the bar where the RSI crosses up through `buy_level`, not on every bar it stays above. The exit fires when it reaches `exit_level`. > **Trap.** A level that the RSI is already above on the first bar after warm-up is not a cross. Nothing fires until the RSI has been below the level first. `bars = fg.bars.Time("H1")` decides on one-hour bars. `compute` is pure: it reads `d` and the settings and nothing else, so it cannot look at the clock, the account or a random number. ## 2. Take it to Forge Download the file. Testing is in Forge on oathra.app, on price history you upload. [Test in Forge ↗](https://oathra.app/en/forge/tester?from=dev-guide-door\§ion=data) Opens oathra.app in a new tab. You need an Oathra account. A test needs price history, [storage and credits](https://oathra.dev/docs/get-started/get-a-verdict#what-you-need), and the verdict is on the paid plan. > **You now have** > > A strategy file, ready to take to Forge. --- Source: https://oathra.dev/docs/get-started/add-your-ai-tool # Set up your AI tool ## 1. Point it at the docs Give your tool the address of the docs list. It names every page as Markdown and carries the instructions the tool must follow. `https://oathra.dev/llms.txt` For all the pages at once, give it [The whole docs as one file](https://oathra.dev/llms-full.txt). Every page also has a Markdown twin at its own address with `.md` on the end. ## 2. Add the rules file Download the rules file and keep it with your project, where your tool reads project rules. `https://oathra.dev/forge-rules.md` Each tool reads project rules from its own place. Claude Code reads `CLAUDE.md`. A ChatGPT project takes the file as an instruction or an attached file. GitHub Copilot reads `.github/copilot-instructions.md`. ## 3. Ask it for code Ask in plain words and tell it to follow the rules file. Replace the words in braces with your own. ```text Write a Forge indicator that {what it draws}. Follow forge-rules.md. ``` ```text Write a Forge strategy that {entry and exit in words}. Follow forge-rules.md. ``` ```text Port this MQL5 file to a Forge strategy. Say which parts have no Forge form. Follow forge-rules.md. ``` Ask it for an indicator, open what it writes in the editor and save it as a project to see it on the chart. Ask it for a strategy, download the file and upload it on the Strategy card in Forge. ## What to expect - An AI tool is good at the shape of a Forge file and bad at names it has not seen. It may use a name that does not exist. - The editor here catches syntax mistakes. Forge checks the rest when a version is saved or the file is uploaded. - Forge refuses a file that reads a bar that has not closed. - A file that loads is not a strategy that works. That is what the test and the verdict are for. - Give the tool the rules file. It lists what the checker refuses. ## What its code must still pass Whoever writes a Forge file, it is judged the same way. It declares `FORGE_API` on its first lines. It never reads a bar that has not closed yet, because Forge refuses look-ahead. And it loads without errors. ## Ask here Every docs page has an Ask about this page row. Log in and the assistant answers from these docs, citing the pages it used. ## Connect the MCP server The Forge MCP server arrives with the public API. > **You now have** > > A tool that knows Forge's rules, and three prompts to start it with. --- Source: https://oathra.dev/docs/get-started/from # From another platform Pick the page that matches what you already know. Each one says what carries over, what is different and which page to read first. - [MetaTrader 5](https://oathra.dev/docs/get-started/from/mt5) - [TradingView](https://oathra.dev/docs/get-started/from/tradingview) - [QuantConnect](https://oathra.dev/docs/get-started/from/quantconnect) - [Plain Python](https://oathra.dev/docs/get-started/from/python) - [New to code](https://oathra.dev/docs/get-started/from/new-to-code) --- Source: https://oathra.dev/docs/get-started/from/mt5 # Coming from MetaTrader 5 ## What is the same You already think in indicators and expert advisors. An MQL5 indicator becomes an `fg.Indicator`, with outputs in place of buffers. An expert advisor becomes an `fg.Strategy`, whose `compute` returns signals. The ideas map across, and the file is Python. ## What is different - **Arrays run oldest first.** Index 0 is the oldest bar and `-1` is the latest closed bar. There is no series mode to switch on. - **Missing values are NaN,** not `EMPTY_VALUE`. - **Decisions are made on closed bars** and filled at the next real price. A strategy does not run on every tick. - **Time is UTC.** The broker's clock is available, converted for you. - **The data is arrays.** `calculate` fills whole arrays instead of looping over `rates_total`. The [traps page](https://oathra.dev/docs/porting/mql5/traps) lists the mistakes that cost the most when porting. ## Where to start Read [From MQL5](https://oathra.dev/docs/porting/mql5/overview) first. Then write [your first indicator](https://oathra.dev/docs/get-started/draw-your-first-indicator), which takes about three minutes. If you already own an MQL5 indicator, the [Indicators](https://oathra.app/forge/indicators) screen in Oathra can convert it. It turns the file into Python and says what translated exactly, what was approximated and what could not be converted. > **Tip.** Signed in, the assistant on this page ports a snippet for you. Paste it and ask for the Forge version. See [Set up your AI tool](https://oathra.dev/docs/get-started/add-your-ai-tool). ## MQL5 names, in Forge - OnInit becomes setup(self, ctx). Refuse bad settings with raise fg.InvalidParams(...). - OnDeinit becomes on_end(self, ctx, reason) - OnTick becomes compute(self, d) for signals over whole arrays; on_bar(self, ctx) for logic per bar - OnTimer becomes @fg.every(...) on a method - OnTrade becomes on_order_event(self, ctx, event) [All 65 names](https://oathra.dev/docs/porting/mql5/to-forge) 13 things in MQL5 have no Forge form. [See the list.](https://oathra.dev/docs/porting/mql5/to-forge#no-forge-form) A test of the same rules can give different numbers in MetaTrader and in Forge. [Why a MetaTrader report and a Forge test differ](https://oathra.dev/docs/porting/why-results-differ) lists the causes. ## MQL4 MQL4 code is not converted. Settings files (.set) from MetaTrader 4 and 5 are read. --- Source: https://oathra.dev/docs/get-started/from/tradingview # Coming from TradingView ## What is the same A Pine indicator maps to an `fg.Indicator` and a Pine strategy to an `fg.Strategy`. The names differ more than the ideas: outputs replace `plot`, settings replace `input`, and the `ta` module carries the indicators you know. ## What is different - **Forge works on whole arrays.** A Pine script runs once per bar and `close[1]` is the previous bar. A Forge `calculate` fills arrays, and a strategy's `compute` returns signals for every bar at once. - **Closed bars only.** Pine recalculates the forming bar on every tick, so a value can change before the bar ends. Forge decides on closed bars. - **No look-ahead switch.** A higher-timeframe series is visible only after its own bar has closed. - **Missing values are NaN,** and `fg.nz` and `fg.isna` replace `nz` and `na`. The [traps page](https://oathra.dev/docs/porting/pine/traps) lists the mistakes that cost the most. ## Where to start Read [From Pine](https://oathra.dev/docs/porting/pine/overview) first, then [repainting and look-ahead](https://oathra.dev/docs/concepts/repainting-and-look-ahead), which names repainting as Pine does. Then write [your first indicator](https://oathra.dev/docs/get-started/draw-your-first-indicator). > **Tip.** Signed in, the assistant on this page ports a snippet for you. Paste it and ask for the Forge version. See [Set up your AI tool](https://oathra.dev/docs/get-started/add-your-ai-tool). ## Pine names, in Forge - //@version=6 becomes FORGE_API = 1 on the first lines - strategy("Name") becomes class Name(fg.Strategy) with meta = fg.Meta(title="Name") - strategy(calc_on_order_fills=) becomes on_fill(self, ctx, fill) runs after each fill - var becomes An attribute set in setup(self, ctx) - barstate.isfirst becomes setup(self, ctx) and on_end(self, ctx, reason) [All 37 names](https://oathra.dev/docs/porting/pine/to-forge) 10 things in Pine have no Forge form. [See the list.](https://oathra.dev/docs/porting/pine/to-forge#no-forge-form) --- Source: https://oathra.dev/docs/get-started/from/quantconnect # Coming from QuantConnect ## What is the same A LEAN algorithm is a class with event handlers. A Forge strategy is a class too, and `compute` returns signals for every bar at once, with optional hooks for events. Both are Python. ## What is different - **Data arrives as arrays.** LEAN calls `OnData` with a slice for each bar. Forge hands `compute` the whole history as arrays, and the engine applies the signals. - **Indicators are functions.** LEAN registers an indicator and feeds it through a consolidator. Forge calls `ta.rsi(close, period)` on an array, and a slower series is `d.tf("H4")`. - **Warm-up is declared.** An indicator states its own warm-up, and a strategy does not trade on values that are still missing. - **Time is UTC.** LEAN's clock follows the time zone you set. The [traps page](https://oathra.dev/docs/porting/lean/traps) lists the mistakes that cost the most. ## Where to start Read [From LEAN](https://oathra.dev/docs/porting/lean/overview) first, then [warm-up](https://oathra.dev/docs/concepts/warm-up) and [broker time and the trading day](https://oathra.dev/docs/concepts/broker-time-and-the-trading-day). Then write [your first indicator](https://oathra.dev/docs/get-started/draw-your-first-indicator). > **Tip.** Signed in, the assistant on this page ports a snippet for you. Paste it and ask for the Forge version. See [Set up your AI tool](https://oathra.dev/docs/get-started/add-your-ai-tool). ## LEAN names, in Forge A short list. A full table for LEAN is not written yet. - `self.rsi`: `forge.ta.rsi` - `self.sma`: `forge.ta.sma` - `self.ema`: `forge.ta.ema` - `initialize`: `forge.strategy.Strategy` - `on_data`: `forge.strategy.Strategy` - `set_holdings`: `forge.types.Context` - `self.debug`: `forge.types.Context` --- Source: https://oathra.dev/docs/get-started/from/python # Coming from plain Python ## What is the same Forge code is Python. An indicator is one file with one class, settings are class attributes, and the work is done in a method that fills arrays. If you can read that, you can read a Forge file. ## What is different - **The file declares its level.** Every Forge file contains `FORGE_API = 1`. A file without it is refused. - **Settings are declared, not passed.** `param.Int(14, min=2, max=200)` is a whole number that defaults to 14 and cannot leave 2 to 200. The same declaration builds the settings form and the values a sweep tries. - **Missing values are NaN.** The first bars of an indicator are empty until it has seen enough bars, and a comparison with `NaN` is false. - **Decisions are made on closed bars.** A bar that is still forming is not read, and a decision at a bar's close cannot fill at that close. - **Time is UTC.** ## Where to start Read three short pages in this order: [repainting and look-ahead](https://oathra.dev/docs/concepts/repainting-and-look-ahead), [warm-up](https://oathra.dev/docs/concepts/warm-up) and [bar close and fills](https://oathra.dev/docs/concepts/bar-close-and-fills). Then write [your first indicator](https://oathra.dev/docs/get-started/draw-your-first-indicator) and [your first strategy](https://oathra.dev/docs/get-started/get-a-verdict). > **Tip.** Signed in, the assistant on this page ports a snippet for you. Paste it and ask for the Forge version. See [Set up your AI tool](https://oathra.dev/docs/get-started/add-your-ai-tool). --- Source: https://oathra.dev/docs/get-started/from/new-to-code # New to code ## What is the same A Forge file is plain Python, and it uses only a few ideas. Each one is shown here once, with a short example. You do not need more to follow the first guide. **An import brings in what the file uses.** ```text import forge as fg from forge import param, ta ``` **A class groups one indicator's settings and work.** ```text class RsiBands(fg.Indicator): name = "RSI bands" ``` **A setting is a name with a default and a range.** ```text period = param.Int(14, min=2, max=200) ``` **A method is a named piece of work. `calculate` is the one that fills the values.** ```text def calculate(self, bars, start) -> None: r = ta.rsi(bars[self.source], self.period) ``` **A comparison gives true or false for every bar at once.** ```text self.out.hot[start:] = r[start:] > 70 ``` ## What is different A price chart is a list of bars, oldest first. An indicator reads that list and writes one value for each bar. The first bars stay empty until the indicator has seen enough of them, and Forge shows them empty instead of inventing a number. Forge only reads bars that have closed. ## Where to start Follow [Draw your first indicator](https://oathra.dev/docs/get-started/draw-your-first-indicator). It is one file with an RSI and two levels, and it takes about three minutes. Then [write your first strategy](https://oathra.dev/docs/get-started/get-a-verdict). > **Tip.** Signed in, the assistant on this page ports a snippet for you. Paste it and ask for the Forge version. See [Set up your AI tool](https://oathra.dev/docs/get-started/add-your-ai-tool). --- # How-to Source: https://oathra.dev/docs/how-to/write-an-indicator # Write an indicator ## The parts An indicator file has one class that extends `fg.Indicator`. It holds four things: the settings, the outputs, the warm-up and the calculation. ```python import forge as fg from forge import param, ta FORGE_API = 1 class Stretch(fg.Indicator): name = "Stretch" short_name = "STR" pane = "new" period = param.Int(20, min=2, max=200) stretch = fg.Output.histogram("Distance from the average") warmup = lambda self: self.period def calculate(self, bars, start) -> None: distance = bars.close - ta.sma(bars.close, self.period) self.out.stretch[start:] = distance[start:] ``` The class draws how far the close is from its average. `pane = "new"` puts it under the price, and `warmup` says the first `period` bars have no value. ## Choose one form An indicator defines exactly one of two methods. - `calculate(self, bars, start)` fills every output from bar `start` on. Use it when the result is a formula over whole arrays. - `on_bar(self, i)` runs once per bar and keeps its memory in declared state fields. Use it when each bar depends on the last. Both forms must give the same values, and a full run must give the same values as one that continues from earlier bars. Write the form that is simpler to read. > **Trap.** `bars` is ordered oldest first. Index `-1` is the latest closed bar, and there is no index for a later one. ## Outputs Each output has a kind that decides how a strategy can use it: a line or histogram is a number, a state is true or false, an event is 1, -1 or 0 on the bar it happens. Every output is read by its name. ## Limits An indicator cannot trade. It cannot place orders or read the account, and a file that tries is refused when it loads. Start from a template in [the examples](https://oathra.dev/examples) if you would rather change a working file than begin from nothing. --- Source: https://oathra.dev/docs/how-to/write-a-strategy # Write a strategy ## The parts A strategy file has one class that extends `fg.Strategy`. Its `compute` method returns the signals for every bar at once. ```python import forge as fg from forge import param, ta FORGE_API = 1 class AboveAverage(fg.Strategy): """Buys while the close is above its moving average.""" meta = fg.Meta(title="Above average", short="AAV", tags=["trend"]) bars = fg.bars.Time("H1") direction = "long" period = param.Int(50, min=10, max=200, step=10, group="Average") def compute(self, d) -> fg.Signals: average = ta.sma(d.close, self.period) return fg.Signals( long_entry=d.close > average, long_exit=d.close < average, stop=fg.Stop.atr(mult=2.0, length=14), target=fg.Target.r(value=2.0), ) ``` ## Two stages Stage one is `compute`. It runs once over the closed bars and gets the price arrays and the settings, nothing else. It has no clock, no account and no random numbers, so it gives the same answer every time. Stage two is optional. A strategy that defines hooks is called when something happens: a bar closes, an order fills, a day starts. A hook receives a snapshot of the run at that moment and can read it and place orders. > **Trap.** A hook sees the run as it was at that moment. It cannot look at a later bar, and an order it places acts at the first real price after the moment. ## The hooks A method whose name starts with `on_` and is not in this table is refused when the file loads, with a suggestion for the closest name. | Hook | When it runs | | --------------------- | ----------------------------------------------------------------------------------------- | | `setup` | Once, before any bar: check settings and prepare state. | | `on_warmup_end` | Once, when warm-up ends and trading is allowed. | | `on_bar` | On every closed bar. Defining it makes every bar an event, the slowest kind. | | `on_order_accepted` | When the market accepts an order. | | `on_fill` | When an order fills, with the fill. | | `on_order_cancelled` | When an order is cancelled. | | `on_order_rejected` | When an order is rejected. | | `on_order_expired` | When an order expires. | | `on_order_event` | After the specific order hook, for every order event. | | `on_position_opened` | When a position opens. | | `on_position_changed` | When a position changes size. | | `on_position_closed` | When a position closes, with the reason. | | `confirm_entry` | A last yes or no before an entry order is sent. | | `confirm_exit` | A last yes or no before an exit order is sent. | | `on_day_start` | At the start of a trading day. | | `on_day_end` | At the end of a trading day. | | `on_week_start` | At the start of a trading week. | | `on_session_open` | When a named session opens. | | `on_session_close` | When a named session closes. | | `on_end` | Once, after the last step, with the reason the run ended. | | `on_prop_warning` | Once, when most of a prop rule's room is used up: 80% unless the rules set another share. | | `on_prop_breach` | Once, after the engine has acted on a broken prop rule, with what it did. | | `size` | Sets the size of an entry. This one keeps the size so far. | | `score` | Defines what best means when results are ranked. | | `accept` | Discards a result that fails a minimum. | | `report` | Adds named numbers to the report. | A strategy with only `compute` is the fastest kind. Adding hooks makes it slower. Defining `on_bar` makes every closed bar an event, which is the slowest. ## The order at one moment When a bar closes, Forge first adds the new bar, then makes slower series visible, then runs scheduled callbacks and day and session hooks, then reads your signals, then `on_bar`. Filters and rules apply, exits are processed, then entries. Orders go to the fill engine and act at the first real price after the moment. Fill and position hooks fire afterwards, in time order. --- Source: https://oathra.dev/docs/how-to/test-in-the-tester # Test a strategy in Forge ## What you need 1. An Oathra account. Creating one is free. 2. Price history for one symbol, exported from MetaTrader. Oathra ships no sample prices. [Bring your price history](https://oathra.dev/docs/how-to/bring-your-price-history) 3. A first test on your own price history needs storage, and the free plan includes none. Storage starts at 40 credits a month. Credit packs are coming soon. 4. Credits for the data check and the test. Both are charged by the minute, and Forge shows the cost of a test before it starts. A standard test uses about 13 credits an hour. 5. The numbers of a test are on both plans. The verdict is on the paid plan, \$29 a month. Open the [Tester](https://oathra.app/forge/tester). It has a Build tab for one configuration, and Queue and History tabs for runs already sent. ## Fill the configuration The Build tab lists its sections down the left, and any of them can be opened at any time. 1. **Instrument & data** picks what is tested, on which data and over which period. 2. **Parameters** lists the strategy's settings and decides, for each one, whether it is one value or a range. 3. **Costs** holds commission, spread, financing and conversion. 4. **Risk & capital** holds the starting balance, how a position is sized and the limits on exposure. 5. **Orders** and **Execution** decide how an entry reaches the market and how a fill is priced. 6. **Method** and **Gates** show the checks the run will apply. 7. **Review & run** shows everything the run is, in one place. The panel on the right shows what the run will cost before it starts. > **Trap.** Costs are the assumption most likely to be wrong. Set them to what your broker charges before you trust a profit. ## Run it Press **Run test** in Review & run. The run goes to the Queue tab while it waits and while it runs, and to History when it has finished. Its result is on the run page, listed under [Runs](https://oathra.app/forge). --- Source: https://oathra.dev/docs/how-to/run-a-sweep # Run a sweep A sweep runs the same strategy with many combinations of its settings. You decide, for each setting, whether it is one value or a range. ## Turn a setting into a range In the [Tester](https://oathra.app/forge/tester), open **Parameters**. Each setting can stay at one value or become a range with a minimum, a maximum and a step. The range a strategy declares in its file is where the Tester starts. A setting that a strategy marks as not to be optimised stays at one value. Settings can also depend on each other: a strategy can refuse combinations such as a fast average that is slower than the slow one. ## Count what you try The number of combinations is the product of the ranges. The panel on the right shows it, with the time and cost it implies, before the run starts. There is a cap on how many values one setting can take in a sweep. See [the limits](https://oathra.dev/docs/limits) for it and for the other caps a run applies. > **Trap.** Every combination is a trial, and the verdict is reduced for how many there were. A wide sweep makes the best result look better and makes a pass harder to earn. ## Read the result A sweep's run page lists the combinations that survived, ranked. Its verdict is about the search as a whole: whether the best of them is likely to be an accident. --- Source: https://oathra.dev/docs/how-to/read-a-verdict # Read a verdict Open a run from [Runs](https://oathra.app/forge). Its page leads with the verdict: one word and one sentence. ## The words | Verdict | What it says | | ---------------------------- | ----------------------------------------------------------------------------- | | Validated | At least one candidate cleared every stage | | Killed at the gates | Every candidate failed the cheap checks, so nothing reached the hold-out | | Killed at the hold-out | Some cleared the cheap checks, and none did well enough on the data held back | | Killed at K-fold | The hold-out was passed, and none survived the rolling windows | | Killed at the expensive tier | The last tests found none of them reliable | | Interrupted | The run stopped before a verdict, and its partial result is not one to act on | ## What to do next - **Killed at the gates** usually means costs, too few trades or too many settings. Start with the Costs section of the Tester. - **Killed at the hold-out** means the result did not hold on data the search never saw. Simplify the strategy before adding anything. - **Killed at K-fold** means the result came from one stretch of history. - **Killed at the expensive tier** means the best settings look like a product of the search. > **Trap.** Changing a strategy after reading its verdict and testing again on the same data uses up the data. Each round makes the next pass less meaningful. See [what the verdict tests](https://oathra.dev/docs/concepts/what-the-verdict-tests) for the stages in full. --- Source: https://oathra.dev/docs/how-to/check-your-code # Check your code A Forge file is checked in three places. ## 1. In the editor The Problems panel lists syntax mistakes as you type, and nothing else. **Format** tidies the file. ## 2. On every saved version of a project Forge checks each version you save. The status line reads **Version 1 passed its checks.**, with the version's own number, or lists what it found. ## 3. On every file you upload to Forge The Strategy card checks the file before it can be chosen. ## What the checks look for - [Lint rules](https://oathra.dev/reference/lint-rules) lists what the checker flags, with the message for each. - [Repainting and look-ahead](https://oathra.dev/docs/concepts/repainting-and-look-ahead) and [Warm-up](https://oathra.dev/docs/concepts/warm-up) are the two concepts the checks look for. --- Source: https://oathra.dev/docs/how-to/status-webhooks # Check a status webhook A status webhook is a POST request with a JSON body, sent when an incident starts, changes or ends. Every request is signed with the secret shown when you added the webhook. Check the signature before you trust the body. ## What a request carries When you add a webhook, one test request goes out first. It is the only body with no incident in it. ```json { "v": 1, "event": "ping" } ``` After that, each incident event sends one body. The event is `incident.started`, `incident.updated` or `incident.resolved`. ```json { "v": 1, "event": "incident.updated", "incident": { "id": "5b0c1f0e-3f6e-4f1e-9d2b-0a6c5d1e2f34", "title": "Runs are slow", "components": ["runs"], "impact": "degraded", "state": "identified", "started_at": "2026-10-01T11:40:00.000Z", "resolved_at": null, "url": "https://example.com/status" }, "update": { "state": "identified", "body": "A worker pool is full. We are adding capacity.", "created_at": "2026-10-01T11:55:00.000Z" }, "unsubscribe_url": "https://example.com/status/unsubscribe?token=..." } ``` `url` and `unsubscribe_url` are addresses on this site, shown here with a placeholder host. The webhook gets the events for the parts of Forge you chose, and either only the start and end of each incident or every update. ## The signature Each request has a header named `X-Forge-Status-Signature`. ```text X-Forge-Status-Signature: t=1700000000,v1=3b1f... ``` `t` is the time the request was signed, in seconds since 1970. `v1` is the HMAC-SHA256 of the text `t`, a dot, then the body exactly as received, under your signing secret, written in lowercase hex. ## Check it This computes the value to compare with `v1`. ```shell BODY='{"v":1,"event":"ping"}' T=1700000000 SECRET='your signing secret' printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" ``` Compare the two values with a constant-time comparison. Reject a request whose timestamp is more than five minutes old, so a captured request cannot be sent again later. ## Answering Answer with any 2xx status within five seconds. A redirect counts as a failure and is not followed. After ten failures in a row the webhook stops, and your list on the status page shows it as stopped. Remove it there, or with the removal link shown when you added it. --- # Concepts Source: https://oathra.dev/docs/concepts/repainting-and-look-ahead # Repainting and look-ahead A backtest sees the future when a decision uses a value that was not final at the moment it was made. The two common causes have two names. **Repainting** is a value that changes after the bar it belongs to. **Look-ahead** is a value taken from a later bar, or from a bar that had not closed. ## What Forge does about it Forge decides on closed bars. A decision is made at a bar's close, and code can reach only bars whose close is at or before that moment. Histories handed to your code are views cut at that moment, never slices of the full array. A slower series follows one rule. Its value becomes visible on the first bar of your series whose close is at or after the close of that slower bar. A strategy on hourly bars that reads a four-hour series sees the four-hour value only after the four-hour bar has closed. There is no switch that loosens this. Pine has a `lookahead` setting on its higher-timeframe call, and Forge does not. An order decided at a close acts at the first real price after it. The decision bar's own close is never a fill price. > **Trap.** Reading a higher-timeframe value before its bar has closed makes every backtest on it look better than it could have been. ## Indicators that redraw Some indicators rewrite their past as new bars arrive, for example zigzag lines and fractals. Their past values are not what a trader saw at the time. A redrawing indicator is marked as repainting, and a strategy that reads it sees what was visible at each bar, not the final drawing. ## Checks before publishing Forge runs a look-ahead check before a script is published. It names patterns that usually leak the future, such as a negative shift, an average over the whole array or a window centred on the current bar. > **Trap.** A centred moving average uses bars on both sides of the current one. It looks smooth on a chart and cannot be computed live. See [the hold-out test](https://oathra.dev/docs/concepts/verdict-checks/hold-out) for how a result is checked on data the search never saw. --- Source: https://oathra.dev/docs/concepts/warm-up # Warm-up An indicator that averages 14 bars has nothing to say until it has seen 14 bars. Those bars are its **warm-up**. Forge leaves them empty instead of inventing a value. ## How the empty bars are marked A missing value is `NaN` in arrays and `null` in JSON. It is never 0 and never a special number such as MQL5's `EMPTY_VALUE`. A comparison with `NaN` is false, so a condition built on a value still in warm-up does not fire. An indicator states its warm-up with `warmup`, either a whole number or a function of its settings: ```python import forge as fg from forge import param, ta FORGE_API = 1 class Smooth(fg.Indicator): name = "Smooth" pane = "price" period = param.Int(20, min=2, max=200) avg = fg.Output.line("Average") warmup = lambda self: self.period def calculate(self, bars, start) -> None: self.out.avg[start:] = ta.sma(bars.close, self.period)[start:] ``` ## Chained indicators add up An indicator built on another indicator starts later than either. A 14-bar average of a 14-bar RSI has no value for its first 27 bars: 14 for the RSI, then 13 more before the average has 14 values to work with. ## On the chart and in a run The chart and the test use the same warm-up. A chart window that starts later than the test still loads the bars before it, so the values on screen are the values the run used. > **Trap.** Trading on a value that is still `NaN` means trading on nothing. Give your strategy a setting for the first bar it may trade, or make sure every condition it uses is built from values that exist. > **Check.** A strategy with an RSI of 14 bars makes no entry on its first 14 bars. --- Source: https://oathra.dev/docs/concepts/bar-close-and-fills # Bar close and fills Forge separates **deciding** from **filling**. A strategy decides when a bar is confirmed. The order is filled at the first real price after that moment. ## Two times on every bar Every bar has a time it opened and a time it closed. For a time bar the close is the end of the period, even when the last price came earlier. For every other type the close is the time of the price that completed the bar. The decision happens at the close. The earliest fill is the next real price after it. > **Trap.** A decision made on a bar at its close cannot fill at that bar's close price. The price has gone by. ## When each bar type is confirmed | Bar type | A bar is confirmed when | | ---------------- | --------------------------------------------------------------------------------------------------------------- | | Time | The period ends | | Heikin-Ashi | The base bar closes. The drawn values are smoothed, and the real open, high, low and close are kept beside them | | Renko | The brick completes | | Range | The range is reached | | Kagi | The line segment completes | | Point and figure | The column completes, or each box when the series is set to boxes | | Line break | The line completes | | Tick | The set number of price updates has arrived | | Count | The set number of base bars has merged | ## Drawn prices are never fill prices Heikin-Ashi, Renko, Kagi, point and figure and line break bars are drawn with values that do not exist in the market. They are marked as synthetic. A strategy may decide on them, but a fill is always on the real price stream, at a price and a time that occurred. > **Trap.** A Renko strategy that fills at a brick's edge is filled at a price that may never have traded. ## The forming bar A bar that has not completed never reaches a strategy. Indicators may draw it on a chart, styled as unconfirmed. Heikin-Ashi also has a settling window at the start of a series, where signals are suppressed. ## Several bars from one price One price jump can complete several bars at once. They share a close time and are marked as such. --- Source: https://oathra.dev/docs/concepts/broker-time-and-the-trading-day # Broker time and the trading day ## One clock inside Forge Every time inside Forge and in storage is UTC, counted in nanoseconds since the Unix epoch. A local time is never stored. On the wire, times are milliseconds since the epoch. Broker server time is a different thing. A broker's terminal shows its own clock, often ahead of UTC by a few hours, and it can move when the broker changes between winter and summer time. Forge reads the broker's offset from the broker facts of the run and converts. Your code can ask for the broker's clock or a named zone, and the answer is always the simulated time of the run. > **Trap.** An hour of the day copied from a chart on a broker terminal is broker time. Pass it to Forge as a named zone, or the session you meant is an hour or two off for part of the year. ## Where a day starts Each series states where its trading day starts. The default depends on the market. | Market | Day starts | | ------------------------------------------------------------------ | ----------------------- | | Forex | 17:00 New York | | Exchange instruments | The venue's own session | | Markets open all week, such as crypto and broker synthetic indices | 00:00 UTC | The forex default is a default, not the only choice. Every bar that spans a day uses the day start of its series, and so do the day and week hooks of a strategy. ## Markets with no day boundary A market that never closes has no natural end of day. Forge still needs one for daily statistics, and it uses the day start of the series. ## Closures When a market closes, a grid bar can continue its grid or reset it across the gap. The series says which. A time bar ignores this setting. > **Trap.** A daily bar built at midnight UTC on a forex pair cuts Friday's trading in two. Build it with the series' own day start. --- Source: https://oathra.dev/docs/concepts/costs-swap-and-funding # Costs, swap and funding A run applies four costs to a trade. Each fill records them: spread, commission, swap and slippage. ## Where the numbers come from The spread and the swap come from the broker facts that your connected account reports. The facts are saved once per broker server and shared by everyone on it. A run gets a copy of the facts for its symbols and is stamped with the hash of that copy, so a result says exactly which facts it used. | Cost | Source | | ---------- | ------------------------------------------------------------------------------------------------------------------- | | Spread | The history when it has one, otherwise the broker's spread in points, which is fixed or floating as the broker says | | Swap | The broker's swap mode, its long and short rates, and the weekday that carries the triple charge | | Commission | A setting in your Forge execution profile | | Slippage | A setting in your Forge execution profile | ## Commission is yours A terminal does not report commission reliably for each symbol, so Forge does not guess. The run uses the commission in your execution profile and says so on the result. ## Swap Swap is charged for holding a position over the broker's rollover. The rate differs for long and short, and some brokers charge three nights on one weekday. A strategy that holds for minutes pays none. A strategy that holds for weeks pays it on most days. > **Trap.** A backtest without swap flatters a slow strategy. Check the swap a position paid before trusting its profit. ## A missing fact is not zero When a broker does not report a fact, the field is empty. It is never read as zero. A run that needs a fact the broker did not give says so, instead of running with a number nobody measured. > **Trap.** Costs are part of the test, not something to add afterwards. A strategy that only works with no costs does not work. --- Source: https://oathra.dev/docs/concepts/what-the-verdict-tests # What the verdict tests A verdict is the answer to one question: did this strategy earn its result, or did the search find luck? A run passes through stages, cheap ones first. A strategy stops at the first stage it fails, and the verdict names that stage. ## The stages 1. **Cheap checks.** Do costs leave any profit, are there enough trades to say anything, and is the strategy's list of settings short enough to trust? A strategy that fails here never reaches the expensive stages. 2. **Hold-out.** The last part of the history is kept back while the search runs. The strategy must still work on it. See [the hold-out test](https://oathra.dev/docs/concepts/verdict-checks/hold-out). 3. **Rolling consistency.** The earlier history is cut into consecutive windows. The strategy must work in most of them, not in one lucky stretch. See [the rolling test](https://oathra.dev/docs/concepts/verdict-checks/k-fold). 4. **The expensive tier.** Four tests that ask how likely it is that the winner is an accident: - [The probability of backtest overfitting](https://oathra.dev/docs/concepts/verdict-checks/chance-this-is-luck): how often the best setting of one half of the history is below average on the other half. - [The deflated Sharpe ratio](https://oathra.dev/docs/concepts/verdict-checks/deflated-sharpe): the Sharpe ratio, reduced for how many things were tried. - **Plateau:** whether the neighbours of the winning settings also work, or only that exact point. - [Walk-forward](https://oathra.dev/docs/concepts/verdict-checks/walk-forward): settings chosen on one window, then tried on the next, again and again. The cost of the trades is tested again with wider spreads. A strategy that dies when the spread triples was living on the spread. See [the cost stress](https://oathra.dev/docs/concepts/verdict-checks/cost-check). > **Trap.** Trying more settings makes the best result look better and be less reliable. The count of what was tried is part of the verdict. ## What the words on a result mean A result says Validated when a candidate cleared every stage. Otherwise it says where the strategy was killed, for example at the gates, at the hold-out or at the expensive tier. Killed is a result, not an error: it tells you which question the strategy could not answer. > **Trap.** A strategy that passes is not a promise of future profit. It says the result was not explained by luck or by the search alone. --- Source: https://oathra.dev/docs/concepts/verdict-checks/cost-check # Cost check Does it still win after spread and commission? This check was called Cost stress. ## Pass level - Costs as a share of gross profit: at most 15% - Tested again at: 3× the spread - Costs as a share of gross profit, at that spread: under 25% Thresholds version 1.19.0 A test that stops at this check reads Killed at the gates. - [Forge help ↗](https://oathra.app/en/forge/methods#cost-check) --- Source: https://oathra.dev/docs/concepts/verdict-checks/sample-check # Sample check Does the test have enough trades to judge? ## Pass level - Trades over the whole history: at least 300 - Trades on the held-back part: at least 30 Thresholds version 1.19.0 A test that stops at this check reads Killed at the gates. - [Forge help ↗](https://oathra.app/en/forge/methods#sample-check) --- Source: https://oathra.dev/docs/concepts/verdict-checks/hold-out # Hold-out Does it hold on price history it was never tuned on? ## Pass level - Share of the history held back: 30% - Profit factor on unseen data: above 1 Thresholds version 1.19.0 A test that stops at this check reads Killed at the hold-out. - [Forge help ↗](https://oathra.app/en/forge/methods#hold-out) --- Source: https://oathra.dev/docs/concepts/verdict-checks/k-fold # K-fold How often it survived across many slices of the same history. This check was called Rolling windows, and Rolling consistency on this site. ## Pass level - Slices of the history before the hold-out: 10 - Scored slices that must pass: at least 60% Thresholds version 1.19.0 A test that stops at this check reads Killed at K-fold. - [Forge help ↗](https://oathra.app/en/forge/methods#k-fold) --- Source: https://oathra.dev/docs/concepts/verdict-checks/walk-forward # Walk-forward How often it survived when it was always tested into the future. ## Pass level - Settings are chosen on: 3 years - Then tested on the next: 1 year - Each window starts later by: 1 year - Windows needed: at least 3 - Windows that must pass: at least 60% Thresholds version 1.19.0 A test that stops at this check reads Killed at the expensive tier. - [Forge help ↗](https://oathra.app/en/forge/methods#walk-forward) --- Source: https://oathra.dev/docs/concepts/verdict-checks/chance-this-is-luck # Chance this is luck The odds that the winner you picked was luck. The probability of backtest overfitting, PBO. This check was called Probability of overfitting. ## Pass level - Chance this is luck: at most 50% - Blocks the history is cut into: 16 Thresholds version 1.19.0 A test that stops at this check reads Killed at the expensive tier. - [Forge help ↗](https://oathra.app/en/forge/methods#chance-this-is-luck) --- Source: https://oathra.dev/docs/concepts/verdict-checks/deflated-sharpe # Deflated Sharpe The Sharpe ratio, with the credit for trying repeatedly taken back off. ## Pass level - Deflated Sharpe: at least 0.95 Thresholds version 1.19.0 A test that stops at this check reads Killed at the expensive tier. - [Forge help ↗](https://oathra.app/en/forge/methods#deflated-sharpe) --- Source: https://oathra.dev/docs/concepts/versions-and-forge-api # Forge API levels Every strategy and indicator file names the Forge API it was written for, on its first lines. ```python FORGE_API = 1 ``` ## What the line does The line is required and must be a whole number that Forge supports. Forge never reads a file as the oldest level when the line is missing. It refuses the file and says why: - No line: `FORGE_API is missing: put FORGE_API = 1 on the first lines of the file` - Not a whole number: `FORGE_API must be a whole number, got 'one'` - A level Forge does not support: `FORGE_API = 7 is not supported; supported: 1` ## Supported levels The latest level is Forge API 1. Supported levels: 1. Each name in the reference says the level it appeared in, and a deprecated name says the level it was deprecated in. ## Older levels Once Forge has more than one level, every docs and reference page shows a level switch. The docs of an older level stay readable under their own address, for example `/v/1/docs/get-started`, with a note that says which level you are reading and a link to the latest. Pages of an older level are not indexed by search engines. --- # Porting Source: https://oathra.dev/docs/porting/mql5/overview # From MQL5 Forge code is Python, but the ideas map across. An MQL5 indicator becomes an `fg.Indicator` with outputs in place of buffers. An MQL5 expert advisor becomes an `fg.Strategy` whose `compute` returns signals. ## What is different - **Arrays run oldest first.** Index 0 is the oldest bar and `-1` is the latest closed bar. There is no series mode to switch on. - **Missing values are NaN,** not `EMPTY_VALUE`. - **Decisions are made on closed bars** and filled at the next real price. A strategy does not run on every tick. - **Time is UTC.** The broker's clock is available, and converted for you. - **The data is arrays.** `calculate` fills whole arrays instead of looping over `rates_total`. The [traps page](https://oathra.dev/docs/porting/mql5/traps) lists the mistakes that cost the most when porting. ## Start from your file The [Indicators](https://oathra.app/forge/indicators) screen in Oathra can convert an MQL5 indicator you already own. It turns the file into Python and says what translated exactly, what was approximated and what could not be converted. It converts indicators, not expert advisors: a file that places orders is refused. Functions that only draw, notify or write files are dropped, because they change no number. Functions that read the wall clock, such as `TimeCurrent`, cannot be reproduced in a backtest and are reported as not translatable. ## Then read Start with [repainting and look-ahead](https://oathra.dev/docs/concepts/repainting-and-look-ahead) and [warm-up](https://oathra.dev/docs/concepts/warm-up). Most ported indicators fail on one of the two. ## Name by name [Every MQL5 name beside what you write in Forge](https://oathra.dev/docs/porting/mql5/to-forge). A test of the same rules can give different numbers in MetaTrader and in Forge: see [why a MetaTrader report and a Forge test differ](https://oathra.dev/docs/porting/why-results-differ). --- Source: https://oathra.dev/docs/porting/mql5/to-forge # Convert an MQL5 EA to Python by hand: MQL5 to Forge There is no automatic converter, and Oathra does not convert MQL4 or MQL5 code. This page sets what you write in MQL5 beside what you write in Forge's Python, job by job, and says what has no Forge form. What you write in MQL5, and what you write in Forge instead. 65 names, grouped by what they are for. ## What has no Forge form - `OnTesterInit`, `OnTesterPass`, `OnTesterDeinit`: Forge runs the sweep and keeps every pass. A strategy has no hook around the sweep in FORGE_API 1. - `OnChartEvent`: A strategy has no chart events. The chart draws a run's plots and markers. - `OnBookEvent`, `MarketBookAdd`, `MarketBookGet`: Forge runs have no order book depth. - `CTrade.PositionCloseBy`, `PositionCloseBy`, `OrderCloseBy`: Forge closes each position at the market. Closing one position by an opposite one is not simulated. - `CTrade.SetTypeFilling`, `ORDER_FILLING_FOK`, `ORDER_FILLING_IOC`: How orders fill is set for the run, not in the strategy. - `CopyTicks`, `CopyTicksRange`: Strategies decide on closed bars. Ticks drive fills, not decisions. - `IndicatorRelease`: Forge frees indicator arrays itself. - `TesterWithdrawal`, `TesterDeposit`: A run's balance moves only by trades and their costs. - `Every tick based on real ticks`, `1 minute OHLC`, `Open prices only`: How finely a run checks fills between bars is a run setting, not part of the strategy. - `Alert`, `SendNotification`, `SendMail`, `PlaySound`, `WebRequest`: A backtest sends nothing. Log it with ctx.log. - `DatabaseOpen`, `DatabaseExecute`, `the other SQLite functions`: A strategy has no database. A lookup table can ship as a declared asset. - `OnnxCreate`, `OnnxRun`: Model files cannot be assets in FORGE_API 1. - `CAppDialog`, `CCanvas`, `on-chart controls`: A strategy has no on-chart controls. ## Name by name ### The program's events - `OnInit`: setup(self, ctx). Refuse bad settings with raise fg.InvalidParams(...). - `OnDeinit`: on_end(self, ctx, reason) - `OnTick`: compute(self, d) for signals over whole arrays; on_bar(self, ctx) for logic per bar - `OnTimer`, `EventSetTimer`: @fg.every(...) on a method - `OnTrade`: on_order_event(self, ctx, event) - `OnTradeTransaction`: on_order_accepted, on_fill, on_order_cancelled, on_order_rejected, on_order_expired, then on_position_opened, on_position_changed, on_position_closed - `#include`, `#import`: A Python import of your own library file, which lists its names in \_\_all\_\_ A #import of a DLL has no equivalent: Forge runs Python only. ### Settings - `input`: param.Int, param.Float, param.Bool, param.Choice, param.Enum, param.Timeframe, param.Symbol, param.Session, param.Time, param.Price on the class - `sinput`: param.Int(..., optimize=False) A setting a sweep never changes. - `input group`: group="..." on each setting - `.set files`: Load MT5 settings on the Tester's strategy card, or program.read_mql5_set(path) in a notebook - `ENUM_MA_METHOD`, `ENUM_TIMEFRAMES`, `ENUM_APPLIED_PRICE`: param.MaMethod, param.Timeframe, param.Source ### Orders - `CTrade.Buy`, `CTrade.Sell`, `OrderSend`, `OrderSendAsync`, `CTrade.PositionOpen`, `PositionOpen`, `MqlTradeRequest`: long_entry= and short_entry= in fg.Signals, or ctx.buy(size, sl=, tp=, tag=) and ctx.sell(...) in a hook - `CTrade.BuyLimit`, `CTrade.BuyStop`, `CTrade.SellLimit`, `CTrade.SellStop`, `CTrade.OrderOpen`: ctx.order(fg.Order.limit(...)) or ctx.order(fg.Order.stop(...)) - `CTrade.PositionClose`, `PositionClose`, `OrderClose`: pos.close(), or ctx.close(tag=...) - `CTrade.PositionClosePartial`: pos.close(portion=0.5) - `CTrade.PositionModify`, `PositionModify`: pos.set_stop(price) and pos.set_target(price), or declared fg.Stop and fg.Target - `CTrade.OrderModify`, `OrderModify`: ticket.modify(...) for a pending order; pos.set_stop and pos.set_target for an open trade MQL4's OrderModify changes both, so check which one the EA meant. - `CTrade.OrderDelete`, `OrderDelete`: ticket.cancel(), or ctx.cancel_all(tag=...) - `CTrade.SetExpertMagicNumber`, `magic numbers`: tag="..." on entries and orders - `CTrade.SetDeviationInPoints`: The run's slippage setting - `OrderCheck`, `OrderCalcMargin`, `OrderCalcProfit`: ctx.check(order), ctx.calc.margin(order), ctx.calc.profit(...) - `MqlTradeResult.retcode`, `TRADE_RETCODE_*`: ticket.status and ticket.reject_reason, one of forge.types.RejectReason - `ORDER_TIME_GTC`, `ORDER_TIME_DAY`, `ORDER_TIME_SPECIFIED`: tif="gtc", tif="day" or expire_at= on fg.Order.limit and fg.Order.stop - `stops and targets in points`: fg.Stop.pips(...), fg.Target.pips(...) ### Positions - `PositionsTotal`: len(ctx.positions()) - `PositionSelect`, `PositionSelectByTicket`, `PositionGetTicket`: ctx.positions(symbol=, tag=), each with pos.id - `PositionGetDouble`: pos.avg_price, pos.stop, pos.target, pos.unrealized, pos.swap, pos.size POSITION_PRICE_OPEN, POSITION_SL, POSITION_TP, POSITION_PROFIT, POSITION_SWAP and POSITION_VOLUME. - `PositionGetInteger`: pos.side, pos.open_time, pos.tag POSITION_TYPE, POSITION_TIME and POSITION_MAGIC. - `PositionGetString`: pos.symbol, pos.entry_reason POSITION_SYMBOL and POSITION_COMMENT. - `CPositionInfo`, `COrderInfo`, `CDealInfo`, `CAccountInfo`, `CSymbolInfo`: The objects above: pos, ticket, ctx.fills, ctx.account, ctx.symbol ### Pending orders - `OrdersTotal`, `OrderSelect`, `OrderGetTicket`, `OrderGetDouble`, `OrderGetInteger`: ctx.orders(), each a ticket with price, stop_price, status and tag ### History - `HistorySelect`, `HistoryDealsTotal`, `HistoryDealGetTicket`, `HistoryDealGetDouble`, `HistoryOrderGetInteger`: ctx.fills and ctx.trades: every fill and closed trade of the run so far ### The account - `AccountInfoDouble`: ctx.account.balance, equity, margin_used, free_margin, margin_level ACCOUNT_BALANCE, ACCOUNT_EQUITY, ACCOUNT_MARGIN, ACCOUNT_MARGIN_FREE and ACCOUNT_MARGIN_LEVEL. - `AccountInfoInteger`, `AccountInfoString`: ctx.account.leverage, ctx.account.mode, ctx.account.currency ACCOUNT_LEVERAGE, ACCOUNT_MARGIN_MODE and ACCOUNT_CURRENCY. ### The symbol - `SymbolInfoDouble`, `_Point`, `_Digits`: ctx.symbol.point, tick_value, tick_size, lot_min, lot_step, lot_max, digits; d.spec in compute() - `SymbolInfoTick`, `SYMBOL_BID`, `SYMBOL_ASK`: ctx.quote.bid and ctx.quote.ask in a hook; d.bid and d.ask series in compute() - `SymbolInfoInteger`: ctx.quote.spread, ctx.symbol.stops_level SYMBOL_SPREAD and SYMBOL_TRADE_STOPS_LEVEL. - `SymbolSelect`, `SymbolsTotal`, `SymbolName`: symbols = \["EURUSD", "GBPUSD"] on the class - `SymbolInfoSessionTrade`, `SymbolInfoSessionQuote`: ctx.calendar.is_open() and fg.Filter.session(...) ### Time - `TimeCurrent`, `TimeTradeServer`: ctx.broker_time In an EA under the tester this is simulated server time, and so is ctx.broker_time. - `TimeGMT`: ctx.time (UTC) - `TimeLocal`: ctx.local(zone) for a named zone A backtest has no local computer clock. - `TimeToStruct`, `MqlDateTime`: Python datetime from ctx.broker_time or d.time_close ### Data - `CopyRates`, `CopyClose`, `CopyOpen`, `CopyHigh`, `CopyLow`, `CopyTime`, `CopyTickVolume`, `CopySpread`, `iClose`, `iOpen`, `iHigh`, `iLow`, `iTime`, `iVolume`: d.close, d.open, d.high, d.low, d.time_close, d.volume, d.spread in compute(); ctx.bars.close.ago(n) or ctx.history("close", n) in a hook - `ArraySetAsSeries`: Nothing: Forge arrays always run oldest first - `another symbol or timeframe in CopyClose`: d\["GBPUSD"].tf("H4").close Every other series is written in the file, so Forge loads it before the run. - `iBars`, `Bars`, `iBarShift`: len(d.close), ctx.bar, numpy.searchsorted(d.time_close, t) - `iHighest`, `iLowest`: fg.ta.highest and fg.ta.lowest over d.high and d.low - `EMPTY_VALUE`: NaN ### Indicators - `iMA and CopyBuffer`: fg.ta.ma(d.close, period, method=...) and the other fg.ta functions, called once in compute() Each MT5 built-in maps to one fg.ta function: the indicator table lists them with their outputs. - `iCustom`: Put the calculation in a library file and call it in compute() ### The tester - `OnTester`: score(self, stats) -> float It ranks a sweep. Every combination that ran still counts as a trial. - `TesterStatistics`: stats\["net_profit"], stats.trade_count ... inside score(); ctx.stats while the run goes STAT_PROFIT, STAT_TRADES, STAT_PROFIT_FACTOR and the rest. - `TesterStop`, `ExpertRemove`: ctx.stop_run(reason) The run keeps its results and still counts as a trial. - `MQLInfoInteger(MQL_TESTER)`, `MQL_OPTIMIZATION`: ctx.mode Reading the mode never changes behaviour. A strategy that trades differently by mode is flagged on its result. - `Modelling quality`, `History quality`: The run's price and fill facts on its run page MetaTrader's figures count how complete the price history is, not whether fills are right. Forge's data quality score grades a dataset's files and is not a modelling quality either. ### Randomness - `MathRand`, `MathSrand`, `rand`, `srand`: ctx.rng, a NumPy generator the run seeds Never seed it by hand: the run's seed makes every run repeatable. ### Side effects - `Print`, `PrintFormat`, `Comment`: ctx.log.info(...) - `GlobalVariableSet`, `GlobalVariableGet`: An attribute set in setup(self, ctx) State lives on the strategy for one run. ### Chart and screen - `ObjectCreate`, `ObjectSetInteger`, `ChartRedraw`: plots= in fg.Signals, or ctx.plot and ctx.mark in a hook ### Files and storage - `FileOpen`, `FileRead*`, `FileWrite*`, `the other file functions`: self.read_asset(path) for a file listed in assets A strategy reads only the files it declares, and writes none. ### Maths and strings - `MathAbs`, `MathMax`, `MathPow`, `ArraySort`, `ArrayMaximum`, `StringFind`, `StringSubstr`, `DoubleToString`, `TimeToString`, `the other maths, array, conversion and string built-ins`: Python and numpy: abs, math, numpy reductions, slicing, sorted, bisect, f-strings, str methods and datetime - `CArrayObj`, `CArrayDouble`, `CList`, `CTree`, `CHashMap`, `CString`, `the other Standard Library collections`: Python's list, dict, set, collections.deque, bisect, heapq and str - `matrix`, `vector`, `their methods`: numpy arrays and numpy.linalg --- Source: https://oathra.dev/docs/porting/mql5/common-jobs # Common jobs, in MQL5 and in Forge One card a job. Each answer is one line, not a whole file. ## Act once per new bar **In MQL5:** `if (iTime(_Symbol, _Period, 0) != last_bar_time) { ... }` ```python Nothing to write: compute() and on_bar see each closed bar once. ``` ## One trade per bar **In MQL5:** `Remember the bar of the last trade and skip until the next.` ```python One new trade per bar on each symbol is the default. fg.Filter.max_trades(per="day", n=1) caps a day. ``` ## Manage only your own trades **In MQL5:** `Filter positions by magic number.` ```python tag="breakout" on the entry, then ctx.close(tag="breakout"). ``` ## Skip entries when the spread is wide **In MQL5:** `if (SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) > max) return;` ```python filters=[fg.Filter.max_spread(pips=2)] ``` ## Trade only in set hours **In MQL5:** `Compare TimeCurrent() hours with start and end inputs.` ```python filters=[fg.Filter.session("08:00-17:00", tz="Europe/London")] ``` Write the zone the hours are in. Broker server time has no zone name. ## Close everything at a total profit or loss **In MQL5:** `Sum the profit of open positions and close them all past a level.` ```python basket = fg.Exit.basket(profit_cash=200, loss_cash=100) ``` ## A grid with a cap **In MQL5:** `Place pending orders at fixed steps up to a maximum count.` ```python In on_fill, place the next level with ctx.order(fg.Order.limit(...)) while len(ctx.positions()) is below your cap. ``` Forge has no grid declaration in FORGE_API 1, so the grid is written in a hook. ## Round lots to the broker's step **In MQL5:** `NormalizeDouble(lots / step, 0) * step, then clamp to the minimum and maximum.` ```python size=fg.Size.risk_pct(1.0) in fg.Signals. Forge rounds down to the lot step and refuses a size below the minimum. ``` --- Source: https://oathra.dev/docs/porting/mql5/traps # MQL5 traps ## Index direction In MQL5 a series array is often read newest first, so index 0 is the current bar. A port that keeps `close[1]` for "the previous bar" reads the wrong bar. ```python import forge as fg FORGE_API = 1 def last_two_closes(close): # Oldest first: [-1] is the latest closed bar, [-2] the one before it. return close[-2], close[-1] ``` > **Trap.** An MQL5 index of 1 is a Forge index of -2. There is no reversed series mode. ## Point, Digits and pips `_Point` is the smallest price step and `_Digits` its number of decimals. A pip is 10 points on symbols with 5 or 3 digits and 1 point elsewhere. Forge prices are raw prices such as 1.08543, never points or pips. A pip or point amount appears only where a price amount is typed, and is resolved through the broker facts. > **Trap.** Converting a stop from pips by multiplying with a fixed 0.0001 is wrong on JPY pairs and on every symbol with a different step. Let Forge resolve the amount from the broker facts. ## EMPTY_VALUE An indicator buffer holds `EMPTY_VALUE` for "no value", which is a very large number. Forge uses NaN, and a comparison with NaN is false. ```python import forge as fg from forge import ta FORGE_API = 1 def ready(value): # A missing value is NaN, never a sentinel number. return ~ta.isna(value) ``` > **Trap.** A test such as `value != EMPTY_VALUE` has no Forge equivalent. Test for NaN with `ta.isna`, or leave the bar empty. ## Hedging and netting An MQL5 account is either hedging, which allows several positions on one symbol, or netting, which keeps one. Forge reads the account's margin mode from the broker facts. A strategy that opens an opposite order on a netting account reduces or reverses the position, and an order the account does not allow is rejected with a named reason. > **Trap.** An expert advisor that hedges by opening both sides works on a hedging account and does not on a netting one. Check the account's mode before porting the logic. ## Ticks and bars An expert advisor reacts in `OnTick`, on every price update. A Forge strategy decides on closed bars and fills at the next real price. A port that depended on the tick it was called on needs another design, or a bar type built from ticks. ## Magic numbers A magic number tags the orders of one expert advisor. Forge orders carry a `tag`, and a strategy lists its own positions by tag. ## prev_calculated `OnCalculate` receives `prev_calculated`, the number of bars already done. Forge passes `start`, the first bar to fill, to `calculate`. Fill every output from `start` on and do not keep your own count. > **Trap.** An indicator that loops from 0 on every call works on a chart and is slow in a sweep. Fill from `start`. --- Source: https://oathra.dev/docs/porting/pine/overview # From Pine A Pine indicator maps to an `fg.Indicator` and a Pine strategy to an `fg.Strategy`. The names differ more than the ideas: outputs replace `plot`, settings replace `input`, and the `ta` module carries the indicators you know. ## What is different - **Forge works on whole arrays.** A Pine script runs once per bar and `close[1]` is the previous bar. A Forge `calculate` fills arrays, and a strategy's `compute` returns signals for every bar at once. - **Closed bars only.** Pine recalculates the forming bar on every tick, so a value can change before the bar ends. Forge decides on closed bars. - **No look-ahead switch.** A higher-timeframe series is visible only after its own bar has closed. - **Missing values are NaN,** and `fg.nz` and `fg.isna` replace `nz` and `na`. The [traps page](https://oathra.dev/docs/porting/pine/traps) lists the mistakes that cost the most. ## Then read Start with [repainting and look-ahead](https://oathra.dev/docs/concepts/repainting-and-look-ahead), which names repainting as Pine does, and [bar close and fills](https://oathra.dev/docs/concepts/bar-close-and-fills). ## Name by name [Every Pine name beside what you write in Forge](https://oathra.dev/docs/porting/pine/to-forge). --- Source: https://oathra.dev/docs/porting/pine/to-forge # Convert a Pine Script strategy to Python: Pine to Forge What you write in Pine, and what you write in Forge instead. 37 names, grouped by what they are for. ## What has no Forge form - `strategy(calc_on_every_tick=)`: Forge decides on closed bars only. - `strategy(process_orders_on_close=)`: An order acts at the first real price after the bar's close, never at that close. - `strategy(fill_orders_on_standard_ohlc=)`: Forge always fills at real prices. - `strategy(use_bar_magnifier=)`: How finely a run checks fills between bars is set for the run, not in the strategy. - `strategy(backtest_fill_limits_assumption=)`: A strategy cannot change how the fill engine treats limit orders. - `strategy(risk_free_rate=)`: A strategy does not set a risk-free rate for the statistics. - `request.security(lookahead=barmerge.lookahead_on)`: Forge never shows a higher timeframe's bar before it closes. - `varip`: Forge has no updates inside a bar, so there is nothing to keep between them. - `barstate.isconfirmed`, `barstate.isnew`, `barstate.isrealtime`: Every bar a strategy sees is closed, so these checks are not needed. - `alertcondition()`, `alert()`: A strategy does not send alerts. Put the alert in an indicator with fg.alert. ## Name by name ### The program's events - `//@version=6`: FORGE_API = 1 on the first lines - `strategy("Name")`: class Name(fg.Strategy) with meta = fg.Meta(title="Name") - `strategy(calc_on_order_fills=)`: on_fill(self, ctx, fill) runs after each fill - `var`: An attribute set in setup(self, ctx) - `barstate.isfirst`, `barstate.islast`: setup(self, ctx) and on_end(self, ctx, reason) - `library()`, `import user/lib/1`: A library file listing its names in \_\_all\_\_, imported with a Python import ### Settings - `strategy(max_bars_back=)`: warmup = n on the class - `input.int()`, `input.float()`, `input.bool()`, `input.string()`, `input.source()`, `input.timeframe()`, `input.symbol()`, `input.session()`, `input.time()`, `input.price()`, `input.color()`: param.Int, param.Float, param.Bool, param.Choice, param.Source, param.Timeframe, param.Symbol, param.Session, param.Time, param.Price, param.Color ### Orders - `strategy(default_qty_type=, default_qty_value=)`: size=fg.Size... in fg.Signals - `strategy(pyramiding=)`: max_entries_per_direction = n on the class - `strategy(close_entries_rule=)`: ctx.close(tag=...) closes the positions of one entry tag - `strategy.entry()`: long_entry= and short_entry= in fg.Signals with tags=, or ctx.buy() and ctx.sell() in a hook - `strategy.order()`: ctx.order(fg.Order.market(...)), .limit(...) or .stop(...) - `strategy.exit()`: stop=fg.Stop..., target=fg.Target..., trail=fg.Trail... in fg.Signals - `strategy.close()`: long_exit= or short_exit= in fg.Signals, or ctx.close(tag=id) - `strategy.close_all()`: ctx.close_all() - `strategy.cancel()`, `strategy.cancel_all()`: ticket.cancel(), ctx.cancel_all(tag=...) - `strategy.risk.allow_entry_in()`: direction = "long" or "short" on the class - `strategy.risk.max_position_size()`: caps = fg.Caps(max_lots=...) - `strategy.risk.max_drawdown()`, `strategy.risk.max_intraday_loss()`, `strategy.risk.max_cons_loss_days()`: fg.prop rules, which stop or pause trading at a limit - `strategy.risk.max_intraday_filled_orders()`: fg.Filter.max_trades(per="day", n=...) ### Positions - `strategy.position_size`, `strategy.position_avg_price`: pos.size and pos.avg_price of ctx.positions() - `strategy.opentrades`: len(ctx.positions()) ### History - `strategy.netprofit`, `strategy.closedtrades`, `strategy.wintrades`: ctx.stats while the run goes; ctx.trades for each closed trade ### The account - `strategy.equity`, `strategy.openprofit`: ctx.account.equity, pos.unrealized ### The symbol - `timeframe.period`, `syminfo.mintick`, `syminfo.pointvalue`: bars = fg.bars.Time("H1"), ctx.symbol.tick_size, ctx.symbol.tick_value ### Time - `time`, `time_close`: d.time_open, d.time_close (UTC nanoseconds) ### Data - `request.security()`: d.tf("H4"), d\["GBPUSD"] or d\["GBPUSD"].tf("H4") Every other series is written in the file, so Forge loads it before the run. - `na`, `na()`, `nz()`: NaN, fg.isna(x), fg.nz(x, 0) - `close[1]`: Whole arrays in compute(); ctx.bars.close.ago(1) in a hook ### Indicators - `ta.*`: fg.ta functions, with Pine's names where the maths is the same: see the function table ### Chart and screen - `plot()`, `plotshape()`, `bgcolor()`: plots= in fg.Signals, or ctx.plot and ctx.mark in a hook ### Run settings - `strategy(initial_capital=)`: The Tester's starting capital. Suggest one with defaults = {"balance": 10000}. - `strategy(currency=)`: The run's account currency, from your account settings. Suggest one with defaults = {"currency": "EUR"}. - `strategy(commission_type=, commission_value=)`: The run's commission, set in the Tester or your execution profile - `strategy(slippage=)`: The run's slippage setting - `strategy(margin_long=, margin_short=)`: Margin comes from the broker's facts for the symbol ## Pine's ta functions Measured means the two were run on the same prices and compared. From the registry means it is read from Forge's own list of indicators. - `ta.sma`: `ta.sma`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.ema`: `ta.ema`. Same maths: Differs. Same order of values: Same. How we know: Measured. - `ta.rma`: `ta.rma`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.wma`: `ta.wma`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.vwma`: `ta.vwma`. Same maths: Same. Same order of values: Same. How we know: From the registry. - `ta.hma`: `ta.hma`. Same maths: Differs. Same order of values: Same. How we know: From the registry. - `ta.alma`: `ta.alma`. Same maths: Same. Same order of values: Same. How we know: From the registry. - `ta.linreg`: `ta.linreg`. Same maths: . Same order of values: Same. How we know: . - `ta.rsi`: `ta.rsi`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.stoch`: `ta.stoch`. Same maths: . Same order of values: Differs. How we know: . - `ta.macd`: `ta.macd`. Same maths: . Same order of values: Same. How we know: . - `ta.cci`: `ta.cci`. Same maths: . Same order of values: Same. How we know: . - `ta.cmo`: `ta.cmo`. Same maths: . Same order of values: Same. How we know: . - `ta.mfi`: `ta.mfi`. Same maths: . Same order of values: Differs. How we know: . - `ta.mom`: `ta.mom`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.roc`: `ta.roc`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.change`: `ta.change`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.wpr`: `ta.wpr`. Same maths: . Same order of values: Same. How we know: . - `ta.tr`: `ta.tr`. Same maths: Same. Same order of values: Differs. How we know: Measured. - `ta.atr`: `ta.atr`. Same maths: Differs. Same order of values: Same. How we know: Measured. - `ta.bb`: `ta.bb`. Same maths: . Same order of values: Same. How we know: . - `ta.bbw`: `ta.bbw`. Same maths: . Same order of values: Same. How we know: . - `ta.kc`: `ta.kc`. Same maths: . Same order of values: Differs. How we know: . - `ta.kcw`: `ta.kcw`. Same maths: . Same order of values: Differs. How we know: . - `ta.stdev`: `ta.stdev`. Same maths: Same. Same order of values: Differs. How we know: Measured. - `ta.variance`: `ta.variance`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.dmi`: `ta.dmi`. Same maths: . Same order of values: Differs. How we know: . - `ta.sar`: `ta.sar`. Same maths: . Same order of values: Differs. How we know: . - `ta.supertrend`: `ta.supertrend`. Same maths: Same. Same order of values: Differs. How we know: From the registry. - `ta.obv`: `ta.obv`. Same maths: . Same order of values: Same. How we know: . - `ta.accdist`: `ta.accdist`. Same maths: . Same order of values: Same. How we know: . - `ta.vwap`: `ta.vwap`. Same maths: . Same order of values: Differs. How we know: . - `ta.highest`: `ta.highest`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.lowest`: `ta.lowest`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.highestbars`: `ta.highest_bars`. Same maths: . Same order of values: Same. How we know: . - `ta.lowestbars`: `ta.lowest_bars`. Same maths: . Same order of values: Same. How we know: . - `ta.rising`: `ta.rising`. Same maths: Differs. Same order of values: Same. How we know: Measured. - `ta.falling`: `ta.falling`. Same maths: Differs. Same order of values: Same. How we know: Measured. - `ta.cum`: `ta.cum`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.percentrank`: `ta.percentrank`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.median`: `ta.median`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.correlation`: `ta.correlation`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.crossover`: `ta.crossover`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.crossunder`: `ta.crossunder`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.cross`: `ta.cross`. Same maths: Differs. Same order of values: Same. How we know: Measured. - `ta.barssince`: `ta.barssince`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.valuewhen`: `ta.valuewhen`. Same maths: Same. Same order of values: Same. How we know: Measured. - `ta.pivothigh`: `ta.pivot_high`. Same maths: Differs. Same order of values: Same. How we know: From the registry. - `ta.pivotlow`: `ta.pivot_low`. Same maths: Differs. Same order of values: Same. How we know: From the registry. - `nz`: `ta.nz`. Same maths: . Same order of values: Same. How we know: . --- Source: https://oathra.dev/docs/porting/pine/traps # Pine traps ## History references `close[1]` in Pine is the previous bar. In Forge an array is ordered oldest first, so the previous bar is index `-2`. ```python import forge as fg FORGE_API = 1 def previous_close(close): # Pine's close[1] is the bar before the latest closed one. return close[-2] ``` > **Trap.** A port that writes `close[-1]` for "one bar back" reads the latest bar, which is the wrong bar. ## Repainting on the forming bar A Pine script recalculates on every tick of the bar that is still forming, so its plot can move and then settle. A backtest only sees the settled value. Forge computes on closed bars, so what a chart shows is what a test used. > **Trap.** A condition that flickers on the forming bar in Pine never fires in a Forge test, and one that fires only after the bar closes never flickers. Compare them on closed bars. ## Look-ahead in higher timeframes Pine's security call can be told to look ahead, which shows a higher-timeframe value before its bar has closed. Forge has no such setting: a slower series is visible on the first bar whose close is at or after its own close. > **Trap.** A strategy that looked good in Pine only because of look-ahead will look worse in Forge. The Forge result is the one that could have been traded. ## Persistent state Pine's `var` keeps a value across bars. A Forge `compute` has no state, because it works on arrays. Express the state as an array operation, or use the bar-by-bar form with declared state fields. ## na Pine's `na` is Forge's NaN. A comparison with it is false in both. ```python import forge as fg from forge import ta FORGE_API = 1 def clean(value): # Pine's nz(value): a missing value becomes zero. return ta.nz(value) ``` > **Trap.** `nz` turns a missing value into a number. Use it on purpose, because a zero is a real value to everything that reads it. ## Order fills A Pine strategy fills a market order at the next bar's open by default. A Forge order decided at a close fills at the first real price after it, which is usually close to the same place and is always a price that traded. --- Source: https://oathra.dev/docs/porting/why-results-differ # Why a MetaTrader report and a Forge test differ The same rules, tested in MetaTrader and in Forge, rarely give the same numbers at first. Each cause below is one the engine names. ## Spread MT5's tester uses the spread set in the tester or recorded in its history; a Forge run uses the spread in its own data or broker facts, so entries and exits land at different prices. ## Server time offset The report's times are the broker's server time and Forge's are UTC, so every time differs by the same offset. ## Daylight saving The broker's offset changes by an hour twice a year, so times differ by one more hour for part of the year only. ## Swap Both testers charge today's swap rate on past nights, and Forge books no credit, so trades held overnight end differently. ## Price detail MT5 may replay real ticks, generated ticks or open prices only, while Forge fills on its data's bars or minutes, so a stop and a target inside one bar can resolve differently. ## Fill rules Which price a pending order or a stop fills at, and which of a stop and a target fills first in one bar, follow each tester's own rule. ## Commission The report uses the account's commission, and a Forge run uses the commission set for it. --- Source: https://oathra.dev/docs/porting/lean/overview # From LEAN A LEAN algorithm is a class with event handlers. A Forge strategy is a class too, and `compute` returns signals for every bar at once, with optional hooks for events. Both are Python. ## What is different - **Data arrives as arrays.** LEAN calls `OnData` with a slice for each bar. Forge hands `compute` the whole history as arrays, and the engine applies the signals. - **Indicators are functions.** LEAN registers an indicator and feeds it through a consolidator. Forge calls `ta.rsi(close, period)` on an array, and a slower series is `d.tf("H4")`. - **Warm-up is declared.** An indicator states its own warm-up, and a strategy does not trade on values that are still missing. - **Time is UTC.** LEAN's clock follows the time zone you set. The [traps page](https://oathra.dev/docs/porting/lean/traps) lists the mistakes that cost the most. ## Then read Start with [warm-up](https://oathra.dev/docs/concepts/warm-up) and [broker time and the trading day](https://oathra.dev/docs/concepts/broker-time-and-the-trading-day). --- Source: https://oathra.dev/docs/porting/lean/traps # LEAN traps ## The algorithm's clock LEAN's algorithm time zone is New York unless the algorithm sets another. A time of day written against it means New York time. Forge stores and reads UTC, and a named zone is written out. > **Trap.** A session written as 09:30 to 16:00 in LEAN is New York time. In Forge, name the zone or the window is off by hours. ## Slices and arrays `OnData` sees one bar at a time. Code that keeps a rolling list of past values by hand is an array operation in Forge. ```python import forge as fg from forge import ta FORGE_API = 1 def above_average(close, period): # The average of the last `period` closes, for every bar at once. return close > ta.sma(close, period) ``` > **Trap.** A loop over past bars inside `compute` is slow and usually unnecessary. Write it as an array operation. ## Warm-up LEAN's `SetWarmUp` feeds history before the algorithm starts. A Forge indicator declares its warm-up, and its first bars are NaN. A condition on a NaN value is false, so nothing fires until the values exist. > **Trap.** An algorithm that trades on its first bar in LEAN may trade on nothing in Forge, because its indicators are still in warm-up. ## Consolidators A LEAN consolidator builds bars of another size from the data the algorithm already receives. In Forge a slower series is declared, and it follows one rule: it becomes visible only after its own bar has closed. > **Trap.** A consolidated bar that is used before it has closed is look-ahead. Forge will not show it early. ## Scheduled events LEAN's schedule calls a function at a time of day. Forge has `fg.schedule.every` and `fg.schedule.on`, and the day boundary comes from the broker facts, not from the algorithm's clock. --- # Data Source: https://oathra.dev/docs/data/what-your-code-runs-on # What your code runs on ## Your own data A run uses history you bring. Each upload becomes a dataset that belongs to your account and is private to it. A dataset holds one symbol, written as 3 to 12 capital letters. A file is uploaded in parts, so a large file can resume after an interruption. When the last part has arrived the dataset is converted and checked, and it moves from uploading to ready, or to failed with the reason. ## Formats | Source | What Forge reads | | --------------------------------------------- | ---------------------- | | MetaTrader 5 | Tick and bar exports | | MetaTrader 4 | Bar exports | | OANDA | Candle exports in JSON | | QuantDataManager, Dukascopy, TrueFX, HistData | CSV files | A CSV file can use a comma, semicolon, tab or vertical bar as its separator, a point or a comma as its decimal mark, and UTF-8 or UTF-16 text. Forge reads the layout from the file and shows it to you before anything is converted, so you confirm which column is the time and which are the prices. ## The clock of each source A file carries its source's own clock, and Forge converts it to UTC. | Source | Clock | | ------------------------ | -------------------------------------------------- | | MetaTrader 4 and 5 | The broker's server time | | Dukascopy, TrueFX, OANDA | UTC | | HistData | A fixed five hours behind UTC, with no summer time | | QuantDataManager | Measured from the data itself | > **Trap.** A file from a broker terminal is in that broker's server time. A dataset converted with the wrong clock puts every session hours away from where it happened. ## What is checked Every row is read and checked. A row that cannot be used is dropped and counted under a named reason. The reasons include a line that cannot be parsed, a truncated last line, a time that goes backwards, a price that is missing, not a number or not above zero, a duplicate row, quotes where the bid is above the ask, and outliers in the spread or the price. Forge also compares the time zone a file declares with what the data itself shows. When the two disagree by more than a set tolerance, the dataset is flagged before you run on it. ## The broker's own history The chart can also draw history from the Deriv public feed. It is a short window: candles go back about a year and ticks about a day. A request outside the window is not refused and returns the latest data instead, so the dates on the result matter. --- Source: https://oathra.dev/docs/data/known-issues # Known issues with the data A standing list. Each entry has the date it was found. ## Deriv public history **Found 16 September 2026.** A request for history outside what the Deriv public feed holds does not fail. It returns the latest data instead. Check the first and last times of a result before trusting it. --- Source: https://oathra.dev/docs/data/misconceptions # Data misconceptions ## Volume The volume on a forex bar is usually a count of price updates, not an amount traded. Every series says which kind it holds: tick volume or traded volume. A strategy that treats tick volume as money changing hands is measuring the broker's feed. > **Trap.** A volume filter built on tick volume is a filter on how busy the feed was. ## Bid, ask and mid Bars are built from one price side. Broker-quoted markets use the bid by default, and exchange markets use the last traded price. A feed with a single price, such as the Deriv public feed, is marked as such. A bar built from the ask is a different bar. ## Time zones Every time inside Forge is UTC. A chart on a broker terminal shows server time, and a file can carry any of several clocks. See [broker time and the trading day](https://oathra.dev/docs/concepts/broker-time-and-the-trading-day). ## Gaps A missing minute is a missing row. Forge does not fill it, and a bar made with no real price in it can never be filled against. Across a weekend or a holiday a bar can continue its grid or reset it, and the series says which. > **Trap.** A weekend gap is not a loss of data. It is a closed market, and the gap between Friday's close and Sunday's open is real. --- # Examples Source: https://oathra.dev/examples/indicator-minimal Kind: indicator. Question: What is the smallest indicator that draws something? # Minimal indicator One setting, one output and a `calculate` method: the smallest file that draws a line. Start here and change the formula. > **Trap.** The output is empty for the first `period` bars. The `warmup` line says so, and leaving it out makes the chart trust values that are not ready. ## moving_average.py ```python import forge as fg from forge import param, ta FORGE_API = 1 class MovingAverage(fg.Indicator): name = "Moving average" short_name = "MA" pane = "price" period = param.Int(20, min=2, max=200) average = fg.Output.line("Average") warmup = lambda self: self.period def calculate(self, bars, start) -> None: self.out.average[start:] = ta.sma(bars.close, self.period)[start:] ``` --- Source: https://oathra.dev/examples/indicator-full Kind: indicator. Question: How do I draw more than one kind of output? # Indicator with several outputs This file shows the output kinds a strategy can read. A line and a histogram are numbers. A state is true or false on each bar. An event is 1, -1 or 0 on the bar it happens. A hidden output is computed and readable but not drawn. `h4` is a second series on four-hour bars. It follows the same rule as every other series: its values appear only after its own bar has closed. > **Trap.** An event is marked on the bar where it happens, not on every bar it stays true. Use a state for that. ## trend_bands.py ```python import forge as fg from forge import param, ta FORGE_API = 1 class TrendBands(fg.Indicator): name = "Trend bands" short_name = "TB" pane = "new" precision = 2 format = "number" period = param.Int(14, min=2, max=200) h4 = fg.Data(timeframe="H4") rsi = fg.Output.line("RSI") momentum = fg.Output.histogram("Distance from 50") overbought = fg.Output.state("Overbought") cross_up = fg.Output.event("Crosses 50 upwards") raw = fg.Output.hidden("raw_rsi") levels = [fg.Level(70, "High"), fg.Level(50, "Mid"), fg.Level(30, "Low")] warmup = lambda self: self.period def calculate(self, bars, start) -> None: value = ta.rsi(bars.close, self.period) self.out.raw[start:] = value[start:] self.out.rsi[start:] = value[start:] self.out.momentum[start:] = (value - 50)[start:] self.out.overbought[start:] = value[start:] > 70 self.out.cross_up[start:] = ta.crossover(value, 50)[start:].astype("int8") ``` --- Source: https://oathra.dev/examples/strategy-minimal Kind: strategy. Question: What is the smallest strategy that can be tested? # Minimal strategy A strategy needs a class, its bars and a `compute` method that returns signals. This one buys while the close is above its average and leaves when it is below. > **Trap.** `compute` must be pure. It reads `d` and the settings and nothing else, so it has no clock and no account. ## above_average.py ```python import forge as fg from forge import param, ta FORGE_API = 1 class AboveAverage(fg.Strategy): bars = fg.bars.Time("H1") direction = "long" period = param.Int(50, min=10, max=200, step=10) def compute(self, d) -> fg.Signals: average = ta.sma(d.close, self.period) return fg.Signals(long_entry=d.close > average, long_exit=d.close < average) ``` --- Source: https://oathra.dev/examples/strategy-full Kind: strategy. Question: Which hooks can a strategy define, and when does each run? # Strategy with every hook Every hook a strategy may define is in this file, each with one line that does nothing harmful and a comment naming when it runs. Delete the ones you do not need: a strategy with only `compute` is the fastest kind. > **Trap.** Defining `on_bar` makes every closed bar an event, and the run slows to match. Use the other hooks when one of them says what you need. ## every_hook.py ```python import forge as fg from forge import param, ta FORGE_API = 1 class EveryHook(fg.Strategy): """A strategy that defines every optional hook, for reference.""" meta = fg.Meta(title="Every hook", short="EVH", tags=["template"]) bars = fg.bars.Time("H1") direction = "long" period = param.Int(14, min=2, max=200) def compute(self, d) -> fg.Signals: rsi = ta.rsi(d.close, self.period) return fg.Signals(long_entry=rsi < 30, long_exit=rsi > 55) def setup(self, ctx) -> None: # Once, before any bar: check settings and prepare state. ctx.log.info("setup") def on_warmup_end(self, ctx) -> None: # Once, when warm-up ends and trading is allowed. ctx.log.info("on_warmup_end") def on_bar(self, ctx) -> None: # On every closed bar. Defining it makes every bar an event, the slowest kind. ctx.log.info("on_bar") def on_order_accepted(self, ctx, order) -> None: # When the market accepts an order. ctx.log.info("on_order_accepted") def on_fill(self, ctx, fill) -> None: # When an order fills, with the fill. ctx.log.info("on_fill") def on_order_cancelled(self, ctx, order) -> None: # When an order is cancelled. ctx.log.info("on_order_cancelled") def on_order_rejected(self, ctx, order) -> None: # When an order is rejected. ctx.log.info("on_order_rejected") def on_order_expired(self, ctx, order) -> None: # When an order expires. ctx.log.info("on_order_expired") def on_order_event(self, ctx, event) -> None: # After the specific order hook, for every order event. ctx.log.info("on_order_event") def on_position_opened(self, ctx, position, reason) -> None: # When a position opens. ctx.log.info("on_position_opened") def on_position_changed(self, ctx, position, reason) -> None: # When a position changes size. ctx.log.info("on_position_changed") def on_position_closed(self, ctx, position, reason) -> None: # When a position closes, with the reason. ctx.log.info("on_position_closed") def confirm_entry(self, ctx, intent) -> bool: # A last yes or no before an entry order is sent. return True def confirm_exit(self, ctx, intent) -> bool: # A last yes or no before an exit order is sent. return True def on_day_start(self, ctx) -> None: # At the start of a trading day. ctx.log.info("on_day_start") def on_day_end(self, ctx) -> None: # At the end of a trading day. ctx.log.info("on_day_end") def on_week_start(self, ctx) -> None: # At the start of a trading week. ctx.log.info("on_week_start") def on_session_open(self, ctx, name) -> None: # When a named session opens. ctx.log.info("on_session_open") def on_session_close(self, ctx, name) -> None: # When a named session closes. ctx.log.info("on_session_close") def on_end(self, ctx, reason) -> None: # Once, after the last step, with the reason the run ended. ctx.log.info("on_end") def on_prop_warning(self, ctx, rule, remaining) -> None: # Once, when most of a prop rule's room is used up, with the room left. ctx.log.info("on_prop_warning") def on_prop_breach(self, ctx, rule, action) -> None: # Once, after the engine has acted on a broken prop rule, with what it did. ctx.log.info("on_prop_breach") def size(self, ctx, intent) -> float: # Sets the size of an entry. This one keeps the size so far. return intent.size def score(self, stats) -> float: # Defines what best means when results are ranked. return 0.0 def accept(self, stats) -> bool: # Discards a result that fails a minimum. return True def report(self, stats) -> dict: # Adds named numbers to the report. return {} ``` --- Source: https://oathra.dev/examples/atr-stop-and-target Kind: strategy. Question: How do I size a stop and a target from the market's own range? # ATR stop and target The stop sits `stop_mult` times the average true range from the entry. The target is `target_r` times the distance to the stop, so a target of 2 risks one to make two. > **Trap.** A stop measured in a fixed number of points is too tight in a quiet market and too wide in a busy one. Measure it from the market's own range. ## atr_stop_and_target.py ```python import forge as fg from forge import param, ta FORGE_API = 1 class AtrStopAndTarget(fg.Strategy): bars = fg.bars.Time("H1") direction = "long" period = param.Int(50, min=10, max=200, step=10, group="Entry") stop_mult = param.Float(2.0, min=1.0, max=4.0, step=0.5, group="Risk") target_r = param.Float(2.0, min=1.0, max=4.0, step=0.5, group="Risk") def compute(self, d) -> fg.Signals: average = ta.sma(d.close, self.period) return fg.Signals( long_entry=ta.crossover(d.close, average), long_exit=d.close < average, stop=fg.Stop.atr(mult=self.stop_mult, length=14), target=fg.Target.r(value=self.target_r), ) ``` --- Source: https://oathra.dev/examples/london-session-breakout Kind: strategy. Question: How do I let a strategy enter only during one session? # London session entries The filter names its hours and its time zone. `mode="entries"` blocks new entries outside the window and leaves exits alone, so a position opened inside the window can still close after it. > **Trap.** Write the window in the zone you mean. London time moves with summer time, and a window written in broker time does not. ## london_session_entries.py ```python import forge as fg from forge import param, ta FORGE_API = 1 class LondonSessionEntries(fg.Strategy): bars = fg.bars.Time("H1") direction = "long" period = param.Int(50, min=10, max=200, step=10) def compute(self, d) -> fg.Signals: average = ta.sma(d.close, self.period) return fg.Signals( long_entry=ta.crossover(d.close, average), long_exit=d.close < average, filters=[ fg.Filter.session( windows="07:00-17:00", tz="Europe/London", mode="entries" ) ], ) ``` --- Source: https://oathra.dev/examples/rsi-cross Kind: strategy. Question: How do I buy when the RSI crosses back above a level? # RSI crossing a level The entry fires on the bar where the RSI crosses up through `buy_level`, not on every bar it stays above. The exit fires when it reaches `exit_level`. > **Trap.** A level that the RSI is already above on the first bar after warm-up is not a cross. Nothing fires until the RSI has been below the level first. ## rsi_cross.py ```python import forge as fg from forge import param, ta FORGE_API = 1 class RsiCross(fg.Strategy): bars = fg.bars.Time("H1") direction = "long" period = param.Int(14, min=2, max=50, group="RSI") buy_level = param.Int(30, min=10, max=45, step=5, group="RSI") exit_level = param.Int(60, min=50, max=80, step=5, group="RSI") def compute(self, d) -> fg.Signals: rsi = ta.rsi(d.close, self.period) return fg.Signals( long_entry=ta.crossover(rsi, self.buy_level), long_exit=rsi > self.exit_level, ) ``` --- Source: https://oathra.dev/examples/rsi-reversion Kind: strategy. Question: How do I buy a dip in the RSI with a stop and a target? # RSI reversion The entry is true on every bar the RSI is under `buy_below`, and the exit on every bar it is over `exit_above`. The stop sits two average true ranges from the entry, and the target is twice the distance to the stop. > **Trap.** Every array in `d` is ordered oldest first. The latest closed bar is the last one, not the first. ## rsi_reversion.py ```python import forge as fg from forge import param, ta FORGE_API = 1 class RsiReversion(fg.Strategy): """Buys when the RSI falls under a level and leaves when it recovers.""" meta = fg.Meta(title="RSI reversion", short="RSIR", tags=["mean reversion"]) bars = fg.bars.Time("H1") direction = "long" period = param.Int(14, min=5, max=40, step=1, group="RSI") buy_below = param.Int(30, min=10, max=40, step=5, group="RSI") exit_above = param.Int(55, min=45, max=70, step=5, group="RSI") def compute(self, d) -> fg.Signals: rsi = ta.rsi(d.close, self.period) return fg.Signals( long_entry=rsi < self.buy_below, long_exit=rsi > self.exit_above, stop=fg.Stop.atr(mult=2.0, length=14), target=fg.Target.r(value=2.0), ) ``` --- # Python reference Source: https://oathra.dev/reference/forge/jit # forge.jit - [cached](/reference/forge/jit/cached.md): Remember a function's result for the rest of one sweep. - [jit](/reference/forge/jit/jit.md): Compile a plain function over arrays and numbers with numba. --- Source: https://oathra.dev/reference/forge/testing # forge.testing - [EngineNotInstalled](/reference/forge/testing/EngineNotInstalled.md): A check needs Forge's engine, and this installation does not include it. - [INSTRUMENT_CLASSES](/reference/forge/testing/INSTRUMENT_CLASSES.md): - [STABLE_TRADE_FIELDS](/reference/forge/testing/STABLE_TRADE_FIELDS.md): - [SampleBars](/reference/forge/testing/SampleBars.md): A set of bars for tests, with the facts of the instrument they imitate. - [TestRun](/reference/forge/testing/TestRun.md): One finished run in a test. - [assert_entry_at](/reference/forge/testing/assert_entry_at.md): An entry was filled on primary bar ``bar``, on the ``side`` given when there is one. - [assert_exit_at](/reference/forge/testing/assert_exit_at.md): An exit was filled on primary bar ``bar``, closing a position of ``side`` and for ``reason``. - [assert_no_lookahead](/reference/forge/testing/assert_no_lookahead.md): Fail unless the look-ahead audit shows no evidence of look-ahead. - [assert_no_trade_between](/reference/forge/testing/assert_no_trade_between.md): No entry was filled from ``start`` to ``end``, both included. - [assert_ready_to_publish](/reference/forge/testing/assert_ready_to_publish.md): Fail unless every row of the pre-publish battery passes, naming each row that did not. - [assert_same_trades](/reference/forge/testing/assert_same_trades.md): The two runs made the same trades, compared one by one in entry-time order. - [assert_stable_warmup](/reference/forge/testing/assert_stable_warmup.md): Fail unless every output of the strategy is stable at its declared warm-up. - [broker_facts](/reference/forge/testing/broker_facts.md): The ``oathra.broker_facts/1`` payload a run receives, for the sample ``symbols``. - [check_lookahead](/reference/forge/testing/check_lookahead.md): Audit a strategy for look-ahead and return the report as plain JSON. - [check_warmup](/reference/forge/testing/check_warmup.md): Measure how much each output of a strategy depends on where its data starts, as plain JSON. - [instrument](/reference/forge/testing/instrument.md): A copy of the sample facts of ``symbol``: ``EURUSD``, ``GBPUSD``, ``USDJPY``, ``US500`` or - [publish_check](/reference/forge/testing/publish_check.md): Run the pre-publish battery on a strategy file and return its report as plain JSON. - [run](/reference/forge/testing/run.md): Run a strategy over sample bars and return what it did. - [snapshot](/reference/forge/testing/snapshot.md): Compare a run, or a mapping of numbers, with the snapshot stored for this test. - [stable_fields](/reference/forge/testing/stable_fields.md): The fields a run's snapshot keeps, by section, in the order the snapshot writes them. - [synthetic](/reference/forge/testing/synthetic.md): A seeded synthetic sample. ``seed`` has no default: an unseeded call is a ``TypeError``. - [to_snapshot](/reference/forge/testing/to_snapshot.md): A run, or a mapping of numbers, text, lists and mappings, as the plain structure a snapshot --- Source: https://oathra.dev/reference/forge/testing # forge.testing - [EngineNotInstalled](/reference/forge/testing/EngineNotInstalled.md): A check needs Forge's engine, and this installation does not include it. - [INSTRUMENT_CLASSES](/reference/forge/testing/INSTRUMENT_CLASSES.md): - [STABLE_TRADE_FIELDS](/reference/forge/testing/STABLE_TRADE_FIELDS.md): - [SampleBars](/reference/forge/testing/SampleBars.md): A set of bars for tests, with the facts of the instrument they imitate. - [TestRun](/reference/forge/testing/TestRun.md): One finished run in a test. - [assert_entry_at](/reference/forge/testing/assert_entry_at.md): An entry was filled on primary bar ``bar``, on the ``side`` given when there is one. - [assert_exit_at](/reference/forge/testing/assert_exit_at.md): An exit was filled on primary bar ``bar``, closing a position of ``side`` and for ``reason``. - [assert_no_lookahead](/reference/forge/testing/assert_no_lookahead.md): Fail unless the look-ahead audit shows no evidence of look-ahead. - [assert_no_trade_between](/reference/forge/testing/assert_no_trade_between.md): No entry was filled from ``start`` to ``end``, both included. - [assert_ready_to_publish](/reference/forge/testing/assert_ready_to_publish.md): Fail unless every row of the pre-publish battery passes, naming each row that did not. - [assert_same_trades](/reference/forge/testing/assert_same_trades.md): The two runs made the same trades, compared one by one in entry-time order. - [assert_stable_warmup](/reference/forge/testing/assert_stable_warmup.md): Fail unless every output of the strategy is stable at its declared warm-up. - [broker_facts](/reference/forge/testing/broker_facts.md): The ``oathra.broker_facts/1`` payload a run receives, for the sample ``symbols``. - [check_lookahead](/reference/forge/testing/check_lookahead.md): Audit a strategy for look-ahead and return the report as plain JSON. - [check_warmup](/reference/forge/testing/check_warmup.md): Measure how much each output of a strategy depends on where its data starts, as plain JSON. - [instrument](/reference/forge/testing/instrument.md): A copy of the sample facts of ``symbol``: ``EURUSD``, ``GBPUSD``, ``USDJPY``, ``US500`` or - [publish_check](/reference/forge/testing/publish_check.md): Run the pre-publish battery on a strategy file and return its report as plain JSON. - [run](/reference/forge/testing/run.md): Run a strategy over sample bars and return what it did. - [snapshot](/reference/forge/testing/snapshot.md): Compare a run, or a mapping of numbers, with the snapshot stored for this test. - [stable_fields](/reference/forge/testing/stable_fields.md): The fields a run's snapshot keeps, by section, in the order the snapshot writes them. - [synthetic](/reference/forge/testing/synthetic.md): A seeded synthetic sample. ``seed`` has no default: an unseeded call is a ``TypeError``. - [to_snapshot](/reference/forge/testing/to_snapshot.md): A run, or a mapping of numbers, text, lists and mappings, as the plain structure a snapshot --- Source: https://oathra.dev/reference/forge/signals/AddOn # forge.signals.AddOn ```python forge.signals.AddOn(*, kind: str = 'add_on', note: str | None = None, every_pips: float, size_mult: float = 1.0) -> None ``` Add to a position that moved in its favour: ``fg.AddOn(every_pips=20, size_mult=0.5)``. Each time the position gains ``every_pips`` more, another entry is added at ``size_mult`` times the first one's size, up to the strategy's ``max_entries_per_direction``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/alerts/AlertCondition # forge.alerts.AlertCondition ```python forge.alerts.AlertCondition(label: str, /, *, when: object, message: str | None = None, frequency: str = 'bar_close') -> None ``` One condition an indicator can alert on: a label, the output that says when, a message. ``AlertCondition("RSI above 70", when=hot, message="{symbol} RSI {out.rsi}")``. The message is optional; without one the alert says its label. See the module text for what ``when``, ``frequency`` and the placeholders mean. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/Band # forge.indicator.Band ```python forge.indicator.Band(a: _OutputDecl | Level | float, b: _OutputDecl | Level | float, *, style: str = 'area', color: Color | str | None = None, color_up: Color | str | None = None, color_down: Color | str | None = None, show: Iterable[str] | None = None) -> None ``` Shade (or draw as bars) the space between two sides of one indicator. A side is a value output of this class, a ``Level`` in its ``levels`` or a plain number. With ``color_up`` and ``color_down`` the band takes the up colour where ``a`` is above ``b`` and the down colour where it is below; a band has no base, width or dash. It is drawing only: a strategy reads the outputs, never a band. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Bracket # forge.signals.Bracket ```python forge.signals.Bracket(entry: OrderSpec, sl: float, tp: float) -> OrderSpec ``` An entry order with its stop and its target: ``fg.Bracket(entry, sl, tp)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/BreakEven # forge.signals.BreakEven ```python forge.signals.BreakEven(*, kind: str = 'break_even', note: str | None = None, at_r: float | None = None, at_pips: float | None = None, offset_pips: float = 0.0) -> None ``` Move the stop to the entry price once the trade is ahead: ``fg.BreakEven(at_r=1.0)``. Give one trigger: ``at_r`` is a multiple of the initial risk, and ``at_pips`` a gain in pips. ``offset_pips`` puts the stop that far beyond the entry, to cover costs, and must be below ``at_pips``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Caps # forge.signals.Caps ```python forge.signals.Caps(*, kind: str = 'caps', note: str | None = None, max_lots: float | None = None, max_positions: int | None = None, max_risk_pct: float | None = None, max_exposure_pct: float | None = None) -> None ``` The most a strategy may hold or risk: ``fg.Caps(max_lots=2, max_positions=3)``. ``max_lots`` is the most one position may be. ``max_positions`` is the most positions open at once, 1 to 64. ``max_risk_pct`` is the most of the account one trade may risk, and ``max_exposure_pct`` the most all open positions may be worth, as percents of the account. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/Context # forge.types.Context ```python forge.types.Context(*args, **kwargs) ``` The point-in-time context every hook receives as ``ctx``. Reads are views cut at the decision time: nothing a bar closing later could change is reachable. The members marked **action** queue something the engine applies at the next real price; everything else only reads. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/Data # forge.indicator.Data ```python forge.indicator.Data(*, timeframe: str | None = None, symbol: str | None = None, bars: str | Mapping[str, Any] | None = None, price: str | None = None, gaps: str = 'hold', day_boundary: str | None = None, **params: Any) -> None ``` Another series an indicator reads: a timeframe, a symbol, a bar type or a price. ``h4 = fg.Data(timeframe="H4")``, ``gbp = fg.Data(symbol="GBPUSD", timeframe="1D")``, ``boxes = fg.Data(bars="renko", brick=10)``. ``bars`` is a bar type name or a mapping with ``bar_type`` and ``params``; ``timeframe`` is C1's spelling or a free one (``7m``, ``4h``, ``1D``); ``price`` is ``bid``, ``ask``, ``mid``, ``last`` or ``quote``; ``symbol`` is a symbol name or the name of a symbol setting (so is ``timeframe``, for a timeframe setting). The declaration never touches data. A timeframe left out on time or Heikin-Ashi bars means the chart's own. A value of this series becomes visible on the first bar of the indicator whose close is at or after that value's own close; there is no switch that shows it earlier. ``gaps="hold"`` repeats the last closed value and ``gaps="nan"`` shows a value only on the bar where it arrived. There is no look-ahead setting: ``fg.Data(lookahead=True)`` is refused. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/EndReason # forge.types.EndReason ```python forge.types.EndReason(*values) ``` Why a run ended, handed to ``on_end``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Entry # forge.signals.Entry ```python forge.signals.Entry(*, kind: str, note: str | None = None, when: np.ndarray | None = None, price: np.ndarray | None = None, stop_price: np.ndarray | None = None, tif: str = 'gtc', tag: str | None = None, comment: str | None = None, alert_text: str | None = None, stop_loss: Stop | None = None, take_profit: Target | None = None, exit_when: np.ndarray | None = None, when_selection: tuple[str, int] | None = None) -> None ``` How entries are placed: ``fg.Entry.market``, ``limit``, ``stop`` or ``stop_limit``. ## Members | Name | Signature | Summary | | --- | --- | --- | | condition | (self) -> np.ndarray | The bars an order is placed on, as True/False. | | limit | (price: np.ndarray, *, tif: str \| None = None, expire_bars: int \| None = None, expire_at: int \| datetime \| None = None, tag: str \| None = None, comment: str \| None = None, alert_text: str \| None = None, note: str \| None = None) -> Entry | Place a limit order at ``price`` on each bar where it is a number. | | market | (when: np.ndarray \| ConditionTree, *, tag: str \| None = None, stop: Stop \| None = None, target: Target \| None = None, exit: np.ndarray \| ConditionTree \| None = None, comment: str \| None = None, alert_text: str \| None = None, note: str \| None = None) -> Entry | Enter at the next price on each bar where ``when`` is True. | | stop | (price: np.ndarray, *, tif: str \| None = None, expire_bars: int \| None = None, expire_at: int \| datetime \| None = None, tag: str \| None = None, comment: str \| None = None, alert_text: str \| None = None, note: str \| None = None) -> Entry | Place a stop order that triggers at ``price`` on each bar where it is a number. | | stop_limit | (stop_price: np.ndarray, price: np.ndarray, *, tif: str \| None = None, expire_bars: int \| None = None, expire_at: int \| datetime \| None = None, tag: str \| None = None, comment: str \| None = None, alert_text: str \| None = None, note: str \| None = None) -> Entry | A stop-limit order: triggered at ``stop_price``, then a limit order at ``price``. | Pass one as ``long_entry`` or ``short_entry`` in ``fg.Signals``. A market entry takes the usual True/False array. The priced kinds take an array of prices, one per bar: an order is placed on each bar whose price is a number, and a NaN bar places none, so write ``np.where(condition, level, np.nan)``. ``tif``, ``expire_bars`` and ``expire_at`` limit how long a resting order waits. ``tag`` names the trade, and ``comment`` and ``alert_text`` ride with it. Forge places these entries itself, on the bar each one fires, with the stop and target worked out from the order's own price. It calls the strategy's ``confirm_entry`` and ``size`` hooks for them in the order the engine calls them for any entry: ``confirm_entry`` first, then ``size``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Exit # forge.signals.Exit ```python forge.signals.Exit() ``` The ways to close trades together or by time: ``fg.Exit.basket``. ## Members | Name | Signature | Summary | | --- | --- | --- | | basket | (*, profit_cash: float \| None = None, loss_cash: float \| None = None, tag: str \| None = None, note: str \| None = None) -> Basket | Close the strategy's positions together at a total profit or loss, in cash. | More constructors are added here as the language grows. They are class attributes of a strategy, like ``basket = fg.Exit.basket(profit_cash=100)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/version/FORGE_API_LATEST # forge.version.FORGE_API_LATEST Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/FillEvent # forge.types.FillEvent ```python forge.types.FillEvent(order_id: int | None, position_id: int, time: int, bar_index: int, price: float, size: float, side: OrderSide, is_entry: bool, reason: FillReason, costs: FillCosts, symbol: str, engine_reason: str, liquidity: str | None = None) -> None ``` One fill: an entry, or a whole or partial exit. ``time`` is int64 nanoseconds UTC. ``bar_index`` is the index in the strategy's primary series of the bar whose interval holds that time. ``reason`` is the coarse reason a strategy reads and ``engine_reason`` the engine's own exit-reason name (``"entry"`` for an entry). ``liquidity`` is ``"maker"`` or ``"taker"`` once an exchange integration supplies it, and ``None`` until then: forex and CFD brokers do not report it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Filter # forge.signals.Filter ```python forge.signals.Filter() ``` Filters for ``fg.Signals(filters=[...])`` and the class attribute ``filters``: where not to trade. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/version/ForgeApiError # forge.version.ForgeApiError A script's ``FORGE_API`` line is missing, malformed or unsupported. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/Indicator # forge.indicator.Indicator ```python forge.indicator.Indicator(**inputs: Any) -> None ``` The one base class of an indicator (IND-001: declared by subclassing, never inferred). Non-time bars: an indicator runs on whatever bars it is given. ``bars.kind`` and ``bars.params`` say which (``bars.kind == "renko"``); ``bars.close`` is the drawn price and ``bars.real.close`` the real one. Sessions, daily levels and anchored averages read ``bars.real.time`` and ``bars.volume`` (the real volume inside each bar). The forming brick is never part of ``bars`` in a test. How candles are drawn (a Heikin-Ashi style) is a chart setting and never changes ``bars``; an indicator reads the data's bar type, not a display style. Bar form: a negative offset is refused when the file is checked and when it runs. Warm-up: recursive indicators (exponential averages, Wilder smoothing) keep a memory of where their data started. Each built-in's settle length in the registry is the history after which more bars change its value by less than 0.1% of its range. An anchored indicator (a running total) depends on its first bar. Repainting: set ``repaints = True`` when your indicator changes values it already showed (a swing marked once it is confirmed, for example). Forge then feeds strategies the value as it was at each bar's close. Mark an output that deliberately reads later bars, such as a training label, with ``causality_exceptions = {"target": "ml_label"}``: the check reports it, and strategies cannot read it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/Intent # forge.types.Intent ```python forge.types.Intent(side: PositionSide, symbol: str, signal: str | None, stop: float | None, target: float | None, size: float | None) -> None ``` What ``confirm_entry``, ``confirm_exit`` and ``size`` receive: a trade about to happen. ``stop`` and ``target`` are absolute prices. ``size`` is in lots; ``None`` means the run's sizing rule decides. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/strategy/InvalidParams # forge.strategy.InvalidParams ```python forge.strategy.InvalidParams(reason: str, *, constraint: int | None = None) -> None ``` Raise it in setup(self, ctx) to reject this combination of settings. A rejected combination is recorded and never traded. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/Level # forge.indicator.Level ```python forge.indicator.Level(value: float, label: str = '', *, color: Color | str | None = None, width: float = 1.0, dash: str = 'dash') -> None ``` A horizontal line in the indicator's pane: ``fg.Level(70, "Overbought")``. ``color`` defaults to ``theme.neutral``, ``width`` to 1 and ``dash`` to ``"dash"``. A level never changes after it is made. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/strategy/Meta # forge.strategy.Meta ```python forge.strategy.Meta(*, title: str | None = None, short: str | None = None, author: str | None = None, tags: tuple[str, ...] = ()) -> None ``` What a strategy is called and how it is found: a title, a short code, an author and tags. The class docstring is the description, so there is no ``description`` field. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Order # forge.signals.Order ```python forge.signals.Order() ``` The orders ``ctx.order`` takes: ``fg.Order.market``, ``limit``, ``stop`` and ``stop_limit``. ## Members | Name | Signature | Summary | | --- | --- | --- | | limit | (side: str, price: float, size: float \| None = None, *, symbol: str \| None = None, sl: float \| None = None, tp: float \| None = None, tag: str \| None = None, comment: str \| None = None, alert_text: str \| None = None, oco: str \| None = None, tif: str \| None = None, expire_bars: int \| None = None, expire_at: int \| datetime \| None = None) -> OrderSpec | An order that waits for ``price`` or better: below the market to buy, above to sell. | | market | (side: str, size: float \| None = None, *, symbol: str \| None = None, sl: float \| None = None, tp: float \| None = None, tag: str \| None = None, comment: str \| None = None, alert_text: str \| None = None, oco: str \| None = None) -> OrderSpec | An order filled at the next price. | | stop | (side: str, stop_price: float, size: float \| None = None, *, symbol: str \| None = None, sl: float \| None = None, tp: float \| None = None, tag: str \| None = None, comment: str \| None = None, alert_text: str \| None = None, oco: str \| None = None, tif: str \| None = None, expire_bars: int \| None = None, expire_at: int \| datetime \| None = None) -> OrderSpec | An order that triggers at ``stop_price``: above the market to buy, below to sell. | | stop_limit | (side: str, stop_price: float, price: float, size: float \| None = None, *, symbol: str \| None = None, sl: float \| None = None, tp: float \| None = None, tag: str \| None = None, comment: str \| None = None, alert_text: str \| None = None, oco: str \| None = None, tif: str \| None = None, expire_bars: int \| None = None, expire_at: int \| datetime \| None = None) -> OrderSpec | A stop that, once triggered at ``stop_price``, becomes a limit order at ``price``. | ``side`` is ``"buy"`` or ``"sell"`` and ``size`` is in lots (``None`` lets the run's sizing rule decide). ``sl`` and ``tp`` are price levels. ``tag`` names the trade for ``ctx.close`` and the trade list, ``comment`` is kept on it, ``alert_text`` is what an alert says, and ``oco`` names a group of orders of which one fills and the rest are cancelled. A resting order lasts until cancelled unless it has ``tif="day"``, ``expire_bars`` or ``expire_at``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/OrderStatus # forge.types.OrderStatus ```python forge.types.OrderStatus(*values) ``` Where an order is in its life. ``PARTIAL`` is reserved for partial fills, which no order produces yet: every fill today is whole. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/OrderTicket # forge.types.OrderTicket ```python forge.types.OrderTicket(record: TicketRecord, owner: TicketOwner | None) -> None ``` An order a strategy sent. Read its state, and change or cancel it while it is working. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/Output # forge.indicator.Output ```python forge.indicator.Output() ``` Output declarations. Each takes a label; the name is the attribute it is assigned to. ``line`` draws a value in any of the 13 value styles, ``histogram`` draws bars from a base, ``hidden`` is a value a strategy can read that the chart does not draw, and ``state`` is a true or false value per bar. ``event`` is 1, -1 or 0 per bar, ``marker`` an event drawn as a shape, ``zone`` a box with a top, a bottom and an active flag, and ``bar_color`` and ``background`` paint the chart only (a strategy cannot read them). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Param # forge.param.Param ```python forge.param.Param(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None) -> None ``` Base of every setting declaration. ``default`` is the only positional argument. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/ParamError # forge.param.ParamError A setting declaration, or a value given for one, is invalid. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Plot # forge.signals.Plot ```python forge.signals.Plot(series: np.ndarray, /, *, style: str | None = None, pane: str = 'price', label: str | None = None, color: object = None, width: float | None = None, dash: str | None = None, color_up: object = None, color_down: object = None, shape: str | None = None, where: str | None = None, base: float | None = None) -> None ``` A named series for ``fg.Signals(plots=...)`` with a style and a pane. ``fg.Plot(series, style="step", color="theme.up", pane="new")``. A True/False array is drawn as a state, any other array as a value. The styles and their limits are the chart's: a style the chart cannot draw for that kind of series is refused with the styles it can. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/Position # forge.types.Position ```python forge.types.Position(record: PositionRecord, owner: PositionOwner | None) -> None ``` An open position. Read it, and move its stop and target, close it or reverse it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Region # forge.signals.Region ```python forge.signals.Region(start: int, end: int, /, *, label: str | None = None, color: object = None) -> None ``` A stretch of bars to shade on the chart: ``fg.Region(start, end, label="news")``. ``start`` and ``end`` are bar numbers, both included. Regions of one plot share one colour. A label is written above the first bar of the region. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/RejectReason # forge.types.RejectReason ```python forge.types.RejectReason(*values) ``` Why an order was refused. One member per engine refusal name, bar block then order block. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/strategy/Requires # forge.strategy.Requires ```python forge.strategy.Requires(*, hedging: bool) -> None ``` The account behaviour a strategy needs: ``account = fg.Requires(hedging=True)``. ## Members | Name | Signature | Summary | | --- | --- | --- | | to_spec | (self) -> dict[str, Any] | The declaration as plain JSON. | ``hedging=True`` needs an account that holds a long and a short position in one symbol at the same time, and ``hedging=False`` one that nets them. The run's broker account decides which it is, and a run on the other kind of account is refused, never changed. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/RunMode # forge.types.RunMode ```python forge.types.RunMode(*values) ``` How the run was started. Readable from a strategy and never a behaviour switch. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/version/SUPPORTED_API_LEVELS # forge.version.SUPPORTED_API_LEVELS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Signals # forge.signals.Signals ```python forge.signals.Signals(*, long_entry: np.ndarray | ConditionTree | Entry | Sequence[Entry] | None = None, short_entry: np.ndarray | ConditionTree | Entry | Sequence[Entry] | None = None, long_exit: np.ndarray | ConditionTree | None = None, short_exit: np.ndarray | ConditionTree | None = None, stop: Stop | None = None, target: Target | Targets | None = None, trail: Trail | None = None, size: Size | None = None, filters: Sequence[object] = (), tags: str | np.ndarray | None = None, plots: Mapping[str, np.ndarray | Plot | Region | Sequence[Region]] | None = None, record: Mapping[str, np.ndarray] | None = None) -> None ``` What ``compute()`` returns: entry and exit conditions, and named series to plot or record. ## Members | Name | Signature | Summary | | --- | --- | --- | | fields | (self) -> dict[str, object] | The fields that were given, by name. | Every argument is optional and keyword-only. An unknown argument is a ``TypeError``. A comparison with a missing value (NaN) is False, so a missing value can never mean "true". Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Size # forge.signals.Size ```python forge.signals.Size(*, kind: str, note: str | None = None, value: float | np.ndarray | None = None, annual: float | None = None, lookback: int | None = None, round: str = 'down') -> None ``` How much each entry trades. Build one with a constructor, like ``fg.Size.risk_pct(1.0)``. ## Members | Name | Signature | Summary | | --- | --- | --- | | fixed_margin | (value: float, *, round: str = 'down', note: str \| None = None) -> Size | A position that uses this much margin, in the account currency. | | lots | (value: float \| np.ndarray, *, round: str = 'down', note: str \| None = None) -> Size | A fixed number of lots, or one number per bar. | | notional | (value: float, *, round: str = 'down', note: str \| None = None) -> Size | A position of this value, in the account currency. | | pct_equity | (value: float, *, round: str = 'down', note: str \| None = None) -> Size | A position worth this percent of the account's equity. | | risk_cash | (value: float, *, round: str = 'down', note: str \| None = None) -> Size | Risk this much money, in the account currency, on each trade. | | risk_pct | (value: float, *, round: str = 'down', note: str \| None = None) -> Size | Risk this percent of the account on each trade, from the entry to the stop. | | units | (value: float \| np.ndarray, *, round: str = 'down', note: str \| None = None) -> Size | A fixed number of units of the instrument, or one number per bar. | | vol_target | (annual: float, *, lookback: int = 20, round: str = 'down', note: str \| None = None) -> Size | Size so the position's yearly volatility is this fraction of the account, like 0.1. | ``lots`` and ``units`` take a number or one per bar. The risk methods size the entry so the stop loses that much, so they need a stop. ``round`` is ``"down"`` (the default, never above the amount) or ``"nearest"``, to the broker's lot step. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Stop # forge.signals.Stop ```python forge.signals.Stop(*, kind: str, note: str | None = None, value: float | np.ndarray | None = None, mult: float | None = None, length: int | None = None, lookback: int | None = None) -> None ``` Where a losing trade is closed. Build one with a constructor, like ``fg.Stop.atr(2.0)``. ## Members | Name | Signature | Summary | | --- | --- | --- | | atr | (mult: float, length: int = 14, *, note: str \| None = None) -> Stop | A multiple of the average true range over ``length`` bars (1 to 500). | | cash | (value: float \| np.ndarray, *, note: str \| None = None) -> Stop | The money the trade loses at the stop, in the account currency. | | pct | (value: float \| np.ndarray, *, note: str \| None = None) -> Stop | A distance as a percent of the price the entry was decided at, below 100. | | pips | (value: float \| np.ndarray, *, note: str \| None = None) -> Stop | A distance in pips from the entry. | | points | (value: float \| np.ndarray, *, note: str \| None = None) -> Stop | A distance in points from the entry. | | price | (level: float \| np.ndarray, *, note: str \| None = None) -> Stop | A price level, or an array of them with NaN where a bar has no stop. | | swing | (lookback: int = 5, *, note: str \| None = None) -> Stop | Just beyond the last swing low (long) or swing high (short) of ``lookback`` bars. | | ticks | (value: float \| np.ndarray, *, note: str \| None = None) -> Stop | A distance in ticks from the entry. | ``pips``, ``points``, ``ticks``, ``pct`` (of the decision price) and ``cash`` (money lost at the stop) take a number or one per bar. ``price`` is a price level, or one per bar where NaN means no stop on that bar. ``atr`` is a multiple of the average true range, and ``swing`` sits beyond the last swing high or low of that many bars. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/strategy/Strategy # forge.strategy.Strategy ```python forge.strategy.Strategy(**values: Any) -> None ``` One strategy class per file, with ``FORGE_API = 1`` at the top of the file. ## Members | Name | Signature | Summary | | --- | --- | --- | | describe | () -> dict[str, Any] | The machine description of this strategy's settings. | | read_asset | (self, path: str) -> bytes | Return a bundled asset's bytes. The path is one listed in assets. | | to_spec | () -> dict[str, Any] | Returns this strategy as a Strategy Spec when it uses only what the Studio can show. | Declare each setting with ``fg.param`` as a class attribute, and write only the hooks the strategy needs. ``setup(self, ctx)`` holds start-up code and may raise ``fg.InvalidParams`` to reject a combination of settings. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Target # forge.signals.Target ```python forge.signals.Target(*, kind: str, note: str | None = None, value: float | np.ndarray | None = None, mult: float | None = None, length: int | None = None) -> None ``` Where a winning trade is closed. Build one with a constructor, like ``fg.Target.r(2.0)``. ## Members | Name | Signature | Summary | | --- | --- | --- | | atr | (mult: float, length: int = 14, *, note: str \| None = None) -> Target | A multiple of the average true range over ``length`` bars (1 to 500). | | pct | (value: float \| np.ndarray, *, note: str \| None = None) -> Target | A distance as a percent of the price the entry was decided at, at most 1000. | | pips | (value: float \| np.ndarray, *, note: str \| None = None) -> Target | A distance in pips from the entry. | | points | (value: float \| np.ndarray, *, note: str \| None = None) -> Target | A distance in points from the entry. | | price | (level: float \| np.ndarray, *, note: str \| None = None) -> Target | A price level, or an array of them with NaN where a bar has no target. | | r | (value: float, *, note: str \| None = None) -> Target | A multiple of the initial risk: ``r(2.0)`` is twice the distance to the stop. | | ticks | (value: float \| np.ndarray, *, note: str \| None = None) -> Target | A distance in ticks from the entry. | ``pips``, ``points``, ``ticks`` and ``pct`` are distances from the entry and take a number or one per bar. ``price`` is a price level, or one per bar where NaN means no target on that bar. ``atr`` is a multiple of the average true range, and ``r`` a multiple of the risk to the stop. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Targets # forge.signals.Targets ```python forge.signals.Targets(rungs: Sequence[tuple[float, Target]], *, note: str | None = None) -> None ``` Several targets that each close a part of the trade: ``fg.Targets([(0.5, t1), (0.5, t2)])``. ## Members | Name | Signature | Summary | | --- | --- | --- | | label | (self) -> str | The declaration as written in a file. | Each rung is a fraction of the trade and a ``fg.Target``. Fractions are above 0 and add up to at most 1, and there are 1 to 10 rungs. Two rungs never share a level. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/TimeStop # forge.signals.TimeStop ```python forge.signals.TimeStop(*, kind: str = 'time_stop', note: str | None = None, bars: int | None = None, duration: str | None = None, only_if: str | None = None) -> None ``` Close a trade that has been open too long: ``fg.TimeStop(bars=48)`` or ``duration="2d"``. ``bars`` counts bars of the strategy's own timeframe. ``duration`` is a number of minutes, hours or days written like ``"90m"``, ``"6h"`` or ``"2d"``, from 1 minute to 365 days. ``only_if="loss"`` closes the trade only while it is not in profit. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/Trade # forge.types.Trade ```python forge.types.Trade(id: int, symbol: str, side: PositionSide, size: float, entry: FillEvent, exit: FillEvent, gross_pnl: float, pnl: float, r_multiple: float | None, bars_held: int, tag: str | None, exit_level: float | None = None, exit_vs_level: float | None = None, exit_flags: tuple[str, ...] = (), mae_r: float | None = None, exit_name: str | None = None, add_count: int = 0, reduce_count: int = 0, peak_size: float | None = None, size_capped_by: str | None = None, risk_cash: float | None = None, risk_pct: float | None = None, target_distance: float | None = None, trail_distance: float | None = None) -> None ``` One closed round trip, or the closed part of one: an entry fill, its exit fill and the money. ``size`` is the lots this row closed, so a partial close is its own row. ``pnl`` is net in account currency and ``gross_pnl`` is before costs. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/Trail # forge.signals.Trail ```python forge.signals.Trail(*, kind: str, note: str | None = None, value: float | np.ndarray | None = None, mult: float | None = None, length: int | None = None, start_pips: float | None = None, start_points: float | None = None, start_r: float | None = None, step: float | None = None, max: float | None = None) -> None ``` A stop that follows the price. Build one with a constructor, like ``fg.Trail.atr(3.0)``. ## Members | Name | Signature | Summary | | --- | --- | --- | | atr | (mult: float, length: int = 14, *, start_r: float \| None = None, step: float \| None = None, note: str \| None = None) -> Trail | Trail a multiple of the average true range, from ``start_r`` times the risk ahead. | | chandelier | (mult: float = 3.0, length: int = 22, *, note: str \| None = None) -> Trail | The chandelier exit: a multiple of the average true range from the best price. | | pct | (value: float, *, note: str \| None = None) -> Trail | Trail a percent of the price behind the best price, below 100. | | pips | (value: float, *, start_pips: float \| None = None, step: float \| None = None, note: str \| None = None) -> Trail | Trail ``value`` pips behind the best price, from ``start_pips`` ahead, in ``step``s. | | points | (value: float, *, start_points: float \| None = None, step: float \| None = None, note: str \| None = None) -> Trail | Trail ``value`` points behind the best price, from ``start_points`` ahead, in steps. | | psar | (step: float = 0.02, max: float = 0.2, *, note: str \| None = None) -> Trail | The parabolic SAR: ``step`` is the acceleration, ``max`` its top, at most 1. | | series | (level: np.ndarray, *, note: str \| None = None) -> Trail | Follow a level you compute, one price per bar. The stop is that level for both sides. | ``pips`` and ``points`` trail at a fixed distance, starting once the trade is that far ahead (``start_pips``, ``start_points``) and moving in steps of ``step``. ``atr`` trails at a multiple of the average true range, from ``start_r`` times the risk. ``pct`` trails at a percent of the price. ``chandelier`` and ``psar`` are the named trailing methods, and ``series`` follows a level you compute, one value per bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/alerts/alert # forge.alerts.alert ```python forge.alerts.alert(message: str, /, *, bar: int | None = None, frequency: str = 'bar_close') -> None ``` Raise an alert from ``on_bar``, or from ``calculate`` with ``bar=``. There is no way to say who is told: only the person who armed the alert hears it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/all_of # forge.signals.all_of ```python forge.signals.all_of(*, name: str | None = None, note: str | None = None, **conditions: np.ndarray | ConditionTree) -> ConditionTree ``` True on a bar where every named condition is true. The names are kept, so a bar can report which conditions were true. ``name`` and ``note`` are the group's own, so a condition cannot be called either. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/any_of # forge.signals.any_of ```python forge.signals.any_of(*, name: str | None = None, note: str | None = None, **conditions: np.ndarray | ConditionTree) -> ConditionTree ``` True on a bar where at least one named condition is true. The names are kept, so a bar can report which conditions were true. ``name`` and ``note`` are the group's own, so a condition cannot be called either. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/bars/Count # forge.bars.Count ```python forge.bars.Count(n: int | Param, of: str | Param, *, basis: str | Param | None = None, day_boundary: str | None = None, on_closure: str | None = None) -> BarDecl ``` One bar for every ``n`` time bars of ``of``, like ``Count(3, "H1")``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/bars/HeikinAshi # forge.bars.HeikinAshi ```python forge.bars.HeikinAshi(timeframe: str | Param = 'H1', *, seed: str = 'mt5_raw', settle_bars: int | Param = 10, basis: str | Param | None = None, day_boundary: str | None = None, on_closure: str | None = None) -> BarDecl ``` Heikin-Ashi bars over time bars. ``seed`` is "mt5_raw" (default) or "textbook"; ``settle_bars`` defaults to 10. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/bars/Kagi # forge.bars.Kagi ```python forge.bars.Kagi(reversal: float | Param | Mapping[str, Any], *, unit: str = 'pips', basis: str | Param | None = None, day_boundary: str | None = None, on_closure: str | None = None, anchor: str | float | None = None) -> BarDecl ``` Kagi lines that reverse after a move of ``reversal`` in ``unit`` (default pips). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/bars/LineBreak # forge.bars.LineBreak ```python forge.bars.LineBreak(lines: int | Param = 3, *, basis: str | Param | None = None, day_boundary: str | None = None, on_closure: str | None = None) -> BarDecl ``` Line break bars that look back ``lines`` lines (default 3). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/bars/PointFigure # forge.bars.PointFigure ```python forge.bars.PointFigure(box: float | Param | Mapping[str, Any] | str, *, unit: str = 'pips', reversal: int | Param = 3, one_box: bool = False, event: str = 'column', basis: str | Param | None = None, day_boundary: str | None = None, on_closure: str | None = None, anchor: str | float | None = None) -> BarDecl ``` Point and figure columns. ``box`` is a size, an ATR or percent box, or "traditional". ``reversal`` (default 3) counts boxes; ``event`` is "column" (default) or "box". Since Forge API 1. --- Source: https://oathra.dev/reference/forge/bars/Range # forge.bars.Range ```python forge.bars.Range(size: float | Param, *, unit: str = 'pips', basis: str | Param | None = None, day_boundary: str | None = None, on_closure: str | None = None) -> BarDecl ``` Range bars: a bar closes when its high to low range reaches ``size`` in ``unit``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/bars/Renko # forge.bars.Renko ```python forge.bars.Renko(box: float | Param | Mapping[str, Any], *, unit: str = 'pips', trend: float | Param = 1, reversal: float | Param = 2, offset: float = 0.0, wicks: bool = False, new_bar_mode: str = 'close_on_threshold', preset: str | None = 'traditional', basis: str | Param | None = None, day_boundary: str | None = None, on_closure: str | None = None, anchor: str | float | None = None) -> BarDecl ``` Renko bricks. ``box`` is the brick size in ``unit`` (default pips), or an ATR or percent box. ``trend`` (default 1) and ``reversal`` (default 2) count boxes; ``offset`` is 0 to 1. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/bars/Tick # forge.bars.Tick ```python forge.bars.Tick(n: int | Param, *, basis: str | Param | None = None, day_boundary: str | None = None, on_closure: str | None = None) -> BarDecl ``` One bar for every ``n`` ticks. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/bars/Time # forge.bars.Time ```python forge.bars.Time(timeframe: str | Param, *, basis: str | Param | None = None, day_boundary: str | None = None, on_closure: str | None = None) -> BarDecl ``` Time bars: one bar per period, like ``Time("H1")``. ``timeframe`` is M5, H1, D1, W1 or MN1. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/jit/cached # forge.jit.cached ```python forge.jit.cached(func: F) -> F ``` Remember a function's result for the rest of one sweep. Put it on a plain function that takes arrays, numbers, text, True/False or None, and returns an array, a tuple or dict of arrays, or a number. A sweep then computes each distinct input once, however many combinations of settings share it. Outside a Forge run it is an ordinary function. A cached call with a keyword and the same call with a positional argument make two entries. Returned arrays are read only. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/version/check_api_level # forge.version.check_api_level ```python forge.version.check_api_level(value: object) -> int ``` Return the declared level if Forge supports it, else raise ``ForgeApiError``. ``value`` is the module's ``FORGE_API`` constant, or ``None`` when the file declares none. ``bool`` is refused even though it is an ``int`` subclass. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/dates/DateRule # forge.dates.DateRule ```python forge.dates.DateRule(kind: str, trading: bool, days: tuple[str, ...] = (), dates: tuple[str, ...] = ()) -> None ``` Which days a scheduled method runs on. ## Members | Name | Signature | Summary | | --- | --- | --- | | to_json | (self) -> dict[str, Any] | The rule as plain JSON: its kind, trading or calendar days, and any days or dates. | Since Forge API 1. --- Source: https://oathra.dev/reference/forge/dates/every_day # forge.dates.every_day ```python forge.dates.every_day(*, trading: bool = True) -> DateRule ``` Every trading day, or every calendar day with ``trading=False``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/dates/month_end # forge.dates.month_end ```python forge.dates.month_end(*, trading: bool = True) -> DateRule ``` The last day of each month. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/dates/month_start # forge.dates.month_start ```python forge.dates.month_start(*, trading: bool = True) -> DateRule ``` The first day of each month. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/dates/on_dates # forge.dates.on_dates ```python forge.dates.on_dates(*dates: str | datetime.date) -> DateRule ``` Specific dates, as ISO text like ``"2026-10-01"`` or ``datetime.date`` values. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/dates/week_end # forge.dates.week_end ```python forge.dates.week_end(*, trading: bool = True) -> DateRule ``` The last day of each week. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/dates/week_start # forge.dates.week_start ```python forge.dates.week_start(*, trading: bool = True) -> DateRule ``` The first day of each week. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/dates/weekdays # forge.dates.weekdays ```python forge.dates.weekdays(*days: str) -> DateRule ``` The named weekdays, like ``weekdays("mon", "fri")``: mon, tue, wed, thu, fri, sat, sun. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/describe_params # forge.param.describe_params ```python forge.param.describe_params(cls: type) -> list[Param] ``` Every setting declared on ``cls`` and its bases, base settings first, in declaration order. A subclass that re-declares a name replaces the base's declaration in place. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/BASE_STYLES # forge.draw.BASE_STYLES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/DASH_STYLES # forge.draw.DASH_STYLES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/DEFAULT_FILL_ALPHA # forge.draw.DEFAULT_FILL_ALPHA Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/DEFAULT_STYLE_BY_KIND # forge.draw.DEFAULT_STYLE_BY_KIND Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/DRAWINGS_PER_KIND # forge.draw.DRAWINGS_PER_KIND Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/DRAWING_KINDS # forge.draw.DRAWING_KINDS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/Drawing # forge.draw.Drawing ```python forge.draw.Drawing(recorder: _Recorder, key: int) -> None ``` A handle on one drawing. It can change or remove the drawing and shows nothing of it. Drawings are for eyes: a strategy cannot read a coordinate, a text or a colour from a handle, so anything a strategy needs must be an output. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/EXTENDS # forge.draw.EXTENDS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/FONTS # forge.draw.FONTS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/LABEL_SIZES # forge.draw.LABEL_SIZES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/LINE_DASHES # forge.draw.LINE_DASHES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/MARKER_SHAPES # forge.draw.MARKER_SHAPES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/MARKER_WHERE # forge.draw.MARKER_WHERE Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/MAX_FUTURE_BARS # forge.draw.MAX_FUTURE_BARS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/MAX_SHIFT # forge.draw.MAX_SHIFT Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/MAX_TEXT # forge.draw.MAX_TEXT Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/MAX_WIDTH # forge.draw.MAX_WIDTH Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/NAMED_COLORS # forge.draw.NAMED_COLORS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/NO_SHIFT_STYLES # forge.draw.NO_SHIFT_STYLES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/OUTPUT_KINDS # forge.draw.OUTPUT_KINDS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/PLOT_STYLES # forge.draw.PLOT_STYLES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/PlotStyle # forge.draw.PlotStyle ```python forge.draw.PlotStyle(style: str, color: str | None = None, width: float | None = None, dash: str | None = None, color_up: str | None = None, color_down: str | None = None, shift: int | str = 0, show: tuple[str, ...] = ('pane', 'scale', 'legend', 'data_window'), shape: str | None = None, where: str | None = None, base: float | None = None) -> None ``` How one output is drawn. Colours are held as wire strings. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/Point # forge.draw.Point ```python forge.draw.Point(bar: int | None = None, time: int | None = None, price: float | None = None) -> None ``` A place on the chart: a bar (``bar=12``) or a time (``time=ns``), and a price. A point is anchored by ``bar=...`` or ``time=...``, never both. ``time`` is nanoseconds since the epoch, UTC. On a chart of time bars a time after the last bar is placed by the bar length; on other bar types only the bar is kept. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/SERIES_TOKENS # forge.draw.SERIES_TOKENS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/SHOW_TARGETS # forge.draw.SHOW_TARGETS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/STYLES_BY_KIND # forge.draw.STYLES_BY_KIND Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/TEXT_ALIGN # forge.draw.TEXT_ALIGN Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/THEME_TOKENS # forge.draw.THEME_TOKENS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/TIME_AXIS_KINDS # forge.draw.TIME_AXIS_KINDS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/UP_DOWN_KINDS # forge.draw.UP_DOWN_KINDS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/WIDTH_STYLES # forge.draw.WIDTH_STYLES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/all # forge.draw.all ```python forge.draw.all(kind: str | None = None) -> tuple[Drawing, ...] ``` Every live drawing, oldest first; ``kind`` keeps one kind (``"label"``, ``"box"``, ...). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/box # forge.draw.box ```python forge.draw.box(left: int | Point, top: float, right: int | Point, bottom: float, *, fill: Color | str | None = None, border: Color | str | None = None, border_width: float = 1.0, border_dash: str = 'solid', text: str = '', text_color: Color | str | None = None, size: str = 'normal', bold: bool = False, italic: bool = False, align: str = 'center', font: str = 'default', extend: str = 'none', id: str | None = None) -> Drawing ``` A rectangle between two bars and two prices, with optional text inside. The corners are put in order: ``left`` is the earlier bar and ``top`` the higher price. A ``Point`` corner takes its price from ``top`` and ``bottom``, so it carries none of its own. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/chart # forge.draw.chart Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/color # forge.draw.color Since Forge API 1. ## Class forge.draw.Color ```python forge.draw.Color(rgb: tuple[int, int, int] | None, token: str | None, alpha: float = 1.0) -> None ``` A fixed colour (``rgb``) or a theme colour (``token``), with an optional alpha. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/find # forge.draw.find ```python forge.draw.find(id: str) -> Drawing | None ``` The live drawing with this id, or ``None``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/gradient # forge.draw.gradient ```python forge.draw.gradient(value: float | ArrayLike | None, lo: float, hi: float, c1: Color | str, c2: Color | str) -> Color | None | list[Color | None] ``` The colour between ``c1`` (at ``lo``) and ``c2`` (at ``hi``) for ``value``. A missing value gives ``None`` (no colour: the drawing's default). An array gives a list. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/hline # forge.draw.hline ```python forge.draw.hline(price: float, *, color: Color | str | None = None, width: float = 1.0, dash: str = 'solid', id: str | None = None) -> Drawing ``` A horizontal line across the whole chart at ``price``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/label # forge.draw.label ```python forge.draw.label(bar: int | Point, price: float | None = None, text: str = '', *, style: Mapping[str, Any] | None = None, where: str | None = None, color: Color | str | None = None, text_color: Color | str | None = None, size: str | None = None, bold: bool | None = None, italic: bool | None = None, align: str | None = None, font: str | None = None, id: str | None = None) -> Drawing ``` Write ``text`` on the chart at a price (``where="at_price"``) or on a bar. ``above_bar`` and ``below_bar`` labels sit on the bar and take no price. Defaults: ``where`` ``"at_price"``, ``color`` ``theme.accent``, ``text_color`` ``theme.text``, ``size`` ``"normal"``, ``bold`` and ``italic`` off, ``align`` ``"center"``, ``font`` ``"default"``. ``style`` takes the same names as a mapping; a name is given once, in ``style`` or as a keyword. Give ``id`` to draw the same label again: a live id replaces the earlier drawing. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/level # forge.draw.level ```python forge.draw.level(price: float, *, start: int | Point | None = None, until: str | None = 'touched', color: Color | str | None = None, width: float = 1.0, dash: str = 'solid', id: str | None = None) -> Drawing ``` A horizontal line from ``start`` that stops at the first bar whose range reaches it. ``until="touched"`` compares the level with each bar's high and low, so it is for levels at a price: an indicator on the price chart or one whose values are prices. ``until=None`` keeps the line going. ``start`` defaults to the current bar inside ``on_bar``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/line # forge.draw.line ```python forge.draw.line(p1: Point, p2: Point, *, extend: str = 'none', color: Color | str | None = None, width: float = 1.0, dash: str = 'solid', id: str | None = None) -> Drawing ``` A straight line between two points (each needs a price); ``extend`` makes it a ray. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/make_style # forge.draw.make_style ```python forge.draw.make_style(kind: str, *, style: str | None = None, color: Color | str | None = None, width: float | None = None, dash: str | None = None, color_up: Color | str | None = None, color_down: Color | str | None = None, shift: int | str = 0, show: Iterable[str] | None = None, shape: str | None = None, where: str | None = None, base: float | None = None, hidden: bool = False) -> PlotStyle ``` Check a style against the vocabulary and fill the defaults. Every refusal is a sentence. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/parse_color # forge.draw.parse_color ```python forge.draw.parse_color(value: Color | str) -> Color ``` A colour from a ``Color`` or one of its written forms; anything else is a ``ValueError``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/rgb # forge.draw.rgb ```python forge.draw.rgb(r: int, g: int, b: int, a: float = 1.0) -> Color ``` A fixed colour from red, green and blue (0 to 255) and an optional alpha (0 to 1). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/validate_wire_style # forge.draw.validate_wire_style ```python forge.draw.validate_wire_style(kind: str, style: Mapping[str, Any]) -> None ``` Check a style as a ``SeriesPage`` or the registry carries it: known keys, then every rule. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/draw/vline # forge.draw.vline ```python forge.draw.vline(bar: int | Point, *, color: Color | str | None = None, width: float = 1.0, dash: str = 'solid', id: str | None = None) -> Drawing ``` A vertical line through one bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/schedule/every # forge.schedule.every ```python forge.schedule.every(interval: str, *, at: str | None = None, tz: str | None = None) -> Callable[[F], F] ``` Run the method on a fixed interval: ``"15m"``, ``"4h"``, ``"1D"`` or ``"1W"``. ``at`` fixes the time of day for a daily or weekly interval: ``"16:55"``, ``"open+30m"`` or ``"close-5m"``. ``tz`` names the time zone of a clock time, like ``"America/New_York"``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/BAR_TYPES # forge.indicator.BAR_TYPES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/DECLARABLE_EXCEPTIONS # forge.indicator.DECLARABLE_EXCEPTIONS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/FORMATS # forge.indicator.FORMATS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/LIVE_CADENCES # forge.indicator.LIVE_CADENCES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/MANIFEST_SCHEMA # forge.indicator.MANIFEST_SCHEMA Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/check_causality_exceptions # forge.indicator.check_causality_exceptions ```python forge.indicator.check_causality_exceptions(value: object, output_names: Iterable[str]) -> dict[str, str] ``` The declared causality exceptions: output names to ``DECLARABLE_EXCEPTIONS``. Every refusal is ``ValueError("causality_invalid: ...")`` and names the offender. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/check_data_declarations # forge.indicator.check_data_declarations ```python forge.indicator.check_data_declarations(series: Sequence[Data], settings: Mapping[str, param.Param], names: set[str]) -> None ``` The checks that need the whole class. Raises ``ValueError`` with its rule id as prefix. The class and the static load check both call it, so they refuse the same things. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/check_output_declarations # forge.indicator.check_output_declarations ```python forge.indicator.check_output_declarations(outputs: Sequence[_OutputDecl], settings: Mapping[str, param.Param], pane: str) -> None ``` The checks that need the whole class: shift names and the price pane. Raises ``ValueError`` with a ``style_invalid:`` or ``output_invalid:`` message. Used by the class and by the static load check, which has no class. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/check_repaints # forge.indicator.check_repaints ```python forge.indicator.check_repaints(value: object) -> bool ``` ``repaints`` is ``True`` or ``False``; anything else is ``metadata_invalid``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/check_timeframe_attribute # forge.indicator.check_timeframe_attribute ```python forge.indicator.check_timeframe_attribute(value: object, settings: Mapping[str, param.Param]) -> Any ``` The class ``timeframe`` in C1's spelling, or a setting's name. A refusal is ``ValueError("timeframe_invalid: ...")``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/describe_bands # forge.indicator.describe_bands ```python forge.indicator.describe_bands(bands: Sequence[Band]) -> list[dict[str, Any]] ``` One manifest entry per band, in declaration order. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/describe_outputs # forge.indicator.describe_outputs ```python forge.indicator.describe_outputs(source: type | Sequence[_OutputDecl]) -> list[dict[str, Any]] ``` One manifest entry per output, in declaration order (all keys always present). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/normalize_timeframe # forge.indicator.normalize_timeframe ```python forge.indicator.normalize_timeframe(text: str) -> str ``` A timeframe in C1's spelling: ``M7``, ``H4``, ``D1``, ``W1``, ``MN1``. ``7m``, ``90m``, ``4h``, ``3D`` and the like are accepted and converted; weeks and months come one at a time (``1W``, ``1M``). Anything else is ``ValueError("data_invalid: ...")``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/output_styles # forge.indicator.output_styles ```python forge.indicator.output_styles(source: type | Sequence[_OutputDecl]) -> dict[str, PlotStyle] ``` Every output's style with its default colour filled in. Each drawn value output takes the series colour of its position among the drawn value outputs, whether or not it names a colour itself, so giving one output a colour never recolours the others. A hidden output is neutral and a zone is a pale accent. A usable state drawn as a background is the warning colour, a display-only background a pale neutral, and any other state or event the accent colour. An event that names both its up and its down colour and no colour keeps no colour: the two say everything. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/timeframe_ns # forge.indicator.timeframe_ns ```python forge.indicator.timeframe_ns(timeframe: str) -> int | None ``` The length of a C1 timeframe in nanoseconds, or ``None`` for a month (no fixed length). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/use_call_targets # forge.indicator.use_call_targets ```python forge.indicator.use_call_targets(class_node: ast.ClassDef) -> list[ast.expr] ``` The first argument of every ``self.use(...)`` in the class's methods, in source order. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/use_target_key # forge.indicator.use_target_key ```python forge.indicator.use_target_key(first: ast.expr) -> str ``` The registry key a ``self.use`` first argument names; ``use_invalid`` when it names none. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/uses_from_source # forge.indicator.uses_from_source ```python forge.indicator.uses_from_source(cls: type) -> list[dict[str, str]] ``` The built-in indicators this class names in ``self.use(...)``, sorted, each once. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/warmup_expression_from_node # forge.indicator.warmup_expression_from_node ```python forge.indicator.warmup_expression_from_node(node: ast.AST, settings: Mapping[str, param.Param]) -> str ``` One warm-up expression from a literal, a ``lambda self: ...`` or a ``def warmup(self)``. The body is one expression built from whole numbers, ``self.`` of a number setting, ``+ - * //``, parentheses, ``max`` and ``min``. Anything else is ``ValueError("warmup_invalid: ...")``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/warmup_manifest_from_node # forge.indicator.warmup_manifest_from_node ```python forge.indicator.warmup_manifest_from_node(node: ast.AST, settings: Mapping[str, param.Param], output_names: set[str]) -> dict[str, Any] ``` The warm-up manifest from a ``warmup`` node: what the class and the load check both say. ``ValueError("warmup_invalid: ...")`` for a form outside the grammar, a key that is not an output, or a value that is negative or fractional at the settings' defaults. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/indicator/warmup_node # forge.indicator.warmup_node ```python forge.indicator.warmup_node(class_node: ast.ClassDef) -> ast.AST | None ``` The class's ``warmup`` assignment value or ``def warmup``, or ``None``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/isna # forge.ta.isna ```python forge.ta.isna(x: ArrayLike | float | None) -> np.ndarray[Any, np.dtype[np.bool_]] | bool ``` True where a value is missing: ``None`` or ``NaN``. An array gives an array of the same shape. Boolean and integer arrays carry no missing value (contract C3 section 2), so they give all ``False``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/jit/jit # forge.jit.jit ```python forge.jit.jit(func: Any = None, /, **options: Any) -> Any ``` Compile a plain function over arrays and numbers with numba. Write ``@fg.jit`` or ``@fg.jit(fastmath=True)``. The first call compiles the function, which takes a second or more. Arrays read from ``d`` are read only, so a jitted function cannot write into them. ``fastmath`` is the only option: Forge runs sweeps in parallel itself, and compiles each run's code fresh. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/runner/load # forge.runner.load ```python forge.runner.load(path: str | os.PathLike[str]) -> Program ``` Run a ``.py`` file's top level, check it and return the checked ``Program``. The result names what the file holds: ``kind`` ("strategy" or "library"), ``name``, ``description``, ``settings``, ``hooks``, ``warmup``, ``symbols``, ``direction``, ``order_mode``, ``exports`` and ``warnings``. ``program.instantiate(values)`` creates the strategy with those setting values. A refused file raises a ``ValueError`` whose ``findings`` list every problem with its line. Loading runs the file's top-level code, so load only code you trust. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/nz # forge.ta.nz ```python forge.ta.nz(x: ArrayLike | float | None, value: float = 0.0) -> np.ndarray[Any, Any] | float ``` Replace a missing value with ``value`` (default 0). A scalar ``None`` or ``NaN`` gives ``value``. A float array gives a new float64 array and never changes the input. Boolean and integer arrays carry no missing value, so a copy comes back unchanged. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/schedule/on # forge.schedule.on ```python forge.schedule.on(rule: DateRule, *, at: str | None = None, tz: str | None = None) -> Callable[[F], F] ``` Run the method on the days a date rule names, like ``fg.dates.month_end()``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Bool # forge.param.Bool ```python forge.param.Bool(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None) -> None ``` Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Choice # forge.param.Choice ```python forge.param.Choice(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None, options: tuple[str, ...] = ()) -> None ``` Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Color # forge.param.Color ```python forge.param.Color(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = False, space: str | None = None, name: str | None = None) -> None ``` Display only: a theme token (``theme.up``) or ``#rrggbb``. Never swept. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Decimal # forge.param.Decimal ```python forge.param.Decimal(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None, min: float | None = None, max: float | None = None, step: float | None = None, values: tuple[float, ...] | None = None, scale: str = 'linear', places: int = 2) -> None ``` A number with a fixed count of decimal places (02 LANG-085). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Enum # forge.param.Enum ```python forge.param.Enum(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None) -> None ``` A dropdown over a Python ``enum.Enum``; the default is one of its members. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Float # forge.param.Float ```python forge.param.Float(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None, min: float | None = None, max: float | None = None, step: float | None = None, values: tuple[float, ...] | None = None, scale: str = 'linear') -> None ``` Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Int # forge.param.Int ```python forge.param.Int(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None, min: int | None = None, max: int | None = None, step: int | None = 1, values: tuple[int, ...] | None = None, scale: str = 'linear') -> None ``` Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/MAX_CANDIDATES # forge.param.MAX_CANDIDATES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/MA_METHODS # forge.param.MA_METHODS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/MaMethod # forge.param.MaMethod ```python forge.param.MaMethod(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None, options: tuple[str, ...] = ('sma', 'ema', 'smma', 'lwma', 'wma', 'hma', 'dema', 'tema')) -> None ``` A moving-average method dropdown (04 IND-044). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Price # forge.param.Price ```python forge.param.Price(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None) -> None ``` A price level, draggable on the Studio chart. ``None`` means unset. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Session # forge.param.Session ```python forge.param.Session(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None, tz: str | None = None) -> None ``` A time window like ``"07:00-10:00"``; several are comma separated. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Source # forge.param.Source ```python forge.param.Source(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None) -> None ``` A price field (``close``, ``hl2``...) or another indicator's output. An output is named ``"."``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Symbol # forge.param.Symbol ```python forge.param.Symbol(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None) -> None ``` A symbol; ``None`` means the run's own symbol. Never swept. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Text # forge.param.Text ```python forge.param.Text(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = False, space: str | None = None, name: str | None = None) -> None ``` Display only: a short text. Never swept. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Time # forge.param.Time ```python forge.param.Time(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None, tz: str | None = None) -> None ``` A clock time like ``"16:55"``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Timeframe # forge.param.Timeframe ```python forge.param.Timeframe(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None, options: tuple[str, ...] | None = None) -> None ``` Since Forge API 1. --- Source: https://oathra.dev/reference/forge/param/Volume # forge.param.Volume ```python forge.param.Volume(default: Any, *, group: str | None = None, label: str | None = None, help: str | None = None, order: int | None = None, inline: str | None = None, optimize: bool = True, space: str | None = None, name: str | None = None, options: tuple[str, ...] = ('tick', 'real')) -> None ``` Tick volume or real traded volume (04 IND-043). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/BREACH # forge.prop.BREACH Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/BestDay # forge.prop.BestDay ```python forge.prop.BestDay(*, max_share: float, denominator: str = 'net_profit', consequence: str = 'fail') -> None ``` No one day may be more than this share of the profit. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/DailyLoss # forge.prop.DailyLoss ```python forge.prop.DailyLoss(*, pct: float | None = None, cash: float | None = None, pct_of: str = 'day_start', reset: str | None = None, tz: str | None = None, basis: str = 'balance_at_reset', includes: tuple[str, ...] = ('swap', 'commission', 'unrealized'), breach: str | None = None, on_breach: str | None = None) -> None ``` The most the account may lose in a day: a percent or an amount, from the day's own start. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/MaxLoss # forge.prop.MaxLoss ```python forge.prop.MaxLoss(*, amount: float | None = None, pct: float | None = None, kind: str = 'static', freeze_at: str | float | None = None, breach: str | None = None, on_breach: str | None = None) -> None ``` The most the account may lose in all: static, or trailing behind the balance or the equity. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/MaxRequests # forge.prop.MaxRequests ```python forge.prop.MaxRequests(*, per_day: int) -> None ``` At most this many requests to the broker in a day. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/NoFloorAsStop # forge.prop.NoFloorAsStop ```python forge.prop.NoFloorAsStop() -> None ``` Never use the account's loss floor as the stop. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/NoHedging # forge.prop.NoHedging ```python forge.prop.NoHedging() -> None ``` No opposite positions on one instrument at once. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/NoMartingale # forge.prop.NoMartingale ```python forge.prop.NoMartingale() -> None ``` No sizing that doubles up after a loss. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/NoNews # forge.prop.NoNews ```python forge.prop.NoNews(*, window: str = '2m') -> None ``` Do not hold trades through the news window around a release. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/NoNewsBrackets # forge.prop.NoNewsBrackets ```python forge.prop.NoNewsBrackets() -> None ``` No pending orders placed around a news release. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/NoOvernight # forge.prop.NoOvernight ```python forge.prop.NoOvernight() -> None ``` No trade open overnight. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/NoWeekendHold # forge.prop.NoWeekendHold ```python forge.prop.NoWeekendHold() -> None ``` No trade open over the weekend. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/ON_BREACH # forge.prop.ON_BREACH Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/Phase # forge.prop.Phase ```python forge.prop.Phase(*, target_pct: float, min_days: int = 0, max_days: int | None = None, start: str = 'initial', leverage: float | None = None, name: str | None = None) -> None ``` One phase of an evaluation: a profit target, with the days it needs. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/RequireStop # forge.prop.RequireStop ```python forge.prop.RequireStop() -> None ``` Every trade carries a stop. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/Rules # forge.prop.Rules ```python forge.prop.Rules(*, kind: str = 'rules', note: str | None = None, daily_loss: DailyLoss | None = None, max_loss: MaxLoss | None = None, phases: tuple[Phase, ...] = (), min_days: int | None = None, day_counts_if: str = 'trade_closed', inactivity_days: int | None = None, consistency: BestDay | None = None, max_lots: float | None = None, scaling: Scaling | None = None, bans: tuple[_Ban, ...] = (), on_breach: str | None = None, warn_at: float = 0.8) -> None ``` Custom prop-firm rules, as the class attribute ``prop = fg.prop.Rules(...)``. ``warn_at`` is when ``on_prop_warning`` fires: the share of a rule's room, as it was when the rule's period began, that is gone. A preset warns at 0.8. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/Scaling # forge.prop.Scaling ```python forge.prop.Scaling(*, table: tuple[tuple[float, float], ...], basis: str = 'balance', applies_to: str = 'open_total') -> None ``` A table of the lots the account may trade as its balance grows. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/WARN_AT # forge.prop.WARN_AT Since Forge API 1. --- Source: https://oathra.dev/reference/forge/prop/preset # forge.prop.preset ```python forge.prop.preset(name: str, *, size: float | None = None, note: str | None = None) -> Preset ``` A named firm's rules from Forge's library, like ``fg.prop.preset("ftmo-2step", size=100_000)``. Since Forge API 1. ## Class forge.prop.Preset ```python forge.prop.Preset(*, kind: str = 'preset', note: str | None = None, name: str, size: float | None = None) -> None ``` A named firm's rules from Forge's dated library, as ``prop = fg.prop.preset("ftmo-2step")``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/runner/run # forge.runner.run ```python forge.runner.run(strategy: str | os.PathLike[str] | type[Strategy] | Mapping[str, Any], *, custom_indicators: Mapping[str, str | os.PathLike[str]] | None = None, bars: pd.DataFrame, symbol: str, broker_facts: Mapping[str, Any], timeframe: str | None = None, settings: Mapping[str, Any] | None = None, extras: Mapping[str, pd.DataFrame | SeriesColumns] | None = None, calendar: CalendarDocument | None = None, seed: int | None = None, capital: float | None = None, execution_model: Mapping[str, Any] | None = None, quotes: pd.DataFrame | None = None) -> RunResult ``` Run a strategy file over a data frame, the way a hosted run does. ``strategy`` is the path of the file, or a strategy class already defined, such as one in a test. A class is checked as its file is, except for the checks that read the whole file. A Strategy Spec (the JSON mapping the Studio saves) runs too, built with ``compile_spec``. ``custom_indicators`` gives the developer indicators a Strategy Spec names, each key ``":"`` (the spec's ``custom`` and ``version``) and each value the indicator's file. A spec that names one that is not given is refused before it runs. ``bars`` holds ``open``, ``high``, ``low``, ``close`` and ``volume``, indexed by each bar's close time on the broker's server clock, without a time zone. The prices are the bid. The ask is the bid plus the broker's stated spread, and volume is tick volume. ``broker_facts`` is the ``oathra.broker_facts/1`` payload of the account, and ``symbol`` a symbol it holds. ``timeframe`` names the bars, like ``"H1"``. A strategy that declares its bars gives it, and a different one passed here is refused. ``settings`` gives setting values, and the defaults fill the rest. A strategy that reads other series needs them in ``extras``, each under the text a refusal prints for it, like ``'d.tf("H4")'`` or ``'d["GBPUSD"]'``. Each is a data frame like ``bars`` or ready-made columns. ``calendar`` is an economic calendar for a strategy that reads ``d.calendar``. ``seed`` fixes the run's random generator, and the same inputs give the same seed when it is left out. The strategy's own use of ``random`` and ``numpy.random`` starts from that seed too, so a run repeats. ``capital`` is the starting balance in the account's currency, and 25,000 when left out. ``execution_model`` is the run's execution settings in the form the Tester stores them, like ``{"orders": {"require_stop": True}}``. What the strategy declares (stops, targets, sizes, caps, exits) wins over the same setting there, and the result's ``flags["declarations"]`` lists each declaration, how it ran and every setting it replaced. The account hedges or nets as the broker facts say, unless the model sets ``{"account": {"margin_mode": "hedging"}}`` or ``"netting"``. ``quotes`` gives the bid and ask prices a strategy that declares a price side decides on. Every entry fills after its decision time, at the first real price after the bar it was decided on. Returns the engine's ``RunResult``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/series/highest # forge.series.highest ```python forge.series.highest(x: ArrayLike, window: int) -> np.ndarray ``` The highest of the last ``window`` values ending at each bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/series/lowest # forge.series.lowest ```python forge.series.lowest(x: ArrayLike, window: int) -> np.ndarray ``` The lowest of the last ``window`` values ending at each bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/series/rolling # forge.series.rolling ```python forge.series.rolling(x: ArrayLike, window: int, how: Literal['mean', 'sum', 'min', 'max', 'std']) -> np.ndarray ``` The ``how`` of the last ``window`` values ending at each bar, NaN until ``window`` values exist. ``how`` is ``"mean"``, ``"sum"``, ``"min"``, ``"max"`` or ``"std"`` (the population standard deviation). A NaN inside a window gives NaN for it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/series/shift # forge.series.shift ```python forge.series.shift(x: ArrayLike, n: int = 1, *, fill: float = nan) -> np.ndarray ``` ``x`` moved ``n`` bars later, so the value at bar ``i`` is ``x[i - n]``. The first ``n`` bars hold ``fill``, NaN unless given. ``n`` is a whole number of bars back, 0 or more. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/ASIA # forge.sessions.ASIA Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/ASIA_KILLZONE # forge.sessions.ASIA_KILLZONE Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/LONDON # forge.sessions.LONDON Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/LONDON_CLOSE_KILLZONE # forge.sessions.LONDON_CLOSE_KILLZONE Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/LONDON_KILLZONE # forge.sessions.LONDON_KILLZONE Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/NY # forge.sessions.NY Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/NY_KILLZONE # forge.sessions.NY_KILLZONE Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/PRESETS # forge.sessions.PRESETS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/SessionPreset # forge.sessions.SessionPreset ```python forge.sessions.SessionPreset(*, name: str, hours: str = '', tz: str = '', days: tuple[str, ...] = ('mon', 'tue', 'wed', 'thu', 'fri'), kind: Literal['window', 'venue'] = 'window', symbol: str | None = None) -> None ``` A named window of hours in a time zone, or a symbol's own venue session. ``hours`` is ``"HH:MM-HH:MM"`` in ``tz``, on ``days``. A window may wrap past midnight. A ``venue`` preset has no hours of its own: every symbol trades in its market's hours, which Forge applies to every run. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/get # forge.sessions.get ```python forge.sessions.get(name: str) -> SessionPreset ``` The preset called ``name``, in any case. A name Forge does not have names the nearest one. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/sessions/venue # forge.sessions.venue ```python forge.sessions.venue(symbol: str | None = None) -> SessionPreset ``` The symbol's own market hours. ``None`` is the strategy's primary symbol. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/count # forge.signals.count ```python forge.signals.count(condition: np.ndarray | ConditionTree, /, *, over: int) -> np.ndarray ``` The number of bars among this bar and the ``over - 1`` before it where the condition is true. ``count(rsi > 50, over=3) >= 3`` means above 50 for the last three bars. A comparison of the result gives a True/False array, so it combines like any condition. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/n_of # forge.signals.n_of ```python forge.signals.n_of(k: int, /, *, name: str | None = None, note: str | None = None, **conditions: np.ndarray | ConditionTree) -> ConditionTree ``` True on a bar where at least ``k`` of the named conditions are true. Adding a condition does not change the others. The names are kept, so a bar can report which conditions were true. ``name`` and ``note`` are the group's own, so a condition cannot be called either. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/none_of # forge.signals.none_of ```python forge.signals.none_of(*, name: str | None = None, note: str | None = None, **conditions: np.ndarray | ConditionTree) -> ConditionTree ``` True on a bar where none of the named conditions is true. The names are kept, so a bar can report which conditions were checked. ``name`` and ``note`` are the group's own, so a condition cannot be called either. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/then # forge.signals.then ```python forge.signals.then(first: np.ndarray | ConditionTree, second: np.ndarray | ConditionTree, /, *, within: int, name: str | None = None, note: str | None = None) -> ConditionTree ``` True where ``second`` becomes true, if ``first`` became true 1 to ``within`` bars earlier. Only the bar each one becomes true counts. The bar ``first`` became true is not counted in the window, so both becoming true on the same bar is not a sequence. ``within=5`` means ``second`` on any of the 5 bars after ``first``'s bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/signals/vote # forge.signals.vote ```python forge.signals.vote(*, open_at: float, close_at: float, name: str | None = None, note: str | None = None, **weighted: tuple[np.ndarray | ConditionTree, float]) -> ConditionTree ``` True from the bar the weighted share of true conditions reaches ``open_at`` percent until the bar it falls to ``close_at`` percent or below. Each condition is given as ``(condition, weight)``, like ``trend=(fast > slow, 2)``. Between the two levels it keeps its last state; before the first time it reaches either, it is false. ``name`` and ``note`` are the group's own, so a condition cannot be called either. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/strategy/RequirementFailed # forge.strategy.RequirementFailed A condition the strategy requires was false. It stops the run at the line of ``fg.strategy.require``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/strategy/require # forge.strategy.require ```python forge.strategy.require(condition: object, message: str) -> None ``` Stop the run with ``message`` unless ``condition`` is true. A plain ``assert`` works the same way. Both stop the run, in ``compute()`` or in a hook, with the file and line of the check. ``condition`` is one true or false value: for an array write ``require(bool(x.all()), ...)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/BWMFI # forge.ta.BWMFI ```python forge.ta.BWMFI(**inputs: Any) -> None ``` Price range per unit of volume, with Bill Williams' four colours. MFI = (high - low) / volume, empty where the volume is 0 phase: 0 green (MFI up, volume up), 1 fade (down, down), 2 fake (MFI up, volume down), 3 squat (MFI down, volume up). The directions start up and change only when the value strictly changes from the previous bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/BearsPower # forge.ta.BearsPower ```python forge.ta.BearsPower(**inputs: Any) -> None ``` The bar's low against an EMA of closes. Bears = low - EMA(close, period) Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/BollingerBands # forge.ta.BollingerBands ```python forge.ta.BollingerBands(**inputs: Any) -> None ``` A moving average with bands set by the standard deviation. middle = the simple average of the source over period upper = middle + deviations * the population standard deviation of the same window, lower = middle - deviations * the same Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/BollingerPercentB # forge.ta.BollingerPercentB ```python forge.ta.BollingerPercentB(**inputs: Any) -> None ``` Where the price sits between the Bollinger Bands. %B = (source - lower) / (upper - lower) No value when the bands meet. middle = the simple average of the source over period upper = middle + deviations * the population standard deviation of the same window, lower = middle - deviations * the same Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/BollingerWidth # forge.ta.BollingerWidth ```python forge.ta.BollingerWidth(**inputs: Any) -> None ``` The distance between the Bollinger Bands relative to the middle line. width = (upper - lower) / middle middle = the simple average of the source over period upper = middle + deviations * the population standard deviation of the same window, lower = middle - deviations * the same Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/BullsPower # forge.ta.BullsPower ```python forge.ta.BullsPower(**inputs: Any) -> None ``` The bar's high against an EMA of closes. Bulls = high - EMA(close, period) Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ChaikinOscillator # forge.ta.ChaikinOscillator ```python forge.ta.ChaikinOscillator(**inputs: Any) -> None ``` The fast average of A/D minus its slow average. The chosen average of accumulation/distribution over the fast period minus the same over the slow period Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/DarkCloudCover # forge.ta.DarkCloudCover ```python forge.ta.DarkCloudCover(**inputs: Any) -> None ``` A fall that closes deep into the previous rise. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first bar rises with a long body; the second falls, opens at or above the first close and closes at or below first close - penetration * first body, above the first open Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/DragonflyDoji # forge.ta.DragonflyDoji ```python forge.ta.DragonflyDoji(**inputs: Any) -> None ``` A doji that opens and closes near its high. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low a doji, and upper wick <= wick_pct / 100 * range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/EfficiencyRatio # forge.ta.EfficiencyRatio ```python forge.ta.EfficiencyRatio(**inputs: Any) -> None ``` How directly the price moved: 1 for a straight line, near 0 for noise. ER[i] = |x[i] - x[i-period]| / sum(|x[j] - x[j-1]|) for j from i-period+1 to i When the sum is zero, ER is 0. A window holding a missing value has no value. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/EveningDojiStar # forge.ta.EveningDojiStar ```python forge.ta.EveningDojiStar(**inputs: Any) -> None ``` An evening star whose middle bar is a doji. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low as an evening star, with the middle bar a doji: its body <= body_pct / 100 * its own range. The star may open at the first close: no gap is required. Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/EveningStar # forge.ta.EveningStar ```python forge.ta.EveningStar(**inputs: Any) -> None ``` A long rise, a small star, then a fall into the rise. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first bar rises with a long body; the star's body <= star_body_pct / 100 * the first body and its bottom >= the first close; the third bar falls and closes at or below first close - penetration * first body. The star may open at the first close: no gap is required. Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/FairValueGaps # forge.ta.FairValueGaps ```python forge.ta.FairValueGaps(**inputs: Any) -> None ``` Three-candle imbalances, shown until price fills them. A bullish gap forms on a bar whose low is above the high two bars earlier, when the bar between them closes above that high. The gap runs from that earlier high up to the bar's low. A bearish gap mirrors it. A gap is kept only when its height is at least min_size. With auto_threshold on, it is also kept only when the middle candle's body is larger than threshold_mult times the average body of the threshold_bars candles before it. A gap shows from the bar it forms on. With mitigation filled, a bullish gap is mitigated when price trades below its bottom; with touched, when price trades into it. Bearish gaps mirror it. bull_mitigated and bear_mitigated mark the bar, and the gap is removed. With extend above zero, a gap shows on the bar it forms and the extend bars after it, then is removed. Slot 1 is the newest live gap of its side; Forge keeps the five newest per side. A missing bar starts the scan again. The gaps are found on the bars the indicator runs on, which can be another timeframe than the chart's. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ForceIndex # forge.ta.ForceIndex ```python forge.ta.ForceIndex(**inputs: Any) -> None ``` Volume times the change of a moving average of price. force = volume[i] * (MA[i] - MA[i-1]) with the moving average of the chosen method Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/GravestoneDoji # forge.ta.GravestoneDoji ```python forge.ta.GravestoneDoji(**inputs: Any) -> None ``` A doji that opens and closes near its low. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low a doji, and lower wick <= wick_pct / 100 * range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/HangingMan # forge.ta.HangingMan ```python forge.ta.HangingMan(**inputs: Any) -> None ``` A long lower wick after a rise. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low lower wick >= wick_mult * body, upper wick <= opposite_pct / 100 * range, the body inside the top body_zone_pct of the range, and after a rise: close[i-1] > close[i-1-trend_bars] Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/HeikinAshi # forge.ta.HeikinAshi ```python forge.ta.HeikinAshi(**inputs: Any) -> None ``` Smoothed candles that make trends easier to read. HA close = (open + high + low + close) / 4 HA open = (previous HA open + previous HA close) / 2 HA high = the highest of high, HA open and HA close, HA low the lowest of low, HA open and HA close The first bar is seeded from the raw bar (mt5_raw) or from the textbook averages. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/HighestBars # forge.ta.HighestBars ```python forge.ta.HighestBars(**inputs: Any) -> None ``` How many bars ago the highest value of the window was. How many bars ago the highest value of the last period bars was, 0 when it is the current bar. When two bars share the highest value, the more recent one counts. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/InsideBar # forge.ta.InsideBar ```python forge.ta.InsideBar(**inputs: Any) -> None ``` A bar whose range sits within the previous bar's range. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low high <= the previous high and low >= the previous low (strictly with equal values off); both bars have a range in_a_row = how many inside bars in a row end at this bar, 0 on a bar that is not one Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/InvertedHammer # forge.ta.InvertedHammer ```python forge.ta.InvertedHammer(**inputs: Any) -> None ``` A long upper wick after a fall. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low upper wick >= wick_mult * body, lower wick <= opposite_pct / 100 * range, the body inside the bottom body_zone_pct of the range, and after a fall: close[i-1] < close[i-1-trend_bars] Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/Keltner # forge.ta.Keltner ```python forge.ta.Keltner(**inputs: Any) -> None ``` An EMA with bands set by the average true range. middle = the EMA of the closes over ema_period upper = middle + atr_factor * ATR, lower = middle - atr_factor * ATR ATR is the simple average of the true range over atr_period, as the MetaTrader 5 Keltner Channel computes it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/KeltnerWidth # forge.ta.KeltnerWidth ```python forge.ta.KeltnerWidth(**inputs: Any) -> None ``` The distance between the Keltner bands relative to the middle line. width = (upper - lower) / middle middle = the EMA of the closes over ema_period upper = middle + atr_factor * ATR, lower = middle - atr_factor * ATR ATR is the simple average of the true range over atr_period, as the MetaTrader 5 Keltner Channel computes it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/LinRegIntercept # forge.ta.LinRegIntercept ```python forge.ta.LinRegIntercept(**inputs: Any) -> None ``` Where the fitted line starts, at the oldest bar of the window. Fit a straight line by least squares to the last period values, with t = 0 at the oldest. b = (period * sum(t*y) - sum(t) * sum(y)) / (period * sum(t^2) - sum(t)^2) a = (sum(y) - b * sum(t)) / period Intercept = a Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/LinRegSlope # forge.ta.LinRegSlope ```python forge.ta.LinRegSlope(**inputs: Any) -> None ``` How much the fitted line rises or falls per bar. Fit a straight line by least squares to the last period values, with t = 0 at the oldest. b = (period * sum(t*y) - sum(t) * sum(y)) / (period * sum(t^2) - sum(t)^2) a = (sum(y) - b * sum(t)) / period Slope = b Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/LogReturn # forge.ta.LogReturn ```python forge.ta.LogReturn(**inputs: Any) -> None ``` The natural log of the value over the previous value. ln(source[i] / source[i-1]). No value on the first bar, or when either value is missing or not above zero. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/LowestBars # forge.ta.LowestBars ```python forge.ta.LowestBars(**inputs: Any) -> None ``` How many bars ago the lowest value of the window was. How many bars ago the lowest value of the last period bars was, 0 when it is the current bar. When two bars share the lowest value, the more recent one counts. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/MarketStructure # forge.ta.MarketStructure ```python forge.ta.MarketStructure(**inputs: Any) -> None ``` Confirmed swings labelled HH, HL, LH and LL, with breaks of structure (BOS) and changes of character (CHoCH), for internal and swing structure. A swing high is a bar whose high is above the high of every bar within swing_len bars on each side; a swing low mirrors it. Equal highs make no swing. A swing is known swing_len bars after it, and every value is written on that later bar; the chart draws the label back on the swing bar. HH and LH mark a swing high above or below the previous one; HL and LL mark a swing low above or below the previous one. A break happens on the first bar that closes above the latest swing high (break_on close) or trades above it (break_on wick). It is a change of character (CHoCH) when the trend was down, and a break of structure (BOS) otherwise; the trend then turns up and that level cannot break again. Breaks below the latest swing low mirror it. Values +1 are upward breaks and -1 downward. Internal structure applies the same rule with internal_len. internal and swing turn each one on or off. With confluence_filter on, an internal break is marked only when its bar closes in the break's direction and the level it breaks is not the swing level. A missing bar starts the structure again. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/Momentum # forge.ta.Momentum ```python forge.ta.Momentum(**inputs: Any) -> None ``` Price now against price a number of bars ago. difference: x[i] - x[i-period] mt5: x[i] * 100 / x[i-period], the MetaTrader 5 form, centred on 100 An earlier value of 0 leaves the mt5 bar empty. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/MorningDojiStar # forge.ta.MorningDojiStar ```python forge.ta.MorningDojiStar(**inputs: Any) -> None ``` A morning star whose middle bar is a doji. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low as a morning star, with the middle bar a doji: its body <= body_pct / 100 * its own range. The star may open at the first close: no gap is required. Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/MorningStar # forge.ta.MorningStar ```python forge.ta.MorningStar(**inputs: Any) -> None ``` A long fall, a small star, then a rise into the fall. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first bar falls with a long body; the star's body <= star_body_pct / 100 * the first body and its top <= the first close; the third bar rises and closes at or above first close + penetration * first body. The star may open at the first close: no gap is required. Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/OpeningRange # forge.ta.OpeningRange ```python forge.ta.OpeningRange(**inputs: Any) -> None ``` The high and low of the first minutes of a session, with breakout signals. The session runs from the start to the end of session_hours in session_tz. Its range bars are those that open within the first minutes minutes after the start, on the local clock. or_high and or_low are the highest high and lowest low of the range bars so far, then of all of them for the rest of the session. They are empty outside the session. breakout is +1 on a bar after the range whose close crosses above or_high, and -1 when the close crosses below or_low. A session already under way at the first bar of the data, or with no bar opening inside its range, has no range. A missing bar ends the session's range from that bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/OrderBlocks # forge.ta.OrderBlocks ```python forge.ta.OrderBlocks(**inputs: Any) -> None ``` The last opposite candle before a break of structure, kept until price trades back through it. Breaks are found as in market structure, with swing_len, internal_len and break_on. For an upward break, Forge takes the lowest low between the broken swing high and the break bar; the order block is the last down candle (close below open) at or before that low, back to the swing high. Its box spans that candle's high and low. A downward break mirrors it with the last up candle. A block is shown from the break bar on. A bullish block is mitigated and removed when price trades below its low (mitigation wick) or closes below it (mitigation close); a bearish block mirrors it. bull_mitigated and bear_mitigated mark the bar. Internal and swing blocks are tracked apart and turned on or off by internal and swing. Slot 1 is the newest unmitigated block of its kind; show_last slots are shown. Forge keeps the five newest unmitigated blocks per kind. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/OutsideBar # forge.ta.OutsideBar ```python forge.ta.OutsideBar(**inputs: Any) -> None ``` A bar whose range covers the previous bar's range. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low high >= the previous high and low <= the previous low (strictly with equal values off), not both equal; both bars have a range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/PeriodOpen # forge.ta.PeriodOpen ```python forge.ta.PeriodOpen(**inputs: Any) -> None ``` The opening price of the current day, week or month. A trading day starts at day_boundary; period D, W or M is defined as for prior levels. period_open is the open of the first bar of the current period, shown on every bar of that period. A period already under way at the first bar of the data has no value. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/PivotHigh # forge.ta.PivotHigh ```python forge.ta.PivotHigh(**inputs: Any) -> None ``` A swing high, shown once enough bars after it prove it was the highest. A pivot high is bar c whose high is strictly above the highs of the left bars before it and the right bars after it. It is written on bar c + right, the bar where it became known, and drawn back on bar c. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/PivotLow # forge.ta.PivotLow ```python forge.ta.PivotLow(**inputs: Any) -> None ``` A swing low, shown once enough bars after it prove it was the lowest. A pivot low is bar c whose low is strictly below the lows of the left bars before it and the right bars after it. It is written on bar c + right, the bar where it became known, and drawn back on bar c. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/PivotPoints # forge.ta.PivotPoints ```python forge.ta.PivotPoints(**inputs: Any) -> None ``` Support and resistance levels projected from the previous day, week or month. H, L, C and O are the highest high, lowest low, last close and first open of the previous period; every bar of a period carries the levels computed from them. standard: P = (H + L + C) / 3, R1 = 2P - L, S1 = 2P - H, R2 = P + (H - L), S2 = P - (H - L), R3 = R1 + (H - L), S3 = S1 - (H - L) fibonacci: P = (H + L + C) / 3, R and S = P plus and minus (H - L) times 0.382, 0.618, 1 and 1.618 for levels 1 to 4 camarilla: P = (H + L + C) / 3, R and S = C plus and minus (H - L) * 1.1 divided by 12, 6, 4 and 2 for levels 1 to 4, R5 = H / L * C, S5 = 2C - R5 woodie: P = (H + L + 2C) / 4, R1 = 2P - L, R2 = P + (H - L), S1 = 2P - H, S2 = P - (H - L) demark: X = H + 2L + C if C < O, 2H + L + C if C > O, else H + L + 2C; P = X / 4, R1 = X / 2 - L, S1 = X / 2 - H A trading day starts at the day boundary in its own time zone, so a change of clocks moves the bars but not the boundary. No value until one full period has closed. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/PremiumDiscount # forge.ta.PremiumDiscount ```python forge.ta.PremiumDiscount(**inputs: Any) -> None ``` The upper and lower halves of the active swing range, split at equilibrium. Swings are found as in market structure with swing_len. The active range runs from the lowest low since the latest swing low to the highest high since the latest swing high. equilibrium is its middle. premium is the upper half, from equilibrium to the top, and discount the lower half, from the bottom to equilibrium. They show once both swings exist. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/PriorLevels # forge.ta.PriorLevels ```python forge.ta.PriorLevels(**inputs: Any) -> None ``` The previous day, week or month open, high, low and close (PDH, PDL), carried forward. A trading day starts at day_boundary. With period D each trading day is one period; with W a week starts on the first trading day of its ISO week (Monday); with M a month starts on its first trading day. prev_open, prev_high, prev_low and prev_close are the first open, the highest high, the lowest low and the last close of the previous period. A value appears on the first bar of a period and stays for the whole period, so it is known before that bar closes. A period already under way at the first bar of the data is not used. A period holding a missing bar has no levels. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/SessionRange # forge.ta.SessionRange ```python forge.ta.SessionRange(**inputs: Any) -> None ``` The high and low of the Asia, London and New York sessions, boxed while each runs. Each session runs from the start to the end of its hours (asia_hours, london_hours, new_york_hours) in its own time zone (asia_tz, london_tz, new_york_tz), so a clock change in one centre moves only that session. A bar belongs to a session when it opens at or after the start and before the end; hours that end before they start run past midnight and belong to the day they started. While a session runs, its box spans the highest high and the lowest low of its bars so far. When it ends, its high and low are shown from the next bar on until the same session ends again. A session already under way at the first bar of the data is not used. A session holding a missing bar has no box or levels from that bar on. On bars longer than a session, a bar counts for the session it opens in. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ShootingStar # forge.ta.ShootingStar ```python forge.ta.ShootingStar(**inputs: Any) -> None ``` A long upper wick after a rise. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low upper wick >= wick_mult * body, lower wick <= opposite_pct / 100 * range, the body inside the bottom body_zone_pct of the range, and after a rise: close[i-1] > close[i-1-trend_bars] Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/SpinningTop # forge.ta.SpinningTop ```python forge.ta.SpinningTop(**inputs: Any) -> None ``` A small body with a longer wick on each side. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low body <= body_pct / 100 * range, upper wick > body and lower wick > body Found: 1 when the close is at or above the open, else -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/StdDev # forge.ta.StdDev ```python forge.ta.StdDev(**inputs: Any) -> None ``` How far values spread around their average over a period. The square root of the mean squared distance of the last period values from the chosen average at the current bar The divisor is period, not period - 1. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/StochRSI # forge.ta.StochRSI ```python forge.ta.StochRSI(**inputs: Any) -> None ``` The stochastic oscillator applied to RSI. RSI is Wilder's relative strength index over rsi_period raw = 100 * (RSI - lowest RSI) / (highest RSI - lowest RSI) over stoch_period; RSI itself when its range is 0 %K = the simple average of raw over k_smooth, %D = the simple average of %K over d_smooth Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/Stochastic # forge.ta.Stochastic ```python forge.ta.Stochastic(**inputs: Any) -> None ``` Where the close sits in the recent high to low range, with a signal line. The highest high and lowest low over K period (the highest and lowest close with close_close) %K = 100 * sum(close - lowest, slowing) / sum(highest - lowest, slowing) %D = the simple average of %K over D period A window with no range reads 50. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ThreeBlackCrows # forge.ta.ThreeBlackCrows ```python forge.ta.ThreeBlackCrows(**inputs: Any) -> None ``` Three falling bars, each opening inside the last body. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low three falling bars with each close below the one before; each of the second and third opens within the body of the bar before; each lower wick <= wick_pct / 100 * its bar's range Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ThreeInside # forge.ta.ThreeInside ```python forge.ta.ThreeInside(**inputs: Any) -> None ``` A harami confirmed by the third bar. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low up: the first two bars are a harami after a fall and the third closes above the first open down: the first two bars are a harami after a rise and the third closes below the first open Found: 1 up, -1 down A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ThreeOutside # forge.ta.ThreeOutside ```python forge.ta.ThreeOutside(**inputs: Any) -> None ``` An engulfing confirmed by the third bar. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low up: the first two bars are a bullish engulfing and the third closes above the second close down: the first two bars are a bearish engulfing and the third closes below the second close Found: 1 up, -1 down A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ThreeWhiteSoldiers # forge.ta.ThreeWhiteSoldiers ```python forge.ta.ThreeWhiteSoldiers(**inputs: Any) -> None ``` Three rising bars, each opening inside the last body. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low three rising bars with each close above the one before; each of the second and third opens within the body of the bar before; each upper wick <= wick_pct / 100 * its bar's range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/TrueRange # forge.ta.TrueRange ```python forge.ta.TrueRange(**inputs: Any) -> None ``` The largest of the bar's range and its gaps from the previous close. TR[i] = max(high[i], close[i-1]) - min(low[i], close[i-1]) No value on the first bar, which has no previous close. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/WilliamsR # forge.ta.WilliamsR ```python forge.ta.WilliamsR(**inputs: Any) -> None ``` Where the close sits in the recent range, from 0 down to -100. %R = -100 * (highest high - close) / (highest high - lowest low) over the last period bars A window with no range reads -50. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ac # forge.ta.ac ```python forge.ta.ac(high: ArrayLike, low: ArrayLike, fast_period: int = 5, slow_period: int = 34, signal_period: int = 5) -> np.ndarray ``` Accelerator oscillator: the Awesome oscillator minus its own ``signal_period`` average. NaN for the first ``slow_period + signal_period - 2`` bars. Since Forge API 1. ## Class forge.ta.AC ```python forge.ta.AC(**inputs: Any) -> None ``` How fast the Awesome oscillator is speeding up or slowing down. AC = AO - SMA(AO, signal_period) Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/accdist # forge.ta.accdist ```python forge.ta.accdist(high: ArrayLike, low: ArrayLike, close: ArrayLike, volume: ArrayLike) -> np.ndarray ``` Accumulation and distribution: a running total of volume weighted by the close's position. Each bar adds ``((close - low) - (high - close)) / (high - low) * volume``, and 0 when the bar has no range. After a missing bar the total starts again. Since Forge API 1. ## Class forge.ta.AccDist ```python forge.ta.AccDist(**inputs: Any) -> None ``` Volume weighted by where the close sits in the bar. The running total of ((close - low) - (high - close)) / (high - low) * volume A bar with no range adds 0. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/adr # forge.ta.adr ```python forge.ta.adr(high: ArrayLike, low: ArrayLike, time_open: ArrayLike, days: int = 14, day_boundary: str = '17:00 America/New_York') -> np.ndarray ``` Average daily range: the mean high to low range of the last ``days`` finished trading days. A trading day starts at ``day_boundary`` ("17:00 America/New_York"). Every bar of a day carries the average of the days before it, so a day's own range is used from the next day on. The first day in the data is not counted, because the data may start inside it. A day with a missing high or low has no range, and the averages that hold it have no value. Since Forge API 1. ## Class forge.ta.ADR ```python forge.ta.ADR(**inputs: Any) -> None ``` The average high to low range of recent trading days. A trading day starts at the day boundary in its own time zone range of a day = its highest high - its lowest low ADR = the average range of the last days finished days; the first day in the data is not counted because the data may start inside it Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/adx # forge.ta.adx ```python forge.ta.adx(high: ArrayLike, low: ArrayLike, close: ArrayLike, period: int = 14, method: str = 'mt5') -> np.ndarray ``` The ADX line of ``dmi``: how strong the trend is, whatever its direction. Since Forge API 1. ## Class forge.ta.ADX ```python forge.ta.ADX(**inputs: Any) -> None ``` How strong the trend is, whatever its direction. TR = the true range, +DM and -DM = the larger of the two directional moves, the other is 0 +DI = 100 * smoothed(+DM) / smoothed(TR), -DI = 100 * smoothed(-DM) / smoothed(TR) DX = 100 * |+DI - -DI| / (+DI + -DI), ADX = smoothed(DX) mt5: smoothing with 2 / (period + 1), starting from zero. wilder: smoothing with 1 / period, starting from the mean of the first period values. Where the true range is zero the lines read 0. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/alligator # forge.ta.alligator ```python forge.ta.alligator(high: ArrayLike, low: ArrayLike, jaw_period: int = 13, jaw_shift: int = 8, teeth_period: int = 8, teeth_shift: int = 5, lips_period: int = 5, lips_shift: int = 3) -> dict[str, np.ndarray] ``` Bill Williams' Alligator: ``jaw``, ``teeth`` and ``lips``. Each is the smoothed average (``rma``) of the median price over its own period. The three shifts only say how far the chart draws each line ahead; the values are not displaced. Since Forge API 1. ## Class forge.ta.Alligator ```python forge.ta.Alligator(**inputs: Any) -> None ``` Three smoothed lines that show when a trend is asleep or waking up. median = (high + low) / 2 jaw, teeth and lips are smoothed averages of the median over their own periods: the first value is the mean of the first period values, then (previous * (period - 1) + median) / period. The shifts are how far the chart draws each line ahead. The values are not displaced. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/alma # forge.ta.alma ```python forge.ta.alma(source: ArrayLike, window: int = 9, offset: float = 0.85, sigma: float = 6.0) -> np.ndarray ``` Arnaud Legoux average: Gaussian weights over ``window`` bars. ``m = offset * (window - 1)``, ``s = window / sigma`` and the weight of the bar ``k`` places from the oldest is ``exp(-(k - m)^2 / (2 s^2))``. An ``offset`` near 1 puts the peak on the newest bar. Since Forge API 1. ## Class forge.ta.ALMA ```python forge.ta.ALMA(**inputs: Any) -> None ``` Gaussian weighted average whose peak you can move toward the newest bar. m = offset * (window - 1), s = window / sigma w[k] = exp(-(k - m)^2 / (2 * s^2)), k = 0 for the oldest bar of the window ALMA[i] = sum(w[k] * x[i-window+1+k]) / sum(w[k]) Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ao # forge.ta.ao ```python forge.ta.ao(high: ArrayLike, low: ArrayLike, fast_period: int = 5, slow_period: int = 34) -> np.ndarray ``` Awesome oscillator: the fast average of the median price minus the slow one. NaN for the first ``slow_period - 1`` bars. Since Forge API 1. ## Class forge.ta.AO ```python forge.ta.AO(**inputs: Any) -> None ``` Momentum from the gap between a fast and a slow average of the median price. AO = SMA(median, fast_period) - SMA(median, slow_period), median = (high + low) / 2 Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/aroon # forge.ta.aroon ```python forge.ta.aroon(high: ArrayLike, low: ArrayLike, period: int = 14) -> dict[str, np.ndarray] ``` Aroon: how recently the highest high and the lowest low of the last ``period + 1`` bars came. 100 when it is the current bar, 0 when it is ``period`` bars back. A tie goes to the newest bar. ``osc`` is ``up - down``. Since Forge API 1. ## Class forge.ta.Aroon ```python forge.ta.Aroon(**inputs: Any) -> None ``` How recently the highest high and the lowest low happened. up = 100 * (period - bars since the highest high of the last period + 1 bars) / period down = the same with the lowest low, osc = up - down A tie goes to the most recent bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/atr # forge.ta.atr ```python forge.ta.atr(high: ArrayLike, low: ArrayLike, close: ArrayLike, period: int = 14, method: str = 'wilder') -> np.ndarray ``` Average true range, Wilder smoothed (``wilder``) or a simple mean (``sma``). Both start from the true range of bar 1, so the first value is on bar ``period``. After a missing bar the average restarts with a fresh warm-up. Since Forge API 1. ## Class forge.ta.ATR ```python forge.ta.ATR(**inputs: Any) -> None ``` The average true range over a period, Wilder smoothed or simple. TR[i] = max(high[i], close[i-1]) - min(low[i], close[i-1]) wilder: the first value is the mean of the first period true ranges, then ATR[i] = (ATR[i-1] * (period - 1) + TR[i]) / period sma: the mean of the last period true ranges No value until period true ranges exist. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/barssince # forge.ta.barssince ```python forge.ta.barssince(condition: ArrayLike) -> np.ndarray ``` How many bars ago the condition last held: 0 on a bar where it holds. Before the first bar where it holds there is no value. A number array counts as true where it is not 0, so an event array works too. Example: ``ta.barssince(ta.crossover(fast, slow))``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/bb # forge.ta.bb ```python forge.ta.bb(source: ArrayLike, period: int = 20, deviations: float = 2.0) -> dict[str, np.ndarray] ``` Bollinger Bands: a simple average with bands ``deviations`` population deviations away. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/bb_percent # forge.ta.bb_percent ```python forge.ta.bb_percent(source: ArrayLike, period: int = 20, deviations: float = 2.0) -> np.ndarray ``` Bollinger Bands %B: ``(source - lower) / (upper - lower)``. No value where the bands meet. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/bbw # forge.ta.bbw ```python forge.ta.bbw(source: ArrayLike, period: int = 20, deviations: float = 2.0) -> np.ndarray ``` Bollinger Bands width: ``(upper - lower) / middle``. No value where the middle is 0. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/bears_power # forge.ta.bears_power ```python forge.ta.bears_power(low: ArrayLike, close: ArrayLike, period: int = 13) -> np.ndarray ``` Bears power: the bar's low minus an EMA of the closes. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/bulls_power # forge.ta.bulls_power ```python forge.ta.bulls_power(high: ArrayLike, close: ArrayLike, period: int = 13) -> np.ndarray ``` Bulls power: the bar's high minus an EMA of the closes. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/bw_mfi # forge.ta.bw_mfi ```python forge.ta.bw_mfi(high: ArrayLike, low: ArrayLike, volume: ArrayLike) -> dict[str, np.ndarray] ``` Market facilitation index: ``mfi = (high - low) / volume``, in price units. NaN where the volume is 0 or a price is missing. ``phase`` is MetaTrader 5's colour: 0 green (MFI up, volume up), 1 fade (down, down), 2 fake (MFI up, volume down), 3 squat (MFI down, volume up). The two directions start up and change only on a strict change from the previous bar; a bar with no MFI leaves them as they were and has no phase, and neither does bar 0. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/cci # forge.ta.cci ```python forge.ta.cci(source: ArrayLike, period: int = 14) -> np.ndarray ``` Commodity channel index: distance from the average in units of mean deviation. ``(x - mean) / (0.015 * mean absolute deviation)`` over the last ``period`` values. A window with no deviation reads 0. Since Forge API 1. ## Class forge.ta.CCI ```python forge.ta.CCI(**inputs: Any) -> None ``` Distance from the average in units of mean deviation. The typical price by default CCI = (x - SMA) / (0.015 * mean absolute deviation) over the last period values No deviation reads 0. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/chaikin_osc # forge.ta.chaikin_osc ```python forge.ta.chaikin_osc(high: ArrayLike, low: ArrayLike, close: ArrayLike, volume: ArrayLike, fast_period: int = 3, slow_period: int = 10, method: str = 'ema') -> np.ndarray ``` Chaikin oscillator: the fast average of accumulation and distribution minus the slow one. ``method`` is the average: ``ema`` (the usual one), ``sma``, ``smma`` or ``lwma``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/change # forge.ta.change ```python forge.ta.change(source: ArrayLike, period: int = 1) -> np.ndarray ``` Value now minus the value ``period`` bars ago. Since Forge API 1. ## Class forge.ta.Change ```python forge.ta.Change(**inputs: Any) -> None ``` Value now minus the value a number of bars ago. change = x[i] - x[i-period] Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/cmo # forge.ta.cmo ```python forge.ta.cmo(source: ArrayLike, period: int = 14) -> np.ndarray ``` Chande momentum oscillator: up moves against down moves over ``period`` bars, -100 to 100. 0 when nothing moved in the window. The first value is on bar ``period``. Since Forge API 1. ## Class forge.ta.CMO ```python forge.ta.CMO(**inputs: Any) -> None ``` Up moves against down moves over a period, from -100 to 100. Up is the change when it is positive, Down is minus the change when it is negative CMO = 100 * (sum of Up - sum of Down) / (sum of Up + sum of Down) over the last period bars 0 when nothing moved. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/correlation # forge.ta.correlation ```python forge.ta.correlation(a: ArrayLike, b: ArrayLike, period: int = 30) -> np.ndarray ``` Rolling correlation of two series over the last ``period`` bars, from -1 to 1. A window where either series is missing a value or does not move has no value. The two series can come from two symbols aligned on the same bars: ``d["GBPUSD"].close`` in a strategy, or a declared second symbol in an indicator. Example: ``ta.correlation(close, other, 30)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/cross # forge.ta.cross ```python forge.ta.cross(a: ArrayLike | float, b: ArrayLike | float) -> np.ndarray ``` 1 on a bar where ``a`` crosses above ``b``, -1 where it crosses below, 0 elsewhere. The result is an ``int8`` array, the event form of ``crossover`` and ``crossunder`` (the two never hold on one bar). Example: ``ta.cross(fast, slow)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/crossover # forge.ta.crossover ```python forge.ta.crossover(a: ArrayLike | float, b: ArrayLike | float) -> np.ndarray ``` True on the bar where ``a`` goes from at or below ``b`` to above it. ``a[i] > b[i]`` and ``a[i-1] <= b[i-1]``, with all four values present; the first bar is false. A touch followed by a move away counts as a cross. Either side may be a plain number, such as a level, but not both. The result is a true or false array, which a strategy's entry field takes as it is. Example: ``ta.crossover(fast, slow)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/crossunder # forge.ta.crossunder ```python forge.ta.crossunder(a: ArrayLike | float, b: ArrayLike | float) -> np.ndarray ``` True on the bar where ``a`` goes from at or above ``b`` to below it. ``a[i] < b[i]`` and ``a[i-1] >= b[i-1]``, with all four values present; the first bar is false. Either side may be a plain number, but not both. The result is a true or false array. Example: ``ta.crossunder(fast, slow)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/cum # forge.ta.cum ```python forge.ta.cum(source: ArrayLike) -> np.ndarray ``` The running total of the source from the first bar. A missing value has no total, and the total starts again on the next bar that has a value. The result depends on where the data starts. Since Forge API 1. ## Class forge.ta.Cum ```python forge.ta.Cum(**inputs: Any) -> None ``` The total of every value from the first bar. The running total of the source from the first bar. A missing value restarts the total on the next bar that has one. The value depends on where the data starts. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/dark_cloud_cover # forge.ta.dark_cloud_cover ```python forge.ta.dark_cloud_cover(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, long_body_pct: float = 50.0, penetration: float = 0.5) -> np.ndarray ``` Dark cloud cover: a fall that closes deep into the previous rise. Found: -1. The first bar rises with a long body, the second opens at or above its close and closes at least ``penetration`` of the first body below that close, still above the first open. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/dayofweek # forge.ta.dayofweek ```python forge.ta.dayofweek(t: Any, tz: str = 'UTC') -> Any ``` The weekday of ``t`` in ``tz``, Monday 0 to Sunday 6 (the engine clock's convention). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/delay # forge.ta.delay ```python forge.ta.delay(source: ArrayLike, n: int = 1) -> np.ndarray ``` The source ``n`` bars ago: ``value[i] = source[i - n]``. A series can only be moved back in time. The first ``n`` bars have no value, and ``n = 0`` is a copy of the source. Since Forge API 1. ## Class forge.ta.Delay ```python forge.ta.Delay(**inputs: Any) -> None ``` The series moved a number of bars back. The source n bars ago: value[i] = source[i - n]. A series can only be moved back in time; the first n bars have no value. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/dema # forge.ta.dema ```python forge.ta.dema(source: ArrayLike, period: int = 14) -> np.ndarray ``` Double exponential average: ``2 * EMA - EMA(EMA)``, the second stage seeded at bar ``period - 1``. After a gap the whole cascade restarts. Since Forge API 1. ## Class forge.ta.DEMA ```python forge.ta.DEMA(**inputs: Any) -> None ``` Exponential average with part of its lag removed. DEMA = 2 * EMA(x) - EMA(EMA(x)) The second average starts at the bar where the first is defined. After a gap the whole calculation starts again. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/demarker # forge.ta.demarker ```python forge.ta.demarker(high: ArrayLike, low: ArrayLike, period: int = 14) -> np.ndarray ``` DeMarker: pressure from higher highs against lower lows, from 0 to 1. DeMax is the rise of the high and DeMin the fall of the low, each 0 when it did not move that way. The result is the simple mean of DeMax over the sum of both means; no movement reads 0.5. The first value is on bar ``period``. Since Forge API 1. ## Class forge.ta.DeMarker ```python forge.ta.DeMarker(**inputs: Any) -> None ``` Pressure from higher highs against lower lows, from 0 to 1. DeMax = the rise of the high when positive, else 0; DeMin = the fall of the low when positive, else 0 DeM = SMA(DeMax) / (SMA(DeMax) + SMA(DeMin)) over the last period bars No movement reads 0.5. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/dmi # forge.ta.dmi ```python forge.ta.dmi(high: ArrayLike, low: ArrayLike, close: ArrayLike, period: int = 14, method: str = 'mt5') -> dict[str, np.ndarray] ``` Directional movement: ``plus_di``, ``minus_di`` and ``adx``. ``method="mt5"`` is MetaTrader 5's ADX (``2 / (period + 1)`` smoothing seeded at zero; every line starts at bar ``period``). ``method="wilder"`` is Wilder's original (``1 / period`` smoothing seeded with plain means; the DI lines start at bar ``period`` and ADX at ``2 * period - 1``). Where the true range is zero the lines read 0. Since Forge API 1. ## Class forge.ta.DMI ```python forge.ta.DMI(**inputs: Any) -> None ``` Trend direction as two lines, with the strength of the trend. TR = the true range, +DM and -DM = the larger of the two directional moves, the other is 0 +DI = 100 * smoothed(+DM) / smoothed(TR), -DI = 100 * smoothed(-DM) / smoothed(TR) DX = 100 * |+DI - -DI| / (+DI + -DI), ADX = smoothed(DX) mt5: smoothing with 2 / (period + 1), starting from zero. wilder: smoothing with 1 / period, starting from the mean of the first period values. Where the true range is zero the lines read 0. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/doji # forge.ta.doji ```python forge.ta.doji(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, body_pct: float = 5.0) -> np.ndarray ``` Doji: a bar whose body is tiny against its range. Found: 1. The body is at most ``body_pct`` percent of the bar's high to low range. Since Forge API 1. ## Class forge.ta.Doji ```python forge.ta.Doji(**inputs: Any) -> None ``` A bar whose body is tiny against its range. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low body <= body_pct / 100 * range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/donchian # forge.ta.donchian ```python forge.ta.donchian(high: ArrayLike, low: ArrayLike, period: int = 20) -> dict[str, np.ndarray] ``` Donchian channel: highest high and lowest low of the last ``period`` bars, and their mean. Since Forge API 1. ## Class forge.ta.Donchian ```python forge.ta.Donchian(**inputs: Any) -> None ``` Highest high and lowest low of the last N bars, with their midpoint. upper = the highest high of the last period bars lower = the lowest low of the last period bars middle = (upper + lower) / 2 Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/dragonfly_doji # forge.ta.dragonfly_doji ```python forge.ta.dragonfly_doji(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, body_pct: float = 5.0, wick_pct: float = 10.0) -> np.ndarray ``` Dragonfly doji: a doji that opens and closes near its high. Found: 1. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/efficiency_ratio # forge.ta.efficiency_ratio ```python forge.ta.efficiency_ratio(source: ArrayLike, period: int = 10) -> np.ndarray ``` Net move over ``period`` bars divided by the path length. 1 for a straight line, near 0 for noise. A flat window (path length zero) gives 0. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ema # forge.ta.ema ```python forge.ta.ema(source: ArrayLike, period: int = 14) -> np.ndarray ``` Exponential average: ``k = 2 / (period + 1)``, seeded with the first price. The first ``period - 1`` values of each unbroken run are left empty. After a gap the recursion restarts and waits ``period - 1`` bars again. Since Forge API 1. ## Class forge.ta.EMA ```python forge.ta.EMA(**inputs: Any) -> None ``` Average that weights recent values more, each step closing part of the gap to the price. k = 2 / (period + 1) EMA[i] = x[i] * k + EMA[i-1] * (1 - k), starting from the first price. The first period - 1 values are left empty. After a gap the average starts again. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/engulfing # forge.ta.engulfing ```python forge.ta.engulfing(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, allow_equal: bool = True) -> np.ndarray ``` Engulfing: a body that covers the previous opposite body. Found: 1 bullish, -1 bearish. Bullish: the first bar falls, the second rises, its open is at or below the first close and its close at or above the first open, and the two bodies are not identical. Wicks are ignored. No gap between the bodies is required or forbidden. With ``allow_equal`` off the open and close must be strictly beyond the first body. Since Forge API 1. ## Class forge.ta.Engulfing ```python forge.ta.Engulfing(**inputs: Any) -> None ``` A body that covers the previous opposite body. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low bullish: the first bar falls, the second rises, open <= the first close and close >= the first open, and the two bodies are not identical bearish: the first bar rises, the second falls, open >= the first close and close <= the first open, and the two bodies are not identical With equal prices off the comparisons are strict. Wicks are ignored. No gap between the bodies is required or forbidden. When the second open equals the first close, as it usually does on a market that trades without a break, the bar still counts while Equal prices count is on. Found: 1 bullish, -1 bearish A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/envelopes # forge.ta.envelopes ```python forge.ta.envelopes(source: ArrayLike, period: int = 14, deviation: float = 0.1, method: str = 'sma', mode: str = 'percent') -> dict[str, np.ndarray] ``` A moving average with a band above and below it. ``middle`` is the moving average named by ``method``. In ``percent`` mode the bands are ``middle * (1 + deviation / 100)`` and ``middle * (1 - deviation / 100)``; in ``fixed`` mode they are ``middle + deviation`` and ``middle - deviation`` in price units. Since Forge API 1. ## Class forge.ta.Envelopes ```python forge.ta.Envelopes(**inputs: Any) -> None ``` A moving average with a band a set distance above and below it. middle = the moving average named by method percent: upper = middle * (1 + deviation / 100), lower = middle * (1 - deviation / 100) fixed: upper = middle + deviation, lower = middle - deviation Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/evening_doji_star # forge.ta.evening_doji_star ```python forge.ta.evening_doji_star(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, long_body_pct: float = 50.0, body_pct: float = 5.0, penetration: float = 0.3) -> np.ndarray ``` Evening doji star: an evening star whose middle bar is a doji. Found: -1. The middle bar's body is at most ``body_pct`` percent of its own range. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/evening_star # forge.ta.evening_star ```python forge.ta.evening_star(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, long_body_pct: float = 50.0, star_body_pct: float = 30.0, penetration: float = 0.3) -> np.ndarray ``` Evening star: a long rise, a small star, then a fall into the rise. Found: -1. The first bar rises with a long body. The star's body is at most ``star_body_pct`` percent of the first body and does not fall below the first close. The third bar falls and closes at least ``penetration`` of the first body below the first close. The star may open at the first close: no gap is required. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/fair_value_gaps # forge.ta.fair_value_gaps ```python forge.ta.fair_value_gaps(open_: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, min_size: float = 0.0, auto_threshold: bool = True, threshold_bars: int = 100, threshold_mult: float = 2.0, extend: int = 0, mitigation: str = 'filled') -> dict[str, np.ndarray | Zone] ``` Three-candle gaps, newest first in five slots per side, until price fills them. A bullish gap forms on a bar whose low is above the high two bars earlier, when the bar between them closes above that high; it runs from that high up to the low. A bearish gap mirrors it. A gap is kept when its height is at least ``min_size`` and, with ``auto_threshold``, when the middle candle's body is larger than ``threshold_mult`` times the average body of the ``threshold_bars`` candles before it. ``mitigation`` says whether price must trade through the whole gap (``filled``) or into it (``touched``); with ``extend`` above zero a gap is removed that many bars after it formed. The parameter is spelled ``open_`` because ``open`` is a Python builtin. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/falling # forge.ta.falling ```python forge.ta.falling(source: ArrayLike, period: int = 3) -> np.ndarray ``` True when each of the last ``period`` bars is below the bar before it. ``source[i-period] > ... > source[i-1] > source[i]``. An equal value or a missing value breaks the run. The result is a true or false array. Example: ``ta.falling(close, 3)``. Since Forge API 1. ## Class forge.ta.Falling ```python forge.ta.Falling(**inputs: Any) -> None ``` True after a number of falls in a row. True when each of the last period bars is below the bar before it: source[i-period] > ... > source[i-1] > source[i]. An equal value or a missing value breaks the run. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/force # forge.ta.force ```python forge.ta.force(source: ArrayLike, volume: ArrayLike, period: int = 13, method: str = 'sma') -> np.ndarray ``` Force index: the volume times the change of a moving average of the price. ``volume[i] * (MA[i] - MA[i-1])`` with the average ``method`` (``sma``, ``ema``, ``smma`` or ``lwma``), as MetaTrader 5 computes it. The first value is on bar ``period``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/fractals # forge.ta.fractals ```python forge.ta.fractals(high: ArrayLike, low: ArrayLike) -> dict[str, np.ndarray] ``` Williams fractals: ``up`` (swing high) and ``down`` (swing low), written two bars late. A bar is a swing high when its high is above the two bars after it and at least the two bars before it (MetaTrader 5's own asymmetry); swing lows mirror that. The value sits on the bar that confirms it, never earlier. NaN on the first 4 bars and wherever the pattern does not hold. Since Forge API 1. ## Class forge.ta.Fractals ```python forge.ta.Fractals(**inputs: Any) -> None ``` Swing highs and lows, shown two bars after they form. An up fractal is a bar whose high is above the next two highs and at least the two before it. A down fractal mirrors it with the lows. It is written on the bar that confirms it, two bars after the centre, and drawn back on the centre bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/frama # forge.ta.frama ```python forge.ta.frama(high: ArrayLike, low: ArrayLike, close: ArrayLike, period: int = 14) -> np.ndarray ``` Fractal adaptive average of the close (Ehlers). ``period`` is half the window: the fractal dimension compares the high-low range of the last ``period`` bars and of the ``period`` bars before them with the range of all ``2 * period``. NaN for the first ``2 * period - 1`` bars. A missing value leaves the bars whose window touches it empty; the average then starts again from the previous raw close. Since Forge API 1. ## Class forge.ta.FRAMA ```python forge.ta.FRAMA(**inputs: Any) -> None ``` Average that follows trends closely and flattens in chop, from the fractal dimension. N1 = (highest high - lowest low over the last period bars) / period N2 = the same over the period bars before those, N3 = the same over all 2 * period bars divided by 2 * period D = (ln(N1 + N2) - ln(N3)) / ln(2), alpha = exp(-4.6 * (D - 1)) FRAMA[i] = alpha * close[i] + (1 - alpha) * FRAMA[i-1] The first 2 * period - 1 values are left empty. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/gator # forge.ta.gator ```python forge.ta.gator(high: ArrayLike, low: ArrayLike, jaw_period: int = 13, jaw_shift: int = 8, teeth_period: int = 8, teeth_shift: int = 5, lips_period: int = 5, lips_shift: int = 3) -> dict[str, np.ndarray] ``` Gator oscillator: the gaps between the Alligator's lines, above and below 0. ``upper[i] = |jaw[i - (jaw_shift - teeth_shift)] - teeth[i]|`` and ``lower[i] = -|teeth[i - (teeth_shift - lips_shift)] - lips[i]|``. The shifts are read only as these two backward offsets, so every value uses bars up to its own. The MetaTrader 5 input ordering is enforced: periods and shifts must both fall from jaw to teeth to lips, and each period must exceed its own shift. Since Forge API 1. ## Class forge.ta.Gator ```python forge.ta.Gator(**inputs: Any) -> None ``` How far apart the Alligator's jaw, teeth and lips are. jaw, teeth and lips are the Alligator's smoothed averages of the median price. upper = |jaw (jaw_shift - teeth_shift bars earlier) - teeth| lower = -|teeth (teeth_shift - lips_shift bars earlier) - lips| The periods and the shifts must both fall from jaw to teeth to lips, and each period must exceed its own shift. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/gravestone_doji # forge.ta.gravestone_doji ```python forge.ta.gravestone_doji(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, body_pct: float = 5.0, wick_pct: float = 10.0) -> np.ndarray ``` Gravestone doji: a doji that opens and closes near its low. Found: 1. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/hammer # forge.ta.hammer ```python forge.ta.hammer(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, wick_mult: float = 2.0, opposite_pct: float = 25.0, body_zone_pct: float = 33.3, trend_bars: int = 3) -> np.ndarray ``` Hammer: a long lower wick after a fall. Found: 1. The lower wick is at least ``wick_mult`` times the body, the upper wick at most ``opposite_pct`` percent of the range, the body sits in the top ``body_zone_pct`` percent of the range, and the close before the bar is below the close ``trend_bars`` bars earlier. Since Forge API 1. ## Class forge.ta.Hammer ```python forge.ta.Hammer(**inputs: Any) -> None ``` A long lower wick after a fall. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low lower wick >= wick_mult * body, upper wick <= opposite_pct / 100 * range, the body inside the top body_zone_pct of the range, and after a fall: close[i-1] < close[i-1-trend_bars] Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/hanging_man # forge.ta.hanging_man ```python forge.ta.hanging_man(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, wick_mult: float = 2.0, opposite_pct: float = 25.0, body_zone_pct: float = 33.3, trend_bars: int = 3) -> np.ndarray ``` Hanging man: a long lower wick after a rise. Found: -1. The same bar as a hammer, told apart by what came before: the close before the bar is above the close ``trend_bars`` bars earlier. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/harami # forge.ta.harami ```python forge.ta.harami(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, long_body_pct: float = 50.0, allow_equal: bool = True) -> np.ndarray ``` Harami: a small body inside the previous long body. Found: 1 after a fall, -1 after a rise. The first body is at least ``long_body_pct`` percent of its bar's range, the second body lies inside it and is smaller. With ``allow_equal`` off it must stay strictly inside. Since Forge API 1. ## Class forge.ta.Harami ```python forge.ta.Harami(**inputs: Any) -> None ``` A small body inside the previous long body. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first body > 0 and >= long_body_pct / 100 * its range; the second body inside the first body (strictly with equal prices off) and smaller than it Found: 1 when the first bar falls, -1 when it rises A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/heikin_ashi # forge.ta.heikin_ashi ```python forge.ta.heikin_ashi(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, seed: str = 'mt5_raw') -> dict[str, np.ndarray] ``` Heikin-Ashi candles: ``ha_open``, ``ha_high``, ``ha_low`` and ``ha_close``. ``seed="mt5_raw"`` starts from the raw first bar (``ha_open = open``, ``ha_close = close``, the way MetaTrader 5 does). ``seed="textbook"`` starts from ``ha_close = (o + h + l + c) / 4`` and ``ha_open = (open + close) / 2``. No warm-up: a value exists on every bar that has all four prices. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/highest # forge.ta.highest ```python forge.ta.highest(source: ArrayLike, period: int = 30) -> np.ndarray ``` The highest value of the last ``period`` bars, the current bar included. A window that holds a missing value has no value. To compare a price with the highest of the bars before it, delay the result by one bar. Since Forge API 1. ## Class forge.ta.Highest ```python forge.ta.Highest(**inputs: Any) -> None ``` The highest value over a number of bars. The highest value of the last period bars, the current bar included. A window holding a missing value has no value. To compare a price with the highest of the bars before it, delay the result by one bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/highest_bars # forge.ta.highest_bars ```python forge.ta.highest_bars(source: ArrayLike, period: int = 30) -> np.ndarray ``` How many bars ago the highest value of the last ``period`` bars was, 0 for the current bar. When two bars share the highest value, the more recent one counts. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/hma # forge.ta.hma ```python forge.ta.hma(source: ArrayLike, period: int = 16) -> np.ndarray ``` Hull average: ``wma(2 * wma(x, half) - wma(x, period), root)``. ``half = floor(period / 2 + 0.5)`` and ``root = floor(sqrt(period) + 0.5)``: halves round up. Since Forge API 1. ## Class forge.ta.HMA ```python forge.ta.HMA(**inputs: Any) -> None ``` Fast, smooth average built from two weighted averages. half = floor(period / 2 + 0.5), root = floor(sqrt(period) + 0.5) HMA = WMA(2 * WMA(x, half) - WMA(x, period), root) Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/hour # forge.ta.hour ```python forge.ta.hour(t: Any, tz: str = 'UTC') -> Any ``` The hour (0 to 23) of nanosecond UTC times ``t`` in the zone ``tz``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ichimoku # forge.ta.ichimoku ```python forge.ta.ichimoku(high: ArrayLike, low: ArrayLike, close: ArrayLike, tenkan_period: int = 9, kijun_period: int = 26, senkou_b_period: int = 52) -> dict[str, np.ndarray] ``` Tenkan, kijun, senkou A and B, and chikou, undisplaced. ``tenkan`` and ``kijun`` are the midpoints of the highest high and lowest low over their periods, ``senkou_a`` is their mean, ``senkou_b`` the midpoint over ``senkou_b_period`` and ``chikou`` is the close. NaN for a window that is not yet full or holds a missing value. Since Forge API 1. ## Class forge.ta.Ichimoku ```python forge.ta.Ichimoku(**inputs: Any) -> None ``` Five lines that show trend, momentum and support in one view. mid(n) = (highest high + lowest low over the last n bars) / 2 Tenkan = mid(tenkan_period), Kijun = mid(kijun_period) Senkou A = (Tenkan + Kijun) / 2, Senkou B = mid(senkou_b_period) Chikou = the close The values are not displaced. The chart draws the two spans kijun_period bars ahead and Chikou kijun_period bars back; strategies read the values as they are. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/inside_bar # forge.ta.inside_bar ```python forge.ta.inside_bar(high: ArrayLike, low: ArrayLike, allow_equal: bool = True) -> dict[str, np.ndarray] ``` Inside bar: a bar whose range sits within the previous bar's range. Found: 1. ``high[i] <= high[i-1]`` and ``low[i] >= low[i-1]``, strictly with ``allow_equal`` off. ``in_a_row`` counts the inside bars in a row that end at the bar: 0 on a bar that is not one, and no value on the first bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/inverted_hammer # forge.ta.inverted_hammer ```python forge.ta.inverted_hammer(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, wick_mult: float = 2.0, opposite_pct: float = 25.0, body_zone_pct: float = 33.3, trend_bars: int = 3) -> np.ndarray ``` Inverted hammer: a long upper wick after a fall. Found: 1. The upper wick is at least ``wick_mult`` times the body, the lower wick at most ``opposite_pct`` percent of the range, the body sits in the bottom ``body_zone_pct`` percent of the range, and the close before the bar is below the close ``trend_bars`` bars earlier. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/kama # forge.ta.kama ```python forge.ta.kama(source: ArrayLike, period: int = 10, fast_period: int = 2, slow_period: int = 30) -> np.ndarray ``` Kaufman adaptive average. The smoothing constant is ``(er * (fast_sc - slow_sc) + slow_sc)^2`` with ``er`` the efficiency ratio over ``period`` bars and ``sc = 2 / (n + 1)`` for the fast and slow periods. NaN for the first ``period`` bars of each unbroken run. Since Forge API 1. ## Class forge.ta.KAMA ```python forge.ta.KAMA(**inputs: Any) -> None ``` Average that speeds up when the price trends and slows down when it chops. sc = (ER * (fast_sc - slow_sc) + slow_sc)^2, with fast_sc = 2 / (fast_period + 1) and slow_sc = 2 / (slow_period + 1) KAMA[i] = KAMA[i-1] + sc * (x[i] - KAMA[i-1]), seeded with x[period-1] The first period values are left empty. After a gap the average starts again. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/kc # forge.ta.kc ```python forge.ta.kc(high: ArrayLike, low: ArrayLike, close: ArrayLike, ema_period: int = 20, atr_period: int = 10, atr_factor: float = 2.0) -> dict[str, np.ndarray] ``` Keltner channel: an EMA of closes with bands ``atr_factor`` average true ranges away. The average true range is the simple average of the true range, as the MetaTrader 5 Keltner Channel computes it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/kcw # forge.ta.kcw ```python forge.ta.kcw(high: ArrayLike, low: ArrayLike, close: ArrayLike, ema_period: int = 20, atr_period: int = 10, atr_factor: float = 2.0) -> np.ndarray ``` Keltner channel width: ``(upper - lower) / middle``. No value where the middle is 0. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/killzones # forge.ta.killzones ```python forge.ta.killzones(time_open: ArrayLike, asia_hours: str = '20:00-00:00', asia_tz: str = 'America/New_York', london_open_hours: str = '02:00-05:00', london_open_tz: str = 'America/New_York', new_york_open_hours: str = '07:00-10:00', new_york_open_tz: str = 'America/New_York', london_close_hours: str = '10:00-12:00', london_close_tz: str = 'America/New_York') -> dict[str, np.ndarray] ``` Four named windows, each true on the bars that open inside it. Each window has its own hours and time zone. A bar is inside a window when it opens at or after the start and before the end; hours that end before they start run past midnight. ``time_open`` is in nanoseconds (UTC). Since Forge API 1. ## Class forge.ta.Killzones ```python forge.ta.Killzones(**inputs: Any) -> None ``` Named session windows, each in its own time zone, shaded on the chart and usable as filters. Each window runs from the start to the end of its hours (asia_hours, london_open_hours, new_york_open_hours, london_close_hours) in its own time zone (asia_tz, london_open_tz, new_york_open_tz, london_close_tz). A bar is inside a window when it opens at or after the start and before the end; hours that end before they start run past midnight. in_asia, in_london_open, in_new_york_open and in_london_close are true on the bars inside each window. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/linreg # forge.ta.linreg ```python forge.ta.linreg(source: ArrayLike, period: int = 14) -> np.ndarray ``` The regression line at the newest bar of the window: ``a + b * (period - 1)``. Since Forge API 1. ## Class forge.ta.LinReg ```python forge.ta.LinReg(**inputs: Any) -> None ``` Least squares line fitted to the last N values, read at the newest bar. Fit a straight line by least squares to the last period values, with t = 0 at the oldest. b = (period * sum(t*y) - sum(t) * sum(y)) / (period * sum(t^2) - sum(t)^2) a = (sum(y) - b * sum(t)) / period LinReg = a + b * (period - 1) Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/linreg_intercept # forge.ta.linreg_intercept ```python forge.ta.linreg_intercept(source: ArrayLike, period: int = 14) -> np.ndarray ``` The regression line at the oldest bar of the window. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/linreg_slope # forge.ta.linreg_slope ```python forge.ta.linreg_slope(source: ArrayLike, period: int = 14) -> np.ndarray ``` The slope of the regression line: its change per bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/liquidity # forge.ta.liquidity ```python forge.ta.liquidity(high: ArrayLike, low: ArrayLike, close: ArrayLike, confirm_bars: int = 3, margin_atr: float = 0.1, atr_period: int = 10) -> dict[str, np.ndarray] ``` Equal highs and lows, resting liquidity above swing highs and below swing lows, and sweeps. A swing high is a bar whose high is above every high within ``confirm_bars`` bars on each side; it is known that many bars later, and every value is written then. ``eqh`` marks a swing high within ``margin_atr`` times the average true range (Wilder's, over ``atr_period`` bars) of the previous swing high; ``eql`` mirrors it. Every swing high leaves buyside liquidity at its price and every swing low sellside liquidity. A bar that trades through a level takes it, and when that bar closes back on the other side it is a sweep (``sweep_high`` or ``sweep_low``). ``buyside`` is the nearest level above price still untaken, ``sellside`` the nearest below. Since Forge API 1. ## Class forge.ta.Liquidity ```python forge.ta.Liquidity(**inputs: Any) -> None ``` Equal highs and lows (EQH, EQL), resting liquidity above swing highs and below swing lows, and the sweeps that take it. A swing high is a bar whose high is above every high within confirm_bars bars on each side; it is known confirm_bars bars later, and values are written then. Swing lows mirror it. EQH marks a swing high within margin_atr times the ATR of the previous swing high. The ATR is Wilder's average true range over atr_period bars. EQL mirrors it. Every swing high leaves buyside liquidity at its price, and every swing low sellside liquidity. When a bar trades above a buyside level it is taken; if the bar closes back below it, that is a sweep (sweep_high). Sellside mirrors it (sweep_low). buyside is the nearest untaken buyside level above price and sellside the nearest untaken sellside level below it. Forge keeps the fifty newest levels per side. A missing bar starts again. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/log_return # forge.ta.log_return ```python forge.ta.log_return(source: ArrayLike) -> np.ndarray ``` The natural log of the value over the previous value: ``ln(source[i] / source[i-1])``. There is no value on the first bar, or when either value is missing or not above zero. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/lowest # forge.ta.lowest ```python forge.ta.lowest(source: ArrayLike, period: int = 30) -> np.ndarray ``` The lowest value of the last ``period`` bars, the current bar included. A window that holds a missing value has no value. To compare a price with the lowest of the bars before it, delay the result by one bar. Since Forge API 1. ## Class forge.ta.Lowest ```python forge.ta.Lowest(**inputs: Any) -> None ``` The lowest value over a number of bars. The lowest value of the last period bars, the current bar included. A window holding a missing value has no value. To compare a price with the lowest of the bars before it, delay the result by one bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/lowest_bars # forge.ta.lowest_bars ```python forge.ta.lowest_bars(source: ArrayLike, period: int = 30) -> np.ndarray ``` How many bars ago the lowest value of the last ``period`` bars was, 0 for the current bar. When two bars share the lowest value, the more recent one counts. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/lwma # forge.ta.lwma ```python forge.ta.lwma(source: ArrayLike, period: int = 13) -> np.ndarray ``` Linearly weighted average: weight ``period`` on the newest value down to 1 on the oldest. Also known as ``lwma``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/ma # forge.ta.ma ```python forge.ta.ma(source: ArrayLike, period: int = 14, method: str = 'sma') -> np.ndarray ``` The moving average named by ``method``: one of sma, ema, smma, lwma, wma, hma, dema, tema. Since Forge API 1. ## Class forge.ta.MA ```python forge.ta.MA(**inputs: Any) -> None ``` Any of the eight moving averages, chosen by name. MA = the average named by method: sma, ema, smma, lwma, wma, hma, dema or tema. Each one follows its own formula and warm-up. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/macd # forge.ta.macd ```python forge.ta.macd(source: ArrayLike, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9) -> dict[str, np.ndarray] ``` MACD: the fast EMA minus the slow EMA, its signal line and the gap between them. Both EMAs are seeded on the first value (MetaTrader 5). The signal is the simple average of the MACD line, as MetaTrader 5 computes it. The histogram is the MACD line minus the signal. Since Forge API 1. ## Class forge.ta.MACD ```python forge.ta.MACD(**inputs: Any) -> None ``` The gap between a fast and a slow EMA, with a signal line and histogram. MACD = EMA(source, fast_period) - EMA(source, slow_period), both seeded on the first value signal = the simple average of MACD over signal_period, as MetaTrader 5 computes it histogram = MACD - signal Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/market_structure # forge.ta.market_structure ```python forge.ta.market_structure(open_: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, swing_len: int = 5, internal_len: int = 2, internal: bool = True, swing: bool = True, confluence_filter: bool = False, break_on: str = 'close') -> dict[str, np.ndarray] ``` Swings labelled HH, HL, LH and LL, with breaks of structure and changes of character. A swing high is a bar whose high is above the high of every bar within ``swing_len`` bars on each side; a swing low mirrors it. It is known ``swing_len`` bars later, and every value is written on that later bar. A break is the first bar that closes (``break_on`` close) or trades (``wick``) beyond the latest swing level; it is a change of character when the trend was the other way and a break of structure otherwise. Internal structure uses ``internal_len``. With ``confluence_filter`` an internal break is marked only when its bar closes in the break's direction and the level it breaks is not the swing level. The parameter is spelled ``open_`` because ``open`` is a Python builtin. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/marubozu # forge.ta.marubozu ```python forge.ta.marubozu(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, wick_pct: float = 5.0) -> np.ndarray ``` Marubozu: a bar with almost no wicks. Found: 1 on a rising bar, -1 on a falling bar. Since Forge API 1. ## Class forge.ta.Marubozu ```python forge.ta.Marubozu(**inputs: Any) -> None ``` A bar with almost no wicks, rising or falling. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low body > 0, upper wick <= wick_pct / 100 * range and lower wick <= wick_pct / 100 * range Found: 1 on a rising bar, -1 on a falling bar A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/max2 # forge.ta.max2 ```python forge.ta.max2(a: ArrayLike | float, b: ArrayLike | float) -> np.ndarray ``` The larger of two series bar by bar, or of a series and a number. A missing value on either side gives no value. Example: ``ta.max2(close, 0.0)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/median # forge.ta.median ```python forge.ta.median(source: ArrayLike, period: int = 20) -> np.ndarray ``` Median of the last ``period`` values. NaN where the window holds a missing value. Since Forge API 1. ## Class forge.ta.Median ```python forge.ta.Median(**inputs: Any) -> None ``` Middle value of the last N values, so one spike cannot move it. Median[i] = the middle of the sorted values x[i-period+1] to x[i] With an even period it is the mean of the two middle values. A window holding a missing value has no value. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/mfi # forge.ta.mfi ```python forge.ta.mfi(high: ArrayLike, low: ArrayLike, close: ArrayLike, volume: ArrayLike, period: int = 14) -> np.ndarray ``` Money flow index: RSI built from the typical price times volume, from 0 to 100. The typical price times volume is positive flow on a bar whose typical price rose, negative on one that fell, and counts in neither when it did not move. ``100 * positive / (positive + negative)`` over ``period`` bars: 100 when only positive flow is left and 50 when there is none. The first value is on bar ``period``. Since Forge API 1. ## Class forge.ta.MFI ```python forge.ta.MFI(**inputs: Any) -> None ``` RSI built from price times volume, from 0 to 100. The typical price times volume is positive flow on a bar whose typical price rose and negative flow on one that fell; it counts in neither when it did not move MFI = 100 * positive / (positive + negative) over the last period bars Only positive flow reads 100, no flow reads 50. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/midpoint # forge.ta.midpoint ```python forge.ta.midpoint(source: ArrayLike, period: int = 14) -> np.ndarray ``` Halfway between the highest and the lowest value of the last ``period`` bars. Since Forge API 1. ## Class forge.ta.MidPoint ```python forge.ta.MidPoint(**inputs: Any) -> None ``` Halfway between the highest and lowest value of a window. (highest + lowest) / 2 of the source over the last period bars. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/midprice # forge.ta.midprice ```python forge.ta.midprice(high: ArrayLike, low: ArrayLike, period: int = 14) -> np.ndarray ``` Halfway between the highest high and the lowest low of the last ``period`` bars. This is not the median price of one bar, which is ``(high + low) / 2``. Since Forge API 1. ## Class forge.ta.MidPrice ```python forge.ta.MidPrice(**inputs: Any) -> None ``` Halfway between the highest high and the lowest low of a window. (highest high + lowest low) / 2 over the last period bars. This is not the median price of one bar, which is (high + low) / 2. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/min2 # forge.ta.min2 ```python forge.ta.min2(a: ArrayLike | float, b: ArrayLike | float) -> np.ndarray ``` The smaller of two series bar by bar, or of a series and a number. A missing value on either side gives no value. Example: ``ta.min2(close, 100.0)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/mom # forge.ta.mom ```python forge.ta.mom(source: ArrayLike, period: int = 14, method: str = 'difference') -> np.ndarray ``` Price now against price ``period`` bars ago. ``difference`` is the two prices subtracted. ``mt5`` is MetaTrader 5's ratio, price now times 100 over the earlier price, centred on 100; an earlier price of 0 leaves the bar empty. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/month # forge.ta.month ```python forge.ta.month(t: Any, tz: str = 'UTC') -> Any ``` The month (1 to 12) of ``t`` in ``tz``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/morning_doji_star # forge.ta.morning_doji_star ```python forge.ta.morning_doji_star(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, long_body_pct: float = 50.0, body_pct: float = 5.0, penetration: float = 0.3) -> np.ndarray ``` Morning doji star: a morning star whose middle bar is a doji. Found: 1. The middle bar's body is at most ``body_pct`` percent of its own range. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/morning_star # forge.ta.morning_star ```python forge.ta.morning_star(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, long_body_pct: float = 50.0, star_body_pct: float = 30.0, penetration: float = 0.3) -> np.ndarray ``` Morning star: a long fall, a small star, then a rise into the fall. Found: 1. The first bar falls with a long body. The star's body is at most ``star_body_pct`` percent of the first body and does not rise above the first close. The third bar rises and closes at least ``penetration`` of the first body above the first close. The star may open at the first close: no gap is required. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/obv # forge.ta.obv ```python forge.ta.obv(close: ArrayLike, volume: ArrayLike) -> np.ndarray ``` On-balance volume: a running total that starts with the first bar's volume. Volume is added on an up close and taken away on a down close. After a missing bar the total starts again. Its level depends on where the data starts; only its shape means anything. Since Forge API 1. ## Class forge.ta.OBV ```python forge.ta.OBV(**inputs: Any) -> None ``` Volume added on up closes and taken away on down closes. A running total of volume, added on an up close and taken away on a down close, starting with the first bar's volume It depends on where the data starts. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/opening_range # forge.ta.opening_range ```python forge.ta.opening_range(high: ArrayLike, low: ArrayLike, close: ArrayLike, time_open: ArrayLike, minutes: int = 30, session_hours: str = '08:00-17:00', session_tz: str = 'Europe/London') -> dict[str, np.ndarray] ``` The high and low of the first minutes of a session, and the closes that cross them. The range bars are those that open within ``minutes`` of the session start on the local clock. The range is the highest high and lowest low of the range bars so far, then of all of them for the rest of the session, and is empty outside it. ``breakout`` is 1 on a bar after the range whose close crosses above the range high, -1 when it crosses below the range low. A session already under way at the first bar of the data, or with no bar opening inside its range, has no range. ``time_open`` is in nanoseconds (UTC). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/order_blocks # forge.ta.order_blocks ```python forge.ta.order_blocks(open_: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, swing_len: int = 5, internal_len: int = 2, internal: bool = True, swing: bool = True, show_last: int = 5, mitigation: str = 'wick', break_on: str = 'close') -> dict[str, np.ndarray | Zone] ``` The last opposite candle before each break of structure, kept until price goes through it. Breaks are found as in market structure, for the swing and the internal level. For an upward break the block is the last down candle at or before the lowest low between the broken swing and the break bar; its box spans that candle's high and low. A downward break mirrors it. ``mitigation`` says whether a wick or only a close through the far edge removes a block, and ``bull_mitigated`` and ``bear_mitigated`` mark the bar. Each level keeps its five newest live blocks per side as slots, newest first, and ``show_last`` of them are shown. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/osma # forge.ta.osma ```python forge.ta.osma(source: ArrayLike, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9) -> np.ndarray ``` OsMA: the MACD line minus its signal line. Since Forge API 1. ## Class forge.ta.OsMA ```python forge.ta.OsMA(**inputs: Any) -> None ``` The MACD histogram: MACD minus its signal line. OsMA = MACD - signal MACD = EMA(source, fast_period) - EMA(source, slow_period), signal = the simple average of MACD over signal_period Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/outside_bar # forge.ta.outside_bar ```python forge.ta.outside_bar(high: ArrayLike, low: ArrayLike, allow_equal: bool = True) -> np.ndarray ``` Outside bar: a bar whose range covers the previous bar's range. Found: 1. ``high[i] >= high[i-1]`` and ``low[i] <= low[i-1]``, strictly with ``allow_equal`` off. A bar with the same high and low as the one before it is an inside bar only. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/percentrank # forge.ta.percentrank ```python forge.ta.percentrank(source: ArrayLike, period: int = 100) -> np.ndarray ``` The share of the previous ``period`` values at or below the current value. The result is from 0 to 100. The current bar is compared with the bars before it, not with itself, so the first value is on bar ``period``. A window that holds a missing value has no value. Since Forge API 1. ## Class forge.ta.PercentRank ```python forge.ta.PercentRank(**inputs: Any) -> None ``` Where the value sits among its previous values, from 0 to 100. The share of the previous period values that are less than or equal to the current value, from 0 to 100. The current bar is compared with the bars before it, not with itself. A window holding a missing value has no value. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/period_open # forge.ta.period_open ```python forge.ta.period_open(open_: ArrayLike, time_open: ArrayLike, period: str = 'D', day_boundary: str = '17:00 America/New_York') -> np.ndarray ``` The open of the current day, week or month on every bar of it. The value is known on the first bar of the period. A period already under way at the first bar of the data has no value. ``time_open`` is in nanoseconds (UTC) and must be in time order. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/piercing # forge.ta.piercing ```python forge.ta.piercing(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, long_body_pct: float = 50.0, penetration: float = 0.5) -> np.ndarray ``` Piercing line: a rise that closes deep into the previous fall. Found: 1. The first bar falls with a long body, the second opens at or below its close and closes at least ``penetration`` of the first body above that close, still below the first open. Since Forge API 1. ## Class forge.ta.Piercing ```python forge.ta.Piercing(**inputs: Any) -> None ``` A rise that closes deep into the previous fall. body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first bar falls with a long body; the second rises, opens at or below the first close and closes at or above first close + penetration * first body, below the first open Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/pivot_high # forge.ta.pivot_high ```python forge.ta.pivot_high(high: ArrayLike, left: int = 5, right: int = 5) -> np.ndarray ``` The high of a swing high, written ``right`` bars after the swing. Bar ``i`` holds ``high[i - right]`` if that bar is strictly above the ``left`` bars before it and the ``right`` bars after it, else NaN. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/pivot_low # forge.ta.pivot_low ```python forge.ta.pivot_low(low: ArrayLike, left: int = 5, right: int = 5) -> np.ndarray ``` The low of a swing low, written ``right`` bars after the swing (the mirror of pivot_high). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/pivot_points # forge.ta.pivot_points ```python forge.ta.pivot_points(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, time_open: ArrayLike, kind: str = 'standard', period: str = 'D', day_boundary: str = '17:00 America/New_York') -> dict[str, np.ndarray] ``` Pivot points for ``kind`` from the previous ``period`` (``D``, ``W`` or ``M``). Every bar of a period carries the levels computed from the open (first), high (highest), low (lowest) and close (last) of the period before it, which is the last earlier one that had bars. The first period has no value, and neither does a period after one that holds a missing price. Levels a kind does not define stay NaN. ``time_open`` is in nanoseconds (UTC) and must be in time order. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/premium_discount # forge.ta.premium_discount ```python forge.ta.premium_discount(high: ArrayLike, low: ArrayLike, swing_len: int = 5) -> dict[str, np.ndarray | Zone] ``` The upper and lower halves of the active swing range, split at equilibrium. The active range runs from the lowest low since the latest swing low to the highest high since the latest swing high, once both swings are confirmed. ``equilibrium`` is its middle, ``premium`` the zone above it and ``discount`` the zone below it. A flat range shows nothing. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/prior_levels # forge.ta.prior_levels ```python forge.ta.prior_levels(open_: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, time_open: ArrayLike, period: str = 'D', day_boundary: str = '17:00 America/New_York') -> dict[str, np.ndarray] ``` The previous day's, week's or month's open, high, low and close on every bar. ``period`` is ``D``, ``W`` or ``M``. Every bar of a period carries the first open, the highest high, the lowest low and the last close of the period before it, which is the last earlier one that had bars. A period already under way at the first bar of the data is not used, and a period holding a missing price has no levels. ``time_open`` is in nanoseconds (UTC) and must be in time order. The parameter is spelled ``open_`` because ``open`` is a Python builtin. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/rising # forge.ta.rising ```python forge.ta.rising(source: ArrayLike, period: int = 3) -> np.ndarray ``` True when each of the last ``period`` bars is above the bar before it. ``source[i-period] < ... < source[i-1] < source[i]``. An equal value or a missing value breaks the run. The result is a true or false array. Example: ``ta.rising(close, 3)``. Since Forge API 1. ## Class forge.ta.Rising ```python forge.ta.Rising(**inputs: Any) -> None ``` True after a number of rises in a row. True when each of the last period bars is above the bar before it: source[i-period] < ... < source[i-1] < source[i]. An equal value or a missing value breaks the run. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/rma # forge.ta.rma ```python forge.ta.rma(source: ArrayLike, period: int = 13) -> np.ndarray ``` Wilder's smoothed average: seeded with the mean of the first ``period`` values. Then ``(previous * (period - 1) + price) / period``. After a gap it restarts with a new seed. Also known as ``smma`` and ``wilder``. Since Forge API 1. ## Class forge.ta.RMA ```python forge.ta.RMA(**inputs: Any) -> None ``` Wilder's smoothing: a slow average seeded with the mean of the first N values. RMA[period-1] = mean of the first period values RMA[i] = (RMA[i-1] * (period - 1) + x[i]) / period After a gap the average starts again with a new seed. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/roc # forge.ta.roc ```python forge.ta.roc(source: ArrayLike, period: int = 12, method: str = 'percent') -> np.ndarray ``` Change over ``period`` bars, as a percent, a fraction or a ratio. ``percent`` is 100 * (price - earlier) / earlier, ``fraction`` the same without the 100, ``ratio`` is price / earlier and ``ratio100`` is 100 * price / earlier. ``mt5`` is MetaTrader 5's 100 * (price - earlier) / price, which divides by the current price. A zero denominator leaves the bar empty. Since Forge API 1. ## Class forge.ta.ROC ```python forge.ta.ROC(**inputs: Any) -> None ``` Change over a number of bars as a percent, fraction or ratio. percent: 100 * (x[i] - x[i-period]) / x[i-period] fraction: (x[i] - x[i-period]) / x[i-period] ratio: x[i] / x[i-period] ratio100: 100 * x[i] / x[i-period] mt5: 100 * (x[i] - x[i-period]) / x[i], the MetaTrader 5 form A zero denominator leaves the bar empty. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/rsi # forge.ta.rsi ```python forge.ta.rsi(source: ArrayLike, period: int = 14, method: str = 'wilder') -> np.ndarray ``` Relative strength index from average gains against average losses, 0 to 100. ``wilder`` smooths gains and losses with Wilder's average, seeded with the mean of the first ``period`` changes (TA-Lib and MetaTrader 5). ``cutler`` uses simple averages. A window with no gain and no loss reads 50. The first value is on bar ``period``. Since Forge API 1. ## Class forge.ta.RSI ```python forge.ta.RSI(**inputs: Any) -> None ``` Momentum bounded 0 to 100, from average gains against average losses. gain = the change when it is positive, loss = minus the change when it is negative wilder: the averages start as the mean of the first period gains and losses, then average = (previous average * (period - 1) + value) / period cutler: the averages are simple means of the last period gains and losses RSI = 100 * AvgGain / (AvgGain + AvgLoss) A window with no gain and no loss reads 50. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/rvi # forge.ta.rvi ```python forge.ta.rvi(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, period: int = 10) -> dict[str, np.ndarray] ``` Relative vigor index: the close against the open, scaled by the range, with a signal line. The body (close - open) and the range (high - low) are each weighted over four bars as 1, 2, 2, 1, then summed over ``period`` bars. ``rvi`` is their ratio, or the body sum alone where the range sum is 0. ``signal`` is ``rvi`` weighted the same way over four bars, divided by 6. Since Forge API 1. ## Class forge.ta.RVI ```python forge.ta.RVI(**inputs: Any) -> None ``` The close against the open, scaled by the range, with a signal line. The body (close - open) and the range (high - low) are each weighted over four bars as 1, 2, 2, 1 RVI = sum of the weighted body / sum of the weighted range over period bars; the body sum alone when the range sum is 0 signal = RVI weighted over four bars as 1, 2, 2, 1, divided by 6 Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/sar # forge.ta.sar ```python forge.ta.sar(high: ArrayLike, low: ArrayLike, af_step: float = 0.02, af_max: float = 0.2) -> np.ndarray ``` Parabolic stop and reverse. ``SAR[i+1] = SAR[i] + AF * (EP - SAR[i])``: ``EP`` is the extreme price of the current trend and ``AF`` starts at ``af_step``, grows by ``af_step`` with each new extreme and stops at ``af_max``. A reversal restarts both. The value is withheld until the first reversal the price itself causes, so a guessed starting direction never reaches the chart. NaN for a run of fewer than 3 bars. Since Forge API 1. ## Class forge.ta.SAR ```python forge.ta.SAR(**inputs: Any) -> None ``` Trailing stop-and-reverse level that speeds up as the trend runs. SAR[i+1] = SAR[i] + AF * (EP - SAR[i]), EP = the extreme price of the trend AF starts at the step, grows by the step with each new extreme and stops at the maximum. A reversal restarts EP and AF. The first leg is seeded short and is left empty until the first reversal the price itself causes. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/session_range # forge.ta.session_range ```python forge.ta.session_range(high: ArrayLike, low: ArrayLike, time_open: ArrayLike, asia_hours: str = '09:00-18:00', asia_tz: str = 'Asia/Tokyo', london_hours: str = '08:00-17:00', london_tz: str = 'Europe/London', new_york_hours: str = '08:00-17:00', new_york_tz: str = 'America/New_York') -> dict[str, np.ndarray | Zone] ``` The Asia, London and New York sessions: a box while each runs, its levels once it ends. For each session the result holds a zone (the highest high and lowest low of its bars so far, active only while it runs) and the finished high and low of its last session, shown from the bar after it ends until the same session ends again. Each session's hours and time zone are their own settings. ``time_open`` is in nanoseconds (UTC). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/shooting_star # forge.ta.shooting_star ```python forge.ta.shooting_star(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, wick_mult: float = 2.0, opposite_pct: float = 25.0, body_zone_pct: float = 33.3, trend_bars: int = 3) -> np.ndarray ``` Shooting star: a long upper wick after a rise. Found: -1. The same bar as an inverted hammer, told apart by what came before: the close before the bar is above the close ``trend_bars`` bars earlier. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/sma # forge.ta.sma ```python forge.ta.sma(source: ArrayLike, period: int = 20) -> np.ndarray ``` Mean of the last ``period`` values, each window summed from scratch. NaN until ``period`` bars exist, and for any window that holds a missing value. Since Forge API 1. ## Class forge.ta.SMA ```python forge.ta.SMA(**inputs: Any) -> None ``` Average of the last N values, each weighted equally. SMA[i] = (x[i] + x[i-1] + ... + x[i-period+1]) / period No value until period bars exist. A window holding a missing value has no value. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/smma # forge.ta.smma ```python forge.ta.smma(source: ArrayLike, period: int = 13) -> np.ndarray ``` Wilder's smoothed average: seeded with the mean of the first ``period`` values. Then ``(previous * (period - 1) + price) / period``. After a gap it restarts with a new seed. Also known as ``smma`` and ``wilder``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/spinning_top # forge.ta.spinning_top ```python forge.ta.spinning_top(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, body_pct: float = 30.0) -> np.ndarray ``` Spinning top: a small body with a longer wick on each side. Found: 1 when the close is at or above the open, else -1. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/stdev # forge.ta.stdev ```python forge.ta.stdev(source: ArrayLike, period: int = 20, method: str = 'sma') -> np.ndarray ``` Standard deviation of the last ``period`` values around their moving average at this bar. ``method`` names the average: ``sma`` (the population deviation), ``ema``, ``smma`` or ``lwma``. The divisor is ``period``, not ``period - 1``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/stoch # forge.ta.stoch ```python forge.ta.stoch(high: ArrayLike, low: ArrayLike, close: ArrayLike, k_period: int = 5, d_period: int = 3, slowing: int = 3, basis: str = 'high_low') -> dict[str, np.ndarray] ``` Stochastic oscillator: ``k`` is the slowed %K and ``d`` its simple average. ``slowing`` sums the close's distance from the lowest low and the high to low range over that many bars before dividing once (MetaTrader 5's smoothing). ``basis`` takes the highest high and lowest low (``high_low``) or the highest and lowest close (``close_close``). A window with no range reads 50. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/stoch_rsi # forge.ta.stoch_rsi ```python forge.ta.stoch_rsi(source: ArrayLike, rsi_period: int = 14, stoch_period: int = 14, k_smooth: int = 3, d_smooth: int = 3) -> dict[str, np.ndarray] ``` The stochastic oscillator applied to the relative strength index. The RSI is Wilder's. Its position in its own range over ``stoch_period`` bars, times 100, reads as the RSI itself when that range is 0. ``k`` and ``d`` are simple averages of it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/sum # forge.ta.sum ```python forge.ta.sum(source: ArrayLike, period: int = 30) -> np.ndarray ``` The sum of the last ``period`` values. A window that holds a missing value has no value. Since Forge API 1. ## Class forge.ta.Sum ```python forge.ta.Sum(**inputs: Any) -> None ``` The sum of the last values over a number of bars. The sum of the last period values. A window holding a missing value has no value. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/supertrend # forge.ta.supertrend ```python forge.ta.supertrend(high: ArrayLike, low: ArrayLike, close: ArrayLike, atr_period: int = 10, factor: float = 3.0) -> dict[str, np.ndarray] ``` SuperTrend: a line ``factor`` average true ranges from the bar's midpoint that only ratchets. ``direction`` is +1 while price is above the line and -1 while below (the negative of TradingView's). The average true range is Wilder's. NaN until it is defined. Since Forge API 1. ## Class forge.ta.SuperTrend ```python forge.ta.SuperTrend(**inputs: Any) -> None ``` Trend-following line built from the average true range. upper = (high + low) / 2 + factor * ATR, lower = (high + low) / 2 - factor * ATR Each band only moves toward price, unless the previous close crossed it. The line is the lower band while the trend is up and the upper band while it is down. direction is 1 up and -1 down. ATR is Wilder's average true range. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/tema # forge.ta.tema ```python forge.ta.tema(source: ArrayLike, period: int = 14) -> np.ndarray ``` Triple exponential average: ``3 * E1 - 3 * E2 + E3``. After a gap the cascade restarts. Since Forge API 1. ## Class forge.ta.TEMA ```python forge.ta.TEMA(**inputs: Any) -> None ``` Exponential average with most of its lag removed. TEMA = 3 * EMA(x) - 3 * EMA(EMA(x)) + EMA(EMA(EMA(x))) Each average starts at the bar where the one before it is defined. After a gap the whole calculation starts again. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/three_black_crows # forge.ta.three_black_crows ```python forge.ta.three_black_crows(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, wick_pct: float = 20.0) -> np.ndarray ``` Three black crows: three falling bars, each opening inside the last body. Found: -1. Each close is below the one before, each of the second and third bars opens within the body of the bar before, and each lower wick is at most ``wick_pct`` percent of its bar's range. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/three_inside # forge.ta.three_inside ```python forge.ta.three_inside(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, long_body_pct: float = 50.0, allow_equal: bool = True) -> np.ndarray ``` Three inside: a harami confirmed by the third bar. Found: 1 up, -1 down. Up: the first two bars are a harami after a fall and the third closes above the first open. Down: a harami after a rise and the third closes below the first open. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/three_outside # forge.ta.three_outside ```python forge.ta.three_outside(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, allow_equal: bool = True) -> np.ndarray ``` Three outside: an engulfing confirmed by the third bar. Found: 1 up, -1 down. Up: the first two bars are a bullish engulfing and the third closes above the second close. Down: a bearish engulfing and the third closes below the second close. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/three_white_soldiers # forge.ta.three_white_soldiers ```python forge.ta.three_white_soldiers(open: ArrayLike, high: ArrayLike, low: ArrayLike, close: ArrayLike, wick_pct: float = 20.0) -> np.ndarray ``` Three white soldiers: three rising bars, each opening inside the last body. Found: 1. Each close is above the one before, each of the second and third bars opens within the body of the bar before, and each upper wick is at most ``wick_pct`` percent of its bar's range. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/timestamp # forge.ta.timestamp ```python forge.ta.timestamp(year: int, month: int, day: int, hour: int = 0, minute: int = 0, second: int = 0, *, tz: str = 'UTC') -> int ``` Nanoseconds UTC of a local time in ``tz``. A local time a DST change skips does not exist and is refused; an ambiguous one (the hour a change repeats) takes the first occurrence, the engine clock's rule. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/tr # forge.ta.tr ```python forge.ta.tr(high: ArrayLike, low: ArrayLike, close: ArrayLike) -> np.ndarray ``` True range: the bar's range widened to take in the gap from the previous close. The first bar has no previous close and no value. NaN on a bar where one of the values it reads is missing, and on the bar after a missing close. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/trix # forge.ta.trix ```python forge.ta.trix(source: ArrayLike, period: int = 14) -> np.ndarray ``` TRIX: the one-bar change of a triple smoothed EMA, as a fraction and not a percent. The three stages start from the first value, from stage 1 at bar ``period - 1`` and from stage 2 at bar ``2 * (period - 1)``, as MetaTrader 5 does. The first value is on bar ``max(3 * period - 3, 1)``. A previous value of 0 leaves the bar empty. Since Forge API 1. ## Class forge.ta.TRIX ```python forge.ta.TRIX(**inputs: Any) -> None ``` The one-bar change of a triple smoothed EMA. E1 = EMA(source, period), E2 = EMA(E1, period), E3 = EMA(E2, period) TRIX = (E3[i] - E3[i-1]) / E3[i-1], a fraction and not a percent E2 starts at bar period - 1 and E3 at bar 2 * (period - 1). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/valuewhen # forge.ta.valuewhen ```python forge.ta.valuewhen(condition: ArrayLike, source: ArrayLike, occurrence: int = 0) -> np.ndarray ``` The source on the bar where the condition last held, or the ``occurrence``-th time before. ``occurrence`` 0 is the most recent bar where the condition held, at or before the current bar. There is no value until the condition has held ``occurrence + 1`` times, or when the source is missing on that bar. Example: ``ta.valuewhen(ta.crossover(fast, slow), high)``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/variance # forge.ta.variance ```python forge.ta.variance(source: ArrayLike, period: int = 20) -> np.ndarray ``` Variance: the mean squared distance of the last ``period`` values from their average. Since Forge API 1. ## Class forge.ta.Variance ```python forge.ta.Variance(**inputs: Any) -> None ``` The square of the standard deviation over a period. The mean squared distance of the last period values from their simple average. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/vidya # forge.ta.vidya ```python forge.ta.vidya(source: ArrayLike, period_cmo: int = 9, period_ema: int = 12) -> np.ndarray ``` Chande's variable index dynamic average. ``alpha = 2 / (period_ema + 1) * |CMO|`` with ``CMO`` the Chande momentum over ``period_cmo`` bars, then ``VIDYA = price * alpha + previous * (1 - alpha)``. NaN for the first ``period_ema + period_cmo - 1`` bars of each unbroken run. Since Forge API 1. ## Class forge.ta.VIDYA ```python forge.ta.VIDYA(**inputs: Any) -> None ``` Exponential average that speeds up when momentum is strong. CMO = (sum of up moves - sum of down moves) / (sum of up moves + sum of down moves) over period_cmo bars alpha = 2 / (period_ema + 1) * |CMO| VIDYA[i] = x[i] * alpha + VIDYA[i-1] * (1 - alpha) After a gap the average starts again. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/volumes # forge.ta.volumes ```python forge.ta.volumes(volume: ArrayLike) -> dict[str, np.ndarray] ``` The volume of each bar, and whether it is above the previous bar's. ``direction`` is +1 where the volume is above the previous bar's and -1 where it is equal or lower (MetaTrader 5's two column colours). The first bar has a volume and no direction, and a bar next to a missing one has none either. Since Forge API 1. ## Class forge.ta.Volumes ```python forge.ta.Volumes(**inputs: Any) -> None ``` Volume per bar, tick or real as the feed carries it. The volume of the bar direction = 1 when the volume is above the previous bar's, else -1 On forex the feed carries tick volume. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/vwap # forge.ta.vwap ```python forge.ta.vwap(source: ArrayLike, volume: ArrayLike, time_open: ArrayLike, anchor: str = 'session', anchor_time: str = '', band_mult: float = 1.0, day_boundary: str = '17:00 America/New_York') -> dict[str, np.ndarray] ``` Volume-weighted average price from each anchor, with bands at its weighted deviation. ``anchor`` starts the sums again at each trading day (``session``, from ``day_boundary``), week or month, or once at ``anchor_time`` (``time``, written 2024-03-01 13:00 in UTC), with no value before it. A missing bar starts the sums again. The bands are ``band_mult`` times the volume-weighted standard deviation. No value while the volume so far is 0. Since Forge API 1. ## Class forge.ta.VWAP ```python forge.ta.VWAP(**inputs: Any) -> None ``` The average price weighted by volume since the session, week, month or a chosen time. VWAP = the running sum of volume * source over the running sum of volume, from each anchor upper and lower = VWAP plus and minus band_mult * the volume-weighted standard deviation A session starts at the day boundary. On forex the volume is tick volume. To anchor at a bar found in code, call it on the data from that bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/vwma # forge.ta.vwma ```python forge.ta.vwma(source: ArrayLike, volume: ArrayLike, period: int = 20) -> np.ndarray ``` Volume weighted average: ``sum(price * volume) / sum(volume)`` over the last ``period`` bars. NaN where the window holds a missing value or its volume adds to zero. Since Forge API 1. ## Class forge.ta.VWMA ```python forge.ta.VWMA(**inputs: Any) -> None ``` Average that weights each price by the volume traded at it. VWMA[i] = sum(x[j] * v[j]) / sum(v[j]) over the last period bars No value until period bars exist, or where the volume in the window adds to zero. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/wilder # forge.ta.wilder ```python forge.ta.wilder(source: ArrayLike, period: int = 13) -> np.ndarray ``` Wilder's smoothed average: seeded with the mean of the first ``period`` values. Then ``(previous * (period - 1) + price) / period``. After a gap it restarts with a new seed. Also known as ``smma`` and ``wilder``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/wma # forge.ta.wma ```python forge.ta.wma(source: ArrayLike, period: int = 13) -> np.ndarray ``` Linearly weighted average: weight ``period`` on the newest value down to 1 on the oldest. Also known as ``lwma``. Since Forge API 1. ## Class forge.ta.WMA ```python forge.ta.WMA(**inputs: Any) -> None ``` Average that weights the newest value N times and the oldest once. WMA[i] = (period * x[i] + (period - 1) * x[i-1] + ... + 1 * x[i-period+1]) / (period * (period + 1) / 2) No value until period bars exist. A window holding a missing value has no value. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/wpr # forge.ta.wpr ```python forge.ta.wpr(high: ArrayLike, low: ArrayLike, close: ArrayLike, period: int = 14) -> np.ndarray ``` Williams %R: where the close sits in the recent range, from 0 down to -100. A window with no range reads -50. The first value is on bar ``period - 1``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/ta/zigzag # forge.ta.zigzag ```python forge.ta.zigzag(high: ArrayLike, low: ArrayLike, close: ArrayLike, depth: int = 12, backstep: int = 3, atr_period: int = 14, atr_mult: float = 1.0) -> dict[str, np.ndarray] ``` Swing highs and lows, written only once a reversal confirms them. ``up`` and ``down`` hold the swing price on the bar that confirmed it, ``offset`` the number of bars back to the swing, ``pivot`` the last confirmed swing price carried forward and ``direction`` +1 after a confirmed low and -1 after a confirmed high. A reversal confirms a swing once it is at least ``atr_mult`` average true ranges (Wilder's) from it. The swing still pending at the end is never written, so no value is ever revised. Since Forge API 1. ## Class forge.ta.ZigZag ```python forge.ta.ZigZag(**inputs: Any) -> None ``` Swing highs and lows, each shown only once a reversal has confirmed it. A bar is a low (high) candidate when its low (high) is the lowest (highest) of the last depth bars. A candidate is final after backstep more bars, once weaker candidates of the same kind within backstep bars are cleared. One swing is pending at a time. A stronger candidate of the same kind replaces it. An opposite candidate at least atr_mult average true ranges away confirms it. The confirmed swing is written on the confirming bar: up or down holds its price and offset the bars back to it. pivot is the last confirmed price, direction is 1 after a low and -1 after a high. The swing pending at the end is never written. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/EngineNotInstalled # forge.testing.EngineNotInstalled ```python forge.testing.EngineNotInstalled(message: str = "This runs Forge's engine, which this installation does not include.") -> None ``` A check needs Forge's engine, and this installation does not include it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/INSTRUMENT_CLASSES # forge.testing.INSTRUMENT_CLASSES Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/STABLE_TRADE_FIELDS # forge.testing.STABLE_TRADE_FIELDS Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/SampleBars # forge.testing.SampleBars ```python forge.testing.SampleBars(name: str, symbol: str, timeframe: str, frame: pd.DataFrame, facts: Mapping[str, Any], seed: int, version: int, description: str, minutes: pd.DataFrame | None = None, utc_offset_minutes: np.ndarray | None = None, bricks: Any = None) -> None ``` A set of bars for tests, with the facts of the instrument they imitate. ``frame`` is close-labelled in broker time. ``minutes`` is the minute path the bars were built from, in UTC, so a sample can be rebuilt at another timeframe. ``utc_offset_minutes`` holds one offset for each bar when the broker's clock changes inside the sample, and is ``None`` otherwise. The prices are Forge's own seeded synthetic prices, never real market data. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/TestRun # forge.testing.TestRun ```python forge.testing.TestRun(result: RunResult, signals: ComputeResult | None, program_name: str, symbol: str, timeframe: str, settings: Mapping[str, Any], seed: int, data_name: str, bar_count: int) -> None ``` One finished run in a test. ``trades``, ``fills``, ``orders`` and ``equity`` are the engine's tables, and ``stats`` its statistics. ``entries`` and ``exits`` list the fills that opened and closed positions, by bar. ``signal`` reads what the strategy's ``compute()`` returned, one value for each bar. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/assert_entry_at # forge.testing.assert_entry_at ```python forge.testing.assert_entry_at(run: TestRun, bar: int, *, side: str | None = None) -> None ``` An entry was filled on primary bar ``bar``, on the ``side`` given when there is one. A negative ``bar`` counts from the end. The failure lists the entries on either side of ``bar``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/assert_exit_at # forge.testing.assert_exit_at ```python forge.testing.assert_exit_at(run: TestRun, bar: int, *, side: str | None = None, reason: str | None = None) -> None ``` An exit was filled on primary bar ``bar``, closing a position of ``side`` and for ``reason``. ``reason`` is the fill's reason in the run's ``exits`` table, such as ``"stop"`` or ``"target"``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/assert_no_lookahead # forge.testing.assert_no_lookahead ```python forge.testing.assert_no_lookahead(strategy: str | os.PathLike[str] | type[Strategy], bars: SampleBars | pd.DataFrame | None = None, params: Mapping[str, Any] | None = None, **kwargs: Any) -> None ``` Fail unless the look-ahead audit shows no evidence of look-ahead. The failure names the verdict, the reason when there is one, and the first three differences with their series and bars. A verdict of ``inconclusive`` fails too: an audit that could not judge has shown nothing. The arguments are those of ``check_lookahead``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/assert_no_trade_between # forge.testing.assert_no_trade_between ```python forge.testing.assert_no_trade_between(run: TestRun, start: int | str | pd.Timestamp, end: int | str | pd.Timestamp) -> None ``` No entry was filled from ``start`` to ``end``, both included. ``start`` and ``end`` are both bars or both UTC times. The failure lists the entries inside. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/assert_ready_to_publish # forge.testing.assert_ready_to_publish ```python forge.testing.assert_ready_to_publish(path: str | os.PathLike[str], **kwargs: Any) -> None ``` Fail unless every row of the pre-publish battery passes, naming each row that did not. The arguments are those of ``publish_check``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/assert_same_trades # forge.testing.assert_same_trades ```python forge.testing.assert_same_trades(run_a: TestRun | RunResult, run_b: TestRun | RunResult, *, price_rel: float = 0.0, size_rel: float = 0.0, fields: Sequence[str] = ('symbol', 'side', 'entry_time', 'exit_time', 'entry_price', 'exit_price', 'size_lots')) -> None ``` The two runs made the same trades, compared one by one in entry-time order. Prices are equal within ``price_rel`` and sizes within ``size_rel``, both relative, and NaN equals NaN. The failure is a readable diff: trades are numbered from 1 in entry-time order, and at most 20 lines are shown. ``fields`` are columns of the trade table. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/assert_stable_warmup # forge.testing.assert_stable_warmup ```python forge.testing.assert_stable_warmup(strategy: str | os.PathLike[str] | type[Strategy], bars: SampleBars | pd.DataFrame | None = None, params: Mapping[str, Any] | None = None, **kwargs: Any) -> None ``` Fail unless every output of the strategy is stable at its declared warm-up. The failure names each flagged output with the note that says what to change. An inconclusive check fails too, with its reason. The arguments are those of ``check_warmup``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/broker_facts # forge.testing.broker_facts ```python forge.testing.broker_facts(*symbols: str, currency: str = 'USD', leverage: int = 100, server_tz_offset_minutes: int = 120, margin_mode: str = 'hedging', overrides: Mapping[str, Mapping[str, Any]] | None = None) -> dict[str, Any] ``` The ``oathra.broker_facts/1`` payload a run receives, for the sample ``symbols``. ``overrides`` maps a symbol of the call to the fields to replace in its copied facts before the payload is hashed, so a test can set ``spread_points`` and still hold a payload with a valid ``content_hash``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/check_lookahead # forge.testing.check_lookahead ```python forge.testing.check_lookahead(strategy: str | os.PathLike[str] | type[Strategy], bars: SampleBars | pd.DataFrame | None = None, params: Mapping[str, Any] | None = None, *, symbol: str | None = None, timeframe: str | None = None, facts: Mapping[str, Any] | None = None, limit_prices: bool = False, min_trades: int | None = None, seed: int = 0) -> dict[str, Any] ``` Audit a strategy for look-ahead and return the report as plain JSON. The strategy runs over ``bars`` (the kit's sample for its symbol when left out) and over shortened copies of them under a fixed profile that removes position limits, sizing and prop rules. ``limit_prices=True`` keeps the strategy's own order types. ``min_trades`` is the fewest trades the audit needs to judge (10 when left out). The arguments are those of ``run``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/check_warmup # forge.testing.check_warmup ```python forge.testing.check_warmup(strategy: str | os.PathLike[str] | type[Strategy], bars: SampleBars | pd.DataFrame | None = None, params: Mapping[str, Any] | None = None, *, symbol: str | None = None, timeframe: str | None = None, facts: Mapping[str, Any] | None = None) -> dict[str, Any] ``` Measure how much each output of a strategy depends on where its data starts, as plain JSON. Every plot, recorded series, declared signal and named condition is computed over the whole history and over histories that start later. A moving average is exact after its window, an exponential average settles after a number of bars the report gives, and a running total never settles. The arguments are those of ``run``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/instrument # forge.testing.instrument ```python forge.testing.instrument(symbol: str) -> dict[str, Any] ``` A copy of the sample facts of ``symbol``: ``EURUSD``, ``GBPUSD``, ``USDJPY``, ``US500`` or ``XAUUSD``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/publish_check # forge.testing.publish_check ```python forge.testing.publish_check(path: str | os.PathLike[str], *, symbols: Sequence[str] | None = None, limit_prices: bool = False) -> dict[str, Any] ``` Run the pre-publish battery on a strategy file and return its report as plain JSON. The battery loads the file, lints it, runs it on Forge's sample prices for several symbols and time frames, and judges the runs and the look-ahead and warm-up checks. It is the same function the publish flow runs, so passing it here means the flow will see the same rows. ``symbols`` names the sample symbols to use (at most five), and ``limit_prices`` keeps the strategy's own order types in the look-ahead audit. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/run # forge.testing.run ```python forge.testing.run(strategy: str | os.PathLike[str] | type[Strategy], bars: SampleBars | pd.DataFrame | None = None, params: Mapping[str, Any] | None = None, *, symbol: str | None = None, timeframe: str | None = None, facts: Mapping[str, Any] | None = None, extras: Mapping[str, pd.DataFrame | SeriesColumns] | None = None, seed: int = 0) -> TestRun ``` Run a strategy over sample bars and return what it did. ``strategy`` is the path of a file or a strategy class already defined, such as one in the test module. With no ``bars`` the strategy runs on the kit's sample for its first declared symbol the kit has facts for, or ``EURUSD``, on its declared bars or ``H1``. ``bars`` can be a ``SampleBars``, which brings its own symbol, facts and clock changes, or a data frame, which needs ``symbol`` and ``facts`` and is indexed like the frame ``forge.run`` takes. ``params`` are setting values. Other series the strategy reads (``d.tf("H4")``, ``d["GBPUSD"]``) are built from the samples unless ``extras`` gives them. A run that ends with an error raises ``AssertionError`` naming the strategy's file and line, so a strategy that crashes fails its test. A clock change inside ``bars`` reaches the series the strategy reads, while the engine's own clock keeps the facts' offset. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/snapshot # forge.testing.snapshot ```python forge.testing.snapshot(value: object, *, name: str | None = None, tolerance: Mapping[str, float] | None = None, path: str | os.PathLike[str] | None = None) -> None ``` Compare a run, or a mapping of numbers, with the snapshot stored for this test. The file is ``path``, or ``__snapshots__//.json`` beside the test file. With ``--update-snapshots`` (or ``FORGE_UPDATE_SNAPSHOTS=1``) the file is written instead, and a missing file is otherwise a failure. ``tolerance`` maps a field's dotted path or last key to the relative and absolute tolerance its number may move by. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/stable_fields # forge.testing.stable_fields ```python forge.testing.stable_fields() -> dict[str, tuple[str, ...]] ``` The fields a run's snapshot keeps, by section, in the order the snapshot writes them. The engine's own numbering (trade, fill and order ids, and the ``flags`` map) is not stable between versions of the engine and is left out. ``refusals`` is each refusal's name and count, sorted by name, and ``end_reason`` is one value. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/synthetic # forge.testing.synthetic ```python forge.testing.synthetic(like: str | SampleBars | pd.DataFrame | None = None, *, seed: int, timeframe: str = 'H1', days: int = 180, weekends: bool = True, gaps: int = 0, halts: int = 0, spikes: int = 0, symbol: str | None = None) -> SampleBars ``` A seeded synthetic sample. ``seed`` has no default: an unseeded call is a ``TypeError``. ``like`` is a sample's name or a ``SampleBars``, whose walk and symbol are copied, or a frame of bars whose volatility is estimated from its closes. With ``like=None`` the walk is that of ``symbol`` (``EURUSD`` when it is also ``None``). Since Forge API 1. --- Source: https://oathra.dev/reference/forge/testing/to_snapshot # forge.testing.to_snapshot ```python forge.testing.to_snapshot(value: TestRun | RunResult | Mapping[str, Any]) -> dict[str, Any] ``` A run, or a mapping of numbers, text, lists and mappings, as the plain structure a snapshot stores. Trades are sorted by entry time, symbol and side, and refusals by name. A mapping keeps its keys sorted. Numbers are kept exactly, ``NaN`` is written as the text ``"NaN"``, and infinity as ``"Infinity"`` and ``"-Infinity"``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/CheckResult # forge.types.CheckResult ```python forge.types.CheckResult(margin_required: float, free_margin_after: float, projected_profit: float | None, reject_reason: RejectReason | None, max_affordable_lots: float = 0.0) -> None ``` What the engine would say about an order sent now. ``margin_required`` is the margin the order would hold. ``free_margin_after`` is what is left of the equity once it is held. ``projected_profit`` is the gross profit at ``tp`` and ``None`` without one. ``reject_reason`` is the refusal the engine's submission checks would give, or ``None`` when none would. ``max_affordable_lots`` is the largest size, in lots, that the free margin before this order would pay for, on the lot step and under the broker's maximum, the run's lot cap and the risk cap (``0.0`` when not even the minimum lot fits): the same figure the run's margin refusal record gives for an order that is then refused. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/FillCosts # forge.types.FillCosts ```python forge.types.FillCosts(spread: float, commission: float, swap: float, slippage: float) -> None ``` What one fill cost, each part in account currency and never negative as a cost. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/FillReason # forge.types.FillReason ```python forge.types.FillReason(*args, **kwargs) ``` Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/OrderEvent # forge.types.OrderEvent ```python forge.types.OrderEvent(kind: Literal['accepted', 'rejected', 'expired', 'cancelled', 'filled'], order: OrderTicket, time: int, fill: FillEvent | None = None, reason: RejectReason | None = None) -> None ``` What ``on_order_event`` receives: one thing that happened to one order. ``kind`` is ``accepted``, ``rejected``, ``expired``, ``cancelled`` or ``filled``. ``fill`` is set for ``filled`` and ``reason`` for ``rejected``. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/OrderRequest # forge.types.OrderRequest ```python forge.types.OrderRequest(symbol: str | None = None, side: OrderSide = 'buy', type: OrderType = 'market', size: float | None = None, price: float | None = None, stop_price: float | None = None, sl: float | None = None, tp: float | None = None, tag: str | None = None, tif: str | None = None, oco: str | None = None) -> None ``` An order a strategy describes before sending it: what ``ctx.check`` and ``calc.margin`` take. ``symbol`` is the primary symbol when ``None``. ``size`` is in lots, and ``None`` means the run's sizing rule decides. ``price`` is a limit price and ``stop_price`` a stop trigger. ``sl`` and ``tp`` are absolute prices. ``oco`` names a one-cancels-other group: when one order of the group fills, expires or is cancelled, the rest of it are cancelled. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/OrderSide # forge.types.OrderSide ```python forge.types.OrderSide(*args, **kwargs) ``` Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/OrderType # forge.types.OrderType ```python forge.types.OrderType(*args, **kwargs) ``` Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/PositionOwner # forge.types.PositionOwner ```python forge.types.PositionOwner(*args, **kwargs) ``` What a position calls to change or close itself. The run implements it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/PositionRecord # forge.types.PositionRecord ```python forge.types.PositionRecord(id: int, symbol: str, side: PositionSide, size: float, avg_price: float, open_time: int, stop: float | None, target: float | None, unrealized: float, swap: float, commission: float, r_multiple: float | None, risk_cash: float | None, mae: float, mfe: float, bars_held: int, tag: str | None, entry_reason: str, add_count: int = 0, reduce_count: int = 0, peak_size: float = 0.0) -> None ``` The mutable state behind a ``Position``. The run writes it; a strategy never sees it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/PositionSide # forge.types.PositionSide ```python forge.types.PositionSide(*args, **kwargs) ``` Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/Quote # forge.types.Quote ```python forge.types.Quote(bid: float, ask: float, spread: float) -> None ``` The bid, ask and spread at the decision time, in price units. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/TicketOwner # forge.types.TicketOwner ```python forge.types.TicketOwner(*args, **kwargs) ``` What an order ticket calls to change or cancel itself. The run implements it. Since Forge API 1. --- Source: https://oathra.dev/reference/forge/types/TicketRecord # forge.types.TicketRecord ```python forge.types.TicketRecord(id: int, symbol: str, side: OrderSide, type: OrderType, size: float, price: float | None, stop_price: float | None, sl: float | None, tp: float | None, tag: str | None, tif: str, status: OrderStatus, filled: float, avg_price: float | None, reject_reason: RejectReason | None, created_time: int) -> None ``` The mutable state behind an ``OrderTicket``. The run writes it; a strategy never sees it. Since Forge API 1. --- Source: https://oathra.dev/reference/schemas/bar-page # BarPage v1 ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "BarPage v1", "type": "object", "additionalProperties": false, "required": [ "v", "series_key", "meta", "i0", "n", "complete", "cols", "prev", "next" ], "properties": { "v": { "const": 1 }, "series_key": { "$ref": "#/$defs/sha256" }, "meta": { "$ref": "#/$defs/meta" }, "i0": { "type": "integer", "minimum": 0 }, "n": { "type": "integer", "minimum": 0, "maximum": 5000 }, "complete": { "type": "boolean" }, "prev": { "type": [ "string", "null" ] }, "next": { "type": [ "string", "null" ] }, "cols": { "type": "object", "additionalProperties": false, "required": [ "t_open", "t_close", "o", "h", "l", "c", "ro", "rh", "rl", "rc", "vol", "spread", "flags", "dir" ], "properties": { "t_open": { "$ref": "#/$defs/ms_array" }, "t_close": { "$ref": "#/$defs/ms_array" }, "o": { "$ref": "#/$defs/num_array" }, "h": { "$ref": "#/$defs/num_array" }, "l": { "$ref": "#/$defs/num_array" }, "c": { "$ref": "#/$defs/num_array" }, "ro": { "$ref": "#/$defs/num_array_or_null" }, "rh": { "$ref": "#/$defs/num_array_or_null" }, "rl": { "$ref": "#/$defs/num_array_or_null" }, "rc": { "$ref": "#/$defs/num_array_or_null" }, "vol": { "$ref": "#/$defs/num_array" }, "spread": { "$ref": "#/$defs/num_array" }, "flags": { "type": "array", "items": { "type": "integer", "minimum": 0, "maximum": 255 } }, "dir": { "type": "array", "items": { "enum": [ -1, 0, 1 ] } }, "box": { "$ref": "#/$defs/num_array" }, "thick": { "type": "array", "items": { "type": "boolean" } }, "evt": { "type": "array", "items": { "enum": [ 0, 1, 2, 3 ] } }, "col": { "type": "array", "items": { "type": "integer", "minimum": -1 } }, "boxes": { "type": "array", "items": { "type": "integer", "minimum": 0 } } } } }, "$defs": { "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "ms_array": { "type": "array", "items": { "type": "integer", "minimum": 0 } }, "num_array": { "type": "array", "items": { "type": [ "number", "null" ] } }, "num_array_or_null": { "oneOf": [ { "type": "null" }, { "$ref": "#/$defs/num_array" } ] }, "meta": { "type": "object", "additionalProperties": false, "required": [ "spec", "series_key", "forge_bars_version", "source", "built_from", "path_rule", "volume_kind", "resolved", "count", "first_time_open", "last_time_close", "warmup_bars", "warmup_real_span_ms", "bars_on_assumed_path", "multi_emit_events", "gap_bars", "filled_forward_bars" ], "properties": { "spec": { "$ref": "bar-spec.schema.json" }, "series_key": { "$ref": "#/$defs/sha256" }, "forge_bars_version": { "type": "string" }, "source": { "type": "object", "additionalProperties": false, "required": [ "kind", "id", "label" ], "properties": { "kind": { "enum": [ "upload", "broker" ] }, "id": { "type": "string", "minLength": 1 }, "label": { "type": "string", "minLength": 1 } } }, "built_from": { "enum": [ "ticks", "m1_path" ] }, "path_rule": { "enum": [ "nearest_extreme_first", null ] }, "volume_kind": { "enum": [ "tick", "traded" ] }, "volume_absent": { "const": true }, "resolved": { "type": "object" }, "count": { "type": "integer", "minimum": 0 }, "first_time_open": { "type": [ "integer", "null" ] }, "last_time_close": { "type": [ "integer", "null" ] }, "warmup_bars": { "type": "integer", "minimum": 0 }, "warmup_real_span_ms": { "type": "integer", "minimum": 0 }, "bars_on_assumed_path": { "type": "integer", "minimum": 0 }, "multi_emit_events": { "type": "integer", "minimum": 0 }, "gap_bars": { "type": "integer", "minimum": 0 }, "filled_forward_bars": { "type": "integer", "minimum": 0 } } } } } ``` --- Source: https://oathra.dev/reference/schemas/bar-spec # BarSpec v1 ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "BarSpec v1", "type": "object", "additionalProperties": false, "required": [ "symbol", "bar_type", "params" ], "properties": { "symbol": { "type": "string", "pattern": "^[A-Z0-9._#-]{1,40}$" }, "bar_type": { "enum": [ "time", "heikin_ashi", "renko", "range", "kagi", "point_figure", "line_break", "tick", "count" ] }, "params": { "type": "object" }, "basis": { "enum": [ "bid", "ask", "mid", "last", "quote" ] }, "day_boundary": { "type": "string", "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9] [A-Za-z_]+(/[A-Za-z_+-]+)*$" }, "on_closure": { "enum": [ "continue_grid", "reset" ] }, "anchor": { "oneOf": [ { "type": "null" }, { "type": "object", "additionalProperties": false, "required": [ "kind" ], "properties": { "kind": { "enum": [ "price_multiple", "history_start" ] } } }, { "type": "object", "additionalProperties": false, "required": [ "kind", "price" ], "properties": { "kind": { "const": "fixed" }, "price": { "type": "number", "exclusiveMinimum": 0 } } } ] } }, "$defs": { "timeframe": { "type": "string", "pattern": "^(M[1-9][0-9]*|H[1-9][0-9]*|D[1-9][0-9]*|W1|MN1)$" }, "price_amount": { "type": "object", "minProperties": 1, "maxProperties": 1, "additionalProperties": false, "properties": { "price": { "type": "number", "exclusiveMinimum": 0 }, "points": { "type": "number", "exclusiveMinimum": 0 }, "pips": { "type": "number", "exclusiveMinimum": 0 }, "ticks": { "type": "number", "exclusiveMinimum": 0 } } }, "box_size": { "oneOf": [ { "$ref": "#/$defs/price_amount" }, { "type": "object", "additionalProperties": false, "required": [ "method", "period" ], "properties": { "method": { "const": "atr" }, "period": { "type": "integer", "minimum": 1 }, "mult": { "type": "number", "exclusiveMinimum": 0 }, "timeframe": { "$ref": "#/$defs/timeframe" } } }, { "type": "object", "additionalProperties": false, "required": [ "method", "value" ], "properties": { "method": { "const": "percent" }, "value": { "type": "number", "exclusiveMinimum": 0 } } }, { "type": "object", "additionalProperties": false, "required": [ "method" ], "properties": { "method": { "const": "traditional" } } } ] } }, "allOf": [ { "if": { "properties": { "bar_type": { "const": "time" } } }, "then": { "properties": { "params": { "type": "object", "additionalProperties": false, "required": [ "timeframe" ], "properties": { "timeframe": { "$ref": "#/$defs/timeframe" } } } } } }, { "if": { "properties": { "bar_type": { "const": "heikin_ashi" } } }, "then": { "properties": { "params": { "type": "object", "additionalProperties": false, "required": [ "timeframe" ], "properties": { "timeframe": { "$ref": "#/$defs/timeframe" }, "seed": { "enum": [ "mt5_raw", "textbook" ] }, "settle_bars": { "type": "integer", "minimum": 0 } } } } } }, { "if": { "properties": { "bar_type": { "const": "renko" } } }, "then": { "properties": { "params": { "type": "object", "additionalProperties": false, "required": [ "box" ], "properties": { "box": { "$ref": "#/$defs/box_size" }, "trend": { "type": "number", "exclusiveMinimum": 0 }, "reversal": { "type": "number", "exclusiveMinimum": 0 }, "offset": { "type": "number", "minimum": 0, "maximum": 1 }, "wicks": { "type": "boolean" }, "new_bar_mode": { "type": "string" }, "preset": { "enum": [ "traditional", "median", "unirenko", "reversal_size", null ] } } } } } }, { "if": { "properties": { "bar_type": { "const": "range" } } }, "then": { "properties": { "params": { "type": "object", "additionalProperties": false, "required": [ "size" ], "properties": { "size": { "$ref": "#/$defs/price_amount" } } } } } }, { "if": { "properties": { "bar_type": { "const": "kagi" } } }, "then": { "properties": { "params": { "type": "object", "additionalProperties": false, "required": [ "reversal" ], "properties": { "reversal": { "$ref": "#/$defs/box_size" } } } } } }, { "if": { "properties": { "bar_type": { "const": "point_figure" } } }, "then": { "properties": { "params": { "type": "object", "additionalProperties": false, "required": [ "box" ], "properties": { "box": { "$ref": "#/$defs/box_size" }, "reversal": { "type": "integer", "minimum": 1 }, "one_box": { "type": "boolean" }, "event": { "enum": [ "column", "box" ] } } } } } }, { "if": { "properties": { "bar_type": { "const": "line_break" } } }, "then": { "properties": { "params": { "type": "object", "additionalProperties": false, "properties": { "lines": { "type": "integer", "minimum": 1 } } } } } }, { "if": { "properties": { "bar_type": { "const": "tick" } } }, "then": { "properties": { "params": { "type": "object", "additionalProperties": false, "required": [ "n" ], "properties": { "n": { "type": "integer", "minimum": 1 } } } } } }, { "if": { "properties": { "bar_type": { "const": "count" } } }, "then": { "properties": { "params": { "type": "object", "additionalProperties": false, "required": [ "n", "of" ], "properties": { "n": { "type": "integer", "minimum": 1 }, "of": { "$ref": "#/$defs/timeframe" } } } } } } ] } ``` --- Source: https://oathra.dev/reference/schemas/broker-facts # oathra.broker_facts/1 ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "oathra.broker_facts/1", "type": "object", "additionalProperties": false, "required": [ "schema", "broker_profile_id", "broker_company", "server_name", "server_tz_offset_minutes", "captured_at", "account", "symbols", "content_hash" ], "properties": { "schema": { "const": "oathra.broker_facts/1" }, "broker_profile_id": { "type": "string", "format": "uuid" }, "broker_company": { "type": "string", "minLength": 1 }, "server_name": { "type": "string", "minLength": 1 }, "server_tz_offset_minutes": { "type": [ "integer", "null" ] }, "captured_at": { "type": "string", "format": "date-time" }, "account": { "type": "object", "additionalProperties": false, "required": [ "trading_account_id", "currency", "leverage", "margin_mode" ], "properties": { "trading_account_id": { "type": [ "string", "null" ] }, "currency": { "type": "string", "pattern": "^[A-Z]{3,5}$" }, "leverage": { "type": [ "number", "null" ], "exclusiveMinimum": 0 }, "margin_mode": { "enum": [ "hedging", "netting", "exchange", null ] } } }, "symbols": { "type": "object", "minProperties": 1, "propertyNames": { "pattern": "^[A-Z0-9._#-]{1,40}$" }, "additionalProperties": { "$ref": "#/$defs/symbol" } }, "content_hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" } }, "$defs": { "num": { "type": [ "number", "null" ] }, "int": { "type": [ "integer", "null" ] }, "session": { "type": "object", "additionalProperties": false, "required": [ "day", "from_s", "to_s" ], "properties": { "day": { "type": "integer", "minimum": 0, "maximum": 6 }, "from_s": { "type": "integer", "minimum": 0, "maximum": 86400 }, "to_s": { "type": "integer", "minimum": 0, "maximum": 86400 } } }, "symbol": { "type": "object", "additionalProperties": false, "required": [ "broker_symbol", "description", "path", "digits", "point", "tick_size", "tick_value", "tick_value_profit", "tick_value_loss", "contract_size", "volume_min", "volume_step", "volume_max", "volume_limit", "stops_level", "freeze_level", "trade_mode", "trade_calc_mode", "filling_mode", "expiration_mode", "currency_base", "currency_profit", "currency_margin", "swap_mode", "swap_long", "swap_short", "swap_rollover_day", "swap_days", "margin_initial", "margin_maintenance", "margin_hedged", "spread_points", "spread_float", "sessions", "updated_at" ], "properties": { "broker_symbol": { "type": "string", "minLength": 1 }, "description": { "type": [ "string", "null" ] }, "path": { "type": [ "string", "null" ] }, "digits": { "type": "integer", "minimum": 0, "maximum": 12 }, "point": { "$ref": "#/$defs/num" }, "tick_size": { "$ref": "#/$defs/num" }, "tick_value": { "$ref": "#/$defs/num" }, "tick_value_profit": { "$ref": "#/$defs/num" }, "tick_value_loss": { "$ref": "#/$defs/num" }, "contract_size": { "$ref": "#/$defs/num" }, "volume_min": { "$ref": "#/$defs/num" }, "volume_step": { "$ref": "#/$defs/num" }, "volume_max": { "$ref": "#/$defs/num" }, "volume_limit": { "$ref": "#/$defs/num" }, "stops_level": { "$ref": "#/$defs/int" }, "freeze_level": { "$ref": "#/$defs/int" }, "trade_mode": { "enum": [ "DISABLED", "LONGONLY", "SHORTONLY", "CLOSEONLY", "FULL", null ] }, "trade_calc_mode": { "type": [ "string", "null" ], "pattern": "^[A-Z_]+$" }, "filling_mode": { "$ref": "#/$defs/int" }, "expiration_mode": { "$ref": "#/$defs/int" }, "currency_base": { "type": [ "string", "null" ] }, "currency_profit": { "type": [ "string", "null" ] }, "currency_margin": { "type": [ "string", "null" ] }, "swap_mode": { "enum": [ "DISABLED", "POINTS", "CURRENCY_SYMBOL", "CURRENCY_MARGIN", "CURRENCY_DEPOSIT", "INTEREST_CURRENT", "INTEREST_OPEN", "REOPEN_CURRENT", "REOPEN_BID", null ] }, "swap_long": { "$ref": "#/$defs/num" }, "swap_short": { "$ref": "#/$defs/num" }, "swap_rollover_day": { "type": [ "integer", "null" ], "minimum": 0, "maximum": 6 }, "swap_days": { "oneOf": [ { "type": "null" }, { "type": "array", "minItems": 7, "maxItems": 7, "items": { "type": "number" } } ] }, "margin_initial": { "$ref": "#/$defs/num" }, "margin_maintenance": { "$ref": "#/$defs/num" }, "margin_hedged": { "$ref": "#/$defs/num" }, "spread_points": { "$ref": "#/$defs/int" }, "spread_float": { "type": [ "boolean", "null" ] }, "sessions": { "type": "object", "additionalProperties": false, "required": [ "quote", "trade" ], "properties": { "quote": { "type": "array", "items": { "$ref": "#/$defs/session" } }, "trade": { "type": "array", "items": { "$ref": "#/$defs/session" } } } }, "updated_at": { "type": [ "string", "null" ], "format": "date-time" } } } } } ``` --- Source: https://oathra.dev/reference/schemas/chart-stream # forge-chart WebSocket messages v1 ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "forge-chart WebSocket messages v1", "oneOf": [ { "$ref": "#/$defs/auth" }, { "$ref": "#/$defs/ready" }, { "$ref": "#/$defs/subscribe" }, { "$ref": "#/$defs/snapshot" }, { "$ref": "#/$defs/bar" }, { "$ref": "#/$defs/series" }, { "$ref": "#/$defs/status" }, { "$ref": "#/$defs/unsubscribe" }, { "$ref": "#/$defs/ping" }, { "$ref": "#/$defs/pong" }, { "$ref": "#/$defs/error" } ], "$defs": { "id": { "type": "string", "minLength": 1, "maxLength": 64 }, "source": { "type": "object", "additionalProperties": false, "required": [ "kind", "id" ], "properties": { "kind": { "enum": [ "upload", "broker" ] }, "id": { "type": "string", "minLength": 1 } } }, "auth": { "type": "object", "additionalProperties": false, "required": [ "type", "token" ], "properties": { "type": { "const": "auth" }, "token": { "type": "string", "minLength": 1 } } }, "ready": { "type": "object", "additionalProperties": false, "required": [ "type", "user_id", "limits" ], "properties": { "type": { "const": "ready" }, "user_id": { "type": "string" }, "limits": { "type": "object", "required": [ "max_subscriptions" ], "properties": { "max_subscriptions": { "type": "integer", "minimum": 1 } } } } }, "subscribe": { "type": "object", "additionalProperties": false, "required": [ "type", "id", "spec", "source", "series" ], "properties": { "type": { "const": "subscribe" }, "id": { "$ref": "#/$defs/id" }, "spec": { "$ref": "bar-spec.schema.json" }, "source": { "$ref": "#/$defs/source" }, "series": { "type": "array", "maxItems": 20, "items": { "type": "object" } } } }, "snapshot": { "type": "object", "additionalProperties": false, "required": [ "type", "id", "page", "series" ], "properties": { "type": { "const": "snapshot" }, "id": { "$ref": "#/$defs/id" }, "page": { "$ref": "bar-page.schema.json" }, "series": { "type": "array", "items": { "$ref": "series-page.schema.json" } } } }, "bar": { "type": "object", "additionalProperties": false, "required": [ "type", "id", "i", "row", "state" ], "properties": { "type": { "const": "bar" }, "id": { "$ref": "#/$defs/id" }, "i": { "type": "integer", "minimum": 0 }, "row": { "type": "object", "required": [ "t_open", "t_close", "o", "h", "l", "c", "flags", "dir" ] }, "state": { "enum": [ "forming", "closed" ] } } }, "series": { "type": "object", "additionalProperties": false, "required": [ "type", "id", "name", "i", "values", "state" ], "properties": { "type": { "const": "series" }, "id": { "$ref": "#/$defs/id" }, "name": { "type": "string" }, "i": { "type": "integer", "minimum": 0 }, "values": { "type": "object" }, "state": { "enum": [ "forming", "closed" ] } } }, "status": { "type": "object", "additionalProperties": false, "required": [ "type", "id", "source", "stale" ], "properties": { "type": { "const": "status" }, "id": { "$ref": "#/$defs/id" }, "source": { "$ref": "#/$defs/source" }, "stale": { "type": "boolean" }, "reason": { "type": [ "string", "null" ] } } }, "unsubscribe": { "type": "object", "additionalProperties": false, "required": [ "type", "id" ], "properties": { "type": { "const": "unsubscribe" }, "id": { "$ref": "#/$defs/id" } } }, "ping": { "type": "object", "additionalProperties": false, "required": [ "type" ], "properties": { "type": { "const": "ping" } } }, "pong": { "type": "object", "additionalProperties": false, "required": [ "type" ], "properties": { "type": { "const": "pong" } } }, "error": { "type": "object", "additionalProperties": false, "required": [ "type", "id", "code", "message" ], "properties": { "type": { "const": "error" }, "id": { "type": [ "string", "null" ] }, "code": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" }, "message": { "type": "string" } } } } } ``` --- Source: https://oathra.dev/reference/schemas/sandbox-compute # forge-sandbox POST /v1/compute request and reply v1 ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "forge-sandbox POST /v1/compute request and reply v1", "oneOf": [ { "$ref": "#/$defs/request" }, { "$ref": "#/$defs/reply_ok" }, { "$ref": "#/$defs/reply_error" } ], "$defs": { "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "request": { "type": "object", "additionalProperties": false, "required": [ "request_id", "script", "inputs", "bars", "start", "limits" ], "properties": { "request_id": { "type": "string", "format": "uuid" }, "script": { "type": "object", "additionalProperties": false, "required": [ "id", "version", "sha256", "forge_api", "source" ], "properties": { "id": { "type": "string" }, "version": { "type": "integer", "minimum": 1 }, "sha256": { "$ref": "#/$defs/sha256" }, "forge_api": { "type": "integer", "minimum": 1 }, "source": { "type": "string", "minLength": 1, "maxLength": 1048576 } } }, "inputs": { "type": "object" }, "bars": { "type": "object", "additionalProperties": false, "required": [ "primary" ], "properties": { "primary": { "$ref": "bar-page.schema.json" }, "extra": { "type": "object", "additionalProperties": { "$ref": "bar-page.schema.json" } } } }, "start": { "type": "integer", "minimum": 0 }, "limits": { "type": "object", "additionalProperties": false, "required": [ "cpu_ms", "wall_ms", "mem_mb", "max_outputs", "max_drawings", "max_log_lines" ], "properties": { "cpu_ms": { "type": "integer", "minimum": 1 }, "wall_ms": { "type": "integer", "minimum": 1 }, "mem_mb": { "type": "integer", "minimum": 16 }, "max_outputs": { "type": "integer", "minimum": 1, "maximum": 64 }, "max_drawings": { "type": "integer", "minimum": 0 }, "max_log_lines": { "type": "integer", "minimum": 0 } } } } }, "reply_ok": { "type": "object", "additionalProperties": false, "required": [ "request_id", "ok", "outputs", "drawings", "logs", "warmup", "usage" ], "properties": { "request_id": { "type": "string" }, "ok": { "const": true }, "outputs": { "type": "object", "maxProperties": 64 }, "drawings": { "type": "array" }, "logs": { "type": "array", "items": { "type": "object", "required": [ "level", "message" ], "properties": { "level": { "enum": [ "info", "warning", "error" ] }, "message": { "type": "string" }, "bar": { "type": [ "integer", "null" ] } } } }, "warmup": { "type": "integer", "minimum": 0 }, "usage": { "type": "object", "additionalProperties": false, "required": [ "cpu_ms", "wall_ms", "peak_mem_mb" ], "properties": { "cpu_ms": { "type": "number" }, "wall_ms": { "type": "number" }, "peak_mem_mb": { "type": "number" } } } } }, "reply_error": { "type": "object", "additionalProperties": false, "required": [ "request_id", "ok", "error" ], "properties": { "request_id": { "type": "string" }, "ok": { "const": false }, "error": { "type": "object", "additionalProperties": false, "required": [ "code", "message", "detail" ], "properties": { "code": { "enum": [ "cap_cpu", "cap_wall", "cap_memory", "cap_outputs", "cap_drawings", "user_exception", "forbidden_call", "forge_api_unsupported", "bad_request", "internal" ] }, "message": { "type": "string" }, "detail": { "type": "object", "properties": { "cap": { "type": "string" }, "used": { "type": "number" }, "allowed": { "type": "number" }, "file": { "type": "string" }, "line": { "type": "integer" } } } } } } } } } ``` --- Source: https://oathra.dev/reference/schemas/series-page # SeriesPage v1 ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "SeriesPage v1", "type": "object", "additionalProperties": false, "required": [ "v", "series_key", "indicator", "indicator_key", "i0", "n", "warmup", "outputs", "levels", "pane", "drawings", "repaints", "versions", "complete" ], "properties": { "v": { "const": 1 }, "series_key": { "$ref": "#/$defs/sha256" }, "indicator_key": { "$ref": "#/$defs/sha256" }, "indicator": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "kind", "name", "inputs" ], "properties": { "kind": { "const": "builtin" }, "name": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" }, "inputs": { "type": "object" } } }, { "type": "object", "additionalProperties": false, "required": [ "kind", "script", "inputs" ], "properties": { "kind": { "const": "user" }, "script": { "type": "object", "additionalProperties": false, "required": [ "id", "version", "sha256" ], "properties": { "id": { "type": "string" }, "version": { "type": "integer", "minimum": 1 }, "sha256": { "$ref": "#/$defs/sha256" } } }, "inputs": { "type": "object" } } } ] }, "i0": { "type": "integer", "minimum": 0 }, "n": { "type": "integer", "minimum": 0, "maximum": 5000 }, "warmup": { "type": "integer", "minimum": 0 }, "outputs": { "type": "object", "maxProperties": 64, "additionalProperties": { "$ref": "#/$defs/output" } }, "levels": { "type": "array", "items": { "type": "object", "additionalProperties": false, "required": [ "value" ], "properties": { "value": { "type": "number" }, "label": { "type": "string" }, "color": { "type": "string" } } } }, "pane": { "type": "string", "minLength": 1 }, "drawings": { "type": "array", "maxItems": 3000, "items": { "$ref": "#/$defs/drawing" } }, "repaints": { "type": "boolean" }, "versions": { "type": "object", "additionalProperties": false, "required": [ "forge_indicators", "script", "engine" ], "properties": { "forge_indicators": { "type": [ "string", "null" ] }, "script": { "type": [ "integer", "null" ] }, "engine": { "type": [ "string", "null" ] } } }, "complete": { "type": "boolean" } }, "$defs": { "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "style": { "type": "object", "additionalProperties": false, "required": [ "style" ], "properties": { "style": { "enum": [ "line", "step", "line_gaps", "histogram", "columns", "area", "area_gaps", "dots", "cross", "square", "dash", "marker", "zigzag", "background", "bar_color" ] }, "width": { "type": "number", "minimum": 0 }, "dash": { "enum": [ "solid", "dot", "dash" ] }, "color": { "type": "string" }, "color_up": { "type": "string" }, "color_down": { "type": "string" }, "shift": { "type": "integer" }, "show": { "type": "array", "items": { "enum": [ "pane", "scale", "legend", "data_window" ] } }, "shape": { "type": "string" }, "where": { "enum": [ "above_bar", "below_bar", "at_price" ] }, "base": { "type": "number" } } }, "output": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "kind", "style", "values" ], "properties": { "kind": { "const": "value" }, "label": { "type": "string" }, "hidden": { "type": "boolean" }, "style": { "$ref": "#/$defs/style" }, "values": { "type": "array", "items": { "type": [ "number", "null" ] } } } }, { "type": "object", "additionalProperties": false, "required": [ "kind", "style", "values" ], "properties": { "kind": { "const": "state" }, "label": { "type": "string" }, "style": { "$ref": "#/$defs/style" }, "values": { "type": "array", "items": { "type": [ "boolean", "null" ] } } } }, { "type": "object", "additionalProperties": false, "required": [ "kind", "style", "values" ], "properties": { "kind": { "const": "event" }, "label": { "type": "string" }, "style": { "$ref": "#/$defs/style" }, "values": { "type": "array", "items": { "enum": [ -1, 0, 1, null ] } } } }, { "type": "object", "additionalProperties": false, "required": [ "kind", "style", "top", "bottom", "active" ], "properties": { "kind": { "const": "zone" }, "label": { "type": "string" }, "style": { "$ref": "#/$defs/style" }, "top": { "type": "array", "items": { "type": [ "number", "null" ] } }, "bottom": { "type": "array", "items": { "type": [ "number", "null" ] } }, "active": { "type": "array", "items": { "type": [ "boolean", "null" ] } } } } ] }, "point": { "type": "object", "additionalProperties": false, "minProperties": 2, "properties": { "bar": { "type": "integer" }, "time": { "type": "integer" }, "price": { "type": "number" } } }, "drawing": { "type": "object", "required": [ "type" ], "properties": { "type": { "enum": [ "label", "line", "hline", "vline", "level", "box" ] }, "id": { "type": "string" }, "p1": { "$ref": "#/$defs/point" }, "p2": { "$ref": "#/$defs/point" }, "price": { "type": "number" }, "bar": { "type": "integer" }, "text": { "type": "string", "maxLength": 500 }, "extend": { "enum": [ "none", "left", "right", "both" ] }, "until": { "enum": [ "touched", null ] }, "style": { "type": "object" } } } } } ``` --- Source: https://oathra.dev/reference/schemas/strategy-preview # Strategy preview v1 The request the Strategy Studio posts to the monitor and the reply it reads. `request` and `reply` are the two documents; every other definition is a part of one of them. ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Strategy preview v1", "description": "The request the Strategy Studio posts to the monitor and the reply it reads. `request` and `reply` are the two documents; every other definition is a part of one of them.", "$defs": { "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, "ms_array": { "type": "array", "items": { "type": "integer", "minimum": 0 }, "maxItems": 10000 }, "num_array": { "type": "array", "items": { "type": "number" }, "maxItems": 10000 }, "num_or_null_array": { "type": "array", "items": { "type": [ "number", "null" ] }, "maxItems": 5000 }, "bit_array": { "type": "array", "items": { "enum": [ 0, 1 ] }, "maxItems": 5000 }, "bit_or_null_array": { "type": "array", "items": { "enum": [ 0, 1, null ] }, "maxItems": 5000 }, "dropped_array": { "type": "array", "items": { "enum": [ null, "warmup", "same_run" ] }, "maxItems": 5000 }, "field": { "enum": [ "long_entry", "short_entry", "long_exit", "short_exit" ] }, "strategy": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "kind", "spec" ], "properties": { "kind": { "const": "spec" }, "spec": { "$ref": "strategy-spec.schema.json" } } }, { "type": "object", "additionalProperties": false, "required": [ "kind", "strategy_id", "revision" ], "properties": { "kind": { "const": "version" }, "strategy_id": { "type": "string", "format": "uuid" }, "revision": { "type": "integer", "minimum": 1 } } } ] }, "series": { "type": "object", "additionalProperties": false, "required": [ "role", "meta", "cols" ], "properties": { "role": { "enum": [ "chart", "other" ] }, "meta": { "type": "object", "additionalProperties": false, "required": [ "spec", "series_key", "volume_kind" ], "properties": { "spec": { "$ref": "bar-spec.schema.json" }, "series_key": { "$ref": "#/$defs/sha256" }, "volume_kind": { "enum": [ "tick", "traded" ] }, "volume_absent": { "const": true } } }, "cols": { "type": "object", "additionalProperties": false, "required": [ "t_open", "t_close", "o", "h", "l", "c", "vol", "flags" ], "properties": { "t_open": { "$ref": "#/$defs/ms_array" }, "t_close": { "$ref": "#/$defs/ms_array" }, "o": { "$ref": "#/$defs/num_array" }, "h": { "$ref": "#/$defs/num_array" }, "l": { "$ref": "#/$defs/num_array" }, "c": { "$ref": "#/$defs/num_array" }, "vol": { "$ref": "#/$defs/num_array" }, "flags": { "type": "array", "items": { "type": "integer", "minimum": 0, "maximum": 255 }, "maxItems": 10000 } } } } }, "request": { "type": "object", "additionalProperties": false, "required": [ "v", "strategy", "series", "visible_from" ], "properties": { "v": { "const": 1 }, "strategy": { "$ref": "#/$defs/strategy" }, "series": { "type": "array", "minItems": 1, "maxItems": 4, "items": { "$ref": "#/$defs/series" } }, "visible_from": { "type": "integer", "minimum": 0 } } }, "plot": { "type": "object", "additionalProperties": false, "required": [ "label", "pane", "values" ], "properties": { "label": { "type": "string" }, "pane": { "enum": [ "price", "own" ] }, "values": { "$ref": "#/$defs/num_or_null_array" } } }, "condition": { "type": "object", "additionalProperties": false, "required": [ "field", "path", "name", "values" ], "properties": { "field": { "$ref": "#/$defs/field" }, "path": { "type": "string" }, "name": { "type": "string" }, "values": { "$ref": "#/$defs/bit_or_null_array" } } }, "page": { "type": "object", "additionalProperties": false, "required": [ "v", "preview_key", "strategy_sha256", "series_key", "t_open", "warmup", "events", "levels", "plots", "conditions", "dropped", "engine" ], "properties": { "v": { "const": 1 }, "preview_key": { "$ref": "#/$defs/sha256" }, "strategy_sha256": { "$ref": "#/$defs/sha256" }, "series_key": { "$ref": "#/$defs/sha256" }, "t_open": { "type": "array", "items": { "type": "integer", "minimum": 0 }, "maxItems": 5000 }, "warmup": { "type": "integer", "minimum": 0 }, "events": { "type": "object", "additionalProperties": false, "required": [ "long_entry", "short_entry", "long_exit", "short_exit" ], "properties": { "long_entry": { "$ref": "#/$defs/bit_array" }, "short_entry": { "$ref": "#/$defs/bit_array" }, "long_exit": { "$ref": "#/$defs/bit_array" }, "short_exit": { "$ref": "#/$defs/bit_array" } } }, "levels": { "type": "object", "additionalProperties": false, "required": [ "stop", "target" ], "properties": { "stop": { "oneOf": [ { "type": "null" }, { "$ref": "#/$defs/num_or_null_array" } ] }, "target": { "oneOf": [ { "type": "null" }, { "$ref": "#/$defs/num_or_null_array" } ] } } }, "plots": { "type": "object", "maxProperties": 64, "additionalProperties": { "$ref": "#/$defs/plot" } }, "conditions": { "type": "array", "maxItems": 256, "items": { "$ref": "#/$defs/condition" } }, "dropped": { "type": "object", "additionalProperties": false, "required": [ "long_entry", "short_entry" ], "properties": { "long_entry": { "$ref": "#/$defs/dropped_array" }, "short_entry": { "$ref": "#/$defs/dropped_array" } } }, "engine": { "type": "string", "minLength": 1 } } }, "finding": { "type": "object", "additionalProperties": false, "required": [ "code", "message" ], "properties": { "code": { "type": "string", "minLength": 1 }, "message": { "type": "string", "minLength": 1 }, "line": { "type": [ "integer", "null" ], "minimum": 1 } } }, "reply": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "v", "status", "id", "page" ], "properties": { "v": { "const": 1 }, "status": { "const": "ready" }, "id": { "type": "string", "minLength": 1, "maxLength": 64 }, "page": { "$ref": "#/$defs/page" } } }, { "type": "object", "additionalProperties": false, "required": [ "v", "status", "id", "retry_after_ms" ], "properties": { "v": { "const": 1 }, "status": { "const": "pending" }, "id": { "type": "string", "minLength": 1, "maxLength": 64 }, "retry_after_ms": { "type": "integer", "minimum": 100, "maximum": 10000 } } }, { "type": "object", "additionalProperties": false, "required": [ "v", "status", "id", "code", "message" ], "properties": { "v": { "const": 1 }, "status": { "const": "failed" }, "id": { "type": "string", "minLength": 1, "maxLength": 64 }, "code": { "enum": [ "strategy_preview_refused", "strategy_preview_needs_series", "strategy_preview_too_slow", "strategy_preview_unavailable" ] }, "message": { "type": "string", "minLength": 1 }, "findings": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/finding" } }, "needs": { "type": "array", "minItems": 1, "maxItems": 3, "items": { "$ref": "bar-spec.schema.json" } } } } ] } } } ``` --- Source: https://oathra.dev/reference/schemas/strategy-spec # Strategy Spec v1 ```json { "$schema": "https://json-schema.org/draft/2020-12/schema", "title": "Strategy Spec v1", "type": "object", "additionalProperties": false, "required": [ "forge_api", "kind", "name", "bars", "symbols", "direction", "params", "constraints", "entries", "exits", "stop", "target", "trail", "size", "filters", "prop", "meta" ], "properties": { "forge_api": { "const": 1 }, "kind": { "const": "strategy_spec" }, "name": { "type": "string", "minLength": 1, "maxLength": 80 }, "bars": { "$ref": "#/$defs/bars" }, "symbols": { "type": "array", "minItems": 1, "maxItems": 50, "items": { "type": "string", "pattern": "^[A-Z0-9._#-]{1,40}$" } }, "direction": { "enum": [ "long", "short", "both" ] }, "params": { "type": "array", "maxItems": 100, "items": { "$ref": "#/$defs/param" } }, "constraints": { "type": "array", "items": { "$ref": "#/$defs/cond" } }, "entries": { "type": "object", "additionalProperties": false, "required": [ "long", "short" ], "properties": { "long": { "$ref": "#/$defs/cond_or_null" }, "short": { "$ref": "#/$defs/cond_or_null" } } }, "exits": { "type": "object", "additionalProperties": false, "required": [ "long", "short", "exit_on_invalid" ], "properties": { "long": { "$ref": "#/$defs/cond_or_null" }, "short": { "$ref": "#/$defs/cond_or_null" }, "exit_on_invalid": { "type": "boolean" } } }, "stop": { "$ref": "#/$defs/kinded_or_null" }, "target": { "$ref": "#/$defs/kinded_or_null" }, "trail": { "$ref": "#/$defs/kinded_or_null" }, "size": { "$ref": "#/$defs/kinded" }, "filters": { "type": "array", "items": { "$ref": "#/$defs/kinded" } }, "prop": { "$ref": "#/$defs/kinded_or_null" }, "meta": { "type": "object", "additionalProperties": false, "properties": { "description": { "type": "string", "maxLength": 4000 }, "tags": { "type": "array", "maxItems": 20, "items": { "type": "string", "maxLength": 40 } } } } }, "$defs": { "bars": { "type": "object", "additionalProperties": false, "required": [ "bar_type", "params" ], "properties": { "bar_type": { "enum": [ "time", "heikin_ashi", "renko", "range", "kagi", "point_figure", "line_break", "tick", "count" ] }, "params": { "type": "object" }, "basis": { "enum": [ "bid", "ask", "mid", "last", "quote" ] }, "day_boundary": { "type": "string" }, "on_closure": { "enum": [ "continue_grid", "reset" ] }, "anchor": { "type": [ "object", "null" ] } } }, "param": { "type": "object", "additionalProperties": false, "required": [ "name", "type", "default" ], "properties": { "name": { "type": "string", "pattern": "^[a-z][a-z0-9_]{0,39}$" }, "type": { "enum": [ "int", "float", "decimal", "bool", "choice", "enum", "timeframe", "symbol", "session", "time", "price", "source", "ma_method", "volume", "color", "text" ] }, "default": {}, "min": { "type": "number" }, "max": { "type": "number" }, "step": { "type": "number", "exclusiveMinimum": 0 }, "places": { "type": "integer", "minimum": 0, "maximum": 10 }, "options": { "type": "array" }, "values": { "type": "array" }, "scale": { "enum": [ "linear", "log" ] }, "optimize": { "type": "boolean" }, "space": { "enum": [ "entry", "exit", "stop", "size", "filter" ] }, "group": { "type": "string", "maxLength": 40 }, "label": { "type": "string", "maxLength": 60 }, "help": { "type": "string", "maxLength": 200 }, "order": { "type": "integer" }, "inline": { "type": "string", "maxLength": 40 }, "tz": { "type": "string", "maxLength": 64 } } }, "kinded": { "type": "object", "required": [ "kind" ], "properties": { "kind": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" } } }, "kinded_or_null": { "oneOf": [ { "type": "null" }, { "$ref": "#/$defs/kinded" } ] }, "cond_or_null": { "oneOf": [ { "type": "null" }, { "$ref": "#/$defs/cond" } ] }, "name": { "type": "string", "maxLength": 60 }, "cond": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "all" ], "properties": { "all": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/cond" } }, "name": { "$ref": "#/$defs/name" } } }, { "type": "object", "additionalProperties": false, "required": [ "any" ], "properties": { "any": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/cond" } }, "name": { "$ref": "#/$defs/name" } } }, { "type": "object", "additionalProperties": false, "required": [ "not" ], "properties": { "not": { "$ref": "#/$defs/cond" }, "name": { "$ref": "#/$defs/name" } } }, { "type": "object", "additionalProperties": false, "required": [ "compare" ], "properties": { "name": { "$ref": "#/$defs/name" }, "compare": { "type": "object", "additionalProperties": false, "required": [ "left", "op", "right" ], "properties": { "left": { "$ref": "#/$defs/operand" }, "op": { "enum": [ "<", "<=", ">", ">=", "==", "!=" ] }, "right": { "$ref": "#/$defs/operand" } } } } }, { "type": "object", "additionalProperties": false, "required": [ "cross" ], "properties": { "name": { "$ref": "#/$defs/name" }, "cross": { "type": "object", "additionalProperties": false, "required": [ "a", "dir", "b" ], "properties": { "a": { "$ref": "#/$defs/operand" }, "dir": { "enum": [ "over", "under" ] }, "b": { "$ref": "#/$defs/operand" } } } } }, { "type": "object", "additionalProperties": false, "required": [ "state" ], "properties": { "state": { "$ref": "#/$defs/operand" }, "name": { "$ref": "#/$defs/name" } } }, { "type": "object", "additionalProperties": false, "required": [ "event" ], "properties": { "name": { "$ref": "#/$defs/name" }, "event": { "type": "object", "additionalProperties": false, "required": [ "of", "value" ], "properties": { "of": { "$ref": "#/$defs/operand" }, "value": { "enum": [ 1, -1 ] } } } } }, { "type": "object", "additionalProperties": false, "required": [ "zone" ], "properties": { "name": { "$ref": "#/$defs/name" }, "zone": { "type": "object", "additionalProperties": false, "required": [ "of", "where" ], "properties": { "of": { "$ref": "#/$defs/ind_ref" }, "out": { "type": "string" }, "where": { "enum": [ "inside", "above", "below" ] } } } } } ] }, "operand": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "price" ], "properties": { "price": { "enum": [ "open", "high", "low", "close", "hl2", "hlc3", "ohlc4", "hlcc4" ] } } }, { "type": "object", "additionalProperties": false, "required": [ "const" ], "properties": { "const": { "type": "number" } } }, { "type": "object", "additionalProperties": false, "required": [ "param" ], "properties": { "param": { "type": "string", "pattern": "^[a-z][a-z0-9_]{0,39}$" } } }, { "type": "object", "additionalProperties": false, "required": [ "ind", "out" ], "properties": { "ind": { "$ref": "#/$defs/ind_ref" }, "out": { "type": "string", "minLength": 1 }, "ago": { "type": "integer", "minimum": 0 } } }, { "type": "object", "additionalProperties": false, "required": [ "tf", "of" ], "properties": { "tf": { "type": "string", "pattern": "^(M[1-9][0-9]*|H[1-9][0-9]*|D[1-9][0-9]*|W1|MN1)$" }, "of": { "$ref": "#/$defs/operand" } } } ] }, "ind_ref": { "oneOf": [ { "type": "object", "additionalProperties": false, "required": [ "builtin", "inputs" ], "properties": { "builtin": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" }, "inputs": { "type": "object" } } }, { "type": "object", "additionalProperties": false, "required": [ "custom", "version", "inputs" ], "properties": { "custom": { "type": "string", "minLength": 1 }, "version": { "type": "integer", "minimum": 1 }, "inputs": { "type": "object" } } } ] } } } ``` --- # Built-in indicators Source: https://oathra.dev/reference/indicators/alma # Arnaud Legoux moving average `alma` Gaussian weighted average whose peak you can move toward the newest bar. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | window | int | 9 | 1 to 5000 | | offset | float | 0.85 | 0 to 1 | | sigma | float | 6 | 0.5 to 50 | ## Outputs | Name | Kind | | --- | --- | | alma | value | Warm-up: window - 1 bars (8 at the defaults). Repaints: No. ## Formula m = offset * (window - 1), s = window / sigma w[k] = exp(-(k - m)^2 / (2 * s^2)), k = 0 for the oldest bar of the window ALMA[i] = sum(w[k] * x[i-window+1+k]) / sum(w[k]) --- Source: https://oathra.dev/reference/indicators/dema # Double exponential moving average `dema` Exponential average with part of its lag removed. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | dema | value | Warm-up: 2 * period - 2 bars (26 at the defaults). Repaints: No. ## Formula DEMA = 2 * EMA(x) - EMA(EMA(x)) The second average starts at the bar where the first is defined. After a gap the whole calculation starts again. --- Source: https://oathra.dev/reference/indicators/efficiency_ratio # Efficiency ratio `efficiency_ratio` How directly the price moved: 1 for a straight line, near 0 for noise. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 10 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | efficiency_ratio | value | Warm-up: period bars (10 at the defaults). Repaints: No. ## Formula ER[i] = |x[i] - x[i-period]| / sum(|x[j] - x[j-1]|) for j from i-period+1 to i When the sum is zero, ER is 0. A window holding a missing value has no value. --- Source: https://oathra.dev/reference/indicators/ema # Exponential moving average `ema` Average that weights recent values more, each step closing part of the gap to the price. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | ema | value | Warm-up: period - 1 bars (13 at the defaults). Repaints: No. ## Formula k = 2 / (period + 1) EMA[i] = x[i] * k + EMA[i-1] * (1 - k), starting from the first price. The first period - 1 values are left empty. After a gap the average starts again. --- Source: https://oathra.dev/reference/indicators/envelopes # Envelopes `envelopes` A moving average with a band a set distance above and below it. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | | deviation | float | 0.1 | 0 to 100 | | method | ma_method | sma | | | mode | choice | percent | | ## Outputs | Name | Kind | | --- | --- | | upper | value | | middle | value | | lower | value | Warm-up: pick(method, {'sma': period - 1, 'ema': period - 1, 'smma': period - 1, 'lwma': period - 1, 'wma': period - 1, 'hma': period + round_half_up(sqrt(period)) - 2, 'dema': 2 * period - 2, 'tema': 3 * period - 3}) bars (13 at the defaults). Repaints: No. ## Formula middle = the moving average named by method percent: upper = middle * (1 + deviation / 100), lower = middle * (1 - deviation / 100) fixed: upper = middle + deviation, lower = middle - deviation --- Source: https://oathra.dev/reference/indicators/frama # Fractal adaptive moving average `frama` Average that follows trends closely and flattens in chop, from the fractal dimension. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | frama | value | Warm-up: 2 * period - 1 bars (27 at the defaults). Repaints: No. ## Formula N1 = (highest high - lowest low over the last period bars) / period N2 = the same over the period bars before those, N3 = the same over all 2 * period bars divided by 2 * period D = (ln(N1 + N2) - ln(N3)) / ln(2), alpha = exp(-4.6 * (D - 1)) FRAMA[i] = alpha * close[i] + (1 - alpha) * FRAMA[i-1] The first 2 * period - 1 values are left empty. --- Source: https://oathra.dev/reference/indicators/hma # Hull moving average `hma` Fast, smooth average built from two weighted averages. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 16 | 2 to 5000 | ## Outputs | Name | Kind | | --- | --- | | hma | value | Warm-up: period + round_half_up(sqrt(period)) - 2 bars (18 at the defaults). Repaints: No. ## Formula half = floor(period / 2 + 0.5), root = floor(sqrt(period) + 0.5) HMA = WMA(2 * WMA(x, half) - WMA(x, period), root) --- Source: https://oathra.dev/reference/indicators/kama # Kaufman adaptive moving average `kama` Average that speeds up when the price trends and slows down when it chops. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 10 | 1 to 5000 | | fast_period | int | 2 | 1 to 5000 | | slow_period | int | 30 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | kama | value | Warm-up: period bars (10 at the defaults). Repaints: No. ## Formula sc = (ER * (fast_sc - slow_sc) + slow_sc)^2, with fast_sc = 2 / (fast_period + 1) and slow_sc = 2 / (slow_period + 1) KAMA[i] = KAMA[i-1] + sc * (x[i] - KAMA[i-1]), seeded with x[period-1] The first period values are left empty. After a gap the average starts again. --- Source: https://oathra.dev/reference/indicators/linreg # Linear regression `linreg` Least squares line fitted to the last N values, read at the newest bar. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 2 to 5000 | ## Outputs | Name | Kind | | --- | --- | | linreg | value | Warm-up: period - 1 bars (13 at the defaults). Repaints: No. ## Formula Fit a straight line by least squares to the last period values, with t = 0 at the oldest. b = (period * sum(t*y) - sum(t) * sum(y)) / (period * sum(t^2) - sum(t)^2) a = (sum(y) - b * sum(t)) / period LinReg = a + b * (period - 1) --- Source: https://oathra.dev/reference/indicators/linreg_intercept # Linear regression intercept `linreg_intercept` Where the fitted line starts, at the oldest bar of the window. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 2 to 5000 | ## Outputs | Name | Kind | | --- | --- | | linreg_intercept | value | Warm-up: period - 1 bars (13 at the defaults). Repaints: No. ## Formula Fit a straight line by least squares to the last period values, with t = 0 at the oldest. b = (period * sum(t*y) - sum(t) * sum(y)) / (period * sum(t^2) - sum(t)^2) a = (sum(y) - b * sum(t)) / period Intercept = a --- Source: https://oathra.dev/reference/indicators/linreg_slope # Linear regression slope `linreg_slope` How much the fitted line rises or falls per bar. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 2 to 5000 | ## Outputs | Name | Kind | | --- | --- | | linreg_slope | value | Warm-up: period - 1 bars (13 at the defaults). Repaints: No. ## Formula Fit a straight line by least squares to the last period values, with t = 0 at the oldest. b = (period * sum(t*y) - sum(t) * sum(y)) / (period * sum(t^2) - sum(t)^2) a = (sum(y) - b * sum(t)) / period Slope = b --- Source: https://oathra.dev/reference/indicators/ma # Moving average `ma` Any of the eight moving averages, chosen by name. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | | method | ma_method | sma | | ## Outputs | Name | Kind | | --- | --- | | ma | value | Warm-up: pick(method, {'sma': period - 1, 'ema': period - 1, 'smma': period - 1, 'lwma': period - 1, 'wma': period - 1, 'hma': period + round_half_up(sqrt(period)) - 2, 'dema': 2 * period - 2, 'tema': 3 * period - 3}) bars (13 at the defaults). Repaints: No. ## Formula MA = the average named by method: sma, ema, smma, lwma, wma, hma, dema or tema. Each one follows its own formula and warm-up. --- Source: https://oathra.dev/reference/indicators/median # Rolling median `median` Middle value of the last N values, so one spike cannot move it. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 20 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | median | value | Warm-up: period - 1 bars (19 at the defaults). Repaints: No. ## Formula Median[i] = the middle of the sorted values x[i-period+1] to x[i] With an even period it is the mean of the two middle values. A window holding a missing value has no value. --- Source: https://oathra.dev/reference/indicators/rma # Smoothed moving average (Wilder) `rma` Wilder's smoothing: a slow average seeded with the mean of the first N values. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 13 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | rma | value | Warm-up: period - 1 bars (12 at the defaults). Repaints: No. ## Formula RMA[period-1] = mean of the first period values RMA[i] = (RMA[i-1] * (period - 1) + x[i]) / period After a gap the average starts again with a new seed. --- Source: https://oathra.dev/reference/indicators/sma # Simple moving average `sma` Average of the last N values, each weighted equally. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 20 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | sma | value | Warm-up: period - 1 bars (19 at the defaults). Repaints: No. ## Formula SMA[i] = (x[i] + x[i-1] + ... + x[i-period+1]) / period No value until period bars exist. A window holding a missing value has no value. --- Source: https://oathra.dev/reference/indicators/tema # Triple exponential moving average `tema` Exponential average with most of its lag removed. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | tema | value | Warm-up: 3 * period - 3 bars (39 at the defaults). Repaints: No. ## Formula TEMA = 3 * EMA(x) - 3 * EMA(EMA(x)) + EMA(EMA(EMA(x))) Each average starts at the bar where the one before it is defined. After a gap the whole calculation starts again. --- Source: https://oathra.dev/reference/indicators/vidya # Variable index dynamic average `vidya` Exponential average that speeds up when momentum is strong. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period_cmo | int | 9 | 1 to 5000 | | period_ema | int | 12 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | vidya | value | Warm-up: period_ema + period_cmo - 1 bars (20 at the defaults). Repaints: No. ## Formula CMO = (sum of up moves - sum of down moves) / (sum of up moves + sum of down moves) over period_cmo bars alpha = 2 / (period_ema + 1) * |CMO| VIDYA[i] = x[i] * alpha + VIDYA[i-1] * (1 - alpha) After a gap the average starts again. --- Source: https://oathra.dev/reference/indicators/vwma # Volume weighted moving average `vwma` Average that weights each price by the volume traded at it. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | volume | volume | tick | | | period | int | 20 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | vwma | value | Warm-up: period - 1 bars (19 at the defaults). Repaints: No. ## Formula VWMA[i] = sum(x[j] * v[j]) / sum(v[j]) over the last period bars No value until period bars exist, or where the volume in the window adds to zero. --- Source: https://oathra.dev/reference/indicators/wma # Linear weighted moving average `wma` Average that weights the newest value N times and the oldest once. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 13 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | wma | value | Warm-up: period - 1 bars (12 at the defaults). Repaints: No. ## Formula WMA[i] = (period * x[i] + (period - 1) * x[i-1] + ... + 1 * x[i-period+1]) / (period * (period + 1) / 2) No value until period bars exist. A window holding a missing value has no value. --- Source: https://oathra.dev/reference/indicators/adx # Average directional index `adx` How strong the trend is, whatever its direction. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 14 | 1 to 500 | | method | choice | mt5 | | ## Outputs | Name | Kind | | --- | --- | | adx | value | Warm-up: pick(method, {'mt5': period, 'wilder': 2 * period - 1}) bars (14 at the defaults). Repaints: No. ## Formula TR = the true range, +DM and -DM = the larger of the two directional moves, the other is 0 +DI = 100 * smoothed(+DM) / smoothed(TR), -DI = 100 * smoothed(-DM) / smoothed(TR) DX = 100 * |+DI - -DI| / (+DI + -DI), ADX = smoothed(DX) mt5: smoothing with 2 / (period + 1), starting from zero. wilder: smoothing with 1 / period, starting from the mean of the first period values. Where the true range is zero the lines read 0. --- Source: https://oathra.dev/reference/indicators/aroon # Aroon `aroon` How recently the highest high and the lowest low happened. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | up | value | | down | value | | osc | value | Warm-up: period bars (14 at the defaults). Repaints: No. ## Formula up = 100 * (period - bars since the highest high of the last period + 1 bars) / period down = the same with the lowest low, osc = up - down A tie goes to the most recent bar. --- Source: https://oathra.dev/reference/indicators/dmi # Directional movement index `dmi` Trend direction as two lines, with the strength of the trend. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 14 | 1 to 500 | | method | choice | mt5 | | ## Outputs | Name | Kind | | --- | --- | | plus_di | value | | minus_di | value | | adx | value | Warm-up: pick(method, {'mt5': period, 'wilder': 2 * period - 1}) bars (14 at the defaults). Repaints: No. ## Formula TR = the true range, +DM and -DM = the larger of the two directional moves, the other is 0 +DI = 100 * smoothed(+DM) / smoothed(TR), -DI = 100 * smoothed(-DM) / smoothed(TR) DX = 100 * |+DI - -DI| / (+DI + -DI), ADX = smoothed(DX) mt5: smoothing with 2 / (period + 1), starting from zero. wilder: smoothing with 1 / period, starting from the mean of the first period values. Where the true range is zero the lines read 0. --- Source: https://oathra.dev/reference/indicators/donchian # Donchian channel `donchian` Highest high and lowest low of the last N bars, with their midpoint. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 20 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | upper | value | | lower | value | | middle | value | Warm-up: period - 1 bars (19 at the defaults). Repaints: No. ## Formula upper = the highest high of the last period bars lower = the lowest low of the last period bars middle = (upper + lower) / 2 --- Source: https://oathra.dev/reference/indicators/heikin_ashi # Heikin-Ashi `heikin_ashi` Smoothed candles that make trends easier to read. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | seed | choice | mt5_raw | | ## Outputs | Name | Kind | | --- | --- | | ha_open | value | | ha_high | value | | ha_low | value | | ha_close | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula HA close = (open + high + low + close) / 4 HA open = (previous HA open + previous HA close) / 2 HA high = the highest of high, HA open and HA close, HA low the lowest of low, HA open and HA close The first bar is seeded from the raw bar (mt5_raw) or from the textbook averages. --- Source: https://oathra.dev/reference/indicators/ichimoku # Ichimoku Kinko Hyo `ichimoku` Five lines that show trend, momentum and support in one view. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | tenkan_period | int | 9 | 1 to 500 | | kijun_period | int | 26 | 1 to 500 | | senkou_b_period | int | 52 | 1 to 500 | ## Outputs | Name | Kind | | --- | --- | | tenkan | value | | kijun | value | | senkou_a | value | | senkou_b | value | | chikou | value | Warm-up: max(tenkan_period, kijun_period, senkou_b_period) - 1 bars (51 at the defaults). Repaints: No. ## Formula mid(n) = (highest high + lowest low over the last n bars) / 2 Tenkan = mid(tenkan_period), Kijun = mid(kijun_period) Senkou A = (Tenkan + Kijun) / 2, Senkou B = mid(senkou_b_period) Chikou = the close The values are not displaced. The chart draws the two spans kijun_period bars ahead and Chikou kijun_period bars back; strategies read the values as they are. --- Source: https://oathra.dev/reference/indicators/pivot_high # Pivot high `pivot_high` A swing high, shown once enough bars after it prove it was the highest. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | left | int | 5 | 1 to 100 | | right | int | 5 | 1 to 100 | ## Outputs | Name | Kind | | --- | --- | | pivot_high | value | Warm-up: left + right bars (10 at the defaults). Repaints: Yes. ## Formula A pivot high is bar c whose high is strictly above the highs of the left bars before it and the right bars after it. It is written on bar c + right, the bar where it became known, and drawn back on bar c. --- Source: https://oathra.dev/reference/indicators/pivot_low # Pivot low `pivot_low` A swing low, shown once enough bars after it prove it was the lowest. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | left | int | 5 | 1 to 100 | | right | int | 5 | 1 to 100 | ## Outputs | Name | Kind | | --- | --- | | pivot_low | value | Warm-up: left + right bars (10 at the defaults). Repaints: Yes. ## Formula A pivot low is bar c whose low is strictly below the lows of the left bars before it and the right bars after it. It is written on bar c + right, the bar where it became known, and drawn back on bar c. --- Source: https://oathra.dev/reference/indicators/pivot_points # Pivot points `pivot_points` Support and resistance levels projected from the previous day, week or month. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | kind | choice | standard | | | period | choice | D | | | day_boundary | text | 17:00 America/New_York | | ## Outputs | Name | Kind | | --- | --- | | pp | value | | r1 | value | | r2 | value | | r3 | value | | r4 | value | | r5 | value | | s1 | value | | s2 | value | | s3 | value | | s4 | value | | s5 | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula H, L, C and O are the highest high, lowest low, last close and first open of the previous period; every bar of a period carries the levels computed from them. standard: P = (H + L + C) / 3, R1 = 2P - L, S1 = 2P - H, R2 = P + (H - L), S2 = P - (H - L), R3 = R1 + (H - L), S3 = S1 - (H - L) fibonacci: P = (H + L + C) / 3, R and S = P plus and minus (H - L) times 0.382, 0.618, 1 and 1.618 for levels 1 to 4 camarilla: P = (H + L + C) / 3, R and S = C plus and minus (H - L) * 1.1 divided by 12, 6, 4 and 2 for levels 1 to 4, R5 = H / L * C, S5 = 2C - R5 woodie: P = (H + L + 2C) / 4, R1 = 2P - L, R2 = P + (H - L), S1 = 2P - H, S2 = P - (H - L) demark: X = H + 2L + C if C < O, 2H + L + C if C > O, else H + L + 2C; P = X / 4, R1 = X / 2 - L, S1 = X / 2 - H A trading day starts at the day boundary in its own time zone, so a change of clocks moves the bars but not the boundary. No value until one full period has closed. --- Source: https://oathra.dev/reference/indicators/sar # Parabolic SAR `sar` Trailing stop-and-reverse level that speeds up as the trend runs. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | af_step | float | 0.02 | 0.001 to 1 | | af_max | float | 0.2 | 0.01 to 1 | ## Outputs | Name | Kind | | --- | --- | | sar | value | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula SAR[i+1] = SAR[i] + AF * (EP - SAR[i]), EP = the extreme price of the trend AF starts at the step, grows by the step with each new extreme and stops at the maximum. A reversal restarts EP and AF. The first leg is seeded short and is left empty until the first reversal the price itself causes. --- Source: https://oathra.dev/reference/indicators/supertrend # SuperTrend `supertrend` Trend-following line built from the average true range. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | atr_period | int | 10 | 1 to 500 | | factor | float | 3 | 0.1 to 20 | ## Outputs | Name | Kind | | --- | --- | | supertrend | value | | direction | value | Warm-up: atr_period - 1 bars (9 at the defaults). Repaints: No. ## Formula upper = (high + low) / 2 + factor * ATR, lower = (high + low) / 2 - factor * ATR Each band only moves toward price, unless the previous close crossed it. The line is the lower band while the trend is up and the upper band while it is down. direction is 1 up and -1 down. ATR is Wilder's average true range. --- Source: https://oathra.dev/reference/indicators/zigzag # ZigZag `zigzag` Swing highs and lows, each shown only once a reversal has confirmed it. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | depth | int | 12 | 2 to 200 | | backstep | int | 3 | 0 to 100 | | atr_period | int | 14 | 1 to 500 | | atr_mult | float | 1 | 0.1 to 10 | ## Outputs | Name | Kind | | --- | --- | | up | value | | down | value | | offset | value | | pivot | value | | direction | value | Warm-up: max(depth - 1, atr_period - 1) + backstep bars (16 at the defaults). Repaints: Yes. ## Formula A bar is a low (high) candidate when its low (high) is the lowest (highest) of the last depth bars. A candidate is final after backstep more bars, once weaker candidates of the same kind within backstep bars are cleared. One swing is pending at a time. A stronger candidate of the same kind replaces it. An opposite candidate at least atr_mult average true ranges away confirms it. The confirmed swing is written on the confirming bar: up or down holds its price and offset the bars back to it. pivot is the last confirmed price, direction is 1 after a low and -1 after a high. The swing pending at the end is never written. --- Source: https://oathra.dev/reference/indicators/ac # Accelerator oscillator `ac` How fast the Awesome oscillator is speeding up or slowing down. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | fast_period | int | 5 | 1 to 5000 | | slow_period | int | 34 | 1 to 5000 | | signal_period | int | 5 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | ac | value | Warm-up: slow_period + signal_period - 2 bars (37 at the defaults). Repaints: No. ## Formula AC = AO - SMA(AO, signal_period) --- Source: https://oathra.dev/reference/indicators/alligator # Alligator `alligator` Three smoothed lines that show when a trend is asleep or waking up. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | jaw_period | int | 13 | 1 to 5000 | | jaw_shift | int | 8 | 0 to 100 | | teeth_period | int | 8 | 1 to 5000 | | teeth_shift | int | 5 | 0 to 100 | | lips_period | int | 5 | 1 to 5000 | | lips_shift | int | 3 | 0 to 100 | ## Outputs | Name | Kind | | --- | --- | | jaw | value | | teeth | value | | lips | value | Warm-up: max(jaw_period, teeth_period, lips_period) - 1 bars (12 at the defaults). Repaints: No. ## Formula median = (high + low) / 2 jaw, teeth and lips are smoothed averages of the median over their own periods: the first value is the mean of the first period values, then (previous * (period - 1) + median) / period. The shifts are how far the chart draws each line ahead. The values are not displaced. --- Source: https://oathra.dev/reference/indicators/ao # Awesome oscillator `ao` Momentum from the gap between a fast and a slow average of the median price. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | fast_period | int | 5 | 1 to 5000 | | slow_period | int | 34 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | ao | value | Warm-up: slow_period - 1 bars (33 at the defaults). Repaints: No. ## Formula AO = SMA(median, fast_period) - SMA(median, slow_period), median = (high + low) / 2 --- Source: https://oathra.dev/reference/indicators/bw_mfi # Market facilitation index `bw_mfi` Price range per unit of volume, with Bill Williams' four colours. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | volume | volume | tick | | ## Outputs | Name | Kind | | --- | --- | | mfi | value | | phase | value | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula MFI = (high - low) / volume, empty where the volume is 0 phase: 0 green (MFI up, volume up), 1 fade (down, down), 2 fake (MFI up, volume down), 3 squat (MFI down, volume up). The directions start up and change only when the value strictly changes from the previous bar. --- Source: https://oathra.dev/reference/indicators/fractals # Fractals `fractals` Swing highs and lows, shown two bars after they form. ## Outputs | Name | Kind | | --- | --- | | up | value | | down | value | Warm-up: 4 bars (4 at the defaults). Repaints: Yes. ## Formula An up fractal is a bar whose high is above the next two highs and at least the two before it. A down fractal mirrors it with the lows. It is written on the bar that confirms it, two bars after the centre, and drawn back on the centre bar. --- Source: https://oathra.dev/reference/indicators/gator # Gator oscillator `gator` How far apart the Alligator's jaw, teeth and lips are. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | jaw_period | int | 13 | 1 to 5000 | | jaw_shift | int | 8 | 0 to 100 | | teeth_period | int | 8 | 1 to 5000 | | teeth_shift | int | 5 | 0 to 100 | | lips_period | int | 5 | 1 to 5000 | | lips_shift | int | 3 | 0 to 100 | ## Outputs | Name | Kind | | --- | --- | | upper | value | | lower | value | Warm-up: max(jaw_period - 1 + jaw_shift - teeth_shift, teeth_period - 1 + teeth_shift - lips_shift) bars (15 at the defaults). Repaints: No. ## Formula jaw, teeth and lips are the Alligator's smoothed averages of the median price. upper = |jaw (jaw_shift - teeth_shift bars earlier) - teeth| lower = -|teeth (teeth_shift - lips_shift bars earlier) - lips| The periods and the shifts must both fall from jaw to teeth to lips, and each period must exceed its own shift. --- Source: https://oathra.dev/reference/indicators/bears_power # Bears power `bears_power` The bar's low against an EMA of closes. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 13 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | bears_power | value | Warm-up: period - 1 bars (12 at the defaults). Repaints: No. ## Formula Bears = low - EMA(close, period) --- Source: https://oathra.dev/reference/indicators/bulls_power # Bulls power `bulls_power` The bar's high against an EMA of closes. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 13 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | bulls_power | value | Warm-up: period - 1 bars (12 at the defaults). Repaints: No. ## Formula Bulls = high - EMA(close, period) --- Source: https://oathra.dev/reference/indicators/cci # Commodity channel index `cci` Distance from the average in units of mean deviation. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | hlc3 | | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | cci | value | Warm-up: period - 1 bars (13 at the defaults). Repaints: No. ## Formula The typical price by default CCI = (x - SMA) / (0.015 * mean absolute deviation) over the last period values No deviation reads 0. --- Source: https://oathra.dev/reference/indicators/change # Change `change` Value now minus the value a number of bars ago. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 1 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | change | value | Warm-up: period bars (1 at the defaults). Repaints: No. ## Formula change = x[i] - x[i-period] --- Source: https://oathra.dev/reference/indicators/cmo # Chande momentum oscillator `cmo` Up moves against down moves over a period, from -100 to 100. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | cmo | value | Warm-up: period bars (14 at the defaults). Repaints: No. ## Formula Up is the change when it is positive, Down is minus the change when it is negative CMO = 100 * (sum of Up - sum of Down) / (sum of Up + sum of Down) over the last period bars 0 when nothing moved. --- Source: https://oathra.dev/reference/indicators/demarker # DeMarker `demarker` Pressure from higher highs against lower lows, from 0 to 1. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | demarker | value | Warm-up: period bars (14 at the defaults). Repaints: No. ## Formula DeMax = the rise of the high when positive, else 0; DeMin = the fall of the low when positive, else 0 DeM = SMA(DeMax) / (SMA(DeMax) + SMA(DeMin)) over the last period bars No movement reads 0.5. --- Source: https://oathra.dev/reference/indicators/macd # MACD `macd` The gap between a fast and a slow EMA, with a signal line and histogram. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | fast_period | int | 12 | 1 to 5000 | | slow_period | int | 26 | 1 to 5000 | | signal_period | int | 9 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | macd | value | | signal | value | | histogram | value | Warm-up: max(fast_period, slow_period) + signal_period - 2 bars (33 at the defaults). Repaints: No. ## Formula MACD = EMA(source, fast_period) - EMA(source, slow_period), both seeded on the first value signal = the simple average of MACD over signal_period, as MetaTrader 5 computes it histogram = MACD - signal --- Source: https://oathra.dev/reference/indicators/mom # Momentum `mom` Price now against price a number of bars ago. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | | method | choice | difference | | ## Outputs | Name | Kind | | --- | --- | | mom | value | Warm-up: period bars (14 at the defaults). Repaints: No. ## Formula difference: x[i] - x[i-period] mt5: x[i] * 100 / x[i-period], the MetaTrader 5 form, centred on 100 An earlier value of 0 leaves the mt5 bar empty. --- Source: https://oathra.dev/reference/indicators/osma # OsMA `osma` The MACD histogram: MACD minus its signal line. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | fast_period | int | 12 | 1 to 5000 | | slow_period | int | 26 | 1 to 5000 | | signal_period | int | 9 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | osma | value | Warm-up: max(fast_period, slow_period) + signal_period - 2 bars (33 at the defaults). Repaints: No. ## Formula OsMA = MACD - signal MACD = EMA(source, fast_period) - EMA(source, slow_period), signal = the simple average of MACD over signal_period --- Source: https://oathra.dev/reference/indicators/roc # Rate of change `roc` Change over a number of bars as a percent, fraction or ratio. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 12 | 1 to 5000 | | method | choice | percent | | ## Outputs | Name | Kind | | --- | --- | | roc | value | Warm-up: period bars (12 at the defaults). Repaints: No. ## Formula percent: 100 * (x[i] - x[i-period]) / x[i-period] fraction: (x[i] - x[i-period]) / x[i-period] ratio: x[i] / x[i-period] ratio100: 100 * x[i] / x[i-period] mt5: 100 * (x[i] - x[i-period]) / x[i], the MetaTrader 5 form A zero denominator leaves the bar empty. --- Source: https://oathra.dev/reference/indicators/rsi # Relative strength index `rsi` Momentum bounded 0 to 100, from average gains against average losses. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | | method | choice | wilder | | ## Outputs | Name | Kind | | --- | --- | | rsi | value | Warm-up: period bars (14 at the defaults). Repaints: No. ## Formula gain = the change when it is positive, loss = minus the change when it is negative wilder: the averages start as the mean of the first period gains and losses, then average = (previous average * (period - 1) + value) / period cutler: the averages are simple means of the last period gains and losses RSI = 100 * AvgGain / (AvgGain + AvgLoss) A window with no gain and no loss reads 50. --- Source: https://oathra.dev/reference/indicators/rvi # Relative vigor index `rvi` The close against the open, scaled by the range, with a signal line. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 10 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | rvi | value | | signal | value | Warm-up: period + 5 bars (15 at the defaults). Repaints: No. ## Formula The body (close - open) and the range (high - low) are each weighted over four bars as 1, 2, 2, 1 RVI = sum of the weighted body / sum of the weighted range over period bars; the body sum alone when the range sum is 0 signal = RVI weighted over four bars as 1, 2, 2, 1, divided by 6 --- Source: https://oathra.dev/reference/indicators/stoch # Stochastic oscillator `stoch` Where the close sits in the recent high to low range, with a signal line. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | k_period | int | 5 | 1 to 5000 | | d_period | int | 3 | 1 to 5000 | | slowing | int | 3 | 1 to 5000 | | basis | choice | high_low | | ## Outputs | Name | Kind | | --- | --- | | k | value | | d | value | Warm-up: k_period + slowing + d_period - 3 bars (8 at the defaults). Repaints: No. ## Formula The highest high and lowest low over K period (the highest and lowest close with close_close) %K = 100 * sum(close - lowest, slowing) / sum(highest - lowest, slowing) %D = the simple average of %K over D period A window with no range reads 50. --- Source: https://oathra.dev/reference/indicators/stoch_rsi # Stochastic RSI `stoch_rsi` The stochastic oscillator applied to RSI. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | rsi_period | int | 14 | 1 to 5000 | | stoch_period | int | 14 | 1 to 5000 | | k_smooth | int | 3 | 1 to 5000 | | d_smooth | int | 3 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | k | value | | d | value | Warm-up: rsi_period + stoch_period + k_smooth + d_smooth - 3 bars (31 at the defaults). Repaints: No. ## Formula RSI is Wilder's relative strength index over rsi_period raw = 100 * (RSI - lowest RSI) / (highest RSI - lowest RSI) over stoch_period; RSI itself when its range is 0 %K = the simple average of raw over k_smooth, %D = the simple average of %K over d_smooth --- Source: https://oathra.dev/reference/indicators/trix # TRIX `trix` The one-bar change of a triple smoothed EMA. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | trix | value | Warm-up: max(3 * period - 3, 1) bars (39 at the defaults). Repaints: No. ## Formula E1 = EMA(source, period), E2 = EMA(E1, period), E3 = EMA(E2, period) TRIX = (E3[i] - E3[i-1]) / E3[i-1], a fraction and not a percent E2 starts at bar period - 1 and E3 at bar 2 * (period - 1). --- Source: https://oathra.dev/reference/indicators/wpr # Williams %R `wpr` Where the close sits in the recent range, from 0 down to -100. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | wpr | value | Warm-up: period - 1 bars (13 at the defaults). Repaints: No. ## Formula %R = -100 * (highest high - close) / (highest high - lowest low) over the last period bars A window with no range reads -50. --- Source: https://oathra.dev/reference/indicators/adr # Average daily range `adr` The average high to low range of recent trading days. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | days | int | 14 | 1 to 500 | | day_boundary | text | 17:00 America/New_York | | ## Outputs | Name | Kind | | --- | --- | | adr | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula A trading day starts at the day boundary in its own time zone range of a day = its highest high - its lowest low ADR = the average range of the last days finished days; the first day in the data is not counted because the data may start inside it --- Source: https://oathra.dev/reference/indicators/atr # Average true range `atr` The average true range over a period, Wilder smoothed or simple. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 14 | 1 to 5000 | | method | choice | wilder | | ## Outputs | Name | Kind | | --- | --- | | atr | value | Warm-up: period bars (14 at the defaults). Repaints: No. ## Formula TR[i] = max(high[i], close[i-1]) - min(low[i], close[i-1]) wilder: the first value is the mean of the first period true ranges, then ATR[i] = (ATR[i-1] * (period - 1) + TR[i]) / period sma: the mean of the last period true ranges No value until period true ranges exist. --- Source: https://oathra.dev/reference/indicators/bb # Bollinger Bands `bb` A moving average with bands set by the standard deviation. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 20 | 1 to 5000 | | deviations | float | 2 | 0 to 10 | ## Outputs | Name | Kind | | --- | --- | | upper | value | | middle | value | | lower | value | Warm-up: period - 1 bars (19 at the defaults). Repaints: No. ## Formula middle = the simple average of the source over period upper = middle + deviations * the population standard deviation of the same window, lower = middle - deviations * the same --- Source: https://oathra.dev/reference/indicators/bb_percent # Bollinger Bands %B `bb_percent` Where the price sits between the Bollinger Bands. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 20 | 1 to 5000 | | deviations | float | 2 | 0 to 10 | ## Outputs | Name | Kind | | --- | --- | | bb_percent | value | Warm-up: period - 1 bars (19 at the defaults). Repaints: No. ## Formula %B = (source - lower) / (upper - lower) No value when the bands meet. middle = the simple average of the source over period upper = middle + deviations * the population standard deviation of the same window, lower = middle - deviations * the same --- Source: https://oathra.dev/reference/indicators/bbw # Bollinger Bands width `bbw` The distance between the Bollinger Bands relative to the middle line. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 20 | 1 to 5000 | | deviations | float | 2 | 0 to 10 | ## Outputs | Name | Kind | | --- | --- | | bbw | value | Warm-up: period - 1 bars (19 at the defaults). Repaints: No. ## Formula width = (upper - lower) / middle middle = the simple average of the source over period upper = middle + deviations * the population standard deviation of the same window, lower = middle - deviations * the same --- Source: https://oathra.dev/reference/indicators/kc # Keltner channel `kc` An EMA with bands set by the average true range. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | ema_period | int | 20 | 1 to 5000 | | atr_period | int | 10 | 1 to 5000 | | atr_factor | float | 2 | 0 to 10 | ## Outputs | Name | Kind | | --- | --- | | upper | value | | middle | value | | lower | value | Warm-up: max(ema_period - 1, atr_period) bars (19 at the defaults). Repaints: No. ## Formula middle = the EMA of the closes over ema_period upper = middle + atr_factor * ATR, lower = middle - atr_factor * ATR ATR is the simple average of the true range over atr_period, as the MetaTrader 5 Keltner Channel computes it. --- Source: https://oathra.dev/reference/indicators/kcw # Keltner channel width `kcw` The distance between the Keltner bands relative to the middle line. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | ema_period | int | 20 | 1 to 5000 | | atr_period | int | 10 | 1 to 5000 | | atr_factor | float | 2 | 0 to 10 | ## Outputs | Name | Kind | | --- | --- | | kcw | value | Warm-up: max(ema_period - 1, atr_period) bars (19 at the defaults). Repaints: No. ## Formula width = (upper - lower) / middle middle = the EMA of the closes over ema_period upper = middle + atr_factor * ATR, lower = middle - atr_factor * ATR ATR is the simple average of the true range over atr_period, as the MetaTrader 5 Keltner Channel computes it. --- Source: https://oathra.dev/reference/indicators/stdev # Standard deviation `stdev` How far values spread around their average over a period. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 20 | 1 to 5000 | | method | ma_method | sma | | ## Outputs | Name | Kind | | --- | --- | | stdev | value | Warm-up: period - 1 bars (19 at the defaults). Repaints: No. ## Formula The square root of the mean squared distance of the last period values from the chosen average at the current bar The divisor is period, not period - 1. --- Source: https://oathra.dev/reference/indicators/tr # True range `tr` The largest of the bar's range and its gaps from the previous close. ## Outputs | Name | Kind | | --- | --- | | tr | value | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula TR[i] = max(high[i], close[i-1]) - min(low[i], close[i-1]) No value on the first bar, which has no previous close. --- Source: https://oathra.dev/reference/indicators/variance # Variance `variance` The square of the standard deviation over a period. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 20 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | variance | value | Warm-up: period - 1 bars (19 at the defaults). Repaints: No. ## Formula The mean squared distance of the last period values from their simple average. --- Source: https://oathra.dev/reference/indicators/accdist # Accumulation/distribution `accdist` Volume weighted by where the close sits in the bar. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | volume | volume | tick | | ## Outputs | Name | Kind | | --- | --- | | accdist | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula The running total of ((close - low) - (high - close)) / (high - low) * volume A bar with no range adds 0. --- Source: https://oathra.dev/reference/indicators/chaikin_osc # Chaikin oscillator `chaikin_osc` The fast average of A/D minus its slow average. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | fast_period | int | 3 | 1 to 5000 | | slow_period | int | 10 | 1 to 5000 | | method | ma_method | ema | | | volume | volume | tick | | ## Outputs | Name | Kind | | --- | --- | | chaikin_osc | value | Warm-up: max(fast_period, slow_period) - 1 bars (9 at the defaults). Repaints: No. ## Formula The chosen average of accumulation/distribution over the fast period minus the same over the slow period --- Source: https://oathra.dev/reference/indicators/force # Force index `force` Volume times the change of a moving average of price. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 13 | 1 to 5000 | | method | ma_method | sma | | | volume | volume | tick | | ## Outputs | Name | Kind | | --- | --- | | force | value | Warm-up: period bars (13 at the defaults). Repaints: No. ## Formula force = volume[i] * (MA[i] - MA[i-1]) with the moving average of the chosen method --- Source: https://oathra.dev/reference/indicators/mfi # Money flow index `mfi` RSI built from price times volume, from 0 to 100. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 14 | 1 to 5000 | | volume | volume | tick | | ## Outputs | Name | Kind | | --- | --- | | mfi | value | Warm-up: period bars (14 at the defaults). Repaints: No. ## Formula The typical price times volume is positive flow on a bar whose typical price rose and negative flow on one that fell; it counts in neither when it did not move MFI = 100 * positive / (positive + negative) over the last period bars Only positive flow reads 100, no flow reads 50. --- Source: https://oathra.dev/reference/indicators/obv # On-balance volume `obv` Volume added on up closes and taken away on down closes. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | volume | volume | tick | | ## Outputs | Name | Kind | | --- | --- | | obv | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula A running total of volume, added on an up close and taken away on a down close, starting with the first bar's volume It depends on where the data starts. --- Source: https://oathra.dev/reference/indicators/volumes # Volumes `volumes` Volume per bar, tick or real as the feed carries it. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | volume | volume | tick | | ## Outputs | Name | Kind | | --- | --- | | volumes | value | | direction | value | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula The volume of the bar direction = 1 when the volume is above the previous bar's, else -1 On forex the feed carries tick volume. --- Source: https://oathra.dev/reference/indicators/vwap # Volume-weighted average price `vwap` The average price weighted by volume since the session, week, month or a chosen time. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | hlc3 | | | volume | volume | tick | | | anchor | choice | session | | | anchor_time | text | | | | band_mult | float | 1 | 0 to 10 | | day_boundary | text | 17:00 America/New_York | | ## Outputs | Name | Kind | | --- | --- | | vwap | value | | upper | value | | lower | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula VWAP = the running sum of volume * source over the running sum of volume, from each anchor upper and lower = VWAP plus and minus band_mult * the volume-weighted standard deviation A session starts at the day boundary. On forex the volume is tick volume. To anchor at a bar found in code, call it on the data from that bar. --- Source: https://oathra.dev/reference/indicators/cum # Running total `cum` The total of every value from the first bar. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | ## Outputs | Name | Kind | | --- | --- | | cum | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula The running total of the source from the first bar. A missing value restarts the total on the next bar that has one. The value depends on where the data starts. --- Source: https://oathra.dev/reference/indicators/delay # Delay `delay` The series moved a number of bars back. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | n | int | 1 | 0 to 5000 | ## Outputs | Name | Kind | | --- | --- | | delay | value | Warm-up: n bars (1 at the defaults). Repaints: No. ## Formula The source n bars ago: value[i] = source[i - n]. A series can only be moved back in time; the first n bars have no value. --- Source: https://oathra.dev/reference/indicators/falling # Falling `falling` True after a number of falls in a row. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 3 | 1 to 500 | ## Outputs | Name | Kind | | --- | --- | | falling | state | Warm-up: period bars (3 at the defaults). Repaints: No. ## Formula True when each of the last period bars is below the bar before it: source[i-period] > ... > source[i-1] > source[i]. An equal value or a missing value breaks the run. --- Source: https://oathra.dev/reference/indicators/highest # Highest value `highest` The highest value over a number of bars. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 30 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | highest | value | Warm-up: period - 1 bars (29 at the defaults). Repaints: No. ## Formula The highest value of the last period bars, the current bar included. A window holding a missing value has no value. To compare a price with the highest of the bars before it, delay the result by one bar. --- Source: https://oathra.dev/reference/indicators/highest_bars # Bars since the highest `highest_bars` How many bars ago the highest value of the window was. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 30 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | highest_bars | value | Warm-up: period - 1 bars (29 at the defaults). Repaints: No. ## Formula How many bars ago the highest value of the last period bars was, 0 when it is the current bar. When two bars share the highest value, the more recent one counts. --- Source: https://oathra.dev/reference/indicators/log_return # Log return `log_return` The natural log of the value over the previous value. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | ## Outputs | Name | Kind | | --- | --- | | log_return | value | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula ln(source[i] / source[i-1]). No value on the first bar, or when either value is missing or not above zero. --- Source: https://oathra.dev/reference/indicators/lowest # Lowest value `lowest` The lowest value over a number of bars. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 30 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | lowest | value | Warm-up: period - 1 bars (29 at the defaults). Repaints: No. ## Formula The lowest value of the last period bars, the current bar included. A window holding a missing value has no value. To compare a price with the lowest of the bars before it, delay the result by one bar. --- Source: https://oathra.dev/reference/indicators/lowest_bars # Bars since the lowest `lowest_bars` How many bars ago the lowest value of the window was. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 30 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | lowest_bars | value | Warm-up: period - 1 bars (29 at the defaults). Repaints: No. ## Formula How many bars ago the lowest value of the last period bars was, 0 when it is the current bar. When two bars share the lowest value, the more recent one counts. --- Source: https://oathra.dev/reference/indicators/midpoint # Midpoint `midpoint` Halfway between the highest and lowest value of a window. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | midpoint | value | Warm-up: period - 1 bars (13 at the defaults). Repaints: No. ## Formula (highest + lowest) / 2 of the source over the last period bars. --- Source: https://oathra.dev/reference/indicators/midprice # Mid price `midprice` Halfway between the highest high and the lowest low of a window. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | int | 14 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | midprice | value | Warm-up: period - 1 bars (13 at the defaults). Repaints: No. ## Formula (highest high + lowest low) / 2 over the last period bars. This is not the median price of one bar, which is (high + low) / 2. --- Source: https://oathra.dev/reference/indicators/percentrank # Percent rank `percentrank` Where the value sits among its previous values, from 0 to 100. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 100 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | percentrank | value | Warm-up: period bars (100 at the defaults). Repaints: No. ## Formula The share of the previous period values that are less than or equal to the current value, from 0 to 100. The current bar is compared with the bars before it, not with itself. A window holding a missing value has no value. --- Source: https://oathra.dev/reference/indicators/rising # Rising `rising` True after a number of rises in a row. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 3 | 1 to 500 | ## Outputs | Name | Kind | | --- | --- | | rising | state | Warm-up: period bars (3 at the defaults). Repaints: No. ## Formula True when each of the last period bars is above the bar before it: source[i-period] < ... < source[i-1] < source[i]. An equal value or a missing value breaks the run. --- Source: https://oathra.dev/reference/indicators/sum # Rolling sum `sum` The sum of the last values over a number of bars. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | source | source | close | | | period | int | 30 | 1 to 5000 | ## Outputs | Name | Kind | | --- | --- | | sum | value | Warm-up: period - 1 bars (29 at the defaults). Repaints: No. ## Formula The sum of the last period values. A window holding a missing value has no value. --- Source: https://oathra.dev/reference/indicators/dark_cloud_cover # Dark cloud cover `dark_cloud_cover` A fall that closes deep into the previous rise. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | long_body_pct | float | 50 | 0 to 100 | | penetration | float | 0.5 | 0 to 1 | ## Outputs | Name | Kind | | --- | --- | | dark_cloud_cover | event | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first bar rises with a long body; the second falls, opens at or above the first close and closes at or below first close - penetration * first body, above the first open Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/doji # Doji `doji` A bar whose body is tiny against its range. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | body_pct | float | 5 | 0 to 50 | ## Outputs | Name | Kind | | --- | --- | | doji | event | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low body <= body_pct / 100 * range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/dragonfly_doji # Dragonfly doji `dragonfly_doji` A doji that opens and closes near its high. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | body_pct | float | 5 | 0 to 50 | | wick_pct | float | 10 | 0 to 50 | ## Outputs | Name | Kind | | --- | --- | | dragonfly_doji | event | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low a doji, and upper wick <= wick_pct / 100 * range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/engulfing # Engulfing `engulfing` A body that covers the previous opposite body. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | allow_equal | bool | true | | ## Outputs | Name | Kind | | --- | --- | | engulfing | event | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low bullish: the first bar falls, the second rises, open <= the first close and close >= the first open, and the two bodies are not identical bearish: the first bar rises, the second falls, open >= the first close and close <= the first open, and the two bodies are not identical With equal prices off the comparisons are strict. Wicks are ignored. No gap between the bodies is required or forbidden. When the second open equals the first close, as it usually does on a market that trades without a break, the bar still counts while Equal prices count is on. Found: 1 bullish, -1 bearish A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/evening_doji_star # Evening doji star `evening_doji_star` An evening star whose middle bar is a doji. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | long_body_pct | float | 50 | 0 to 100 | | body_pct | float | 5 | 0 to 50 | | penetration | float | 0.3 | 0 to 1 | ## Outputs | Name | Kind | | --- | --- | | evening_doji_star | event | Warm-up: 2 bars (2 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low as an evening star, with the middle bar a doji: its body <= body_pct / 100 * its own range. The star may open at the first close: no gap is required. Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/evening_star # Evening star `evening_star` A long rise, a small star, then a fall into the rise. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | long_body_pct | float | 50 | 0 to 100 | | star_body_pct | float | 30 | 0 to 100 | | penetration | float | 0.3 | 0 to 1 | ## Outputs | Name | Kind | | --- | --- | | evening_star | event | Warm-up: 2 bars (2 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first bar rises with a long body; the star's body <= star_body_pct / 100 * the first body and its bottom >= the first close; the third bar falls and closes at or below first close - penetration * first body. The star may open at the first close: no gap is required. Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/gravestone_doji # Gravestone doji `gravestone_doji` A doji that opens and closes near its low. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | body_pct | float | 5 | 0 to 50 | | wick_pct | float | 10 | 0 to 50 | ## Outputs | Name | Kind | | --- | --- | | gravestone_doji | event | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low a doji, and lower wick <= wick_pct / 100 * range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/hammer # Hammer `hammer` A long lower wick after a fall. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | wick_mult | float | 2 | 0 to 10 | | opposite_pct | float | 25 | 0 to 100 | | body_zone_pct | float | 33.3 | 1 to 100 | | trend_bars | int | 3 | 1 to 50 | ## Outputs | Name | Kind | | --- | --- | | hammer | event | Warm-up: trend_bars + 1 bars (4 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low lower wick >= wick_mult * body, upper wick <= opposite_pct / 100 * range, the body inside the top body_zone_pct of the range, and after a fall: close[i-1] < close[i-1-trend_bars] Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/hanging_man # Hanging man `hanging_man` A long lower wick after a rise. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | wick_mult | float | 2 | 0 to 10 | | opposite_pct | float | 25 | 0 to 100 | | body_zone_pct | float | 33.3 | 1 to 100 | | trend_bars | int | 3 | 1 to 50 | ## Outputs | Name | Kind | | --- | --- | | hanging_man | event | Warm-up: trend_bars + 1 bars (4 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low lower wick >= wick_mult * body, upper wick <= opposite_pct / 100 * range, the body inside the top body_zone_pct of the range, and after a rise: close[i-1] > close[i-1-trend_bars] Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/harami # Harami `harami` A small body inside the previous long body. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | long_body_pct | float | 50 | 0 to 100 | | allow_equal | bool | true | | ## Outputs | Name | Kind | | --- | --- | | harami | event | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first body > 0 and >= long_body_pct / 100 * its range; the second body inside the first body (strictly with equal prices off) and smaller than it Found: 1 when the first bar falls, -1 when it rises A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/inside_bar # Inside bar `inside_bar` A bar whose range sits within the previous bar's range. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | allow_equal | bool | true | | ## Outputs | Name | Kind | | --- | --- | | inside_bar | event | | in_a_row | value | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low high <= the previous high and low >= the previous low (strictly with equal values off); both bars have a range in_a_row = how many inside bars in a row end at this bar, 0 on a bar that is not one Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/inverted_hammer # Inverted hammer `inverted_hammer` A long upper wick after a fall. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | wick_mult | float | 2 | 0 to 10 | | opposite_pct | float | 25 | 0 to 100 | | body_zone_pct | float | 33.3 | 1 to 100 | | trend_bars | int | 3 | 1 to 50 | ## Outputs | Name | Kind | | --- | --- | | inverted_hammer | event | Warm-up: trend_bars + 1 bars (4 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low upper wick >= wick_mult * body, lower wick <= opposite_pct / 100 * range, the body inside the bottom body_zone_pct of the range, and after a fall: close[i-1] < close[i-1-trend_bars] Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/marubozu # Marubozu `marubozu` A bar with almost no wicks, rising or falling. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | wick_pct | float | 5 | 0 to 50 | ## Outputs | Name | Kind | | --- | --- | | marubozu | event | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low body > 0, upper wick <= wick_pct / 100 * range and lower wick <= wick_pct / 100 * range Found: 1 on a rising bar, -1 on a falling bar A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/morning_doji_star # Morning doji star `morning_doji_star` A morning star whose middle bar is a doji. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | long_body_pct | float | 50 | 0 to 100 | | body_pct | float | 5 | 0 to 50 | | penetration | float | 0.3 | 0 to 1 | ## Outputs | Name | Kind | | --- | --- | | morning_doji_star | event | Warm-up: 2 bars (2 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low as a morning star, with the middle bar a doji: its body <= body_pct / 100 * its own range. The star may open at the first close: no gap is required. Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/morning_star # Morning star `morning_star` A long fall, a small star, then a rise into the fall. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | long_body_pct | float | 50 | 0 to 100 | | star_body_pct | float | 30 | 0 to 100 | | penetration | float | 0.3 | 0 to 1 | ## Outputs | Name | Kind | | --- | --- | | morning_star | event | Warm-up: 2 bars (2 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first bar falls with a long body; the star's body <= star_body_pct / 100 * the first body and its top <= the first close; the third bar rises and closes at or above first close + penetration * first body. The star may open at the first close: no gap is required. Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/outside_bar # Outside bar `outside_bar` A bar whose range covers the previous bar's range. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | allow_equal | bool | true | | ## Outputs | Name | Kind | | --- | --- | | outside_bar | event | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low high >= the previous high and low <= the previous low (strictly with equal values off), not both equal; both bars have a range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/piercing # Piercing line `piercing` A rise that closes deep into the previous fall. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | long_body_pct | float | 50 | 0 to 100 | | penetration | float | 0.5 | 0 to 1 | ## Outputs | Name | Kind | | --- | --- | | piercing | event | Warm-up: 1 bars (1 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low the first bar falls with a long body; the second rises, opens at or below the first close and closes at or above first close + penetration * first body, below the first open Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/shooting_star # Shooting star `shooting_star` A long upper wick after a rise. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | wick_mult | float | 2 | 0 to 10 | | opposite_pct | float | 25 | 0 to 100 | | body_zone_pct | float | 33.3 | 1 to 100 | | trend_bars | int | 3 | 1 to 50 | ## Outputs | Name | Kind | | --- | --- | | shooting_star | event | Warm-up: trend_bars + 1 bars (4 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low upper wick >= wick_mult * body, lower wick <= opposite_pct / 100 * range, the body inside the bottom body_zone_pct of the range, and after a rise: close[i-1] > close[i-1-trend_bars] Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/spinning_top # Spinning top `spinning_top` A small body with a longer wick on each side. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | body_pct | float | 30 | 0 to 100 | ## Outputs | Name | Kind | | --- | --- | | spinning_top | event | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low body <= body_pct / 100 * range, upper wick > body and lower wick > body Found: 1 when the close is at or above the open, else -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/three_black_crows # Three black crows `three_black_crows` Three falling bars, each opening inside the last body. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | wick_pct | float | 20 | 0 to 100 | ## Outputs | Name | Kind | | --- | --- | | three_black_crows | event | Warm-up: 2 bars (2 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low three falling bars with each close below the one before; each of the second and third opens within the body of the bar before; each lower wick <= wick_pct / 100 * its bar's range Found: -1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/three_inside # Three inside `three_inside` A harami confirmed by the third bar. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | long_body_pct | float | 50 | 0 to 100 | | allow_equal | bool | true | | ## Outputs | Name | Kind | | --- | --- | | three_inside | event | Warm-up: 2 bars (2 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low up: the first two bars are a harami after a fall and the third closes above the first open down: the first two bars are a harami after a rise and the third closes below the first open Found: 1 up, -1 down A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/three_outside # Three outside `three_outside` An engulfing confirmed by the third bar. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | allow_equal | bool | true | | ## Outputs | Name | Kind | | --- | --- | | three_outside | event | Warm-up: 2 bars (2 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low up: the first two bars are a bullish engulfing and the third closes above the second close down: the first two bars are a bearish engulfing and the third closes below the second close Found: 1 up, -1 down A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/three_white_soldiers # Three white soldiers `three_white_soldiers` Three rising bars, each opening inside the last body. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | wick_pct | float | 20 | 0 to 100 | ## Outputs | Name | Kind | | --- | --- | | three_white_soldiers | event | Warm-up: 2 bars (2 at the defaults). Repaints: No. ## Formula body = |close - open|, range = high - low, upper wick = high - max(open, close), lower wick = min(open, close) - low three rising bars with each close above the one before; each of the second and third opens within the body of the bar before; each upper wick <= wick_pct / 100 * its bar's range Found: 1 A bar whose high equals its low, or that has a missing price, is never part of a pattern. Computed on the prices the bars are drawn with: on Heikin-Ashi or Renko bars those are the built candles. --- Source: https://oathra.dev/reference/indicators/period_open # Period open `period_open` The opening price of the current day, week or month. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | choice | D | | | day_boundary | text | 17:00 America/New_York | | ## Outputs | Name | Kind | | --- | --- | | period_open | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula A trading day starts at day_boundary; period D, W or M is defined as for prior levels. period_open is the open of the first bar of the current period, shown on every bar of that period. A period already under way at the first bar of the data has no value. --- Source: https://oathra.dev/reference/indicators/prior_levels # Prior period levels `prior_levels` The previous day, week or month open, high, low and close (PDH, PDL), carried forward. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | period | choice | D | | | day_boundary | text | 17:00 America/New_York | | ## Outputs | Name | Kind | | --- | --- | | prev_open | value | | prev_high | value | | prev_low | value | | prev_close | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula A trading day starts at day_boundary. With period D each trading day is one period; with W a week starts on the first trading day of its ISO week (Monday); with M a month starts on its first trading day. prev_open, prev_high, prev_low and prev_close are the first open, the highest high, the lowest low and the last close of the previous period. A value appears on the first bar of a period and stays for the whole period, so it is known before that bar closes. A period already under way at the first bar of the data is not used. A period holding a missing bar has no levels. --- Source: https://oathra.dev/reference/indicators/killzones # Killzones `killzones` Named session windows, each in its own time zone, shaded on the chart and usable as filters. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | asia_hours | session | 20:00-00:00 | | | asia_tz | text | America/New_York | | | london_open_hours | session | 02:00-05:00 | | | london_open_tz | text | America/New_York | | | new_york_open_hours | session | 07:00-10:00 | | | new_york_open_tz | text | America/New_York | | | london_close_hours | session | 10:00-12:00 | | | london_close_tz | text | America/New_York | | ## Outputs | Name | Kind | | --- | --- | | in_asia | state | | in_london_open | state | | in_new_york_open | state | | in_london_close | state | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula Each window runs from the start to the end of its hours (asia_hours, london_open_hours, new_york_open_hours, london_close_hours) in its own time zone (asia_tz, london_open_tz, new_york_open_tz, london_close_tz). A bar is inside a window when it opens at or after the start and before the end; hours that end before they start run past midnight. in_asia, in_london_open, in_new_york_open and in_london_close are true on the bars inside each window. --- Source: https://oathra.dev/reference/indicators/opening_range # Opening range `opening_range` The high and low of the first minutes of a session, with breakout signals. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | minutes | int | 30 | 1 to 1440 | | session_hours | session | 08:00-17:00 | | | session_tz | text | Europe/London | | ## Outputs | Name | Kind | | --- | --- | | or_high | value | | or_low | value | | breakout | event | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula The session runs from the start to the end of session_hours in session_tz. Its range bars are those that open within the first minutes minutes after the start, on the local clock. or_high and or_low are the highest high and lowest low of the range bars so far, then of all of them for the rest of the session. They are empty outside the session. breakout is +1 on a bar after the range whose close crosses above or_high, and -1 when the close crosses below or_low. A session already under way at the first bar of the data, or with no bar opening inside its range, has no range. A missing bar ends the session's range from that bar. --- Source: https://oathra.dev/reference/indicators/session_range # Session ranges `session_range` The high and low of the Asia, London and New York sessions, boxed while each runs. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | asia_hours | session | 09:00-18:00 | | | asia_tz | text | Asia/Tokyo | | | london_hours | session | 08:00-17:00 | | | london_tz | text | Europe/London | | | new_york_hours | session | 08:00-17:00 | | | new_york_tz | text | America/New_York | | ## Outputs | Name | Kind | | --- | --- | | asia | zone | | asia_high | value | | asia_low | value | | london | zone | | london_high | value | | london_low | value | | new_york | zone | | new_york_high | value | | new_york_low | value | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula Each session runs from the start to the end of its hours (asia_hours, london_hours, new_york_hours) in its own time zone (asia_tz, london_tz, new_york_tz), so a clock change in one centre moves only that session. A bar belongs to a session when it opens at or after the start and before the end; hours that end before they start run past midnight and belong to the day they started. While a session runs, its box spans the highest high and the lowest low of its bars so far. When it ends, its high and low are shown from the next bar on until the same session ends again. A session already under way at the first bar of the data is not used. A session holding a missing bar has no box or levels from that bar on. On bars longer than a session, a bar counts for the session it opens in. --- Source: https://oathra.dev/reference/indicators/fair_value_gaps # Fair value gaps `fair_value_gaps` Three-candle imbalances, shown until price fills them. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | min_size | float | 0 | | | auto_threshold | bool | true | | | threshold_bars | int | 100 | 1 to 5000 | | threshold_mult | float | 2 | 0.1 to 10 | | extend | int | 0 | 0 to 5000 | | mitigation | choice | filled | | ## Outputs | Name | Kind | | --- | --- | | bull_1 | zone | | bull_2 | zone | | bull_3 | zone | | bull_4 | zone | | bull_5 | zone | | bear_1 | zone | | bear_2 | zone | | bear_3 | zone | | bear_4 | zone | | bear_5 | zone | | bull_new | event | | bear_new | event | | bull_mitigated | event | | bear_mitigated | event | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula A bullish gap forms on a bar whose low is above the high two bars earlier, when the bar between them closes above that high. The gap runs from that earlier high up to the bar's low. A bearish gap mirrors it. A gap is kept only when its height is at least min_size. With auto_threshold on, it is also kept only when the middle candle's body is larger than threshold_mult times the average body of the threshold_bars candles before it. A gap shows from the bar it forms on. With mitigation filled, a bullish gap is mitigated when price trades below its bottom; with touched, when price trades into it. Bearish gaps mirror it. bull_mitigated and bear_mitigated mark the bar, and the gap is removed. With extend above zero, a gap shows on the bar it forms and the extend bars after it, then is removed. Slot 1 is the newest live gap of its side; Forge keeps the five newest per side. A missing bar starts the scan again. The gaps are found on the bars the indicator runs on, which can be another timeframe than the chart's. --- Source: https://oathra.dev/reference/indicators/liquidity # Liquidity `liquidity` Equal highs and lows (EQH, EQL), resting liquidity above swing highs and below swing lows, and the sweeps that take it. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | confirm_bars | int | 3 | 1 to 50 | | margin_atr | float | 0.1 | 0 to 5 | | atr_period | int | 10 | 1 to 500 | ## Outputs | Name | Kind | | --- | --- | | eqh | value | | eql | value | | sweep_high | event | | sweep_low | event | | buyside | value | | sellside | value | Warm-up: 2 * confirm_bars bars (6 at the defaults). Repaints: Yes. ## Formula A swing high is a bar whose high is above every high within confirm_bars bars on each side; it is known confirm_bars bars later, and values are written then. Swing lows mirror it. EQH marks a swing high within margin_atr times the ATR of the previous swing high. The ATR is Wilder's average true range over atr_period bars. EQL mirrors it. Every swing high leaves buyside liquidity at its price, and every swing low sellside liquidity. When a bar trades above a buyside level it is taken; if the bar closes back below it, that is a sweep (sweep_high). Sellside mirrors it (sweep_low). buyside is the nearest untaken buyside level above price and sellside the nearest untaken sellside level below it. Forge keeps the fifty newest levels per side. A missing bar starts again. --- Source: https://oathra.dev/reference/indicators/market_structure # Market structure `market_structure` Confirmed swings labelled HH, HL, LH and LL, with breaks of structure (BOS) and changes of character (CHoCH), for internal and swing structure. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | swing_len | int | 5 | 2 to 100 | | internal_len | int | 2 | 1 to 99 | | internal | bool | true | | | swing | bool | true | | | confluence_filter | bool | false | | | break_on | choice | close | | ## Outputs | Name | Kind | | --- | --- | | bos | event | | choch | event | | internal_bos | event | | internal_choch | event | | hh | value | | lh | value | | hl | value | | ll | value | | swing_high | value | | swing_low | value | | internal_high | value | | internal_low | value | | trend | value | | internal_trend | value | Warm-up: 2 * internal_len bars (4 at the defaults). Repaints: Yes. ## Formula A swing high is a bar whose high is above the high of every bar within swing_len bars on each side; a swing low mirrors it. Equal highs make no swing. A swing is known swing_len bars after it, and every value is written on that later bar; the chart draws the label back on the swing bar. HH and LH mark a swing high above or below the previous one; HL and LL mark a swing low above or below the previous one. A break happens on the first bar that closes above the latest swing high (break_on close) or trades above it (break_on wick). It is a change of character (CHoCH) when the trend was down, and a break of structure (BOS) otherwise; the trend then turns up and that level cannot break again. Breaks below the latest swing low mirror it. Values +1 are upward breaks and -1 downward. Internal structure applies the same rule with internal_len. internal and swing turn each one on or off. With confluence_filter on, an internal break is marked only when its bar closes in the break's direction and the level it breaks is not the swing level. A missing bar starts the structure again. --- Source: https://oathra.dev/reference/indicators/order_blocks # Order blocks `order_blocks` The last opposite candle before a break of structure, kept until price trades back through it. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | swing_len | int | 5 | 2 to 100 | | internal_len | int | 2 | 1 to 99 | | internal | bool | true | | | swing | bool | true | | | show_last | int | 5 | 1 to 5 | | mitigation | choice | wick | | | break_on | choice | close | | ## Outputs | Name | Kind | | --- | --- | | swing_bull_1 | zone | | swing_bull_2 | zone | | swing_bull_3 | zone | | swing_bull_4 | zone | | swing_bull_5 | zone | | swing_bear_1 | zone | | swing_bear_2 | zone | | swing_bear_3 | zone | | swing_bear_4 | zone | | swing_bear_5 | zone | | internal_bull_1 | zone | | internal_bull_2 | zone | | internal_bull_3 | zone | | internal_bull_4 | zone | | internal_bull_5 | zone | | internal_bear_1 | zone | | internal_bear_2 | zone | | internal_bear_3 | zone | | internal_bear_4 | zone | | internal_bear_5 | zone | | bull_mitigated | event | | bear_mitigated | event | Warm-up: 0 bars (0 at the defaults). Repaints: No. ## Formula Breaks are found as in market structure, with swing_len, internal_len and break_on. For an upward break, Forge takes the lowest low between the broken swing high and the break bar; the order block is the last down candle (close below open) at or before that low, back to the swing high. Its box spans that candle's high and low. A downward break mirrors it with the last up candle. A block is shown from the break bar on. A bullish block is mitigated and removed when price trades below its low (mitigation wick) or closes below it (mitigation close); a bearish block mirrors it. bull_mitigated and bear_mitigated mark the bar. Internal and swing blocks are tracked apart and turned on or off by internal and swing. Slot 1 is the newest unmitigated block of its kind; show_last slots are shown. Forge keeps the five newest unmitigated blocks per kind. --- Source: https://oathra.dev/reference/indicators/premium_discount # Premium and discount `premium_discount` The upper and lower halves of the active swing range, split at equilibrium. ## Inputs | Name | Type | Default | Range | | --- | --- | --- | --- | | swing_len | int | 5 | 2 to 100 | ## Outputs | Name | Kind | | --- | --- | | premium | zone | | discount | zone | | equilibrium | value | Warm-up: 2 * swing_len bars (10 at the defaults). Repaints: No. ## Formula Swings are found as in market structure with swing_len. The active range runs from the lowest low since the latest swing low to the highest high since the latest swing high. equilibrium is its middle. premium is the upper half, from equilibrium to the top, and discount the lower half, from the bottom to equilibrium. They show once both swings exist. --- # Error codes Source: https://oathra.dev/reference/errors/COMPUTE_FAILED # COMPUTE_FAILED compute() raised an error. ## What happened `compute()` raised an error. The message names the file, the line and the function where it was raised, then the error's own type and text, and ends with this code. ### Common causes - An exception inside `compute()`, or inside a function of your file that it calls. - An `assert` that failed. The message then reads `A requirement failed:` and the assert's own text, or `An assert failed.` when it has none. ## What to do Open your file at the line the message names and fix the error there. ### Related - [Warm-up](https://oathra.dev//docs/concepts/warm-up), for an error on the first bars. - [Strategy](https://oathra.dev//reference/forge/strategy/Strategy) ## Example ```text strategy.py, line 12, in compute: compute() raised ZeroDivisionError: division by zero [COMPUTE_FAILED] ``` The wording of the message depends on your file, and the code does not. --- Source: https://oathra.dev/reference/errors/COMPUTE_NOT_PURE # COMPUTE_NOT_PURE compute() changed the strategy's own attributes or the data it was given. ## What happened `compute()` changed one of the strategy's own attributes. Forge compares the strategy's attributes before and after `compute()` runs, and stops when one was added, removed or replaced. The message names the attribute. ### Common causes - Setting an attribute on `self` inside `compute()`, such as `self.count = 0`. - Replacing or deleting an attribute the strategy already had. ## What to do Keep what `compute()` works out in local variables. `compute()` gets the data and the settings only, and keeps no state between runs. ### Related - [Strategy](https://oathra.dev//reference/forge/strategy/Strategy) ## Example ```text strategy.py: compute() changed self.count. compute() gets the data and the settings only, and keeps no state between runs. [COMPUTE_NOT_PURE] ``` The wording of the message depends on your file, and the code does not. --- Source: https://oathra.dev/reference/errors/HOOK_FAILED # HOOK_FAILED A hook raised an error during the run. ## What happened A hook of your strategy raised an error while the test ran. A hook that raises ends the run. The error is kept on the result with this code, the file, the line and the bar it happened on. ## What to do Open your file at the line the message names and fix the error in that hook. ### Related - [Strategy](https://oathra.dev//reference/forge/strategy/Strategy) ## Example ```text strategy.py, line 31, in on_bar: on_bar raised KeyError: 'atr' [HOOK_FAILED] ``` The wording of the message depends on your file, and the code does not. --- Source: https://oathra.dev/reference/errors/PASS_HOOK_FAILED # PASS_HOOK_FAILED score, accept or report raised an error or returned a value Forge cannot use. ## What happened One of the three hooks that run after a pass, `score`, `accept` or `report`, raised an error or returned a value Forge cannot use. ### Common causes - The hook raised an error. The message names the file, the line and the function. - `score` returned a value that is not a finite number. - `accept` returned a value that is not `True` or `False`. - `report` returned something that is not a dict of names to numbers. ## What to do Return a finite number from `score`, `True` or `False` from `accept`, and a dict of names to numbers from `report`. ### Related - [Strategy](https://oathra.dev//reference/forge/strategy/Strategy) ## Example ```text strategy.py, line 44, in score: score raised ZeroDivisionError: division by zero [PASS_HOOK_FAILED] strategy.py, in score: score returned nan, not a finite number [PASS_HOOK_FAILED] strategy.py, in accept: accept must return True or False, got int [PASS_HOOK_FAILED] ``` The wording of the message depends on your file, and the code does not. A value Forge cannot use has no line of its own. --- Source: https://oathra.dev/reference/errors/SIGNALS_INVALID # SIGNALS_INVALID compute() returned signals Forge cannot use. ## What happened `compute()` returned something Forge cannot use as signals. The message says what, and points at the line where `compute` is defined. ### Common causes - `compute()` did not return `fg.Signals(...)`. - An array in `fg.Signals` does not have one value per bar. This is checked for the entries and exits, for `plots`, for `record` and for each filter. - `long_entry` and `short_entry` are both true on the same bar. - A region in `plots` ends after the last bar of the data. ## What to do Return `fg.Signals(...)` from `compute()`, with every array as long as the data, and one side at most on each bar. ### Related - [Signals](https://oathra.dev//reference/forge/signals/Signals) - [Warm-up](https://oathra.dev//docs/concepts/warm-up) ## Example ```text strategy.py, line 9, in compute: compute() must return fg.Signals(...), got tuple. [SIGNALS_INVALID] strategy.py, line 9, in compute: long_entry has 499 values, but the data has 500 bars. Every array in fg.Signals has one value per bar. [SIGNALS_INVALID] strategy.py, line 9, in compute: long_entry and short_entry are both true on 3 bars, first at bar 41. Choose one side per bar. [SIGNALS_INVALID] ``` The wording of the message depends on your file, and the code does not. --- Source: https://oathra.dev/reference/errors/ASSET_NOT_BUNDLED # ASSET_NOT_BUNDLED A declared asset was not provided with the source. ## What happened A strategy lists the files it needs, such as a table of values, in `assets`. A listed file that arrives without the source cannot be read. ## What to do Send the file together with the source, or take it out of `assets`. ## Example ```text strategy.py, line 12: `rates.csv` is listed in assets but was not provided with the source [ASSET_NOT_BUNDLED] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/BAD_ASSET # BAD_ASSET A declared asset path or file is refused. ## What happened A path or a file in `assets` is refused: the path is not relative to the strategy's folder, is listed twice, points outside the folder, was not found, has a kind of file that is not accepted, or is over the size limits. ## What to do Use a relative path inside the strategy's folder, with / between its parts, list each file once, and keep the files within the limits the message names. ## Example ```text strategy.py, line 12: `rates.csv` was not found next to the strategy file [BAD_ASSET] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/BAD_ATTRIBUTE # BAD_ATTRIBUTE symbols, direction, order_mode or warmup has a value Forge does not accept. ## What happened `symbols`, `direction`, `order_mode` or `warmup` has a value Forge does not accept. `symbols` is a list of 1 to 50 symbol names, `direction` is long, short or both, `order_mode` is managed or unmanaged, and `warmup` is a whole number of bars. ## What to do Change the value to one of those. ## Example ```text strategy.py, line 9: direction must be "long", "short" or "both" [BAD_ATTRIBUTE] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/BAD_BARS # BAD_BARS The bars declaration is invalid. ## What happened The `bars` declaration is not a bar declaration, uses a setting the class does not declare, or has values that cannot be resolved. ## What to do Write `bars` as a declaration such as `fg.bars.Time("H1")`, and declare any setting it uses on the class first. ## Example ```text strategy.py, line 14: The bars use a setting that is not declared on the class. Declare it first, like box = param.Float(10, min=5, max=30), then write bars = fg.bars.Renko(box). [BAD_BARS] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/BAD_CONSTRAINT # BAD_CONSTRAINT A constraint is not a function of the settings that returns True or False. ## What happened A constraint is a function of the settings that returns True or False. It was not a function of one argument, `constraints` was not a list, or the function raised an error when it was tried on the defaults. ## What to do Write each constraint as a function of one argument, for example `lambda p: p.fast < p.slow`, and keep them in a list. ## Example ```text strategy.py, line 16: constraint 1 must be a function of one argument, like lambda p: p.fast < p.slow [BAD_CONSTRAINT] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/BAD_EXPORTS # BAD_EXPORTS A library's __all__ is not a list of names the file defines. ## What happened A library file lists its public names in `__all__`. That list is not a list of names the file defines. ## What to do Write `__all__` as a list of the names the file defines, such as `__all__ = ["crossed"]`. ## Example ```text strategy.py, line 3: __all__ must be a list of names, like __all__ = ["crossed"] [BAD_EXPORTS] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/BAD_META # BAD_META meta or the class docstring is invalid. ## What happened The strategy's `meta` is not a `fg.Meta(...)`, or its docstring, which is its description, is longer than 4000 characters. ## What to do Write `meta = fg.Meta(title=..., short=..., tags=[...])`, and keep the docstring under 4000 characters. ## Example ```text strategy.py, line 8: meta must be fg.Meta(title=..., short=..., tags=[...]) [BAD_META] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/BAD_OBJECTIVE # BAD_OBJECTIVE objective names a way to rank results that Forge does not have, or custom without a score method. ## What happened `objective` names a way to rank results that Forge does not have, or is `custom` while the strategy has no `score` method. ## What to do Use the name of an objective Forge has, such as `"sharpe"`, or `None`. For `custom`, add a `score` method. ## Example ```text strategy.py, line 11: objective must be the name of a way to rank results, like "sharpe", or None. [BAD_OBJECTIVE] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/BAD_SCHEDULE # BAD_SCHEDULE A scheduled method is declared wrongly. ## What happened A method scheduled with `@fg.every` or `@fg.on` is declared wrongly: it is a hook as well, it takes the wrong arguments, or the scheduling sits on a module-level function. ## What to do Schedule a method of the strategy class, give it the arguments `(self, ctx)`, and do not schedule a hook. ## Example ```text strategy.py, line 31: A scheduled method takes (self, ctx). [BAD_SCHEDULE] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/BAD_SETTING # BAD_SETTING A setting declaration is invalid. ## What happened A setting declaration is invalid: a label, group or inline text over its length, a time zone that is not a zone name, values listed twice, or a range that cannot be built. ## What to do Change the setting the message names so that it follows the rule it states. ## Example ```text strategy.py, line 10: fast: label is at most 60 characters [BAD_SETTING] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/CHART_ONLY_CALL # CHART_ONLY_CALL A strategy uses fg.draw or fg.alert. Strategies draw through plots and ctx.plot. ## What happened `fg.draw` and `fg.alert` are for indicators. A strategy that uses them is refused, because a strategy's drawings go through its plots. ## What to do Draw with `plots=` in `fg.Signals`, or with `ctx.plot`, or move the drawing into an indicator. ## Example ```text strategy.py, line 6: `fg.draw` is for indicators. A strategy draws through plots= in fg.Signals or ctx.plot. [CHART_ONLY_CALL] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/CLASS_CHECKED # CLASS_CHECKED The strategy was loaded from a class, so the checks that read the file have not run yet. ## What happened A strategy handed over as a class, in a notebook or a test, is checked as a class. The checks that read the file itself, such as the position of `FORGE_API` and the lint rules, run when the file is saved. ## What to do Nothing, while you are trying things out. Save the file and check it before you rely on the result. ## Example ```text strategy.py, line 1: Loaded from a class: the file-level checks run when the file is saved. [CLASS_CHECKED] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/CONSTRAINT_NEEDS_CODE # CONSTRAINT_NEEDS_CODE A constraint is not a plain comparison of number settings, so only running the strategy checks it. ## What happened This is a warning, and the file still loads. A constraint that is not a plain comparison of number settings cannot be read without running the strategy, so before a sweep the number of combinations shown is an upper limit. ## What to do To get an exact count, write the constraint as a plain comparison, such as `lambda p: p.fast < p.slow`. ## Example ```text strategy.py, line 16: constraint 2 is not a plain comparison of number settings, so only running the strategy checks it. Where the strategy is not run first, the number of combinations shown before a sweep is an upper limit. [CONSTRAINT_NEEDS_CODE] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/DEFAULTS_REJECTED # DEFAULTS_REJECTED The default settings break a constraint. ## What happened The default value of every setting is tried against the constraints when the file loads. The defaults you wrote break one of them, so the strategy could not run even once as written. ## What to do Change a default, or the constraint, so that the defaults pass. ## Example ```text strategy.py, line 16: The default settings do not pass: constraint 1 is not met: fast=20, slow=10. Change a default or the constraint. [DEFAULTS_REJECTED] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/DEPRECATED # DEPRECATED The file uses a Forge name that is deprecated. ## What happened A name the file uses still works at its `FORGE_API` level and is marked to stop working at a later one. The message names the replacement when there is one, and the level at which the name stops working when that is set. ## What to do Change the name to the replacement the message gives. The [deprecation policy](https://oathra.dev//docs/deprecation-policy) says how long a deprecated name keeps working. ## Example ```text strategy.py, line 14: `forge.series.old_name` is deprecated. Use `forge.series.new_name` instead. It stops working at FORGE_API 2. [DEPRECATED] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/DYNAMIC_SERIES # DYNAMIC_SERIES A series is chosen in a way Forge cannot read before the run. ## What happened Forge reads which series a strategy asks for before the run, so it can load only the data it needs. A series chosen in a way it cannot read, for example from a variable computed at run time, is refused. ## What to do Write the price field, the symbol or the timeframe in the file, or use a setting. ## Example ```text strategy.py, line 42: d[...] takes a price field or a symbol written in the file, or a setting. [DYNAMIC_SERIES] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/FORGE_API_INVALID # FORGE_API_INVALID FORGE_API is not a supported whole number, or is set more than once. ## What happened `FORGE_API` is not a whole number Forge supports, is set more than once, is not written as a plain number, or is set inside a block instead of at the top of the file. ## What to do Write `FORGE_API = 1` once, at the top level, near the top of the file. ## Example ```text strategy.py, line 1: FORGE_API = 2 is not supported; supported: 1 [FORGE_API_INVALID] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/FORGE_API_LATE # FORGE_API_LATE FORGE_API comes after the first class or function. ## What happened `FORGE_API` has to come before the first class or function, because the level decides how the rest of the file is read. ## What to do Move the `FORGE_API` line above the first class or function. ## Example ```text strategy.py, line 6: Put FORGE_API = 1 before the first class or function. [FORGE_API_LATE] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/FORGE_API_MISSING # FORGE_API_MISSING The file has no FORGE_API line. ## What happened Every file declares the Forge API level it was written for, so an engine that changes later can still read it as it was meant. ## What to do Add `FORGE_API = 1` on the first lines of the file. ## Example ```text strategy.py, line 1: FORGE_API is missing: put FORGE_API = 1 on the first lines of the file [FORGE_API_MISSING] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/FORGE_API_OLD # FORGE_API_OLD The file's FORGE_API level has a newer one after it and has a date after which it is retired. ## What happened The level the file declares still runs, and a newer level exists. The message gives the date the older level is supported until. ## What to do Move the file to the level the message names before that date. The [deprecation policy](https://oathra.dev//docs/deprecation-policy) says how long a level is supported. ## Example ```text strategy.py, line 1: FORGE_API 1 is supported until 2027-06-30. Move this file to FORGE_API 2 before then. [FORGE_API_OLD] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/FORGE_API_RETIRED # FORGE_API_RETIRED The file's FORGE_API level is retired. Saved versions still open and run. ## What happened The level the file declares is past its retirement date. A version saved before that date still opens and still runs, so old results stay reproducible. A new version has to use a current level. ## What to do Change `FORGE_API` to the level the message names when you next edit the file, and fix what the checks then report. ## Example ```text strategy.py, line 1: FORGE_API 1 was retired on 2027-06-30. Saved versions still open and run, but a new version must use FORGE_API 2. [FORGE_API_RETIRED] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/FORGE_API_RETIRED_SAVE # FORGE_API_RETIRED_SAVE A new version cannot be saved at a retired FORGE_API level. ## What happened Saving makes a new version, and a new version cannot declare a level that is retired. Versions saved earlier are not affected. ## What to do Change `FORGE_API` to the level the message names, fix what the checks then report, and save again. ## Example ```text strategy.py, line 1: FORGE_API 1 was retired on 2027-06-30. Change the file to FORGE_API 2 to save a new version. [FORGE_API_RETIRED_SAVE] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/HOOK_NAME_CLOSE # HOOK_NAME_CLOSE A method name is close to a hook name, so Forge never calls it. ## What happened This is a warning, and the file still loads. A method whose name is close to a hook's name, but is not it, is never called by Forge, which is usually a typing mistake. ## What to do Rename the method to the hook's name, or give it a name that is not close to one if it is meant to be your own. ## Example ```text strategy.py, line 22: `sizee` is not a hook, so Forge never calls it. Did you mean `size`? [HOOK_NAME_CLOSE] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/HOOK_RETURN # HOOK_RETURN A hook's return annotation does not match what Forge expects. ## What happened A hook's return annotation says it returns something other than what Forge reads from it: a number for `size`, a true or false for a check, a dictionary for a hook that returns one. ## What to do Change the annotation, or remove it, so that it matches what the hook returns. ## Example ```text strategy.py, line 22: `size` must return float, but is annotated str [HOOK_RETURN] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/HOOK_SIGNATURE # HOOK_SIGNATURE A hook takes the wrong arguments, or is not a plain method. ## What happened A hook has to be a plain method that takes the arguments Forge passes it. One written as a static method, a class method, an async function, or with other arguments is refused. ## What to do Write the hook as a plain `def` inside the class with the arguments the message names. ## Example ```text strategy.py, line 22: `size` must be a plain method, without @staticmethod or @classmethod [HOOK_SIGNATURE] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/IMPURE_COMPUTE # IMPURE_COMPUTE compute() reads the clock or uses randomness. ## What happened `compute()` has to give the same signals every time it is run on the same bars. A call that reads the clock or makes random numbers inside it breaks that. ## What to do Use the bar times in `d`, and do random work in a hook with `ctx.rng`. ## Example ```text strategy.py, line 18: compute() must give the same signals every time: time.time() reads the clock or makes random numbers. Use the bar times in d, and do random work in a hook with ctx.rng. [IMPURE_COMPUTE] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/INTERNAL_IMPORT # INTERNAL_IMPORT The file imports btpro. Developer code imports forge only. ## What happened `btpro` is Forge's own code and is not available to a strategy. Developer code imports `forge` only. ## What to do Import from `forge`, as in `import forge as fg`. ## Example ```text strategy.py, line 2: Import forge, not btpro: btpro is Forge's own code and is not available to strategies. [INTERNAL_IMPORT] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/LATER_HOOK # LATER_HOOK The hook is not available at this FORGE_API level. ## What happened The hook is a name Forge reserves for a later API level, so it is not called at the level your file declares. ## What to do Use another way to do the same work at this level, or rename the method if it is not meant to be a hook. ## Example ```text strategy.py, line 27: `on_tick` is not a hook in FORGE_API 1. [LATER_HOOK] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/LINT_CENTRED_WINDOW # LINT_CENTRED_WINDOW A centred window reads bars after the one it is placed on. ## What happened A rolling window with its centre on the current bar takes half of its values from later bars. In a backtest those bars exist already. In live trading they do not, so the two disagree. ## What to do Use a trailing window, which ends on the current bar. [Repainting and look-ahead](https://oathra.dev//docs/concepts/repainting-and-look-ahead) explains the difference. ## Example ```text strategy.py, line 22: A centred window reads bars after the one it is placed on. [LINT_CENTRED_WINDOW] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/LINT_FIT_FULL_HISTORY # LINT_FIT_FULL_HISTORY A model is fitted on the whole history inside compute(), so early bars learn from later ones. ## What happened `compute()` receives the whole history at once. A model fitted there has seen every bar, so the values it gives for early bars were shaped by bars that came after them. ## What to do Fit on a window that ends before the bar being decided, and fit again as the window moves. ## Example ```text strategy.py, line 31: A model is fitted on the whole history inside compute(), so early bars learn from later ones. [LINT_FIT_FULL_HISTORY] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/LINT_LAST_ROW_IN_COMPUTE # LINT_LAST_ROW_IN_COMPUTE Code in compute() reads the last bar of the whole history. ## What happened Inside `compute()` the last row is the final bar of the data, not the bar being decided. Reading it puts a value from the end of the history onto every earlier bar. ## What to do Work on the whole series, so each bar gets a value from itself and the bars before it. ## Example ```text strategy.py, line 27: `close.iloc[-1]` reads the last bar of the whole history, which is later than most bars it decides on. [LINT_LAST_ROW_IN_COMPUTE] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/LINT_MODE_BRANCH # LINT_MODE_BRANCH The code depends on the run mode, so a backtest and a live run can trade differently. ## What happened The file reads the run mode and takes a different path for it. If that path changes what is traded, the backtest no longer describes what the strategy does live. ## What to do Keep the trading logic the same in every mode. A branch that only changes logging or drawing is harmless, and the warning can stay. ## Example ```text strategy.py, line 40: This code depends on the run mode. If it changes trading, a backtest and a live run trade differently. [LINT_MODE_BRANCH] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/LINT_MODULE_STATE # LINT_MODULE_STATE A module-level name is changed by strategy code, so results depend on what ran before. ## What happened A list, dictionary or set defined at the top of the file is changed inside a hook or a function. It keeps its contents between runs in the same process, so the second run does not start where the first one did. ## What to do Keep state on the strategy itself, set in `on_start`, so every run starts clean. ## Example ```text strategy.py, line 9: A module-level `seen` is changed by strategy code, so results depend on what ran before. [LINT_MODULE_STATE] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/LINT_NEGATIVE_SHIFT # LINT_NEGATIVE_SHIFT A negative shift moves a later bar's value onto an earlier bar. ## What happened `.shift()` with a negative number pulls values back in time, so each bar reads a bar that had not happened yet. ## What to do Shift by a number of bars back, 0 or more. ## Example ```text strategy.py, line 18: `.shift(-1)` moves a later bar's value onto an earlier bar. Use a number of bars back, 0 or more. [LINT_NEGATIVE_SHIFT] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/LINT_REVERSED_SERIES # LINT_REVERSED_SERIES A series is reversed before a running calculation, so early bars read later ones. ## What happened A running calculation on a reversed series walks from the last bar to the first. Turned back round, each bar holds a value built from the bars after it. ## What to do Run the calculation on the series in its own order. ## Example ```text strategy.py, line 25: Reversing a series before a running calculation lets early bars read later ones. [LINT_REVERSED_SERIES] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/LINT_UNSEEDED_RANDOM # LINT_UNSEEDED_RANDOM A random source is not seeded, so two runs of the same file differ. ## What happened The file draws random numbers from a source with no fixed seed. Two runs with the same settings then give different results, and neither can be reproduced. ## What to do Use `ctx.rng` inside hooks, or `numpy.random.default_rng(seed)` with a fixed seed elsewhere. ## Example ```text strategy.py, line 12: This random source is not seeded, so two runs differ. Use ctx.rng in hooks, or numpy.random.default_rng(seed) with a fixed seed. [LINT_UNSEEDED_RANDOM] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/LINT_WHOLE_SERIES_AGGREGATE # LINT_WHOLE_SERIES_AGGREGATE A calculation in compute() uses every bar, including later ones. ## What happened A mean, a maximum, a quantile or a similar figure taken over the whole series includes bars after the one being decided, so each bar is judged with knowledge of the future. ## What to do Use a trailing window such as `forge.series.rolling`. ## Example ```text strategy.py, line 29: `close.mean()` uses every bar, including later ones. Use a trailing window such as forge.series.rolling. [LINT_WHOLE_SERIES_AGGREGATE] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/LOAD_FAILED # LOAD_FAILED Running the top level of the file raised an error. ## What happened Loading a file runs the top level of it once. When that raises an error, the file cannot be used. The message is the error's own name and text. ## What to do Fix the error the message names. Keep what runs at the top level of the file short and free of anything that can fail. ## Example ```text strategy.py, line 9: NameError: name 'slow_period' is not defined [LOAD_FAILED] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/MANY_PROGRAMS # MANY_PROGRAMS The file defines more than one strategy class, or more than one indicator class. ## What happened A file holds exactly one strategy class, or one indicator class. A second one is refused. ## What to do Move the second class to its own file. ## Example ```text strategy.py, line 40: `Breakout` is a second strategy class. A file holds exactly one; move it to its own file. [MANY_PROGRAMS] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/MIXED_PROGRAM # MIXED_PROGRAM The file defines both a strategy and an indicator. ## What happened A file holds a strategy or an indicator, not both. ## What to do Put the indicator and the strategy in separate files. ## Example ```text strategy.py, line 40: `Trend` is an indicator and `Breakout` is a strategy. Put each in its own file. [MIXED_PROGRAM] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/NAMING # NAMING A class or function name does not follow Forge's naming style. ## What happened This is a warning, and the file still loads. A class name should use CapWords and a function or method name should use snake_case, which is the style the rest of Forge's code uses. ## What to do Rename it as the message says. ## Example ```text strategy.py, line 8: Class names use CapWords, like TrendPullback: rename `trend_pullback`. [NAMING] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/NO_PROGRAM # NO_PROGRAM The file defines no strategy class and no library names in __all__. ## What happened A file must define one class that subclasses `fg.Strategy`, or be a library that lists its public names in `__all__`. This one is neither. ## What to do Define a strategy class, or add an `__all__` list of the names the file offers. ## Example ```text strategy.py, line 1: Define one class that subclasses fg.Strategy, or list a library's public names in __all__. [NO_PROGRAM] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/PROP_AUTOMATION_BANNED # PROP_AUTOMATION_BANNED The firm behind the chosen rule preset does not allow automated trading. ## What happened The `prop` attribute names a firm's rule preset, and that firm's rules do not allow automated trading. The strategy still loads and can be tested. ## What to do Read the firm's rules before trading this strategy on one of its accounts, or choose another preset. ## Example ```text strategy.py, line 7: Example Firm does not allow automated trading under this preset. [PROP_AUTOMATION_BANNED] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/PROP_PRESET_UNKNOWN # PROP_PRESET_UNKNOWN The rule preset the file names is not one Forge has. ## What happened The `prop` attribute names a rule preset that is not in this version of Forge. When a preset with a close name exists, the message suggests it. ## What to do Correct the name, or remove `prop` and set the rules yourself. ## Example ```text strategy.py, line 7: my_firm_100k is not a rule preset Forge has. [PROP_PRESET_UNKNOWN] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/PROP_RULES_OLD # PROP_RULES_OLD The rule preset was last checked a while ago, and the firm may have changed its rules. ## What happened Forge records the date each firm's rules were last checked. This preset is old enough that its limits may no longer match the firm's. ## What to do Compare the preset with the firm's current rules before you rely on a pass. ## Example ```text strategy.py, line 7: These rules were last checked on 2026-03-01, 214 days ago. The firm may have changed them. [PROP_RULES_OLD] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/RANGE_MERGED # RANGE_MERGED A whole-number setting on a log scale gives fewer values than its step implies. ## What happened This is a warning, and the file still loads. A whole-number setting on a log scale rounds nearby values to the same number, so a sweep tries fewer values than the step suggests. ## What to do Use a linear scale, or a smaller range, or list the values you want with `values=`. ## Example ```text strategy.py, line 10: fast: the log scale gives 7 whole numbers, not 9, because nearby values round to the same number. [RANGE_MERGED] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/RANGE_WITHOUT_STEP # RANGE_WITHOUT_STEP A number setting has min and max but no step or values, so a sweep keeps it at its default. ## What happened This is a warning, and the file still loads. A number setting with `min` and `max` but no `step` or `values` gives a sweep nothing to step through, so it stays at its default. ## What to do Add `step=` or `values=` to the setting. ## Example ```text strategy.py, line 11: slow has min and max but no step or values, so a sweep keeps it at 30. Add step= or values=. [RANGE_WITHOUT_STEP] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/REMOVED_NAME # REMOVED_NAME The file uses a Forge name that was removed at its FORGE_API level. ## What happened A name the file uses was deprecated at an earlier level and no longer exists at the level the file declares. ## What to do Use the replacement the message gives. The [deprecation policy](https://oathra.dev//docs/deprecation-policy) says how names are removed. ## Example ```text strategy.py, line 14: `forge.series.old_name` was removed at FORGE_API 2. Use `forge.series.new_name` instead. [REMOVED_NAME] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/RESERVED_OVERRIDE # RESERVED_OVERRIDE The strategy replaces a method Forge relies on, such as __init__. ## What happened A method Forge relies on, such as `__init__`, belongs to Forge. A strategy that replaces it is refused. ## What to do Remove the method. Put start-up code in `setup(self, ctx)`. ## Example ```text strategy.py, line 14: `__init__` belongs to Forge. Put start-up code in setup(self, ctx). [RESERVED_OVERRIDE] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/RUN_SETTING_IN_CODE # RUN_SETTING_IN_CODE A class attribute is named like a run setting, which the Tester sets for each run. ## What happened Run settings such as the starting balance belong to a run, not to the file. A class attribute with one of those names changes nothing, because the Tester sets its own value for each run. ## What to do Remove the attribute. To suggest a value, put it in `defaults`. ## Example ```text strategy.py, line 6: `initial_capital` does not change the starting balance: the Tester sets that for each run. To suggest a value, use defaults = {...}. [RUN_SETTING_IN_CODE] ``` The wording of the message depends on your file, and the code does not. A warning does not stop the file from loading. --- Source: https://oathra.dev/reference/errors/SYNTAX_ERROR # SYNTAX_ERROR The file is not valid Python, or not UTF-8 text. ## What happened The file is not valid Python, or is not text in UTF-8. Nothing else can be checked until it is. ## What to do Fix the syntax at the line the message names. The message is Python's own. ## Example ```text strategy.py, line 3: invalid syntax [SYNTAX_ERROR] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/UNDECLARED_SYMBOL # UNDECLARED_SYMBOL The strategy reads a symbol it does not list in symbols. ## What happened A strategy reads the data of a symbol it does not list in `symbols`, so Forge would not have loaded it. ## What to do Add the symbol to `symbols = [...]`. ## Example ```text strategy.py, line 38: d["GBPUSD"] reads GBPUSD, which is not in symbols. Add it to symbols = [...]. [UNDECLARED_SYMBOL] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/UNGROUPED_AND_OR # UNGROUPED_AND_OR An expression mixes & and | without brackets. ## What happened Python joins `&` before `|`, so an expression that mixes them without brackets does not run in the order it reads. Forge refuses it so the order is always on the page. ## What to do Add brackets: `(a & b) | c` or `a & (b | c)`. ## Example ```text strategy.py, line 44: Add brackets to a & b | c. Python joins & before |, so a & b | c runs as (a & b) | c. Write (a & b) | c or a & (b | c). [UNGROUPED_AND_OR] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/UNKNOWN_HOOK # UNKNOWN_HOOK A method whose name starts with on_ is not a Forge hook. ## What happened A method whose name starts with `on_` is read as a hook. This one is not a hook Forge has. ## What to do Rename it, or schedule it with `@fg.every` or `@fg.on`. When the name is close to a hook, the message suggests it. ## Example ```text strategy.py, line 27: `on_signal` is not a Forge hook. Rename it, or schedule it with @fg.every or @fg.on. [UNKNOWN_HOOK] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/UNSAFE_LOAD # UNSAFE_LOAD The file reads data in a way that can run code hidden in it, such as pickle. ## What happened Some ways of reading a file, such as pickle, run whatever code the file carries. Forge does not allow them in a strategy or a library, in any function. Writing data that way is not refused. ## What to do Read data with numpy, pandas or json from a file listed in `assets`. ## Example ```text strategy.py, line 11: `pickle.load` can run code hidden in the data it reads, so Forge does not allow it. Read data with numpy, pandas or json from a file listed in assets. [UNSAFE_LOAD] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/WRONG_KIND # WRONG_KIND The file is an indicator where a strategy or a library was expected. ## What happened The file defines an indicator where a strategy or a library was expected, for example an indicator file given to a place that loads strategies. ## What to do Use the file where indicators are loaded, or write a strategy class instead. ## Example ```text strategy.py, line 5: `Trend` is an indicator. Only strategies and libraries load here. [WRONG_KIND] ``` The wording of the message depends on your file, and the code does not. An error stops the file from loading. --- Source: https://oathra.dev/reference/errors/CAL_COVERAGE_SHORT # CAL_COVERAGE_SHORT The news calendar does not cover the run's full date range. ## What happened The calendar attached to this run does not cover the run's full date range. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Shorten the tested range to fit the calendar, upload a calendar that covers it, or let Forge trim the run to what the calendar covers. ## Example ```json { "status": "failed", "error": { "code": "CAL_COVERAGE_SHORT", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/CAL_NO_TZ # CAL_NO_TZ The news calendar has no time zone Forge can resolve. ## What happened The calendar attached to this run has no time zone Forge can resolve, so its events cannot be lined up with the price data. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Upload the calendar again with a time zone set, or pick a different one. ## Example ```json { "status": "failed", "error": { "code": "CAL_NO_TZ", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/CAL_UNKNOWN # CAL_UNKNOWN The news calendar the run points to is not on file. ## What happened The calendar this run points to does not match anything on file. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. When the calendar is not available to your account, starting the run is refused with 403 and the same code. The engine's own text is in `detail.reason`. ## What to do Pick a calendar from the list, or upload the one you meant to use. ## Example ```json { "error": { "code": "CAL_UNKNOWN", "message": "The uploaded news calendar for this run is not available to your account.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/CLOCK_DISAGREE # CLOCK_DISAGREE The run's instruments were measured on broker clocks too far apart for one offset. ## What happened This run's instruments were measured on broker clocks more than an hour apart, so one time offset cannot serve them all. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Set the broker time offset by hand in Execution, or test these instruments separately. ## Example ```json { "status": "failed", "error": { "code": "CLOCK_DISAGREE", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/CLOCK_INDETERMINATE # CLOCK_INDETERMINATE Forge cannot work out the broker's time offset for any instrument in the run. ## What happened None of this run's instruments has a broker time offset that Forge can work out on its own. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Set the broker time offset by hand in Execution, or use instruments ingested with a known time zone. ## Example ```json { "status": "failed", "error": { "code": "CLOCK_INDETERMINATE", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/CONV_SIDE_UNAVAILABLE # CONV_SIDE_UNAVAILABLE Forge has no exchange rate to convert an instrument into the account currency. ## What happened Forge could not find the exchange rate it needs to convert one of this run's instruments into the account currency. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Choose an account currency that matches the instrument, or wait for that rate to be ingested. ## Example ```json { "status": "failed", "error": { "code": "CONV_SIDE_UNAVAILABLE", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/EM_FIELD_INVALID # EM_FIELD_INVALID A value in the run's execution settings is not one Forge recognises. ## What happened One of the values in this run's execution settings is not one Forge recognises. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Check each field in Execution for a typed-in value where a chosen one belongs, then save again. ## Example ```json { "status": "failed", "error": { "code": "EM_FIELD_INVALID", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/EM_LEAF_UNSUPPORTED # EM_LEAF_UNSUPPORTED An execution setting is not available yet on either engine. ## What happened One of this run's execution settings is not available yet on Forge's classic engine or on its newer one. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Turn that setting off, or leave it at its default value. ## Example ```json { "status": "failed", "error": { "code": "EM_LEAF_UNSUPPORTED", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/EM_VERSION_AHEAD # EM_VERSION_AHEAD The run's execution settings are newer than this engine can read. ## What happened This run's execution settings were saved by a newer version of Forge than this engine can read. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Reset the execution settings to their defaults, then set them again. ## Example ```json { "status": "failed", "error": { "code": "EM_VERSION_AHEAD", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/KERNEL_V2_DONCHIAN # KERNEL_V2_DONCHIAN A Donchian edge cannot run on the engine the execution settings select. ## What happened Donchian edges run only on Forge's classic engine, and this run's execution settings force the newer one. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Choose a different edge for this run, or leave the execution settings at their defaults. ## Example ```json { "status": "failed", "error": { "code": "KERNEL_V2_DONCHIAN", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/PROFILE_FIELD_ABSENT # PROFILE_FIELD_ABSENT The selected broker profile lacks a figure the run needs. ## What happened This run needs a broker figure, such as a minimum stop distance or a margin rate, that is missing from the selected broker profile. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Add the missing figure to the broker profile and try again. ## Example ```json { "status": "failed", "error": { "code": "PROFILE_FIELD_ABSENT", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/QUOTES_PARTIAL # QUOTES_PARTIAL Buy and sell prices cover only part of the run's date range. ## What happened Forge holds separate buy and sell prices for only part of this run's date range, and the execution settings need them for all of it. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Shorten the tested range to the part that has them, or set the spread source back to its default. ## Example ```json { "status": "failed", "error": { "code": "QUOTES_PARTIAL", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/RULE_PROFILE_INVALID # RULE_PROFILE_INVALID The rules profile attached to the run failed its own checks. ## What happened The rules profile attached to this run failed its own checks and cannot be used. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Choose a different rules profile, or fix the one that failed. ## Example ```json { "status": "failed", "error": { "code": "RULE_PROFILE_INVALID", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/SEED_REQUIRED # SEED_REQUIRED Random slippage is on without a fixed seed, so the result could not be repeated. ## What happened This run turns on random slippage without a fixed seed, so its result could not be reproduced. Before a run starts, the engine settles every setting it depends on, and a setting that cannot be settled ends the run with this code without testing anything. The engine's own text is in `detail.reason`. ## What to do Set a seed for the run, or turn random slippage off. ## Example ```json { "status": "failed", "error": { "code": "SEED_REQUIRED", "message": "The engine refused this run's settings before it started.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/batch_cap_exceeded # batch_cap_exceeded One request already stored the most runs a single request may store. ## What happened A sweep stores one run per combination it finishes, and one request may store only a fixed number. When a machine tries to store one more, the answer is 409 with this code. The runs already stored are kept. ## What to do To get more results, split the sweep into several requests. The cap is the Runs in one request row on the [Limits](https://oathra.dev//docs/limits) page. ## Example ```json { "error": "This run could not be stored.", "code": "batch_cap_exceeded" } ``` --- Source: https://oathra.dev/reference/errors/broker_profile_required # broker_profile_required Stop and target distances are in points, and no broker profile says what a point is. ## What happened A point is a different size on different symbols, so a distance typed in points means nothing without a broker profile. Forge does not quietly treat it as pips, which would change what your number means by a factor of ten. The run is refused with 409 before anything is queued. ## What to do Add a broker profile in Forge settings and start the run again, or switch the distance unit back to pips. ## Example ```json { "error": { "code": "broker_profile_required", "message": "Running with points needs a broker profile. Add one in Forge settings, then start the run again.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/combo_cap_exceeded # combo_cap_exceeded The run asks for more combinations than one run may test. ## What happened Before it tests anything, the machine counts the combinations your settings and ranges produce. When the count is over the cap for one request, it ends the run with this code and says how many it would have produced. This is the error of a run that failed, not an answer to the request that started it. ## What to do Narrow the ranges or fix some settings to one value, then start the run again. The cap is the Runs in one request row on the [Limits](https://oathra.dev//docs/limits) page. ## Example ```json { "status": "failed", "error": { "code": "combo_cap_exceeded", "message": "This run asks for more combinations than one run may test.", "detail": { "reason": "this sweep would produce 4800 run(s); the cap for one request is 2000. Narrow the selection and try again." } } } ``` --- Source: https://oathra.dev/reference/errors/credits_exhausted # credits_exhausted The run stopped because the account ran out of credits while it was running. ## What happened A running hosted run keeps topping up what it has reserved. When a top-up cannot be taken, the run is stopped. It is never an answer to a request: you read it on the run, where its status is cancelled and `cost.stopped_for_credits` is true. ## What to do Add credits, then start the run again. ## Example ```json { "status": "cancelled", "error": { "code": "credits_exhausted", "message": "The run stopped because the account ran out of credits.", "detail": {} }, "cost": { "stopped_for_credits": true } } ``` --- Source: https://oathra.dev/reference/errors/daily_run_limit # daily_run_limit An account on the free allowance can start only so many runs in one UTC day. ## What happened Accounts on the free allowance, including a trial or disputed paid account, have a daily limit on runs started. Starting another is refused with 429. The reply carries `limit` and `resets_at`. ## What to do Wait until `resets_at`, which is 00:00 UTC. ## Example ```json { "error": { "code": "daily_run_limit", "message": "You have started the most runs an account on the free allowance can start in one day. More open at 00:00 UTC.", "detail": { "limit": 10, "resets_at": "2026-10-02T00:00:00.000Z", "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/devcode_import_cooldown # devcode_import_cooldown Your code timed out while loading three times in a row, so runs that use it are paused. ## What happened Loading a file takes a limited time. Three load timeouts in a row pause every run and check that uses your own code for a while, so a file that never loads cannot keep taking machines. Other runs are not affected. The reply carries `locked_until`. ## What to do Wait until the time in `locked_until`. Then look at what your file does while it is being loaded, outside any function, and keep that short. ## Example ```json { "error": { "code": "devcode_import_cooldown", "message": "Your code timed out while loading three times in a row, so runs that use it are paused for a while.", "detail": { "locked_until": "2026-10-01T13:05:00.000Z", "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/forge_code_cooldown # forge_code_cooldown Runs that use your own code are paused for a while after repeated resource or rule breaches. ## What happened Runs that hit resource limits, or made calls the sandbox does not allow, several times in a row pause every run and check that uses your own code. Other runs are not affected. The reply carries `until`. ## What to do Wait until the time in `until`. Meanwhile read the errors of the runs that failed, and fix the code before running it again. ## Example ```json { "error": { "code": "forge_code_cooldown", "message": "Runs that use your own code are paused for a while after repeated runs hit resource limits or made calls that are not allowed.", "detail": { "until": "2026-10-01T13:05:00.000Z", "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/forge_compute_suspended # forge_compute_suspended Forge compute is suspended on the account after repeated runs broke the usage rules. ## What happened After repeated breaches of the usage rules, an account's Forge compute is suspended and every run request is refused with 403. ## What to do [Contact support](https://oathra.dev//support) to have it reviewed. ## Example ```json { "error": { "code": "forge_compute_suspended", "message": "Forge compute is suspended on your account after repeated runs broke the usage rules. Contact Oathra support to have it reviewed.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/gate_off # gate_off Forge cloud is switched off at the platform level, for every account. ## What happened Forge cloud work goes through one switch that Oathra controls, and both halves of it must be on. When either is off, every route that starts or changes cloud work answers 503 with this code. It has nothing to do with your plan or your settings. ## What to do Nothing on your side changes it. Check the [status page](https://oathra.dev//status) and try again later. Stopping a run you already started is never blocked by this switch. ## Example ```json { "error": { "code": "gate_off", "message": "Forge cloud runs are not available for this account right now.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/insufficient_credits # insufficient_credits There are not enough credits to hold what this run or upload needs. ## What happened A hosted run or an upload holds credits before it starts. When your balance cannot cover even the first hold, the request is refused with 402 and nothing is queued. The reply carries `available` and `required`. ## What to do Add credits or wait for your monthly credits, or make the work smaller. A run that does not fit under your monthly spending cap is a different code, `spend_cap_reached`. ## Example ```json { "error": { "code": "insufficient_credits", "message": "Not enough credits to run this.", "detail": { "available": 12, "required": 40, "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/listing_platform_account_missing # listing_platform_account_missing Forge could not start the test of a published strategy. ## What happened The tests of published strategies run on an account of Forge's own. That account was not available, so the test did not start. Nothing is wrong with the strategy. ## What to do Try again later. If it lasts, [contact support](https://oathra.dev//support). ## Example ```json { "error": { "code": "listing_platform_account_missing", "message": "Forge cannot start strategy tests right now. Try again later.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/listing_platform_strategy_limit # listing_platform_strategy_limit Forge has no room to take on another strategy for testing. ## What happened The account Forge tests published strategies on holds as many strategies as it may, so this one could not be added. Nothing is wrong with the strategy. ## What to do Try again later. If it lasts, [contact support](https://oathra.dev//support). ## Example ```json { "error": { "code": "listing_platform_strategy_limit", "message": "Forge cannot take on another strategy for testing right now. Try again later.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/listing_setup_data_missing # listing_setup_data_missing Forge does not hold the licensed market data the test of this strategy needs. ## What happened The standard setup for the strategy's market needs market data that Forge holds a licence for. Forge does not hold it for this market yet, so the strategy cannot be tested and the listing has no verdict. ## What to do Nothing can be changed in the strategy. The listing gets its verdict once Forge holds the data. ## Example ```json { "error": { "code": "listing_setup_data_missing", "message": "Forge does not hold the licensed market data this test needs yet, so this strategy cannot be tested.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/listing_setup_invalid # listing_setup_invalid The standard setup for the strategy's market is missing or cannot be run. ## What happened Every market has one standard setup that all strategies listed for it are tested on. The setup for this market is missing or is not valid, so no test can run on it. Nothing is wrong with the strategy. ## What to do [Contact support](https://oathra.dev//support) and name the listing. ## Example ```json { "error": { "code": "listing_setup_invalid", "message": "The standard setup for this market is missing or cannot be run.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/listing_verdict_budget_reached # listing_verdict_budget_reached Forge has run as many strategy tests as it runs in one day. ## What happened Forge tests each published strategy on a standard setup, and it runs a limited number of those tests a day across all listings. The test for this listing waits for the next day. ## What to do Nothing. The test stays queued and is tried again after 00:00 UTC. ## Example ```json { "error": { "code": "listing_verdict_budget_reached", "message": "Forge has reached the limit for strategy tests today. Try again after 00:00 UTC.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/listing_version_not_runnable # listing_version_not_runnable The published version of the strategy cannot be tested. ## What happened The version the listing points at was removed or withdrawn, or it has not passed its check, so Forge has nothing it can run. ## What to do Publish a version that has passed its check. [Check before publishing](https://oathra.dev//docs/how-to/check-before-publishing) lists what the check looks at. ## Example ```json { "error": { "code": "listing_version_not_runnable", "message": "This version of the strategy cannot be tested. It may have been removed or withdrawn, or it has not passed its check.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/max_agents # max_agents Your account already has the most paired machines it may. ## What happened Asking for a new pairing code while you are at the limit is refused with 409 and this code. Revoked machines do not count. ## What to do Revoke a machine you no longer use from the Engine page in the web app, then pair again. The limit is the Local agents per account row on the [Limits](https://oathra.dev//docs/limits) page. ## Example ```json { "error": "You have reached the maximum number of paired machines.", "code": "max_agents" } ``` --- Source: https://oathra.dev/reference/errors/no_combos_produced # no_combos_produced The settings produce no combinations to test, so nothing ran. ## What happened A run that would test zero combinations is an error, not an empty success. The usual reasons are a symbol that has no data yet, a setting that cannot be resolved, or nothing selected. The run ends before it spends any compute. ## What to do Open the run's detail for the reasons it lists, fix the selection, and start the run again. ## Example ```json { "status": "failed", "error": { "code": "no_combos_produced", "message": "These settings produce no combinations to test.", "detail": { "reason": "this selection produces zero combinations" } } } ``` --- Source: https://oathra.dev/reference/errors/not_leased # not_leased The run is not currently held by the machine that reported on it. ## What happened A run is leased to one machine at a time, for a limited time. A machine that reports progress, stores a result or finishes a run it does not hold gets 409 with this code. Another machine may have picked the run up, or the lease may have run out. ## What to do Nothing needs fixing on the run itself. Open its page to see which state it reached. If this repeats on a local machine, check that the machine stays online while it works. ## Example ```json { "error": "This run is not currently held by this machine.", "code": "not_leased" } ``` --- Source: https://oathra.dev/reference/errors/pair_failed # pair_failed The pairing code is wrong, has expired or was already used. ## What happened A pairing code works once and expires 10 minutes after it is made. A code that matches no live code is refused with 401, and the reply never says which of the three it was. ## What to do Get a new code from the Engine page in the web app and enter it exactly. The dashes are only there to be read and the case does not matter, but spaces at either end do. ## Example ```json { "error": "That pairing code is wrong, expired, or already used.", "code": "pair_failed" } ``` --- Source: https://oathra.dev/reference/errors/payload_too_large # payload_too_large The request body is larger than the route accepts. ## What happened Every route that takes a body has a size limit. A body over it is refused with 413. Saving a strategy, saving chart settings, a drawing or an alert, uploading data, and starting a run each have their own limit. ## What to do Send less. For a strategy, the limit is the Largest saved strategy row on the [Limits](https://oathra.dev//docs/limits) page. ## Example ```json { "error": "Request body is too large.", "code": "payload_too_large" } ``` --- Source: https://oathra.dev/reference/errors/preset_limit # preset_limit You already have the most saved setups your account may hold. ## What happened A new saved setup past the limit is refused with 409 and this code. ## What to do Delete a setup you no longer use, then save again. ## Example ```json { "error": "You have 50 saved setups. Delete one to save another.", "code": "preset_limit" } ``` --- Source: https://oathra.dev/reference/errors/preset_name_taken # preset_name_taken A saved setup already has this name. ## What happened Saved setups are named, and two of yours cannot share a name. The save is refused with 409 and nothing is changed. ## What to do Choose another name, or change the setup that already has it. ## Example ```json { "error": "A saved setup already has this name.", "code": "preset_name_taken" } ``` --- Source: https://oathra.dev/reference/errors/preset_not_found # preset_not_found That saved setup does not exist or is not yours. ## What happened A saved setup you did not make and one that does not exist get the same answer, 404. ## What to do Check the id and that the setup is yours. ## Example ```json { "error": "Saved setup not found.", "code": "preset_not_found" } ``` --- Source: https://oathra.dev/reference/errors/published_cap_exceeded # published_cap_exceeded Your account already stores the most published runs it may. ## What happened Publishing sends a run you computed on your own machine to your account. The number of published runs is capped per account, and one more is refused with 409 and this code. Runs that Forge ran for you do not count against this cap. ## What to do Publishing stops until the count is lower. The cap is the Finished runs kept row on the [Limits](https://oathra.dev//docs/limits) page. ## Example ```json { "error": "This run could not be stored.", "code": "published_cap_exceeded" } ``` --- Source: https://oathra.dev/reference/errors/quota_exhausted # quota_exhausted An account allowance is full: custom indicators, their versions, or unfinished indicator chats. ## What happened Two places return this today. Uploading a custom indicator, or one more version of one, past your limit answers 429 with `limit`, `used` and `scope`. Starting another indicator conversion chat while you have the most unfinished chats allowed answers 429 with `limit` and `used`. Run requests no longer return it: credits replaced the monthly request quota, so a run you cannot afford is `insufficient_credits`. ## What to do Delete an indicator or a version you no longer need, or finish or discard an unfinished chat, then try again. The limits are the Custom indicators and Versions per indicator rows on the [Limits](https://oathra.dev//docs/limits) page. ## Example ```json { "error": "You have reached your limit of unfinished chats. Finish or discard one first.", "code": "quota_exhausted", "limit": 3, "used": 3 } ``` --- Source: https://oathra.dev/reference/errors/spend_cap_raise_first # spend_cap_raise_first Resuming paused runs needs a higher cap first, because the month's spending is already at it. ## What happened Resuming runs that were paused at the cap works only when one run's hold fits under it. When this month's spending is already at the cap, the resume is refused with 409 and the reply carries `cap` and `committed`. ## What to do Raise the cap on the Compute page in the web app, then resume. ## Example ```json { "error": { "code": "spend_cap_raise_first", "message": "This month's Forge spending is already at your cap. Raise the cap, then resume.", "detail": { "cap": 500, "committed": 500, "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/spend_cap_reached # spend_cap_reached This run does not fit under your monthly spending cap. ## What happened You can set a cap on what Forge spends in a month. A run that needs more room than is left under it, or any run while hosted runs are paused at the cap, is refused with 409. The reply carries `reason`, `cap`, `committed` and `room`. A run that was already queued can also end with this code when the machine that claims it finds no room left. ## What to do Raise the cap or resume runs on the Compute page in the web app, then start the run again. ## Example ```json { "error": { "code": "spend_cap_reached", "message": "This run does not fit under your monthly spending cap. Raise the cap or resume runs on the Compute page.", "detail": { "reason": "cap_reached", "cap": 500, "committed": 500, "room": 0, "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/strategy_check_bad_format # strategy_check_bad_format A check's report is exported as json or csv, and the request named another format. ## What happened The export route takes a `format` of `json` or `csv`. Anything else is refused with 400. ## What to do Ask for `json` or `csv`. ## Example ```json { "error": "Choose json or csv.", "code": "strategy_check_bad_format" } ``` --- Source: https://oathra.dev/reference/errors/strategy_check_code_only # strategy_check_code_only Checks run on code versions, and this version is a Studio version. ## What happened The check reads a strategy file. A Studio version has no file to read, so starting a check on one is refused with 409. ## What to do Start the check on a version written as code. ## Example ```json { "error": "Checks run on code versions.", "code": "strategy_check_code_only" } ``` --- Source: https://oathra.dev/reference/errors/strategy_check_not_finished # strategy_check_not_finished The check is still running, so its report cannot be exported yet. ## What happened An export holds the whole report of a check. A check that has not finished has no whole report, so the export is refused with 409. ## What to do Read the check until its state is finished, then export it. ## Example ```json { "error": "This check has not finished.", "code": "strategy_check_not_finished" } ``` --- Source: https://oathra.dev/reference/errors/strategy_check_not_found # strategy_check_not_found That check does not exist for this version. ## What happened A check is named by its strategy, its version and its own id. An id the version does not hold is answered with 404. ## What to do List the version's checks and use an id from the list. ## Example ```json { "error": "Check not found.", "code": "strategy_check_not_found" } ``` --- Source: https://oathra.dev/reference/errors/strategy_check_not_ready # strategy_check_not_ready The version has not passed its file check yet, so the full check cannot start. ## What happened Saving a code version checks that the file loads. The full check starts from a version that passed. One that failed, or is still being checked, is refused with 409. ## What to do Wait for the file check to finish, or fix what it reports and save a new version. ## Example ```json { "error": "This version has not passed its file check yet.", "code": "strategy_check_not_ready" } ``` --- Source: https://oathra.dev/reference/errors/strategy_conflict # strategy_conflict The strategy was changed somewhere else after the copy your save was based on. ## What happened Every strategy has a revision, and an update names the revision it was made from. If the strategy has moved on since, the save is refused with 409 and this code, and nothing is overwritten. The reply holds the newer copy in `strategy`. ## What to do Read the newer copy, apply your change to it and save again with its revision. The sample shows only part of `strategy`. ## Example ```json { "error": "This strategy was changed somewhere else. The newer copy is shown.", "code": "strategy_conflict", "strategy": { "id": "6d2f3b6e-4c9a-4f6e-8a2d-9b1e5c7a0d42", "name": "Breakout", "revision": 8 } } ``` --- Source: https://oathra.dev/reference/errors/strategy_limit # strategy_limit You already have the most active strategies your account may hold. ## What happened Creating or importing a strategy, or restoring an archived one, counts the strategies that are not archived. At the limit it is refused with 409 and this code. ## What to do Archive or delete a strategy you no longer use, then try again. ## Example ```json { "error": "You have 200 strategies. Archive or delete one to add another.", "code": "strategy_limit" } ``` --- Source: https://oathra.dev/reference/errors/strategy_not_checked # strategy_not_checked That version of a code strategy has not passed its check yet. ## What happened A version written as code is checked after it is saved, and only a version that passed can be run. Naming one that failed its check, or whose check has not finished, is refused with 409 before anything is queued. ## What to do Fix what the check reports, save a new version and run that one. [Check before publishing](https://oathra.dev//docs/how-to/check-before-publishing) lists what the check looks at. ## Example ```json { "error": { "code": "strategy_not_checked", "message": "This version of the strategy has not passed its check yet.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/strategy_not_declarative # strategy_not_declarative The strategy uses code the Studio has no block for. ## What happened The Studio shows a strategy as blocks, and a block exists for part of the language only. This strategy uses something outside that part, so it cannot be shown as blocks. The reply is 422, and `reasons` lists each one with its line. ## What to do Keep editing it as code, or rewrite the lines the reply names so that they use what the Studio shows. ## Example ```json { "error": "This strategy uses code the Studio cannot show.", "code": "strategy_not_declarative", "reasons": [ { "line": 42, "code": "statement", "message": "A loop has no block in the Studio." } ] } ``` --- Source: https://oathra.dev/reference/errors/strategy_not_found # strategy_not_found That strategy does not exist, is archived, or is not yours. ## What happened A strategy you did not make and a strategy that does not exist get the same answer, 404, so the reply never tells you whether someone else's id exists. A run can only name a strategy that is yours and not archived. ## What to do Check the id, that the strategy is yours, and that it is not archived. ## Example ```json { "error": { "code": "strategy_not_found", "message": "The saved strategy was not found.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/strategy_revision_not_found # strategy_revision_not_found The strategy has no version with that number. ## What happened A run can name a version of a strategy with `strategy_revision`. A number the strategy never had is refused with 404 before anything is queued. ## What to do Use a version number the strategy has. ## Example ```json { "error": { "code": "strategy_revision_not_found", "message": "That version of the strategy was not found.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/strategy_spec_invalid # strategy_spec_invalid The Strategy Spec does not follow its schema. ## What happened A Studio version is saved as a Strategy Spec, a JSON document with a published schema. A spec that breaks the schema is refused with 400 and nothing is written. `path` points at the first part that is wrong. ## What to do Fix the part `path` names. The [Strategy Spec schema](https://oathra.dev//reference/schemas/strategy-spec) lists every field. ## Example ```json { "error": "forge_api must be a whole number, like 1.", "code": "strategy_spec_invalid", "path": "forge_api" } ``` --- Source: https://oathra.dev/reference/errors/strategy_spec_legacy # strategy_spec_legacy The Strategy Spec has no forge_api, so it is in the older Studio format. ## What happened Every Strategy Spec names the `FORGE_API` level it was written for. One with no `forge_api` was made by the earlier Studio and is refused with 400. ## What to do Open the strategy in the Studio, which updates it, and save it again. ## Example ```json { "error": "This strategy is in the older Studio format. Open it in the Studio to update it.", "code": "strategy_spec_legacy", "path": "forge_api" } ``` --- Source: https://oathra.dev/reference/errors/strategy_spec_level_unsupported # strategy_spec_level_unsupported The Strategy Spec was saved for a FORGE_API level this version of Forge does not run. ## What happened The `forge_api` of the spec is a level this Forge does not run: one that is retired, or one newer than this version knows. The save is refused with 400. ## What to do Save the spec at a level the message lists. ## Example ```json { "error": "This strategy was saved for FORGE_API 3. This version of Forge runs FORGE_API 1.", "code": "strategy_spec_level_unsupported", "path": "forge_api" } ``` --- Source: https://oathra.dev/reference/errors/strategy_to_spec_busy # strategy_to_spec_busy Reading the code for the Studio took too long, or the service that reads it was busy. ## What happened Turning code into a Studio version runs the file in a sandbox. That did not answer in time, or the sandbox was not available, so the request is refused with 503. Nothing was changed. ## What to do Try again. ## Example ```json { "error": "Reading the code is taking longer than usual. Try again.", "code": "strategy_to_spec_busy" } ``` --- Source: https://oathra.dev/reference/errors/strategy_version_limit # strategy_version_limit The strategy already holds the most saved versions one strategy may have. ## What happened Every save of a strategy adds a version, and a strategy holds at most 500. At the limit a save is refused with 409 and nothing is written. ## What to do Start a new strategy from the latest version, or delete this one if you no longer need its history. ## Example ```json { "error": "This strategy has 500 saved versions. Delete it or start a new one.", "code": "strategy_version_limit" } ``` --- Source: https://oathra.dev/reference/errors/strategy_version_not_code # strategy_version_not_code Only a code version that passed its check can be opened in the Studio. ## What happened Opening a version in the Studio reads its code and rebuilds it as blocks. That works from a version written as code whose check has passed. A version that is already a Studio version, or whose check failed or has not finished, is refused with 409. ## What to do Wait for the check to finish, or fix what it reports and save again. ## Example ```json { "error": "Only a checked code version can open in the Studio.", "code": "strategy_version_not_code" } ``` --- Source: https://oathra.dev/reference/errors/strategy_version_not_found # strategy_version_not_found That saved version does not exist, or the strategy is not yours. ## What happened A version is named by its strategy and its number. A number the strategy does not hold, and a strategy that is not yours, get the same answer, 404. ## What to do List the strategy's versions and use a number from the list. ## Example ```json { "error": "Saved version not found.", "code": "strategy_version_not_found" } ``` --- Source: https://oathra.dev/reference/errors/tier_required # tier_required The route you called belongs to a feature that is part of the Paid plan. ## What happened Running Forge is open to every plan and is paid for in credits. Some features sit on the Paid plan, such as the verdict with its ten checks and pass or fail, and the strategy decay evidence. A route for one of those answers 403 with this code. The reply names the plan it needs in `required_tier` and yours in `current_tier`, and `upgrade_url` is the address of the plan page. ## What to do Move to the Paid plan, or leave that feature out of what you ask for. ## Example ```json { "error": "This feature requires the Paid plan.", "code": "tier_required", "required_tier": "paid", "current_tier": "starter" } ``` The reply also carries `upgrade_url`, left out of this sample. --- Source: https://oathra.dev/reference/errors/too_many_queued # too_many_queued You already have the most runs waiting or running at one time. ## What happened Each account can have a fixed number of run requests queued or running at once. Starting another while you are at that number is refused with 429 before anything is queued. ## What to do Stop a queued run or wait for one to finish, then start again. Stopping a queued run frees its place at once. The number is the Requests waiting at once row on the [Limits](https://oathra.dev//docs/limits) page. ## Example ```json { "error": { "code": "too_many_queued", "message": "You already have the most runs queued or running at once. Wait for one to finish, or stop one.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/unknown_agent # unknown_agent The machine is not paired with your account, or its pairing was revoked. ## What happened A local machine proves who it is with the key it received when it was paired. When that key matches no machine on your account, the machine's requests are refused with 401. A pairing you revoked on the Engine page stops working at once. Storing a result from a machine that is not yours answers 403 with the same code. ## What to do Pair the machine again with a new code from the Engine page in the web app. ## Example ```json { "error": "That machine is not paired, or its pairing was revoked.", "code": "unknown_agent" } ``` --- Source: https://oathra.dev/reference/errors/broker_profile_invalid # broker_profile_invalid The broker profile saved with the run could not be read. ## What happened A run carries a snapshot of the broker profile it was started with. A snapshot that is present but malformed ends that one run with this code, before it reaches the engine, instead of running on a guessed default. The reason is in `detail.reason`. ## What to do Open the broker profile in Forge settings, check it and save it again, then start the run again. ## Example ```json { "status": "failed", "error": { "code": "broker_profile_invalid", "message": "The broker profile for this run could not be read.", "detail": {} } } ``` The reply also carries `detail.reason`, left out of this sample. --- Source: https://oathra.dev/reference/errors/conversion_pair_missing # conversion_pair_missing A currency-conversion dataset for your account currency is missing. ## What happened Profit in a run is shown in your account currency. When a symbol is quoted in another currency, Forge needs the dataset for the pair that converts it, and that dataset must be ready. A missing one is refused with 409 before anything is queued, and `symbols` names the pairs. ## What to do Upload the data for the pair named in the reply, or choose an account currency that matches the symbols you test. ## Example ```json { "error": { "code": "conversion_pair_missing", "message": "A currency-conversion dataset for your account currency is missing.", "detail": { "symbols": [ "GBPUSD" ], "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/dataset_bad_symbol # dataset_bad_symbol The symbol is not 2 to 20 capital letters and digits. ## What happened The symbol is the name your runs use for this data. It must be 2 to 20 characters, capital letters and digits only. Anything else is refused with 400 before the data is processed. ## What to do Change the symbol, for example from `eurusd` to `EURUSD`. ## Example ```json { "error": { "code": "dataset_bad_symbol", "message": "Use capital letters and digits only, 2 to 20 characters.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/dataset_empty_file # dataset_empty_file The dataset record declares a size of zero bytes. ## What happened A dataset record is created with the size of the file it is for. A size that is missing or zero is refused when the record is created. The upload flow checks the file first and answers `import_empty_file`, so this code is the dataset record's own refusal. ## What to do Choose a file that has data in it. ## Example ```text This file is empty. ``` The sentence above is what the upload screen shows for an empty file. --- Source: https://oathra.dev/reference/errors/dataset_exists # dataset_exists You already have data for this symbol. ## What happened An account holds one live dataset per symbol. A second upload for a symbol that already has one, ingesting or ready, is refused with 409. A dataset that failed or was deleted does not count. The reply carries the symbol in `detail`. ## What to do Use the data you have. To replace it, delete it first; a dataset that a queued or running run uses cannot be deleted (see `dataset_in_use`). ## Example ```json { "error": { "code": "dataset_exists", "message": "You already have data for this symbol.", "detail": { "symbol": "EURUSD" } } } ``` --- Source: https://oathra.dev/reference/errors/dataset_in_use # dataset_in_use A queued or running run uses this dataset, so it cannot be deleted yet. ## What happened A dataset that a live run request names cannot be removed from under it. The delete is refused with 409, and `request_ids` lists the runs that block it. ## What to do Wait for those runs to finish, or stop them, then delete the dataset. ## Example ```json { "error": "This dataset is referenced by a queued or running run request.", "code": "dataset_in_use", "request_ids": [ "8f1c2d3e-4b5a-4c6d-9e7f-0a1b2c3d4e5f" ] } ``` --- Source: https://oathra.dev/reference/errors/dataset_ingesting # dataset_ingesting The dataset is still being processed, so it cannot be deleted yet. ## What happened A dataset that is being ingested cannot be removed until processing ends. The delete is refused with 409. ## What to do Wait for processing to finish, then delete it. ## Example ```json { "error": "This dataset is being ingested; you can remove it once it finishes.", "code": "dataset_ingesting" } ``` --- Source: https://oathra.dev/reference/errors/dataset_not_ready # dataset_not_ready A symbol the run names has no dataset that is ready. ## What happened Every symbol a run names must have a dataset whose processing has finished. A symbol with none, or whose dataset is still being processed or failed, is refused with 409 before anything is queued. The reply lists each symbol with the state it is in. Reading a dataset's bars before it is ready gives the same code. ## What to do Upload the data for that symbol, or wait until its processing has finished, then start the run again. ## Example ```json { "error": { "code": "dataset_not_ready", "message": "One or more symbols have no ready dataset.", "detail": { "symbols": [ { "symbol": "EURUSD", "status": "ingesting" } ], "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/dataset_too_large # dataset_too_large The plan for sending the file in parts is outside what is accepted. ## What happened A file is sent in parts, and the plan has to keep each part between the smallest and largest part size and the number of parts under the most allowed. A plan outside those is refused when the record is created, before any part is sent. The upload flow checks the file's size first and answers `import_too_large`. ## What to do Use a smaller file, or split it into several files. ## Example ```text This file is larger than we can accept. ``` The sentence above is what the upload screen shows for a file that is too large. --- Source: https://oathra.dev/reference/errors/digest_mismatch # digest_mismatch The uploaded parts do not add up to the checksum expected for the whole file. ## What happened When processing starts, the parts held are checked against the checksum of the whole upload. If they differ, nothing is processed and the dataset ends as failed with this code. ## What to do Upload the file again from the start. ## Example ```text The uploaded parts do not match the expected checksum. ``` The sentence above is the reason shown on the failed dataset. --- Source: https://oathra.dev/reference/errors/forge_upstream_unconfigured # forge_upstream_unconfigured Uploads are not available because the upload service is not set up. ## What happened Uploads go through a separate storage and processing service. When the address or the key for it is missing on Forge's side, the request is refused with 503 before anything is sent. This is a platform fault, not a problem with your file. ## What to do Nothing on your side changes it. Check the [status page](https://oathra.dev//status), try again later, and [contact support](https://oathra.dev//support) if it lasts. ## Example ```json { "error": { "code": "forge_upstream_unconfigured", "message": "Uploads are not available right now.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/ingest_dispatch_uncertain # ingest_dispatch_uncertain Processing has very likely started, but Forge lost the confirmation. ## What happened Once an upload is complete, Forge asks the processing service to start. If that call fails after it may have been received, Forge cannot tell whether processing began, and answers 503 with this code rather than guess. ## What to do Check the upload again in a minute. The upload keeps its data, and Retry on the upload screen asks again. ## Example ```json { "error": { "code": "ingest_dispatch_uncertain", "message": "Processing has very likely started, but it was not confirmed. Check again in a minute.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/ingest_scratch_insufficient # ingest_scratch_insufficient The file is larger than the processing service can hold while it works on it. ## What happened Processing needs about twice the file's size in working space on the machine that does it. A file over the ceiling Forge sets for that is refused with 400 when the upload is created, before a byte is sent. ## What to do Send a smaller file. ## Example ```json { "error": { "code": "ingest_scratch_insufficient", "message": "This file is larger than we can accept.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/part_digest_mismatch # part_digest_mismatch A part of the upload does not match the checksum sent with it. ## What happened Each part is sent with its own checksum, and the server checks it against what it received. A part that arrived changed, for example on a bad connection, is refused with 400 and is not stored. ## What to do Send that part again. The upload keeps the parts it already holds. ## Example ```json { "error": { "code": "part_digest_mismatch", "message": "The part does not match its checksum.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/part_plan_inconsistent # part_plan_inconsistent The number of parts does not match the file size and the part size. ## What happened An upload declares its size, the size of each part and the number of parts. The number must be exactly the file size divided by the part size, rounded up. Otherwise a client could store far more than the size it declared, so the request is refused with 400. ## What to do This comes from a client that computed the plan wrongly. Use the upload screen, or fix the plan: the number of parts is the file size divided by the part size, rounded up. ## Example ```json { "error": { "code": "part_plan_inconsistent", "message": "This file is larger than we can accept.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/parts_missing # parts_missing Some parts of the upload have not been received. ## What happened Finishing an upload checks that every part from the first to the last is held. When some are not, the answer is 409 and the reply lists the first missing part numbers in `missing` and how many are missing in `missing_count`. The same code ends an ingest that finds a part gone. ## What to do Send the missing parts, then finish the upload again. ## Example ```json { "error": { "code": "parts_missing", "message": "Some parts have not been received.", "detail": { "missing": [ 3, 7 ], "missing_count": 2 } } } ``` --- Source: https://oathra.dev/reference/errors/parts_unavailable # parts_unavailable Storage did not answer in time. ## What happened Uploads are kept in storage that Forge reaches over the network. A call that times out or fails to connect is answered with 503 and the header `Retry-After`. Nothing is lost: the request did not change anything. ## What to do Try again in a minute. ## Example ```json { "error": { "code": "parts_unavailable", "message": "Storage is slow to answer. Try again in a minute.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/storage_frozen # storage_frozen Your storage is frozen until the storage balance is paid. ## What happened When storage charges are unpaid, the account's storage is frozen, and new uploads are refused with 409 until the balance is paid. ## What to do Pay the balance, then upload again. ## Example ```json { "error": { "code": "storage_frozen", "message": "Your storage is frozen until the balance is paid.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/storage_quota_exceeded # storage_quota_exceeded The file does not fit in the storage your plan includes. ## What happened Before a file is accepted, Forge adds up your stored data, the uploads still open and the size of this file, and compares the total with your storage. A total over it is refused with 409 and nothing is stored. ## What to do Delete data you no longer use, or send a smaller file. The storage included is the Storage included row on the [Limits](https://oathra.dev//docs/limits) page. ## Example ```json { "error": { "code": "storage_quota_exceeded", "message": "This file does not fit in your storage.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/ticket_budget_exhausted # ticket_budget_exhausted The upload link has been used for as many requests as it allows. ## What happened An upload link is good for a fixed number of part requests: the number of parts plus 32 for retries. When it is spent, the upload service refuses the next request with this code. The upload screen words it for you, so there is no fixed sentence to quote. ## What to do Refresh the upload to get a new link, then send the parts that are missing. ## Example ```text code: ticket_budget_exhausted ``` --- Source: https://oathra.dev/reference/errors/custom_indicator_not_available # custom_indicator_not_available A custom indicator in the run does not exist, is not yours, or has not passed its checks. ## What happened Those three cases get the same answer on purpose, 403, so the reply never tells you whether someone else's id exists. A run that was queued can also end with this code when an indicator it pins is no longer available to the account. ## What to do Use a version of your own indicator that is marked runnable. A version becomes runnable when it passes its checks. ## Example ```json { "error": { "code": "custom_indicator_not_available", "message": "One or more custom indicators in this run are not available to your account.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/custom_indicators_not_publishable # custom_indicators_not_publishable A run cannot be published because a custom indicator it used is no longer available. ## What happened Publishing stores a run you computed on your own machine, together with the version of each custom indicator it used. When Forge cannot pin a version, because it is no longer available, the store is refused with 409. ## What to do Run it again with indicators that are available, then publish that run. ## Example ```json { "error": "This run could not be stored.", "code": "custom_indicators_not_publishable" } ``` --- Source: https://oathra.dev/reference/errors/description_too_short # description_too_short The description is too short to build an indicator from. ## What happened A chat message that describes an indicator needs at least 24 characters that are not spaces. A shorter one is refused with 400 before anything is reserved or charged. ## What to do Say more about what the indicator should show. ## Example ```json { "error": "Tell us a bit more \u2014 at least 24 characters.", "code": "description_too_short" } ``` --- Source: https://oathra.dev/reference/errors/duplicate_indicator_name # duplicate_indicator_name You already have an indicator with that name. ## What happened Two live indicators of yours cannot share a name. Creating one, or renaming one, to a name another of yours has is refused with 409. ## What to do Choose another name, or rename the indicator that has it. ## Example ```json { "error": "You already have an indicator with that name.", "code": "duplicate_indicator_name" } ``` --- Source: https://oathra.dev/reference/errors/duplicate_indicator_versions # duplicate_indicator_versions The run names two versions of the same custom indicator. ## What happened A run uses one version of each custom indicator. Naming two versions of the same one is refused with 409 before anything is queued. ## What to do Keep one version of that indicator in the run. ## Example ```json { "error": { "code": "duplicate_indicator_versions", "message": "This run names two versions of the same custom indicator.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/expert_advisor_not_supported # expert_advisor_not_supported The file places trades, so it is an Expert Advisor and not an indicator. ## What happened Only indicators can be converted. A file that the scan identifies as an Expert Advisor is refused with 400, and the reply lists the places that gave it away in `markers`. ## What to do Convert the indicator that the Expert Advisor uses, and write the trading rules as a Forge strategy. ## Example ```json { "error": "This file places trades (an Expert Advisor) \u2014 only indicators can be converted.", "code": "expert_advisor_not_supported" } ``` The reply also carries `markers`, left out of this sample. --- Source: https://oathra.dev/reference/errors/file_too_large # file_too_large The file is larger than the limit for an indicator's source. ## What happened A source file, or an uploaded indicator file, over 256 KiB is refused with 413 and is not stored. ## What to do Shorten the file, or split it into a main file and the files it includes. ## Example ```json { "error": "The file is too large. The limit is 256 KiB.", "code": "file_too_large" } ``` --- Source: https://oathra.dev/reference/errors/indicator_deleted # indicator_deleted The indicator was deleted, so its chat cannot continue. ## What happened A chat, or a rename, that belongs to an indicator you deleted is refused with 409. ## What to do Start a new chat to build the indicator again. ## Example ```json { "error": "This indicator has been deleted.", "code": "indicator_deleted" } ``` --- Source: https://oathra.dev/reference/errors/invalid_primary_output # invalid_primary_output The indicator has several outputs and no main one. ## What happened An indicator that declares more than one output must say which one is the signal, with `primary`, and it must be one of the declared outputs. Which one is the signal is never guessed, so a spec without one is refused. ## What to do Tell the chat which output is the main signal, or give the indicator a single output. ## Example ```text This spec has more than one output but doesn't say which one is the main signal. ``` The sentence above is what the chat shows. --- Source: https://oathra.dev/reference/errors/model_pricing_unconfigured # model_pricing_unconfigured The assistant is not fully set up, so the turn was not started. ## What happened A turn is priced before it starts. When the price of the selected model is not set on Forge's side, the turn is refused with 503 and nothing is reserved or charged. ## What to do Nothing on your side changes it. Try again later and check the [status page](https://oathra.dev//status). ## Example ```json { "error": "Forge indicator building is not available right now \u2014 nothing has been charged.", "code": "model_pricing_unconfigured" } ``` --- Source: https://oathra.dev/reference/errors/model_unavailable # model_unavailable The assistant's model could not be found, so the turn was not started. ## What happened When the model Forge is set to use for a turn cannot be found, the turn is refused with 503 and nothing is reserved or charged. ## What to do Nothing on your side changes it. Try again later and check the [status page](https://oathra.dev//status). ## Example ```json { "error": "Forge indicator building is not available right now \u2014 nothing has been charged.", "code": "model_unavailable" } ``` --- Source: https://oathra.dev/reference/errors/no_executor_available # no_executor_available No machine is available to check or build indicators right now. ## What happened Checking an indicator, and the assistant that builds one, run only on Forge's hosted machines. When none has reported in recently, the request is refused with 503 before anything is reserved, so nothing is charged. ## What to do Try again shortly. Check the [status page](https://oathra.dev//status) if it lasts. ## Example ```json { "error": "Forge indicator building is not available right now \u2014 nothing has been charged.", "code": "no_executor_available" } ``` --- Source: https://oathra.dev/reference/errors/rights_not_confirmed # rights_not_confirmed A conversion needs your confirmation that you own the code or may convert it. ## What happened Converting code from another platform needs a current rights statement, confirmed by you. Without it, starting a conversion chat is refused with 409 and uploading a source file with 400. No AI call is made and nothing is charged. ## What to do Confirm the rights statement on the conversion screen, using the current version of it, then start again. ## Example ```json { "error": "You must confirm your rights to this source before starting a conversion.", "code": "rights_not_confirmed" } ``` --- Source: https://oathra.dev/reference/errors/source_too_large_for_model # source_too_large_for_model The conversation and source are too long for the assistant to read in one turn. ## What happened Each turn sends the conversation and the source to the assistant, and the text has a size limit. A turn over it is refused with 413 before anything is reserved, so nothing is charged. ## What to do Start a new chat with a smaller or simpler source. ## Example ```json { "error": "This conversation is too long for a single turn \u2014 start a new chat.", "code": "source_too_large_for_model" } ``` --- Source: https://oathra.dev/reference/errors/state_conflict # state_conflict The chat moved on, or is not ready for that action. ## What happened A chat moves through states, and an action is accepted only in the state it belongs to. When the chat is in another state, because something else changed it, because the text you were confirming changed, or because another turn is still running, the action is refused with 409 and nothing is changed. The reply names the chat's state in `state`. ## What to do Refresh the chat to see where it is, then repeat the action. If another turn is still running, wait for it to finish. ## Example ```json { "error": "This chat is not ready for that action right now.", "code": "state_conflict", "state": "in_flight_elsewhere" } ``` --- Source: https://oathra.dev/reference/errors/too_many_files # too_many_files The source bundle already holds the most files it may. ## What happened An indicator's source can be a bundle of a few files. One more than the bundle may hold is refused with 400. ## What to do Combine the files, or leave out the ones the main file does not include. ## Example ```json { "error": "A bundle can hold at most 5 files.", "code": "too_many_files" } ``` --- Source: https://oathra.dev/reference/errors/unresolved_translation # unresolved_translation Some parts of the source could not be translated, and each needs your decision. ## What happened A conversion lists the parts of the source that have no direct Forge equivalent. Confirming the spec is refused with 409 while any of them has no decision recorded. ## What to do Go back to the chat and answer each part it asks about, then confirm again. ## Example ```json { "error": "Some parts of the uploaded source need your answer before this spec can be confirmed \u2014 go back to the chat.", "code": "unresolved_translation" } ``` --- Source: https://oathra.dev/reference/errors/unsupported_file # unsupported_file The file is not a kind that can be converted. ## What happened Conversion accepts MQL5 source files, with the extensions `.mq5` and `.mqh`, sent as the raw body of the request. Anything else, or a request with no file body, is refused with 400. The reply can carry a `reason`. ## What to do Send the indicator's own `.mq5` source file, or an `.mqh` file it includes. ## Example ```json { "error": "A raw file body is required.", "code": "unsupported_file" } ``` --- Source: https://oathra.dev/reference/errors/bad_request # bad_request The request is not valid: a field is missing, malformed, or over a size the service accepts. ## What happened A request that does not match the shape the service expects is refused with 400 before anything runs. The sandbox's job request, and every route of the API, use this code for a body or a parameter they cannot read. ## What to do Check the body and the parameters against the reference, then send the request again. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "bad_request", "message": "The job request is not valid.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/cap_cpu # cap_cpu The script used more CPU time than allowed. ## What happened A script runs with a limit on the CPU time it may use. When it goes over, the sandbox stops it and answers with this code. `detail` names the cap that tripped (`cpu_ms`), how much the script used and how much was allowed. The caps differ for a chart and for a run, and the code sandbox section of the [Limits](https://oathra.dev//docs/limits) page lists them. ## What to do Do less work per call: compute over fewer bars, and avoid Python loops over every bar where a built-in indicator or a series operation does the same job. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "cap_cpu", "message": "The script used more CPU time than allowed.", "detail": { "cap": "cpu_ms", "used": 2310, "allowed": 2000 } } } ``` --- Source: https://oathra.dev/reference/errors/cap_disk # cap_disk The script wrote more scratch data than allowed. ## What happened A run has a limit on the scratch space on disk. When the script goes over, the sandbox stops it and the run ends with this code. `detail` names the cap (`scratch_mb`), how much was used and how much was allowed. The caps for a run are in the code sandbox section of the [Limits](https://oathra.dev//docs/limits) page. ## What to do Write less to disk, or none: keep intermediate results in memory. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "cap_disk", "message": "The script wrote more scratch data than allowed.", "detail": { "cap": "scratch_mb", "used": 640, "allowed": 512 } } } ``` --- Source: https://oathra.dev/reference/errors/cap_drawings # cap_drawings The script produced more drawings than allowed. ## What happened A script runs with a limit on the drawings it may use. When it goes over, the sandbox stops it and answers with this code. `detail` names the cap that tripped (`max_drawings`), how much the script used and how much was allowed. The caps differ for a chart and for a run, and the code sandbox section of the [Limits](https://oathra.dev//docs/limits) page lists them. ## What to do Draw fewer objects, for example only on the bars that matter. Each line, box and label counts. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "cap_drawings", "message": "The script produced more drawings than allowed.", "detail": { "cap": "max_drawings", "used": 620, "allowed": 500 } } } ``` --- Source: https://oathra.dev/reference/errors/cap_idle # cap_idle The script stopped making progress, so it was ended. ## What happened A run has a limit on the time without making progress. When the script goes over, the sandbox stops it and the run ends with this code. `detail` names the cap (`idle_ms`), how much was used and how much was allowed. The caps for a run are in the code sandbox section of the [Limits](https://oathra.dev//docs/limits) page. ## What to do Find what the script is waiting on, or looping on without moving forward, and remove it. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "cap_idle", "message": "The script stopped making progress, so it was ended.", "detail": { "cap": "idle_ms", "used": 61000, "allowed": 60000 } } } ``` --- Source: https://oathra.dev/reference/errors/cap_import # cap_import The script took too long to load. ## What happened A run has a limit on the time to load. When the script goes over, the sandbox stops it and the run ends with this code. `detail` names the cap (`import_ms`), how much was used and how much was allowed. The caps for a run are in the code sandbox section of the [Limits](https://oathra.dev//docs/limits) page. ## What to do Look at what your file does while it is being loaded, outside any function, and keep that short. Three load timeouts in a row pause your code runs: see `devcode_import_cooldown`. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "cap_import", "message": "The script took too long to load.", "detail": { "cap": "import_ms", "used": 21500, "allowed": 20000 } } } ``` --- Source: https://oathra.dev/reference/errors/cap_memory # cap_memory The script used more memory than allowed. ## What happened A script runs with a limit on the memory it may use. When it goes over, the sandbox stops it and answers with this code. `detail` names the cap that tripped (`mem_mb`), how much the script used and how much was allowed. The caps differ for a chart and for a run, and the code sandbox section of the [Limits](https://oathra.dev//docs/limits) page lists them. ## What to do Keep fewer values alive at once: build one series at a time, and drop intermediate lists and tables you no longer use. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "cap_memory", "message": "The script used more memory than allowed.", "detail": { "cap": "mem_mb", "used": 560, "allowed": 512 } } } ``` --- Source: https://oathra.dev/reference/errors/cap_outputs # cap_outputs The script produced more output than allowed. ## What happened A script runs with a limit on the outputs it may use. When it goes over, the sandbox stops it and answers with this code. `detail` names the cap that tripped (`max_outputs`), how much the script used and how much was allowed. The caps differ for a chart and for a run, and the code sandbox section of the [Limits](https://oathra.dev//docs/limits) page lists them. ## What to do Return fewer series. Each named output counts. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "cap_outputs", "message": "The script produced more output than allowed.", "detail": { "cap": "max_outputs", "used": 80, "allowed": 64 } } } ``` --- Source: https://oathra.dev/reference/errors/cap_processes # cap_processes The script started more processes than allowed. ## What happened A run has a limit on the number of processes. When the script goes over, the sandbox stops it and the run ends with this code. `detail` names the cap (`processes`), how much was used and how much was allowed. The caps for a run are in the code sandbox section of the [Limits](https://oathra.dev//docs/limits) page. ## What to do Do the work in the script itself instead of starting other processes. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "cap_processes", "message": "The script started more processes than allowed.", "detail": { "cap": "processes", "used": 70, "allowed": 64 } } } ``` --- Source: https://oathra.dev/reference/errors/cap_wall # cap_wall The script ran longer than allowed. ## What happened A script runs with a limit on the elapsed time it may use. When it goes over, the sandbox stops it and answers with this code. `detail` names the cap that tripped (`wall_ms`), how much the script used and how much was allowed. The caps differ for a chart and for a run, and the code sandbox section of the [Limits](https://oathra.dev//docs/limits) page lists them. ## What to do Do less work per call, and check that the script does not wait on anything. Time spent waiting counts here even when the CPU is idle. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "cap_wall", "message": "The script ran longer than allowed.", "detail": { "cap": "wall_ms", "used": 5400, "allowed": 5000 } } } ``` --- Source: https://oathra.dev/reference/errors/forbidden_call # forbidden_call The script made a call the sandbox does not allow. ## What happened The sandbox lets a script read the bars it is given and compute, and nothing else. A call that opens a network connection, starts a new process, writes a file, or reaches into the kernel is refused when it is made, and the answer carries the line of your own file. ## What to do Remove the call at that line. Data and settings come in through the bars and the settings Forge gives your code, and results go out as outputs. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "forbidden_call", "message": "The script made a call the sandbox does not allow.", "detail": { "file": "indicator.py", "line": 5 } } } ``` --- Source: https://oathra.dev/reference/errors/forge_api_unsupported # forge_api_unsupported The script targets a Forge API level this engine does not support. ## What happened A file declares the Forge API level it was written for with a `FORGE_API` line. When the engine that runs it does not support that level, the script is refused before it runs. ## What to do Set `FORGE_API` to a level the engine supports. The supported levels are listed on [Versions and the Forge API](https://oathra.dev//docs/concepts/versions-and-forge-api). ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "forge_api_unsupported", "message": "The script targets a Forge API level this engine does not support.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/internal # internal Something went wrong on Forge's side, not in your code or your request. ## What happened The sandbox, or a route of the API, hit a fault of its own. Nothing in your file or request caused it, and the reply carries no detail that could point at Forge's internals. ## What to do Try again shortly. If it keeps happening, [contact support](https://oathra.dev//support) with the request id. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "internal", "message": "The sandbox could not run this script.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/sandbox_unavailable # sandbox_unavailable The code sandbox is not available right now. Try again in a few minutes. ## What happened Developer code runs only in the code sandbox. When it cannot be reached, a chart, a check or a run that needs it is refused or ends with this code. It says nothing about your code. ## What to do Try again in a few minutes, and check the [status page](https://oathra.dev//status). ## Example ```json { "error": { "code": "sandbox_unavailable", "message": "The code sandbox is not available right now. Try again in a few minutes.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/user_exception # user_exception The script raised an error. ## What happened The script raised an error of its own while it ran. The sandbox reports only the file and the line in your own code, never a path or a trace from Forge's side, so a message cannot leak how the machine is built. ## What to do Open the file at the line in `detail` and fix what it does there. Run it on the same bars again to check. ## Example ```json { "request_id": "7d2b1c44-95a0-4e3b-8a6f-1c0d9e2f5a37", "ok": false, "error": { "code": "user_exception", "message": "The script raised an error.", "detail": { "file": "indicator.py", "line": 12 } } } ``` --- Source: https://oathra.dev/reference/errors/ACCOUNT_PENDING_DELETION # ACCOUNT_PENDING_DELETION The account is scheduled for deletion. ## What happened An account that asked to be deleted keeps a grace period of 14 days. During it, routes are refused with 403 and this code. ## What to do Cancel the deletion in the web app to use the account again. ## Example ```json { "error": { "code": "ACCOUNT_PENDING_DELETION", "message": "This account is scheduled for deletion. Cancel the deletion to use it again.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/MFA_REQUIRED # MFA_REQUIRED The session needs two-step verification before it can use this route. ## What happened An account with a second factor must finish it before its session can call most routes. A session that has not is refused with 403 and this code. ## What to do Finish the two-step verification, then send the request again. ## Example ```json { "error": { "code": "MFA_REQUIRED", "message": "This session needs two-step verification. Finish it, then try again.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/account_on_hold # account_on_hold Forge is paused on the account. ## What happened Oathra has put a hold on the account's use of Forge. Run requests and checks are refused with 423 until the hold is lifted. ## What to do Read the notification that explains the hold. If you think it is a mistake, [contact support](https://oathra.dev//support). ## Example ```json { "error": { "code": "account_on_hold", "message": "Forge is paused on your account. Your notifications say why.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/agent_upgrade_required # agent_upgrade_required The machine that took the run is older than the run needs. ## What happened When a machine that is too old to read a run's execution settings safely takes the run, the run ends with this code instead of running on a guess. ## What to do Update the machine, then run again. ## Example ```json { "status": "failed", "error": { "code": "agent_upgrade_required", "message": "The machine that took this run needs an update before it can run it.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/auth_unavailable # auth_unavailable Sign-in could not be checked right now. ## What happened The token is checked with the sign-in service. When that check itself fails, the request is refused with 401 and this code. It says nothing about your token. ## What to do Try again shortly. ## Example ```json { "error": { "code": "auth_unavailable", "message": "Sign-in could not be checked right now. Try again shortly.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/code_on_hold # code_on_hold Running your own code is paused on the account. Built-in strategies still run. ## What happened Oathra has put a hold on running the account's own code. A run or a check that uses it is refused with 423. Runs of built-in strategies are not affected. ## What to do Read the notification that explains the hold. If you think it is a mistake, [contact support](https://oathra.dev//support). ## Example ```json { "error": { "code": "code_on_hold", "message": "Running your own code is paused on your account. Built-in strategies still run. Your notifications say why.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/code_paused # code_paused Running developer code is paused on Forge for everyone. Built-in strategies still run. ## What happened Oathra has stopped developer code across Forge, for every account, while a problem is looked at. A run or a check that uses your own code is refused with 503. ## What to do Wait and try again. The [status page](https://oathra.dev//status) shows the state of Forge. ## Example ```json { "error": { "code": "code_paused", "message": "Running your own code is paused on Forge right now. Built-in strategies still run.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/code_status_unavailable # code_status_unavailable Forge could not confirm that your code may run, so it did not start it. ## What happened Before your own code runs, Forge reads whether code is stopped and whether the account has a hold. When that read fails, the request is refused with 503 so that nothing runs unchecked. ## What to do Try again in a moment. ## Example ```json { "error": { "code": "code_status_unavailable", "message": "Forge could not confirm that your code may run right now. Try again in a moment.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/combo_axis_over_budget # combo_axis_over_budget One setting in the run has too many values to test. ## What happened Before testing anything, the machine counts the values of each setting in your ranges. A setting whose range gives more values than one setting may have ends the run with this code, and `detail.reason` says which. ## What to do Use a larger step or fewer listed values for that setting, then start the run again. ## Example ```json { "status": "failed", "error": { "code": "combo_axis_over_budget", "message": "One setting in this run has too many values to test.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/custom_indicator_unavailable # custom_indicator_unavailable A custom indicator the run uses could not be installed on the machine. ## What happened A run that uses your own indicators installs them on the machine first. When that fails, the run ends with this code and the machine's reason in `detail.reason`. A different code, `custom_indicator_not_available`, is the refusal when an indicator is not yours or is not runnable. ## What to do Check the indicator, save a version that passes its checks, and start the run again. ## Example ```json { "status": "failed", "error": { "code": "custom_indicator_unavailable", "message": "A custom indicator this run uses is no longer available.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/data_load_failed # data_load_failed The machine could not load the data the run needs. ## What happened A run reads its datasets when it starts. When the machine cannot load them, the run ends with this code and the loader's reason in `detail.reason`. ## What to do Check that the datasets the run names are ready, then start the run again. ## Example ```json { "status": "failed", "error": { "code": "data_load_failed", "message": "The run could not load its data.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/dataset_quality_rejected # dataset_quality_rejected A dataset the run needs failed the data-quality check. ## What happened Every dataset is measured when it is processed. One whose measure is a reject cannot be run on, because the run's numbers would not mean anything. The run is refused with 409 before it is queued, and `symbols` lists each symbol with its quality score. ## What to do Open the dataset's quality report, and replace the data with a cleaner file for that symbol. ## Example ```json { "error": { "code": "dataset_quality_rejected", "message": "One or more datasets this run needs failed the data-quality check.", "detail": { "symbols": [ { "symbol": "EURUSD", "score": 41 } ], "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/dispatch_unavailable # dispatch_unavailable No machine could be started for the run. ## What happened A run that was taken, whose machine then went silent, is given more chances. When more than a day has passed since the run was queued and every attempt to find a live machine has failed, it ends with this code. ## What to do Start the run again, and check the [status page](https://oathra.dev//status). ## Example ```json { "status": "failed", "error": { "code": "dispatch_unavailable", "message": "No machine could be started for this run.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/forge_paused # forge_paused Testing is paused for maintenance. ## What happened Testing is paused for maintenance. While it is paused, a request that starts a test answers 503 with this code and starts nothing. It has nothing to do with your plan or your settings. A test that was already queued or running is not stopped, and a dry run still answers. ## What to do Nothing on your side changes it. Check the [status page](https://oathra.dev//status) and send the test again later. ## Example ```json { "error": { "code": "forge_paused", "message": "Testing is paused for maintenance.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/idempotency_key_required # idempotency_key_required Starting a run needs an Idempotency-Key header. ## What happened `POST /runs` takes an `Idempotency-Key` header of 1 to 200 visible characters, so that a retry can never start a second run. A request without one, or with one that is not made of visible characters, is refused with 400. ## What to do Send a new unique value in `Idempotency-Key` for every run you mean to start, and send the same value only to retry that run. ## Example ```json { "error": { "code": "idempotency_key_required", "message": "Send an Idempotency-Key header of 1 to 200 visible characters.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/idempotency_key_reused # idempotency_key_reused The Idempotency-Key was already used for a different request. ## What happened The same key with the same body again returns the first answer and never starts a second run. The same key with a different body is refused with 422, because the key can stand for only one request. ## What to do Use a new key for a new request. To repeat the first request, send it with its own body unchanged. ## Example ```json { "error": { "code": "idempotency_key_reused", "message": "This Idempotency-Key was already used for a different request. Use a new key.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/max_claims_exhausted # max_claims_exhausted The run was restarted too many times and was stopped. ## What happened A run can be taken again when its machine goes silent, but only a fixed number of times. After the last one, it ends with this code. ## What to do Start the run again. If it stops this way each time, [contact support](https://oathra.dev//support) with the run's id. ## Example ```json { "status": "failed", "error": { "code": "max_claims_exhausted", "message": "The run was restarted too many times and was stopped.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/middleware_order # middleware_order A fault on Forge's side: a check ran before the sign-in check. ## What happened A route's checks are meant to run in a fixed order. When one runs before the sign-in check has identified the account, the request is refused with 500. Nothing in your request caused it. ## What to do Try again shortly. If it keeps happening, [contact support](https://oathra.dev//support) with the request id. ## Example ```json { "error": { "code": "middleware_order", "message": "Something went wrong on our side. Try again shortly.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/not_found # not_found There is nothing at the address. ## What happened A 404 answer without a more specific code is reported as this one: the path is not a route of the API. ## What to do Check the path and the method against the API reference. ## Example ```json { "error": { "code": "not_found", "message": "There is nothing at this address.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/queue_ttl_expired # queue_ttl_expired The run waited in the queue too long and was not started. ## What happened A queued run has a time to live. When no machine took it in that time, it is marked expired with this code, and nothing was run. ## What to do Start the run again. If runs keep expiring, check the [status page](https://oathra.dev//status). ## Example ```json { "status": "expired", "error": { "code": "queue_ttl_expired", "message": "The run waited in the queue too long and was not started.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/rate_limited # rate_limited Too many requests came from this account or address in a short time. ## What happened Each route allows a number of requests in a window of time. Over it, the request is refused with 429, and the header `Retry-After` says how long to wait. ## What to do Wait for the time in `Retry-After`, then send the request again, and spread requests out. ## Example ```json { "error": { "code": "rate_limited", "message": "Too many requests. Wait a little, then try again.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/request_failed # request_failed The request was refused, with no more specific reason. ## What happened A refusal from an older route that carries no code Forge can name is reported under this one, with the other fields of its body in `detail`. ## What to do Read `detail` and the HTTP status, and check the request against the API reference. ## Example ```json { "error": { "code": "request_failed", "message": "The request was refused.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/request_timeout # request_timeout The request took longer than the service allows to answer. ## What happened A request that has not been answered within 30 seconds is ended with 503 and this code. Work it started may or may not have finished. ## What to do Check the state before you repeat it: read the run if the request started one. A retry of `POST /runs` with the same `Idempotency-Key` never starts a second run. ## Example ```json { "error": { "code": "request_timeout", "message": "The request took too long. Try again.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/run_already_finished # run_already_finished The run has finished, so it cannot be stopped. ## What happened Stopping a run that has already succeeded, failed, expired or been stopped is refused with 409. The reply carries the run's state in `detail.status`. ## What to do Read the run to see how it ended. Nothing needs to be stopped. ## Example ```json { "error": { "code": "run_already_finished", "message": "This run already finished, so it cannot be stopped.", "detail": { "status": "succeeded", "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/run_failed # run_failed The run stopped with an error that has no more specific code. ## What happened A run that ends as failed without naming a cause is reported with this code. The engine's own text is in `detail.reason`. ## What to do Read `detail.reason`. If it does not say what to change, start the run again, and [contact support](https://oathra.dev//support) with the run's id if it fails again. ## Example ```json { "status": "failed", "error": { "code": "run_failed", "message": "The run stopped with an error.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/run_not_found # run_not_found No run with that id exists, or it is not yours. ## What happened A run that does not exist and a run that belongs to someone else get the same answer, 404, so the reply never says whether another account's id exists. ## What to do Check the id, and that the run was started by this account. ## Example ```json { "error": { "code": "run_not_found", "message": "No run with this id was found.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/service_unavailable # service_unavailable The service is busy and could not take the request. ## What happened A 503 answer without a more specific code is reported as this one. The request did not change anything. ## What to do Try again shortly. Check the [status page](https://oathra.dev//status) if it lasts. ## Example ```json { "error": { "code": "service_unavailable", "message": "The service is busy. Try again shortly.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/strategy_needs_hosted # strategy_needs_hosted The run uses your own code, which runs only on Oathra's cloud runner. ## What happened A strategy written as code, or one that uses a developer indicator, runs in the code sandbox, which only the cloud runner has. A local machine that takes such a run gives it back with this code. ## What to do Run it on the cloud runner instead of a local machine. ## Example ```json { "status": "failed", "error": { "code": "strategy_needs_hosted", "message": "This run uses your own code, which runs only on Oathra's cloud runner.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/strategy_source_unavailable # strategy_source_unavailable The saved strategy version the run names can no longer be read. ## What happened A run that names a saved version reads its source when a machine takes it. When the version is gone, or its source cannot be read, the run ends with this code. ## What to do Save the strategy again and start the run on the new version. ## Example ```json { "status": "failed", "error": { "code": "strategy_source_unavailable", "message": "The saved strategy version for this run is no longer available.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/errors/token_expired # token_expired The access token has expired. ## What happened An access token is good for a limited time. One whose expiry has passed is refused with 401 and this code. ## What to do Sign in again, or refresh the session, and send the new token. ## Example ```json { "error": { "code": "token_expired", "message": "Your session expired. Sign in again.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/token_invalid # token_invalid The access token is not valid. ## What happened A token that sign-in does not recognise, and that has not simply expired, is refused with 401. ## What to do Sign in again and use the new token. ## Example ```json { "error": { "code": "token_invalid", "message": "The access token is not valid. Sign in again.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/token_required # token_required The request has no access token. ## What happened A route that belongs to an account needs the access token as a Bearer token in the `Authorization` header. A request with none, or with an empty one, is refused with 401. ## What to do Sign in, then send `Authorization: Bearer ` with the request. ## Example ```json { "error": { "code": "token_required", "message": "Sign in first, and send the access token as a Bearer token.", "detail": { "request_id": "3c6f9a52-8e1d-4b7a-9f20-6d41a8c5e7b3" } } } ``` --- Source: https://oathra.dev/reference/errors/unexpected_error # unexpected_error The machine hit an error it was not written to expect. ## What happened Whatever goes wrong while a machine is dispatching a run, and has no code of its own, ends the run with this one. The error's own name and text are in `detail.reason`. ## What to do Start the run again. If it fails the same way, [contact support](https://oathra.dev//support) with the run's id. ## Example ```json { "status": "failed", "error": { "code": "unexpected_error", "message": "The run stopped with an unexpected error.", "detail": {} } } ``` --- Source: https://oathra.dev/reference/refusals # Refusals Why Forge skipped an entry or a configuration during a run. A refusal is counted in the run's results and the run goes on. ## Entries | Number | Code | What it means | |---|---|---| | 20 | `ORD_MAX_PENDING` | The strategy already has the most pending orders the settings allow, so a new one was not placed. | | 21 | `ORD_EXPIRED` | A pending order reached its expiry before it filled and was removed. | | 22 | `ORD_MIN_STOP_DISTANCE` | A stop or target level was closer than the broker's minimum stop distance, measured from the price the order fills at. | | 23 | `ORD_FREEZE_LEVEL` | The broker's freeze distance refused a change, a cancel or a close of something inside it. It never refuses placing an order. | | 24 | `ORD_RANGE_GUARD` | The price moved past the range the order allows, so the fill was refused. | | 25 | `ORD_GAP_REJECT` | The price jumped across the order's level on a gap, and the order's gap rule refuses a fill there. | | 26 | `ORD_NO_STOP` | The order had no stop, and the settings require one. | | 27 | `MARGIN_INSUFFICIENT` | There was not enough free margin to open the position. | | 28 | `SIZE_BELOW_MIN` | The size worked out below the broker's smallest lot, and the settings refuse a size that small. | | 29 | `SIZE_ABOVE_VOLUME_MAX` | The size worked out above the broker's largest lot, and the settings refuse a size that large. | | 30 | `SIZE_NO_STOP_DISTANCE` | Sizing from the risk of the trade needs a stop distance, and this entry had none. | | 31 | `SIZE_ABOVE_MAX_LOTS` | The size was above the lot cap in your own settings. | | 32 | `BOOK_MAX_CONCURRENT` | The most open positions the settings allow were already open. | | 33 | `BOOK_MAX_PER_INSTRUMENT` | The most positions the settings allow in one instrument were already open. | | 34 | `BOOK_MAX_PER_DIRECTION` | The most positions the settings allow in one direction were already open. | | 35 | `BOOK_MAX_GROSS` | The most gross exposure the settings allow was already reached. | | 36 | `BOOK_PYRAMID_CAP` | Adding to a position would pass the pyramiding limit. | | 37 | `COOLDOWN` | The entry came inside the cooldown the settings set after an exit. | | 38 | `SAME_BAR_REENTRY` | The entry came on the bar a position closed on, which the settings do not allow. | | 39 | `MAX_TRADES_PER_DAY` | The daily limit on trades had been reached. | | 40 | `STATE_HALTED` | The account was halted, so no entry was accepted. | | 41 | `STATE_REDUCE_ONLY` | The account was only allowed to close positions, so a new entry was refused. | | 42 | `GATE_SESSION` | The entry came outside the trading session the settings allow. | | 43 | `GATE_HOLIDAY` | The entry came on a holiday the settings respect. | | 44 | `GATE_WEEKDAY` | The entry came on a weekday the settings exclude. | | 45 | `GATE_DATE` | The entry came on a date the settings exclude. | | 46 | `GATE_NEWS` | The entry fell inside a news window your settings block. | | 47 | `GATE_SPREAD` | The spread was wider than the settings allow for an entry. | | 48 | `GATE_ATR_LOW` | Volatility, measured by ATR, was below the minimum the settings require. | | 49 | `GATE_ATR_HIGH` | Volatility, measured by ATR, was above the maximum the settings allow. | | 50 | `GATE_FILTER` | A filter on the entry blocked it. | | 51 | `GATE_DIRECTION` | The entry was in a direction the settings do not allow. | | 52 | `MARGIN_AT_FILL` | Margin was enough when the order was placed and short when it filled. | | 53 | `BAR_ABSENT` | The entry's instrument had no bar at that time. | ## Configurations | Number | Code | What it means | |---|---|---| | 60 | `TF_TOO_COARSE` | A setting that has no meaning on bars of D1 or coarser is in use on bars that coarse. | | 61 | `BOOK_TOO_LARGE` | The book settings allow more open positions than one run can hold. | | 62 | `TRADE_ARRAY_OVERFLOW` | The configuration made more fills than one run can hold. | | 63 | `PRIOR_NOT_RECONSTITUTABLE` | A run that builds on an earlier result could not rebuild that earlier run's inputs. | | 64 | `TOO_MANY_INSTRUMENTS` | The configuration trades more symbols than one run can trade. | ## Order checks | Number | Code | What it means | |---|---|---| | 70 | `SIZE_BAD_STEP` | The order's size is not a multiple of the symbol's lot step. | | 71 | `MARKET_CLOSED` | The order was placed while the symbol's market was closed. | | 72 | `TRADE_MODE_CLOSE_ONLY` | The broker allows only closing positions in this symbol. | | 73 | `TRADE_MODE_LONG_ONLY` | The broker allows only buying in this symbol. | | 74 | `TRADE_MODE_SHORT_ONLY` | The broker allows only selling in this symbol. | | 75 | `HEDGING_NOT_ALLOWED` | The account does not allow a position opposite to one already open in the same symbol. | | 76 | `FIFO` | The account's first-in, first-out rule refused the order. | | 77 | `WARMING_UP` | The order came before the strategy's warm-up bars had passed. | | 78 | `PROP_RULE` | A prop-firm rule had no room left for the order. | | 79 | `SIGNAL_STALE` | The signal was older than the age limit when the order was placed. | | 80 | `ENTRY_BLACKOUT_END` | The entry came inside the blackout before the end of the run. | | 81 | `VETOED` | The strategy's confirm_entry hook said no. | | 82 | `CONFIRMATION_NOT_MET` | A stop-limit order's trigger was reached, but its confirmation was not met. | | 83 | `STOP_LIMIT_MISSED` | A stop-limit order's price moved past its limit before it could fill. | | 84 | `LEVEL_OUT_OF_RANGE` | A stop or target worked out at or below zero. | | 85 | `STOP_WOULD_LOOSEN` | A new stop was further away than the one it replaces, while stops may only tighten. | | 86 | `VOLUME_LIMIT` | An entry would take the open and resting lots of an instrument and side past the lot limit. | | 87 | `SIZE_ZERO` | A size of exactly zero was asked for, by the size hook or by an order of zero lots. | | 88 | `BEFORE_OPEN_FROM` | The order was placed before the moment this run starts trading. | | 89 | `COSTS_EXCEED_STOP` | The trade's costs are larger than the money the stop allows, so the stop cannot be placed. | | 90 | `MARKET_QUOTE_ONLY` | The market was quoted but not open for trading, so the order was refused. | | 91 | `NOT_LISTED` | The symbol was not listed at that time, so the order was refused. | | 92 | `NEWS_CLOSE_DEFERRED` | A news window held this close back until the window ended. | | 93 | `NEWS_MODIFY_DEFERRED` | A news window held this change to a stop or target back. | | 94 | `MAX_TRADES_PER_WEEK` | The limit on trades opened in one week was already reached. | | 95 | `MAX_REQUESTS_PER_DAY` | The limit on order messages in one day was already reached. | | 96 | `LOSS_LOCK` | Trading was paused after losses, so the entry was not taken. | | 97 | `STOP_OUT_GUARD` | Trading was paused after stop exits, so the entry was not taken. | | 98 | `ORDER_KIND_NOT_OFFERED` | The broker does not accept this kind of order on the symbol. | | 99 | `VALUE_ABOVE_CAP` | The order's value was above the limit set for one order. | | 100 | `SYMBOL_RESTRICTED` | This symbol is on the run's restricted list, so no new entry was taken. | | 101 | `DRAWDOWN_LOCK` | Trading was paused after the account fell from its recent high, so the entry was not taken. | Refusals that stop a whole run are listed under [Error codes](/reference/errors). --- # Statistics | Statistic | What it means | Units | |---|---|---| | Trades | How many closing fills the run made. A position closed in pieces counts once per piece. | count | | Net profit | Sum of every trade's result after all costs, swap included. | money | | Gross profit | Sum of the trades that made money, after all costs. | money | | Gross loss | Sum of the trades that lost money, after all costs. | money | | Largest win | The single best trade, after all costs. | money | | Largest loss | The single worst trade, after all costs. | money | | Loss rate | Share of trades that lost money. | fraction | | Break-even trades | Trades that made exactly nothing. They are neither wins nor losses. | count | | Average winning streak | Average length of a run of winning trades in a row. | count | | Average losing streak | Average length of a run of losing trades in a row. | count | | Trades in the best winning run | How many trades made up the winning run that earned the most. | count | | Trades in the worst losing run | How many trades made up the losing run that cost the most. | count | | Profit factor | Total won divided by total lost, after commission, spread and slippage. | ratio | | Win rate | Share of trades that made money. | fraction | | Average win | Average result of the trades that made money. | money | | Average loss | Average size of the losing trades, shown as a positive amount. | money | | Payoff ratio | Average win divided by average loss. | ratio | | Expectancy | Average result per trade, after commission, spread and slippage. | money | | Skew | Whether the big results lean to the upside (positive) or the downside (negative). | ratio | | Excess kurtosis | How fat the tails are beyond a normal curve. High means rare, extreme trades. | ratio | | Longest winning streak | Most winning trades in a row. Below it: the most money a winning run made. | count | | Longest losing streak | Most losing trades in a row. Below it: the most money a losing run cost. | count | | Best winning run | What the best winning run earned in total. | money | | Worst losing run | What the worst losing run cost in total, as a negative number. | money | | Sharpe, daily | The return per unit of day-to-day swing, scaled to a year. | ratio | | Sortino, daily | The return per unit of downside swing, scaled to a year. | ratio | | Sharpe, per trade | Mean return of a closing fill on the balance it met, divided by the standard deviation of those returns. Not scaled to a year. On one run it can differ from the daily Sharpe by a factor of two. | ratio | | Sortino, per trade | The return per unit of downside swing, per trade. Not scaled to a year. | ratio | | MAR, full window | Growth a year over the whole traded window, divided by its worst drawdown. | ratio | | Calmar, last 36 months | Growth a year over the last 36 months of the traded window, divided by the worst drawdown inside those months. | ratio | | SQN | Tharp's System Quality Number: the square root of the number of trades with an R, capped at 100, times the mean R, divided by the standard deviation of R. 2.0 to 2.5 is average, 2.5 to 3.0 good, 3.0 to 5.0 excellent, above 5.0 superb. A figure without the cap is not called SQN. | score | | Minimum margin level | How close the account came to a margin call: the lowest equity as a percent of the margin held. | percent | | Maximum deposit load | How much of the account the open positions tied up at the most. | percent | | Deposit load, by share of bars | How the deposit load was spread: the share of time spent in each band, the last band being 90 and above. | fraction | | Long trades won | The share of long trades that made money. | percent | | Short trades won | The share of short trades that made money. | percent | | Cash over the same period | What the same money would have earned in cash at the run's risk-free rate. | fraction | | Return above cash | How much more than cash the strategy made over the test. | fraction | | Trading days a year | The trading days in a year this run's data contains, which every annualised figure uses. | days | | Bars a year | The bars in a year this run's data contains, for figures taken per bar. | bars | | Positions | How many positions the fills add up to. An add to a position is not a new one. | count | | What a trade is | Every count and average here counts closing fills, so a position closed in pieces counts once per piece. | count | | How much evidence | How many trades the figures rest on, and how many the gates asked for. | count | | Recovery factor | Net profit divided by the money size of the worst drawdown. | ratio | | Longest time without a new high, in bars | The longest stretch without a new equity high, counted in bars and in calendar days. A stretch still open at the end of the run counts. | bars | | Longest time without a new high, in days | The longest stretch without a new equity high, counted in bars and in calendar days. A stretch still open at the end of the run counts. | days | | worst drawdown, intrabar | The worst fall from a peak to a later low, as a share of the peak, on the equity curve this figure names. Negative; zero when the curve never fell. | fraction | | worst drawdown in money, intrabar | The worst fall from a peak to a later low in money, on the equity curve this figure names. Negative; zero when the curve never fell. | money | | worst drawdown, bar | The worst fall from a peak to a later low, as a share of the peak, on the equity curve this figure names. Negative; zero when the curve never fell. | fraction | | worst drawdown in money, bar | The worst fall from a peak to a later low in money, on the equity curve this figure names. Negative; zero when the curve never fell. | money | | worst drawdown, daily | The worst fall from a peak to a later low, as a share of the peak, on the equity curve this figure names. Negative; zero when the curve never fell. | fraction | | worst drawdown in money, daily | The worst fall from a peak to a later low in money, on the equity curve this figure names. Negative; zero when the curve never fell. | money | | Balance drawdown, absolute | MT5's absolute drawdown of the balance curve, in MT5's own terms. | money | | Balance drawdown, maximal | MT5's maximal drawdown of the balance curve, in MT5's own terms. | money | | Balance drawdown, maximal (percent) | MT5's maximal (percent) drawdown of the balance curve, in MT5's own terms. | fraction | | Balance drawdown, relative | MT5's relative drawdown of the balance curve, in MT5's own terms. | fraction | | Balance drawdown, relative (money) | MT5's relative (money) drawdown of the balance curve, in MT5's own terms. | money | | Equity drawdown, absolute | MT5's absolute drawdown of the equity curve, in MT5's own terms. | money | | Equity drawdown, maximal | MT5's maximal drawdown of the equity curve, in MT5's own terms. | money | | Equity drawdown, maximal (percent) | MT5's maximal (percent) drawdown of the equity curve, in MT5's own terms. | fraction | | Equity drawdown, relative | MT5's relative drawdown of the equity curve, in MT5's own terms. | fraction | | Equity drawdown, relative (money) | MT5's relative (money) drawdown of the equity curve, in MT5's own terms. | money | | Compound annual growth | The yearly growth rate that takes the first equity to the last over the traded window. | fraction | | Return over maximal drawdown | What the run made for each unit of money of its worst fall, with no scaling to a year. | ratio | | Omega | The size of the days that made money against the days that lost it, added up. | ratio | | Tail ratio | How big the best days run against the worst, over the same series: above 1, the good days are bigger. | ratio | | Variability-weighted return | The yearly return, marked down by how far the equity wandered from a smooth line (Bernut). | percent | | Annual volatility | How much the daily returns swing, scaled to a year. | fraction | | Annual variance | The square of the annual volatility: the daily variance scaled to a year. | fraction | | LR correlation | How straight the balance curve is: 1 is a perfectly straight rise. | ratio | | LR standard error | How far the balance strays from its straight line, in money. | money | | Z-score of the runs | Whether wins and losses come in streaks: negative when they clump, positive when they alternate. | score | | Average holding period return | The average factor each trade multiplied the balance by. | ratio | | Geometric holding period return | The one factor that, repeated for every trade, carries the starting balance to the final one. | ratio | | Mean R | What the average trade made, in units of the risk it was sized to take. | ratio | | Worst R | The worst trade in units of its planned risk. Below -1, a stop let more through than planned. | ratio | | Loss against planned risk, median | How much a typical losing trade lost against its plan: 1 is a loss exactly as planned. | ratio | | Loss against planned risk, 95th percentile | How much the bad losing trades lost against their plan: 1 is a loss exactly as planned. | ratio | | Loss against planned risk, worst | The losing trade that went furthest past its plan: 1 is a loss exactly as planned. | ratio | | Worst loss, share of equity | The largest loss on one trade as a share of the account equity when that trade opened. | fraction | | Losses past their planned risk | Losing trades that lost more than the risk they were sized to take. A loss exactly as planned does not count. | count | | Trades without a stop | A trade with no stop has no stop distance, so it has no R. These trades are counted here and left out of every R figure. | count | | Share of trades without a stop | The share of the trades that had no stop, so no R. | fraction | | Trades past minus one R | Trades whose loss went past the risk they were sized to take: a stop that let more through. | count | | Trades with an R of exactly zero | Trades that made or lost exactly nothing, whose planned risk cannot be worked back from their R. | count | | Adverse excursion, mean | Adverse excursion is how far the trade went against its entry before it closed, measured on the closing side from the entry price. | money | | Adverse excursion, largest | Adverse excursion is how far the trade went against its entry before it closed, measured on the closing side from the entry price. | money | | Adverse excursion, mean, in R | Adverse excursion is how far the trade went against its entry before it closed, measured on the closing side from the entry price. | ratio | | Adverse excursion, largest, in R | Adverse excursion is how far the trade went against its entry before it closed, measured on the closing side from the entry price. | ratio | | Adverse excursion, mean, net of costs | Adverse excursion is how far the trade went against its entry before it closed, measured on the closing side from the entry price. | money | | Adverse excursion, largest, net of costs | Adverse excursion is how far the trade went against its entry before it closed, measured on the closing side from the entry price. | money | | Favourable excursion, mean | Favourable excursion is how far the trade went in its favour before it closed, measured on the closing side from the entry price. | money | | Favourable excursion, largest | Favourable excursion is how far the trade went in its favour before it closed, measured on the closing side from the entry price. | money | | Favourable excursion, mean, in R | Favourable excursion is how far the trade went in its favour before it closed, measured on the closing side from the entry price. | ratio | | Favourable excursion, largest, in R | Favourable excursion is how far the trade went in its favour before it closed, measured on the closing side from the entry price. | ratio | | Favourable excursion, mean, net of costs | Favourable excursion is how far the trade went in its favour before it closed, measured on the closing side from the entry price. | money | | Favourable excursion, largest, net of costs | Favourable excursion is how far the trade went in its favour before it closed, measured on the closing side from the entry price. | money | | Give-back, mean | How much of the best price the trade had it handed back before closing, in money. | money | | Give-back, worst | How much of the best price the trade had it handed back before closing, in money. | money | | Give-back, total | How much of the best price the trade had it handed back before closing, in money. | money | | Holding time in minutes, min | The shortest time a trade was held, in minutes, for all trades, the winners and the losers. | minutes | | Holding time in minutes, max | The longest time a trade was held, in minutes, for all trades, the winners and the losers. | minutes | | Holding time in minutes, mean | The mean time a trade was held, in minutes, for all trades, the winners and the losers. | minutes | | Holding time in minutes, median | The median time a trade was held, in minutes, for all trades, the winners and the losers. | minutes | | Holding time in bars, min | The shortest time a trade was held, in bars, for all trades, the winners and the losers. | bars | | Holding time in bars, max | The longest time a trade was held, in bars, for all trades, the winners and the losers. | bars | | Holding time in bars, mean | The mean time a trade was held, in bars, for all trades, the winners and the losers. | bars | | Holding time in bars, median | The median time a trade was held, in bars, for all trades, the winners and the losers. | bars | | Profit factor of a period | The profit factor of the trades that closed in one week, month, year or weekday. | ratio | | Best trade | What the best trade made, and how much of the net profit that was. | money | | Best day | What the best day made, and how much of the net profit that was. | money | | Best month | What the best month made, and how much of the net profit that was. | money | | Best year | What the best year made, and how much of the net profit that was. | money | | Best day against the days that made money | The best day read against the sum of the winning days, the base some firms' consistency rule uses. | money | | Trades by direction | How many trades were long and how many were short. | count | | Margin level, lowest | How close the account came to a margin call: the lowest level reached, with the bar it happened on. | fraction | | Margin in use, most | The most margin the run held at once, with the bar it happened on. | money | | Legs | How many legs the run traded. A position closed in pieces is one leg. | count | | Most positions open at once | The most legs open together, with the first bar it happened on and the legs then open. | count | | Positions open, average | How many legs were open on an average bar of the test. | count | | Worst floating loss | The deepest unrealised loss the open positions reached, with the first bar it happened on. | money | | Most risk open at once | The most the open legs were sized to lose at once, in money and as a share of the equity then. | money | | Most risk open at once, with correlation | The most risk open at once once instruments that move together are counted as one. | money | | Sharpe of a held curve | The Sharpe of the strategy and of the same money held in each instrument, on the same days. | ratio | | Return above the held instrument | How much more the strategy made over the test than the same money held in the instrument. | fraction | | Alpha | What the strategy earned a year beyond what its exposure to the instrument explains. | fraction | | Beta | How much the strategy moved for each unit the held instrument moved. | ratio | | Treynor ratio | The strategy's yearly excess return for each unit of beta. | fraction | | Information ratio | The yearly return above the instrument for each unit of how far the strategy strayed from it. | ratio | | Tracking error | How far the strategy's daily returns strayed from the instrument's, scaled to a year. | fraction | | Up capture | How much of the instrument's good days the strategy kept. | ratio | | Down capture | How much of the instrument's bad days the strategy took. | ratio | | Trading days | The days the data traded on, which the yearly constant is measured from. | days | | Span in days | How long the traded window is, in calendar days. | days | | Risk-free rate | The yearly return of holding cash that the run's risk-adjusted figures are measured above. | fraction | | Out-of-sample profit factor | Gross profit over gross loss on data the search never selected on. This is the engine's ranking key. | ratio | | In-sample profit factor | The same ratio on the data the search chose this setting from. On its own it says little: every setting in a sweep looks good in sample. | ratio | | Maximum drawdown | The worst fall from a peak to a later low on the out-of-sample equity. Negative; -1 means the account blew up. | fraction | | Trades | How many trades the out-of-sample test made. | count | | Skew of the trade results | Whether the big results lean to the upside (positive) or the downside (negative). | score | | Probability of backtest overfitting | How often the best setting on one half of the history lands below the middle on the other half. Above 0.5, the search is choosing noise. | probability | | Deflated Sharpe | The chance the Sharpe is real after allowing for how many settings were tried. A raw Sharpe does not know that. | probability | | Plateau score | How many of the settings next to this one also work. A setting that works alone is fitting noise. | fraction | | K-fold pass rate | How many of the consecutive folds of the search period the setting passed. | fraction | | Walk-forward pass rate | How many of the consecutive later windows inside the search period the setting passed. | fraction | | Win rate, out of sample | Share of the out-of-sample trades that made money. | fraction | | Payoff ratio, out of sample | Average win divided by average loss on the out-of-sample trades. | ratio | | Largest win in average losses | The best trade measured in average losses: the bound the live tripwire is set from. | ratio | | Average hold, out of sample | How long an out-of-sample trade was held on average, in minutes. | minutes | | Friction | How much of what the trades made before costs went to costs. | fraction | | Interval on a statistic | The range a statistic could plausibly have had on other draws of the same trades, and whether it includes the value that means no edge. | ratio | | Is the average trade above zero | Whether the average trade is more than luck. The t figure assumes independent trades, and says so. | score | | Cost multiple at break-even | How many times the costs this run paid it could take before the average trade makes nothing. | ratio | | Costs by kind | What the run paid in each of the four costs, and how much of the gross profit that was. | money | | Baseline for tracking the run live | What the test said the strategy would do, frozen with the result so a live record can be held to it. | ratio | | Weekly grid | How the account moved week by week. | money | | Monthly grid | How the account moved month by month. | money | | Annual grid | How the account moved year by year. | money | | Ranking key, test profit factor | How a run without an objective orders its candidates: best out-of-sample profit factor first. | ratio | | Ranking key, chosen objective | How a run with an objective orders its settings: by that objective on the search block, never by the hold-out. | ratio | | Noise rule for the leaderboard | A result that cannot be told from no edge is listed after the ones that can, whatever its figure is. | ratio | | Objective: net profit | The sum of profit and loss over the in-sample trades, in account currency. | money | | Objective: profit factor | Gross profit divided by gross loss over the in-sample trades. Undefined with no losing trade. | ratio | | Objective: win rate | The share of in-sample trades that made money, as a percentage. | percent | | Objective: expectancy | The average profit or loss per in-sample trade, in account currency. | money | | Objective: max drawdown | The largest fall of in-sample equity from its running peak, as a percentage of that peak. | percent | | Objective: recovery factor | In-sample net profit divided by the largest fall of equity from a peak, in account currency. | ratio | | Objective: sharpe ratio | The mean return per bar of in-sample equity divided by its standard deviation, annualised. | ratio | | Objective: sortino ratio | The mean return per bar of in-sample equity divided by the deviation of its losing bars, annualised. | ratio | | Objective: calmar ratio | The annual growth rate of in-sample equity divided by its maximum drawdown. | ratio | | Objective: profit factor, win rate and trade count | Profit factor times the win rate as a fraction, scaled down below 100 trades by the trade count over 100. | ratio | | Objective: worst instrument profit factor | The lowest profit factor among the instruments that traded. | ratio | | Verdict from the flags | One level for the whole result: the worst thing found sets it, and the other checks cannot soften it. | score | | Chain verdict | How far the combination got and what ended it. A combination whose passers were never graded is never filed as if it had been. | score | | Gate 4, cost | Kills a setting whose costs eat too much of what its trades make, or that dies when the spread widens. | fraction | | Gate 5, sample | Kills a setting that history could not produce enough evidence about. | count | | Gate 6, bounded tail | Flags a hidden tail for review. It never kills a setting: the person owns this verdict. | score | | Gate 7, parameter budget | Scores how many knobs the search could turn: every one multiplies the ways the optimiser can fool itself. | count | | Hold-out | The first check on data the search did not see. Passing it is necessary and never sufficient. | ratio | | K-fold pass rate | Whether the setting holds up on most of the consecutive pieces of the search period. | fraction | | Walk-forward pass rate | Whether the setting keeps working in the period straight after the one it was fitted on, again and again. | fraction | | Probability of backtest overfitting | A low figure is good: it says the winner of the search is not just the luckiest of many. | probability | | Deflated Sharpe | Whether the Sharpe survives having been picked as the best of many. | probability | | Plateau | Whether the settings next to this one work too: a setting that works alone is fitting noise. | fraction | # Limits and costs Source: https://oathra.dev/docs/limits # Limits The numbers Forge enforces today, read from its live settings. The numbers that depend on your plan are on the page itself: https://oathra.dev/docs/limits ## Your code | Limit | All plans | |---|---| | Values one setting can take in a sweep | 10,000 | ## Code sandbox | Limit | Default | Most | |---|---|---| | CPU time per computation | 2,000 ms | 10,000 ms | | Time per computation | 5,000 ms | 30,000 ms | | Memory | 512 MB | 1,024 MB | | Outputs per indicator | 64 | 64 | | Drawings per computation | 500 | 3,000 | | Log lines kept | 200 | 1,000 | --- Source: https://oathra.dev/docs/costs # Costs ## Forge compute Hosted work is charged by the minute from your credits. | Kind | Machine | Credits per minute | Held at start | |---|---|---|---| | Tests | 4 cores, 8 GB | 0.216 | 3 credits for 10 minutes | | Data uploads | 2 cores, 8 GB | 0.136 | 2 credits for 10 minutes | | Indicator checks | 2 cores, 4 GB | 0.108 | 2 credits for 10 minutes | ## Credits in each plan Free: 15 credits a month. Paid: 200 credits a month, $29 a month. One credit is worth $0.1. | Credits | Price | Saving | |---|---|---| | 50 | $5 | 0% | | 250 | $20 | 20% | | 700 | $50 | 29% | | 1,500 | $100 | 33% | ## Storage The free plan includes no storage. The paid plan includes 20 GB. | Size | Price | |---|---| | 20 GB | 40 credits a month | | 100 GB | 200 credits a month | | 500 GB | 950 credits a month | ## The verdict Forge's verdict on your own runs is part of the Paid plan. What fits: [Limits](/docs/limits.md) --- Source: https://oathra.dev/docs/style-guide # Style guide ## Setting names A setting name is snake_case and at most 40 characters. Forge refuses anything else when the file loads. ```python import forge as fg from forge import param FORGE_API = 1 fast_period = param.Int(20, min=5, max=60) ``` ```text FastPeriod = param.Int(20, min=5, max=60) ``` ## Class names A class name is in CapWords, and a file holds one class. Its name is the default display name. ```python import forge as fg FORGE_API = 1 class TrendPullback(fg.Strategy): bars = fg.bars.Time("H1") def compute(self, d) -> fg.Signals: return fg.Signals(long_entry=d.close > d.open) ``` ```text class trend_pullback(fg.Strategy): ``` ## File order The file starts with `import forge as fg`, then the `from forge import ...` lines, then `FORGE_API = 1`, then the class. ```python import forge as fg from forge import param, ta FORGE_API = 1 period = param.Int(14, min=2, max=200) ``` ```text FORGE_API = 1 import forge as fg ``` ## Comments Comment what the code cannot say: why a value was chosen, or what a market does that surprises. Do not comment what the next line says. ```text # add one to the count count = count + 1 ``` ## Formatting Format files with black at its default of 88 columns. A long call breaks at its arguments. ```python import forge as fg from forge import param FORGE_API = 1 slow_period = param.Int( 100, min=50, max=300, step=25, group="A group name that is long enough to wrap" ) ``` ```text slow_period = param.Int(100, min=50, max=300, step=25, group="A group name that is long enough to wrap") ``` ## What is not open yet A thing is in one of three states. | State | What the site does | | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Open | Shown and linked | | Coming soon | Named once, on the page [Coming soon](https://oathra.dev/docs/coming-soon), with one line and no date. It has no row in the header, the docs tree, the footer columns, the dashboard menu or the status page, no page of its own, and no disabled button. The home page links to the one page in one quiet row | | Undecided, or not for users | Not shown anywhere | --- Source: https://oathra.dev/docs/coming-soon # Coming soon ## Publishing and the Library Share a strategy or an indicator with its test result. Publishing opens with the developer terms. [How ranking works](https://oathra.dev/library/how-ranking-works) ## The public API A public API for your tests and the Library, with the same error codes the web app uses. API keys arrive with it. [Get changelog emails about the API](https://oathra.dev/changelog?emails=api) ## MCP A hosted MCP server, so an AI tool can read the docs and start a test, with your yes before anything spends or publishes. [Get changelog emails about MCP](https://oathra.dev/changelog?emails=mcp) Open today: [the editor](https://oathra.dev/play), [the Python reference](https://oathra.dev/reference), and [testing a strategy in Forge on oathra.app](https://oathra.app/forge). --- Source: https://oathra.dev/docs/faq/indicators # Indicators FAQ ## Can an indicator place orders? No. An indicator draws and reports values. Importing or calling any order, account or context function in an indicator file is refused when it loads. See [write an indicator](https://oathra.dev/docs/how-to/write-an-indicator). ## Why are the first values empty? The indicator is in its warm-up and has not seen enough bars. Those values are NaN on purpose. See [warm-up](https://oathra.dev/docs/concepts/warm-up). ## Can an indicator use a slower timeframe? Yes. Declare the series, for example `h4 = fg.Data(timeframe="H4")`. Its values appear only after its own bar has closed, as with every other series. ## How many outputs can one indicator have? Up to 64. Each one is read by its name. ## Does my indicator repaint? A value that changes after its bar has closed is repainting. Forge computes on closed bars, and an indicator that redraws its past is marked as doing so. See [repainting and look-ahead](https://oathra.dev/docs/concepts/repainting-and-look-ahead). ## Can I bring an MQL5 indicator? The Indicators screen in Oathra can convert one you own, and the [MQL5 traps](https://oathra.dev/docs/porting/mql5/traps) list what usually needs a second look. --- Source: https://oathra.dev/docs/faq/strategies # Strategies FAQ ## Can I write in my own editor? Yes. A strategy or an indicator is one `.py` file. Write it anywhere and keep it where you like. Upload a strategy on the Strategy card in Forge, or paste it into the [editor](https://oathra.dev/play) here. Tests run in Forge on oathra.app, not on your computer. ## How many strategy classes can a file hold? One. Its class name is the default display name. See the [style guide](https://oathra.dev/docs/style-guide). ## Can a strategy trade more than one symbol? Yes. Declare the symbols and read another one with `d["GBPUSD"]`, which has the same attributes as the main series. ## When does an order fill? At the first real price after the bar it was decided on has closed. See [bar close and fills](https://oathra.dev/docs/concepts/bar-close-and-fills). ## Which hooks can a strategy define? See [write a strategy](https://oathra.dev/docs/how-to/write-a-strategy) for every hook and when it runs. ## What is FORGE_API for? It names the level of the Forge API the file was written for. A file without it, or with a level Forge does not support, is refused. ## Why does compute have to be pure? It runs once over all the bars. It cannot read the clock, the account or a random number, so it gives the same answer every time. --- Source: https://oathra.dev/docs/faq/data # Data FAQ ## Whose data does my code run on? Yours. A run uses the datasets you upload, and each one is private to your account. See [what your code runs on](https://oathra.dev/docs/data/what-your-code-runs-on). ## Which file formats can I upload? MetaTrader 5 tick and bar exports, MetaTrader 4 bar exports, OANDA candle files and CSV files from QuantDataManager, Dukascopy, TrueFX and HistData. ## Which clock are my files in? Each source has its own. Forge converts every file to UTC. The [data page](https://oathra.dev/docs/data/what-your-code-runs-on) lists them. ## What happens to a bad row? It is dropped and counted under a named reason, so you see how many rows were lost and why. ## Are missing minutes filled in? No. A missing minute stays missing. See [data misconceptions](https://oathra.dev/docs/data/misconceptions). ## Can I use the broker's own history? The chart can draw the Deriv public feed, which holds about a year of candles and about a day of ticks. --- Source: https://oathra.dev/docs/faq/verdict # Verdict FAQ ## What does Validated mean? At least one candidate cleared every stage. It says the result was not explained by luck or by the search alone. It is not a promise of future profit. ## What does Killed at the hold-out mean? Some candidates cleared the cheap checks, and none did well enough on the data the search never saw. See [read a verdict](https://oathra.dev/docs/how-to/read-a-verdict). ## Why do more settings make a pass harder? Every combination tried is a trial, and the verdict is reduced for how many there were. See [what the verdict tests](https://oathra.dev/docs/concepts/what-the-verdict-tests). ## Are costs part of the verdict? Yes. The cost of trades is applied in every stage, and tested again with wider spreads. See [costs, swap and funding](https://oathra.dev/docs/concepts/costs-swap-and-funding). ## What does Interrupted mean? The run stopped before a verdict. Its partial result is not one to act on. ## Can I change a strategy and test again on the same data? You can, and each round uses up some of what the data can tell you. Change less, and change it for a reason. --- Source: https://oathra.dev/docs/faq/account-and-billing # Account and billing ## Is it the same account as oathra.app? Yes. One Oathra account works on oathra.app and here. ## Why do I log in twice? The two sites are on different addresses, and each keeps its own log-in. Use the same email, or the same Google or Apple account, on both. ## What does a first test cost on the free plan? ## What you need 1. An Oathra account. Creating one is free. 2. Price history for one symbol, exported from MetaTrader. Oathra ships no sample prices. [Bring your price history](https://oathra.dev/docs/how-to/bring-your-price-history) 3. A first test on your own price history needs storage, and the free plan includes none. Storage starts at 40 credits a month. Credit packs are coming soon. 4. Credits for the data check and the test. Both are charged by the minute, and Forge shows the cost of a test before it starts. A standard test uses about 13 credits an hour. 5. The numbers of a test are on both plans. The verdict is on the paid plan, \$29 a month. ## What is paid? The verdict on a test is on the paid plan, \$29 a month. Writing code, saving projects, the chart beside a project and the numbers of a test are on both plans. A test spends credits on both plans. ## Where do I see my credits? On [Usage](https://oathra.dev/dashboard/usage) in your dashboard here, and on [Compute ↗](https://oathra.app/forge/usage) in Forge on oathra.app. ## How do I export or delete my work? [Settings](https://oathra.dev/dashboard/settings) in your dashboard has Export. A project is deleted on [Projects](https://oathra.dev/projects), and it is removed from Forge too. Deleting your account is done on oathra.app, and the link is in Settings. ---