Skip to content

phasequant/signal-fabric

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

245 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Signal Fabric

Signal Fabric is a lightweight server application framework for generating market signals on demand, developed in Python 3. It supports various targets (asset classes, markets, etc.) and signal operations through a modular architecture that allows for easy extension and customization.

Its uniqueness lies in providing signals that are valuable over meaningful time frames and that help reveal macro trends — while also being useful for fast-acting systems.

Current Version: 0.1.29 (alpha)

Core Principles

  • Scoped to financial domain, whether decentralized or centralized.

  • Remain fully open source to promote transparency and wider adoption.

  • Be entirely free for personal use and research purposes.

Design Goals

  • Offer automatic trading systems raw or processed data to support decisions-making.

  • Prototype composite signals from raw ones using Jupyter Notebook or similar environments.

Non-Goals

  • Not designed for high‑frequency trading (HFT) or other fast trading systems.

  • Direct interaction with markets is explicitly out of scope.

Further Notes

  • The codebase is still stabilizing, and contributions via pull request are not open yet. Key interfaces are subject to refinement; feel free to experiment with modules, but keep in mind that breaking changes may occur.

  • Backtesting is supported for signals that produce predictions. This feature is useful for evaluating how such signals perform on historical data, but it is secondary to the project’s primary goal of delivering market metrics.

Modules

Path:

src
└── server
    ├── handlers      # signal handlers
    ├── services      # core services
    └── serializers   # result encoders

Handlers:

  • RSISignal: RSI indicator signal handler
  • MASignal: MA indicator signal handler (supports sma, ema, wma)
  • ATRSignal: ATR indicator signal handler
  • DMISignal: DMI indicator signal handler(combines ADX, DI+, DI-)
  • PriceStructSignal: Price structure signal handler
  • TrendDirectSignal: Trend direction signal handler
  • MntmQualitySignal: Momentum Quality signal handler
  • MACDSignal: MACD indicator signal handler

Services:

  • CacheManager: factory-pattern based caching to create scoped caches
  • ExchangeClient: abstract base type for exchange implementations
  • BinanceClient: provides access to OHLCV data from Binance CEX
  • HyperliquidClient: provides access to OHLCV data from Hyperliquid DEX
  • OHLCVFetcher: abstracts OHLCV data fetching and provides caching
  • OHLCVFileLoader: data provider that loads candles from JSON files on disk

Serializers:

  • json: built‑in option for JSON‑encoded output

Signal Sample

def _compute_signal_impl(self, target: str, signal_op: str, request: Optional[dict]) -> Outcome:
    """
    Compute RSI signal
    """
    try:
        # Extract parameters
        period = int(request['period'])
        timeframe = request['timeframe']

        # Get OHLCVFetcher service via RuntimeContext
        ohlcv_fetcher = self._context.get_service(self._ohlcv_service_name)

        # Check service exists and has required method (duck typing to avoid class identity issues)
        if not ohlcv_fetcher or not hasattr(ohlcv_fetcher, 'retrieve_ohlcv'):
            return self._error(f"OHLCV service '{self._ohlcv_service_name}' not found or invalid")

        # Fetch OHLCV data - need period + 100 candles for RSI warmup
        limit = period + 100
        candles = ohlcv_fetcher.retrieve_ohlcv(
            symbol=self._symbol,
            timeframe=timeframe,
            limit=limit
        )

        if not candles or len(candles) < period:
            return self._error(f"Insufficient candle data: need {period}, got {len(candles) if candles else 0}")

        # Extract close prices as numpy array
        close_prices = np.array([candle.close for candle in candles])

        # Calculate RSI using TA-Lib
        rsi_values = talib.RSI(close_prices, timeperiod=period)

        # Prepare the list for output
        rsi_list = rsi_values[~np.isnan(rsi_values)].round(2).tolist()

        # Get the latest RSI value (last non-NaN value)
        latest_rsi = None
        for i in range(len(rsi_values) - 1, -1, -1):
            if not np.isnan(rsi_values[i]):
                latest_rsi = float(rsi_values[i])
                break

        if latest_rsi is None:
            return self._error('Failed to compute RSI: all values are NaN')

        # Determine regime
        if latest_rsi >= 70:
            regime = 'overbought'
        elif latest_rsi <= 30:
            regime = 'oversold'
        else:
            regime = 'neutral'

        # Build result object - will be serialized by SignalProcessor
        result_obj = {
            'symbol': self._symbol,
            'rsi': rsi_list,
            'latest_rsi': round(latest_rsi, 2),
            'regime': regime
        }

        return Outcome(
            result=result_obj,
            computation=self.get_metadata()['name']
        )

    except Exception as e:
        self.logger.error(f"Error computing RSI: {e}", exc_info=True)
        return self._error(str(e))

Client Sample

from signal_fabric import GrpcClient, SignalOutcome
import signal_fabric
print(f"Using signal_fabric from: {signal_fabric.__file__}")

import json

with GrpcClient(host='localhost', port=9090, use_tls=False) as client:
    outcome : SignalOutcome = client.process_signal(
        target='BTC',
        signal_name='rsi_hyperliquid',
        signal_op='compute_rsi',
        handler_request={
            "period":  14,
            "timeframe": "1h"
        }
    )
    # Check for errors first
    if outcome.errors:
        print('We got errors:')
        for err_id, err_message in outcome.errors.items():
            print(f"  - {err_id}: {err_message}")
        print(f"\nResult: {outcome.result}")
        if outcome.result_format:
            print(f"Result format: {outcome.result_format}")
        print(f"Computation: {outcome.computation}")
        if outcome.handler_request:
            print(f"Handler request (echoed): {outcome.handler_request}")
    else:
        if not outcome.result:
            print("Error: Empty result received")
        else:
            format_info = f"result_format: {outcome.result_format}\n" if outcome.result_format else ""
            print(f"""target: {outcome.target}
latest_rsi: {outcome.result['latest_rsi']}
regime: {outcome.result['regime']}
{format_info}computed_at: {outcome.computed_at}
handler_request (echoed): {outcome.handler_request if outcome.handler_request else 'None'}
""")

Test the Sample

pip install signal-fabric-client
python ./rsi_sample.py

Output

target: BTC
latest_rsi: 59.06050385691097
regime: neutral
result_format: json
computed_at: 2025-12-25T17:36:09.613636Z
handler_request (echoed): {'period': 14, 'timeframe': '1h'}

Server Configuration

server:
  host: localhost
  port: 9090
  grpc_workers: 10
  grpc_protocol: http # https also available
  serializer: json

signals:
  - rsi_hyperliquid:
      _binding: rsi_signal
      ohlcv_service: ohlcv_hyperliquid

  - dmi_hyperliquid:
      _binding: dmi_signal
      ohlcv_service: ohlcv_hyperliquid

  - atr_hyperliquid:
      _binding: atr_signal
      ohlcv_service: ohlcv_hyperliquid

  - supertrend_hyperliquid:
      _binding: supertrend_signal
      ohlcv_service: ohlcv_hyperliquid
      atr_signal: atr_hyperliquid

  - ma_hyperliquid:
      _binding: ma_signal
      ohlcv_service: ohlcv_hyperliquid

  - macd_hyperliquid:
      _binding: macd_signal
      ma_signal: ma_hyperliquid
      fast_period: 12
      slow_period: 26
      signal_period: 9

services:
  - cache_manager:
      _service: cache
      _binding: CacheManager
      max_items: 10000
      max_bytes: 104857600

  - hyperliquid_client:
      _service: exchange/hyperliquid_client
      _binding: HyperliquidClient
      _api_url: https://api.hyperliquid.xyz
      _run_mode: prod
      timeout_sec: 10.0
      max_retries: 1
      retry_delay: 0

  - ohlcv_hyperliquid:
      _service: exchange/data_providers
      _binding: OHLCVFetcher
      _using:
        - hyperliquid_client
        - cache_manager
      ttl_1m: 60
      ttl_5m: 300
      ttl_1h: 3600
      ttl_1d: 86400
      ttl_default: closest # Auto-select nearest TTL

Releases

No releases published

Packages

No packages published

Languages