Skip to content

10 - Flow Architecture

Overview

Flow is Tradeboard's visual workflow automation system built with XYFlow (React Flow). It enables users to create trading strategies as visual node graphs without coding, supporting scheduled execution, webhook triggers, and price alerts.

Architecture Diagram

┌───────────────────────────────────────────────────────────────────────────────┐
│                               Flow Architecture                               │
└───────────────────────────────────────────────────────────────────────────────┘

┌───────────────────────────────────────────────────────────────────────────────┐
│                         React Flow Canvas (Frontend)                          │
│                                                                               │
│  ┌────────────┐     ┌────────────┐     ┌────────────┐     ┌────────────┐      │
│  │  Trigger   │────▶│  Condition │────▶│   Action   │────▶│   Output   │      │
│  │   Nodes    │     │   Nodes    │     │   Nodes    │     │   Nodes    │      │
│  └────────────┘     └────────────┘     └────────────┘     └────────────┘      │
└───────────────────────────────────────────────────────────────────────────────┘

                                        │ Save/Execute

┌───────────────────────────────────────────────────────────────────────────────┐
│                            Flow Blueprint (/flow)                             │
│                                                                               │
│  ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐             │
│  │  Workflow CRUD   │  │  Webhook Handler │  │  Scheduler Jobs  │             │
│  │  /api/workflows  │  │  /webhook/{token}│  │  APScheduler     │             │
│  └──────────────────┘  └──────────────────┘  └──────────────────┘             │
└───────────────────────────────────────────────────────────────────────────────┘


┌───────────────────────────────────────────────────────────────────────────────┐
│                             Flow Execution Engine                             │
│                                                                               │
│  WorkflowContext ─── Variables, Conditions, Interpolation                     │
│  NodeExecutor ────── 60+ Node Type Handlers                                   │
│  FlowTradeboardClient ─ Tradeboard API Wrapper                                    │
└───────────────────────────────────────────────────────────────────────────────┘


┌───────────────────────────────────────────────────────────────────────────────┐
│                               Database (SQLite)                               │
│                                                                               │
│  flow_workflows │ flow_workflow_executions │ flow_apscheduler_jobs            │
└───────────────────────────────────────────────────────────────────────────────┘

Node Types

services/flow_executor_service.py dispatches 61 node types. Each has a matching React component under frontend/src/components/flow/nodes/. The type values below are the exact strings stored in the workflow's nodes JSON.

Trigger Nodes

Node typeDescriptionConfiguration
startScheduled triggerscheduleType, time, days, intervalValue, intervalUnit, executeAt
webhookTriggerExternal HTTP triggersymbol, exchange (optional)
priceAlertPrice condition triggersymbol, condition, price, percentage
orderUpdateTriggerBroker order-update triggerfilters on the order update event

Order Execution Nodes

Node typeDescriptionConfiguration
placeOrderSingle ordersymbol, exchange, action, quantity, priceType, product
smartOrderPosition-aware orderSame + positionSize
optionsOrderSingle options legunderlying, expiry, offset, optionType
optionsMultiOrderMulti-leg options orderunderlying, legs
modifyOrderModify existingorderId, updated fields
cancelOrderCancel single orderorderId
cancelAllOrdersCancel all open-
closePositionsClose positionsymbol, exchange, product
basketOrderMultiple ordersorders (CSV or array)
splitOrderChunked ordersymbol, quantity, splitSize

An order node whose order-defining fields still contain an unresolved {{...}} reference is failed rather than sent to the broker. The guarded set is ORDER_NODE_TYPES in flow_executor_service.py.

Market Data Nodes

Node typeDescriptionReturns
getQuoteReal-time quoteltp, open, high, low, close, volume
getDepthOrder bookbids, asks, totalbuyqty, totalsellqty
multiQuotesQuotes for several symbolsArray of quotes
historyOHLCV dataArray of candles
priorPeriodOhlcPrevious period OHLCopen, high, low, close
barOffsetValue from an earlier barSingle bar's fields
indicatorTechnical indicatorIndicator series or latest value
intervalsSupported history intervalsArray of intervals
getOrderStatusStatus of one orderOrder status fields
openPositionPosition for symbolquantity, avgprice, pnl
optionChainOptions datacalls, puts, spot_price
optionSymbolResolve an option symbolSymbol string
symbolResolve a symbolSymbol metadata
expiryExpiry datesArray of expiries
syntheticFutureSynthetic future priceComputed price
strategyPnlP&L for a strategyP&L fields
orderBookAll ordersArray of orders
tradeBookAll tradesArray of trades
positionBookAll positionsArray of positions
holdingsDelivery holdingsArray of holdings
fundsAccount balanceavailablecash, marginused
marginMargin requirementMargin fields
calendarMarket calendarCalendar data
holidaysMarket holidaysArray of holidays
timingsMarket timingsSession times

Condition Nodes

Node typeDescriptionOutput Handles
priceConditionCompare priceyes / no
varConditionCompare a workflow variableyes / no
positionCheckCheck position qtyyes / no
fundCheckCheck available fundsyes / no
timeWindowCheck time rangeyes / no
timeConditionCompare with target timeyes / no
andGateLogical ANDsingle output
orGateLogical ORsingle output
notGateLogical NOTsingle output

Gates wait until every wired input has been evaluated before firing, and produce exactly one result per run. This is what prevents an A AND B gate from firing on A alone and then firing a second time for B.

Streaming Nodes

Node typeDescriptionBehavior
subscribeLtpReal-time LTPWebSocket, REST fallback
subscribeQuoteReal-time quoteWebSocket mode 2
subscribeDepthReal-time depthWebSocket mode 3
unsubscribeStop streamingCleanup subscription

Utility Nodes

Node typeDescription
variableSet/get/arithmetic operations
mathExpressionEvaluate an arithmetic expression
logDebug logging
delayWait for duration, capped at DELAY_MAX_SECONDS = 300
waitUntilWait until time
httpRequestExternal API call, timeout capped at HTTP_TIMEOUT_MAX_MS = 60000
telegramAlertSend Telegram notification
whatsappAlertSend WhatsApp notification
groupContainer node for grouping other nodes

Database Schema

Location: database/flow_db.py

FlowWorkflow Table

Model FlowWorkflow, __tablename__ = 'flow_workflows'.

sql
CREATE TABLE flow_workflows (
    id                INTEGER PRIMARY KEY,
    name              VARCHAR(255) NOT NULL,
    description       TEXT,
    nodes             JSON DEFAULT [],      -- React Flow nodes
    edges             JSON DEFAULT [],      -- React Flow edges
    is_active         BOOLEAN DEFAULT FALSE,
    schedule_job_id   VARCHAR(255),         -- APScheduler job ID
    webhook_token     VARCHAR(64) UNIQUE,   -- URL-safe token, generated on insert
    webhook_secret    VARCHAR(64),          -- Generated on insert
    webhook_enabled   BOOLEAN DEFAULT FALSE,
    webhook_auth_type VARCHAR(20) DEFAULT 'payload',  -- 'payload' or 'url'
    api_key           VARCHAR(255),         -- Stored on activation
    created_at        DATETIME,             -- server_default now()
    updated_at        DATETIME              -- server_default now(), onupdate now()
);

webhook_token and webhook_secret are populated by the column defaults generate_webhook_token() and generate_webhook_secret(), so every row gets a webhook identity whether or not the webhook is enabled.

FlowWorkflowExecution Table

Model FlowWorkflowExecution, __tablename__ = 'flow_workflow_executions'.

sql
CREATE TABLE flow_workflow_executions (
    id           INTEGER PRIMARY KEY,
    workflow_id  INTEGER NOT NULL REFERENCES flow_workflows(id),
    status       VARCHAR(50) DEFAULT 'pending',  -- pending, running, completed, failed
    started_at   DATETIME,
    completed_at DATETIME,
    logs         JSON DEFAULT [],
    error        TEXT
);

Execution Engine

Location: services/flow_executor_service.py

Execution Flow

1. Trigger received (webhook/schedule/manual)


2. Load workflow (nodes + edges)


3. Initialize context (variables, conditions)


4. Find trigger node in graph


5. Execute nodes sequentially
   ┌───────┴───────┐
   │ For each node │
   │   • Get input │
   │   • Execute   │
   │   • Store out │
   │   • Log result│
   └───────┬───────┘


6. Handle conditions (yes/no branching)


7. Complete execution, save logs

Safety Limits

python
MAX_NODE_DEPTH = 100      # Maximum nesting depth
MAX_NODE_VISITS = 500     # Maximum total node visits

# Per-workflow mutex (prevent concurrent execution of the same workflow).
# Held weakly so a deleted workflow's lock is collected rather than leaked.
_workflow_locks: "weakref.WeakValueDictionary[int, threading.Lock]" = weakref.WeakValueDictionary()
_locks_mutex = threading.Lock()

Exceeding either limit raises Maximum node depth (100) exceeded or Maximum node visits (500) exceeded and fails the execution.

WorkflowContext

Manages variables and interpolation during execution:

python
class WorkflowContext:
    variables: Dict[str, Any]           # User variables
    condition_results: Dict[str, bool]  # Condition outcomes

    def interpolate(text: str) -> str:
        # Replace {{var}} patterns with values

Built-in Variables

Available in any text field via {{variable}} syntax:

VariableExample Output
{{timestamp}}2024-01-15 14:30:45
{{iso_timestamp}}2024-01-15T14:30:45
{{date}}2024-01-15
{{session_date}}2024-01-15 (trading session date, differs from date between midnight and the 03:00 IST rollover)
{{time}}14:30:45
{{year}} / {{month}} / {{day}}2024 / 01 / 15
{{hour}} / {{minute}} / {{second}}14 / 30 / 45
{{weekday}}Monday
{{weekday_num}}1 (ISO weekday, Monday is 1)
{{quarter}}1
{{week_of_year}}3
{{day_of_year}}15
{{webhook.field}}Webhook payload data

Webhook System

Webhook URLs

POST /flow/webhook/{token}
POST /flow/webhook/{token}/{symbol}

Authentication Methods

Payload Authentication (default):

json
POST /flow/webhook/abc123
{
  "secret": "your_webhook_secret",
  "symbol": "NSE:SBIN-EQ",
  "price": 500.50
}

URL Parameter Authentication:

POST /flow/webhook/abc123?secret=your_webhook_secret

The auth type is per workflow (webhook_auth_type, default payload) and is switched through /flow/api/workflows/{id}/webhook/auth-type. Both comparisons use hmac.compare_digest. In payload mode the secret field is popped out of the body before the workflow sees it. The webhook returns 404 for an unknown token, 403 when the webhook is disabled or the workflow is inactive, and 401 for a missing or wrong secret.

The API key used for execution is resolved in order: the workflow's own api_key stored at activation (decrypted by get_workflow_api_key()), then the session key, then TRADEBOARD_API_KEY from the environment. With none of the three, the call fails with HTTP 500 asking the user to re-activate the workflow.

TradingView Integration

json
// Webhook URL: https://your-domain/flow/webhook/{token}
{
  "secret": "your_secret",
  "symbol": "{{ticker}}",
  "action": "{{strategy.order.action}}",
  "price": "{{close}}"
}

Scheduling System

Location: services/flow_scheduler_service.py

Uses APScheduler with a SQLAlchemy job store for persistence. The table name is FLOW_JOBSTORE_TABLE = "flow_apscheduler_jobs", defined in database/apscheduler_jobstore_db.py.

Schedule Types

TypeConfigurationTrigger
manual-No job is scheduled. A missing scheduleType is treated the same way
dailytime: "09:15"CronTrigger(hour, minute)
weeklytime, days: [1,3,5]CronTrigger(day_of_week, hour, minute)
intervalvalue: 5, unit: "minutes"IntervalTrigger. unit accepts seconds, minutes or hours, defaulting to minutes, and value defaults to 1
onceexecuteAt: ISO datetimeDateTrigger(run_date)

Any other combination raises Invalid schedule configuration.

Cron Examples

python
# Daily at 09:15
CronTrigger(hour=9, minute=15)

# Mon-Fri at 14:30
CronTrigger(day_of_week="mon-fri", hour=14, minute=30)

# Every 5 minutes
IntervalTrigger(minutes=5)

Price Monitoring

Location: services/flow_price_monitor_service.py

Polling-based monitor for price alert triggers.

Alert Conditions

ConditionDescription
greater_thanLTP > target
less_thanLTP < target
crossingPrice within 0.1 percent of target
crossing_upPrevious price at or below target and current price above it
crossing_downPrevious price at or above target and current price below it
entering_channel / inside_channelPrice inside [price_lower, price_upper]
exiting_channel / outside_channelPrice outside [price_lower, price_upper]
moving_upPrice higher than the previous poll
moving_downPrice lower than the previous poll
moving_up_percentPercent increase since the previous poll reaches the configured percentage
moving_down_percentPercent decrease since the previous poll reaches the configured percentage

An unrecognized condition is logged as an error rather than silently evaluating to false.

Monitor Lifecycle

1. Workflow activated with priceAlert trigger


2. Add alert to monitor (symbol, condition, price)


3. Monitor polls every 5 seconds


4. Condition met → Execute workflow


5. Remove alert from monitor

API Endpoints

Blueprint: flow, url_prefix="/flow". No route in this blueprint carries a @limiter.limit decorator.

Workflow Management

EndpointMethodDescription
/flow/api/workflowsGETList all workflows
/flow/api/workflowsPOSTCreate workflow
/flow/api/workflows/{id}GET/PUT/DELETECRUD operations
/flow/api/workflows/{id}/activatePOSTActivate workflow
/flow/api/workflows/{id}/deactivatePOSTDeactivate workflow
/flow/api/workflows/{id}/executePOSTManual execute
/flow/api/workflows/{id}/executionsGETExecution history
/flow/api/workflows/{id}/exportGETExport workflow as JSON
/flow/api/workflows/importPOSTImport a workflow as a new record
/flow/api/workflows/{id}/replacePOSTReplace an existing workflow's graph

Webhook Management

EndpointMethodDescription
/flow/api/workflows/{id}/webhookGETGet webhook config
/flow/api/workflows/{id}/webhook/enablePOSTEnable webhook
/flow/api/workflows/{id}/webhook/disablePOSTDisable webhook
/flow/api/workflows/{id}/webhook/regeneratePOSTNew token and secret
/flow/api/workflows/{id}/webhook/regenerate-secretPOSTNew secret only, token unchanged
/flow/api/workflows/{id}/webhook/auth-typePOSTSwitch between payload and url auth

Public Webhook

EndpointMethodDescription
/flow/webhook/{token}POSTTrigger workflow
/flow/webhook/{token}/{symbol}POSTTrigger with symbol

Helper Endpoints

EndpointMethodDescription
/flow/api/monitor/statusGETPrice monitor status (tracked alerts, poll interval)
/flow/api/index-symbolsGETIndex symbol list for node dropdowns
/flow/api/symbol-lotsizesPOSTLot sizes for a batch of symbols

Key Files Reference

FilePurpose
blueprints/flow.pyFlow API endpoints and webhook handler
database/flow_db.pyDatabase models (FlowWorkflow, FlowWorkflowExecution)
services/flow_executor_service.pyExecution engine (WorkflowContext, NodeExecutor)
services/flow_scheduler_service.pyAPScheduler integration
services/flow_price_monitor_service.pyPrice alert monitoring
services/flow_tradeboard_client.pyTradeboard API client wrapper
frontend/src/pages/flow/FlowIndex.tsxWorkflow list UI
frontend/src/pages/flow/FlowEditor.tsxVisual editor (XYFlow)
frontend/src/components/flow/nodes/Custom node components
frontend/src/components/flow/panels/ConfigPanel, ExecutionLogPanel