The basic unit of every quantitative strategy — and why it's not what most people think.
A trading signal is not a forecast. It's a binary decision produced by applying a rule to market data. At any given bar, the rule outputs one of three values:
| Value | Meaning |
|---|---|
1.0 | Hold a long position |
0.0 | Hold no position (flat) |
NaN | Keep whatever position you had before |
That's it. The strategy doesn't "predict" the future — it defines a systematic rule for when to be in and out of the market.
Most beginners confuse the stages. Here's how a signal actually becomes money moving on an exchange:
A raw number computed from price or volume data. Example: SMA(20) = 42,300, SMA(50) = 41,800.
A rule applied to the indicator produces 1.0 or 0.0. Example: SMA_fast > SMA_slow → signal = 1.0.
The runner converts the signal to a capital allocation. Example: signal = 1.0 → deploy 100% of capital.
The reconciler compares target position vs. actual position and sends orders to close the gap.
In BlaveClaw Type A strategies, signals are computed at the close of bar T, but orders execute at the open of bar T+1. This is not a limitation — it's intentional.
In practice, for 1h bars on a liquid market like BTCUSDT, the next-open slippage is typically small. For illiquid or low-float assets it can be significant — always model fees and slippage honestly.
Two series cross each other. Long when fast > slow, flat when slow > fast. Classic SMA cross, MACD signal line cross, etc.
signal = 1.0 if SMA_fast > SMA_slow else 0.0
A single indicator exceeds a level. Entry when indicator > ENTRY_TH, exit when < EXIT_TH. Allows a "dead zone" to avoid flip-flopping.
signal = 1.0 if TI > 1.69 else (0.0 if TI < -0.45 else NaN)
A second indicator gates the primary signal. Only enter when the market regime is favorable. Reduces false signals in choppy markets.
signal = primary_signal if market_dir > 0 else 0.0
The btc_sma_cross example strategy is the simplest possible Type A strategy:
def compute_signals(df):
signal = pd.Series(np.nan, index=df.index)
golden = (df['SMA_F'] > df['SMA_S']) # fast crosses above slow
death = (df['SMA_F'] < df['SMA_S']) # fast crosses below slow
signal[golden] = 1.0 # long
signal[death] = 0.0 # flat
return signal
The signal is NaN until both SMAs have enough history to compute (the warm-up period). During warm-up, BlaveClaw holds no position. Once the first crossover fires, it switches between long and flat based on the rule.