Blog

Why the builder refused that connection

TradeSmithy Team6 min read

A wire that will not drop is annoying, and it is more useful than it looks. Every refusal in the canvas corresponds to something that would otherwise compile into an EA and misbehave quietly. Here is the whole list.

The rules, in one table

The messageWhat it meansWhat to do
Data type mismatchA number is being fed to a boolean input, or the reversePut a comparison node between them
This input only allows one connectionInputs take exactly one edgeDelete the existing wire first, or use a logic node to combine
Only a Variable node can be connected hereAn assignment must name a declarationWire the Variable node's ref output
An event starts the flow — it is not a condition to combineAn event is being fed into AND/OR/NOTWire the conditions into the logic node instead
This connection would create a cycleThe graph would feed back into itselfStore the value in a Variable and read it next tick
Source handle must be an outputTwo inputs or two outputsDrag from a port on the right of a node to one on the left

Port types

Every port carries a type, and the dot is coloured by it. There are four:

  • numeric — an indicator value, a price, a parameter, a math result.
  • boolean — a condition's answer, and what a trading node's trigger wants.
  • any — for ports that genuinely accept either, such as a comparison's two sides.
  • variableRef — a place to store a value, not a value.

variableRef is the odd one, and it is checked before the "any goes with anything" rule rather than after. Without that exception a global parameter's untyped output would satisfy an assignment target, and you would have an EA assigning to something that is not a variable.

The rule to internalise: an indicator does not connect to a Buy node. RSI produces a number and a Buy node wants a yes or no. The comparison node in between is not ceremony, it is where you say what about the RSI should cause a trade.

One edge per input, with one exception

Inputs take exactly one connection. Outputs can feed as many places as you like.

The exception is the end-of-handler nodes — On Tick End and On New Candle End — which accept any number. An end node is where the handler returns, and a real strategy reaches it down more than one path: the long branch, the short branch, and the branch that decided to do nothing all finish there. With a single-edge port only one of them could say so, and the others would run off the end of the graph with nothing marking them as terminated.

If you need two things to feed one input, that is what the AND and OR nodes are for. They declare how many inputs they have (two to six), and wiring can raise that count but never lower it — the count is your declaration, and the stepper refuses to drop below the highest input you have actually wired rather than orphaning an edge.

An event is not a condition

"On Tick AND rsi > 70" is not a thing you can say, and the builder says so rather than letting it through.

An event is control flow — it is what makes the question worth asking, not half of the question. This is a structural rule rather than a type, and the reason is worth stating because it looks like an inconsistency: an if-condition's true output is genuinely both a value (feed it to an AND) and a trigger (feed it to a Buy). Typing events as their own thing would force the order trigger to be that thing too, and break the single most common edge in the app. So the narrow rule states the one nonsense case instead of inventing a type the schema cannot support.

No cycles

A connection that would create a loop is refused, because the generated code would need a value that does not exist yet.

If you want this tick's decision to depend on last tick's value, that is what a Variable node is for: write to it at the end of the handler, read from it at the start of the next one. The loop happens in time, where it belongs, instead of in the graph.

Ids drive logic; labels are decoration

Underneath all of this is one rule that explains most of the app's behaviour: an edge stores node ids and port ids. Never a label.

So renaming things is always safe. Rename a custom indicator's output from "Upper band" to "Resistance" and every wire stays put. Rename the node itself and nothing moves. There is no operation in the builder where changing a piece of text you read on screen rewires a strategy.

The corollary is that a port which does not exist cannot be wired to. When a graph is loaded, an edge pointing at an unknown port is dropped rather than guessed at — failing closed, because the alternative is an edge that quietly attaches to the wrong thing.

Deleting is also a graph operation

Deleting a node removes the edges attached to it, because a dangling edge fails validation the next time the project loads.

It also re-runs the synchronisation pass, which matters more than it sounds. Delete the wire feeding a Buy node's Lot Size port and the node goes back to using its typed lot — it does not sit there claiming to be wired with no wire attached. The same applies to every port that can be either typed or fed: unplugging always restores the value that was underneath.

When a refusal is the cheapest thing that happens

The canvas refuses what it can see. A second set of checks runs before an export, and refuses things that are only knowable at that point: an indicator your target platform does not ship, an assignment with nothing connected to it, a parameter name that is not a legal identifier, a custom indicator compiled for the other terminal.

Both of those happen before anything is spent on generating code, and each names the node responsible. Finding out in MetaEditor instead is a slower way to learn the same fact. If you are new to the canvas, the overview of how the graph becomes code is the place to start.

Keep reading

← More from the TradeSmithy blog