-
Notifications
You must be signed in to change notification settings - Fork 0
1. Generic Nodes (csp.baselib)
CSP comes with some basic constructs readily available and commonly used. The latest set of baselib nodes / adapters can be found in the csp.baselib module.
All of the nodes / adapters noted here are imported directly into the csp namespace when importing csp. These are all graph-time constructs.
csp.timer(
interval: timedelta,
value: '~T' = True,
allow_deviation: bool = False
)This will create a repeating timer edge that will tick on the given timedelta with the given value (value defaults to True, returning a ts[bool])
Args:
-
interval: how often to tick value -
value: the actual value that will tick every interval (defaults to the valueTrue) -
allow_deviation: When running in realtime the engine will ensure timers execute exactly when they requested on their intervals. If your engine begins to lag, timers will still execute at the expected time "in the past" as the engine catches up (imagine having acsp.timerfire every 1/2 second but the engine becomes delayed for 1 second. By default the half seconds will still execute until time catches up to wallclock). Whenallow_deviationisTrue, and the engine is in realtime mode, subsequent timers will always be scheduled from the current wallclock + interval, so they won't end up lagging behind at the expensive of the timer skewing.
csp.const(
value: '~T',
delay: timedelta = timedelta()
)This will create an edge that ticks one time with the value provided. By default this will tick at the start of the engine, delta can be provided to delay the tick
csp.curve(
typ: 'T',
data: typing.Union[list, tuple]
)This allows you to convert a list of non-csp data into a ticking edge in csp
Args:
-
typ: is the type of the value of the data of this edge -
data: is either a list of tuples of(datetime, value), or a tuple of two equal-length numpy ndarrays, the first with datetimes and the second with values. In either case, that will tick on the returned edge into the engine, and the data must be in time order. Note that for the list of tuples case, you can also provide tuples of (timedelta, value) where timedelta will be the offset from the engine's start time.
csp.add_graph_output(
key: object,
input: ts['T'],
tick_count: int = -1,
tick_history: timedelta = timedelta()
)This allows you to connect an edge as a "graph output".
All edges added as outputs will be returned to the caller from csp.run as a dictionary of key: [(datetime, value)]
(list of datetime, values that ticked on the edge) or if csp.run is passed output_numpy=True, as a dictionary of
key: (array, array) (tuple of two numpy arrays, one with datetimes and one with values).
See Collecting Graph Outputs
Args:
-
key: key to return the results as from csp.run -
input: edge to connect -
tick_count: number of ticks to keep in the buffer (defaults to -1 - all ticks) -
tick_history: amount of ticks to keep by time window (defaults to keeping all history)
csp.feedback(typ)csp.feedback is a construct that can be used to create artificial loops in the graph.
Use feedbacks in order to delay bind an input to a node in order to be able to create a loop
(think of writing a simulated exchange that takes orders in and needs to feed responses back to the originating node).
csp.feedback itself is not an edge, its a construct that allows you to access the delayed edge / bind a delayed input.
Args:
-
typ: type of the edge's data to be bound
Methods:
-
out(): call this method on the feedback object to get the edge which can be wired as an input -
bind(x: ts[object]): call this to bind an edge to the feedback
csp.print(
tag: str,
x: ts['T'])This node will print (using python print()) the time, tag and value of x for every tick of x
csp.log(
level: int,
tag: str,
x: ts['T'],
logger: typing.Optional[logging.Logger] = None,
logger_tz: object = None,
use_thread: bool = False
)Similar to csp.print, this will log ticks using the logger on the provided level.
The default 'csp' logger is used if none is provided to the node.
Args:
-
logger_tz: time zone to use for log entries -
use_thread: ifTrue, the logging calls will occur in a separate thread as to not block graph execution. This can be useful when printing large strings in log calls. If individual time-series values are subject to modification after the log call, then the user must pass in a copy of the time-series if they wish to have proper threaded logging.
csp.sample(
trigger: ts[object],
x: ts['T']
) → ts['T']Use this to down-sample an input.
csp.sample will return the current value of x any time trigger ticks.
This can be combined with csp.timer to sample the input on a time interval.
csp.firstN(
x: ts['T'],
N: int
) → ts['T']Only output the first N ticks of the input.
csp.count(x: ts[object]) → ts[int]Returns the ticking count of ticks of the input
csp.delay(
x: ts['T'],
delay: typing.Union[timedelta, int]
) → ts['T']This will delay all ticks of the input x by the given delay, which can be given as a timedelta to delay a specified amount of time, or as an int to delay a specified number of ticks (delay must be positive)
csp.diff(
x: ts['T'],
lag: typing.Union[timedelta, int]
) → ts['T']When x ticks, output difference between current tick and value time or ticks ago (once that exists)
csp.merge( x: ts['T'], y: ts['T']) → ts['T']Merges the two timeseries x and y into a single series.
If both tick on the same cycle, the first input (x) wins and the value of y is dropped.
For loss-less merging see csp.flatten
csp.split(
flag: ts[bool],
x: ts['T']
) → {false: ts['T'], true: ts['T']}Splits input x onto two outputs depending on the value of flag.
If flag is True when x ticks, output 'true' will tick with the value of x.
If flag is False at the time of the input tick, then 'false' will tick.
Note that if flag is not valid at the time of the input tick, the input will be dropped.
csp.filter(flag: ts[bool], x: ts['T']) → ts['T']Will only tick out input ticks of x if the current value of flag is True.
If flag is False, or if flag is not valid (hasn't ticked yet) then x is suppressed.
csp.drop_dups(x: ts['T']) → ts['T']Will drop consecutive duplicate values from the input.
csp.unroll(x: ts[['T']]) → ts['T']Given a timeseries of a list of values, unroll will "unroll" the values in the list into a timeseries of the elements.
unroll will ensure to preserve the order across all list ticks.
Ticks will be unrolled in subsequent engine cycles.
csp.collect(x: [ts['T']]) → ts[['T']]Given a basket of inputs, return a timeseries of a list of all values that ticked
csp.flatten(x: [ts['T']]) → ts['T']Given a basket of inputs, return all ticks across all inputs as a single timeseries of type 'T'
(This is similar to csp.merge except that it can take more than two inputs, and is lossless)
csp.default(
x: ts['T'],
default: '~T',
delay: timedelta = timedelta()
)Defaults the input series to the value of default at start of the engine, or after delay if delay is provided.
If x ticks right at the start of the engine, or before delay if delay is provided, default value will be discarded.
csp.gate(
x: ts['T'],
release: ts[bool]
) → ts[['T']]csp.gate will hold values of the input series until release ticks True, at which point all pending values will be output as a burst.
release can tick open / closed repeatedly.
While open, the input will tick out as a single value burst.
While closed, input ticks will buffer up until they can be released.
csp.apply(
x: csp.ts['T'],
f: Callable[['T'], 'O'],
result_type: 'O'
) → ts['O']Applies the provided callable f on every tick of the input and returns the result of the callable.
csp.null_ts(typ: 'T')Returns a "null" timeseries of the given type which will never tick.
csp.stop_engine(x: ts['T'])Forces the engine to stop if x ticks
csp.multiplex(
x: {'K': ts['T']},
key: ts['K'],
tick_on_index: bool = False,
raise_on_bad_key: bool = False
) → ts['T']Given a dictionary basket of inputs and a key timeseries, tick out ticks from the input basket timeseries matching the current key.
Args:
-
x: dictionary basket of timeseries inputs -
key: timeseries of keys that will be used as the multiplex key -
tick_on_index: ifTrue, will tick the current value of the input basket whenever the key ticks (defaults toFalse) -
raise_on_bad_key: ifTruean exception will be raised if key ticks with an unrecognized key (defaults toFalse)
csp.demultiplex(
x: ts['T'],
key: ts['K'],
keys: ['K'],
raise_on_bad_key: bool = False
) → {key: ts['T']}Given a single timeseries input, a key timeseries to demultiplex on and a set of expected keys, will output the given input onto the corresponding basket output of the current value of key.
A good example use case of this is demultiplexing a timeseries of trades by account.
Assuming your trade struct has an account field, you can demultiplex(trades, trades.account, [ 'acct1', 'acct2', ... ]).
Args:
-
x: the input timeseries to demultiplex -
key: a ticking timeseries of the current key to output to -
keys: a list of expected keys that will define the shape of the output basket. The list of keys must be known at graph building time -
raise_on_bad_key: ifTruean exception will be raised of key ticks with an unrecognized key (defaults toFalse)
csp.dynamic_demultiplex(
x: ts['T'],
key: ts['K']
) → {ts['K']: ts['T']}Similar to csp.demultiplex, this version will return a Dynamic Basket output that will dynamically add new keys as they are seen.
csp.dynamic_collect(
x: {ts['K']: ts['T']}
) → ts[{'K': 'T'}]Similar to csp.collect, this function takes a Dynamic Basket input and returns a dictionary of the key-value pairs corresponding to the values that ticked.
csp.drop_nans(x: ts[float]) → ts[float]Filters nan (Not-a-number) values out of the time series.
csp.times(x: ts['T']) → ts[datetime]Given a timeseries, returns the time at which that series ticks
csp.times_ns(x: ts['T']) → ts[int]Given a timeseries, returns the epoch time in nanoseconds at which that series ticks
csp.accum(x: ts["T"], start: "~T" = 0) -> ts["T"]Given a timeseries, accumulate via += with starting value start.
See Math Nodes.
Edges in csp contain some methods to serve as syntactic sugar for stringing nodes together in a pipeline. This makes it easier to read/modify workflows and avoids the need for nested brackets.
Edge.apply(self, func, *args, **kwargs)Calls csp.apply on the edge with the provided python func.
Args:
-
func: A scalar function that will be applied on each value of the Edge. If a different output type is returned, pass a tuple(f, typ), wheretypis the output type of f -
args: Positional arguments passed intofunc -
kwargs: Dictionary of keyword arguments passed into func
Edge.pipe(self, node, *args, **kwargs)Calls the node on the edge.
Args:
-
node: A graph node that will be applied to the Edge, which is passed into node as the first argument. Alternatively, a(node, edge_keyword)tuple whereedge_keywordis a string indicating the keyword of node that expects the edge. -
args: Positional arguments passed intonode -
kwargs: Dictionary of keyword arguments passed intonode
Edge.run(self, node, *args, **kwargs)Alias for csp.run(self, *args, **kwargs)
import csp
from datetime import datetime, timedelta
import math
(csp.timer(timedelta(minutes=1))
.pipe(csp.count)
.pipe(csp.delay, timedelta(seconds=1))
.pipe((csp.sample, 'x'), trigger=csp.timer(timedelta(minutes=2)))
.apply((math.sin, float))
.apply(math.pow, 3)
.pipe(csp.firstN, 10)
.run(starttime=datetime(2000,1,1), endtime=datetime(2000,1,2)))csp.exprtk(
expression_str: str,
inputs: {str: ts[object]},
state_vars: dict = {},
trigger: ts[object] = None,
functions: dict = {},
constants: dict = {},
output_ndarray: bool = False)Given a mathematical expression, and a set of timeseries corresponding to variables in that expression, tick out the result of that expression, either every time an input ticks, or on the trigger if provided.
Args:
-
expression_str: an expression, as per the C++ Mathematical Expression Library (see readme -
inputs: a dict basket of timeseries. The keys will correspond to the variables in the expression. The timeseries can be of float or string -
state_vars: an optional dictionary of variables to be held in state between executions, and assignable within the expression. Keys are the variable names and values are the starting values -
trigger: an optional trigger for when to calculate. By default will calculate on any input tick -
functions: an optional dictionary whose keys are function names that can be used in the expression, and whose values are of the form(("arg1", ..), "function body"), for example{"foo": (("x","y"), "x\*y")} -
constants: an optional dictionary of constants. Keys are constant names and values are their values -
output_ndarray: ifTrue, output ndarray (1D) instead of float. Note that to outputndarray, the expression needs to use return likereturn [a, b, c]. The length of the array can vary between ticks.