data-finder 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data-finder/calc/src/calc/__init__.py +0 -0
- data-finder/calc/src/calc/calc_protocol.py +56 -0
- data-finder/calc/src/calc/simple_price.py +24 -0
- data-finder/calc/tests/test_calc.py +52 -0
- data-finder/datafinder/src/datafinder/__init__.py +6 -0
- data-finder/datafinder/src/datafinder/attribute.py +26 -0
- data-finder/datafinder/src/datafinder/finder.py +7 -0
- data-finder/datafinder/src/datafinder/operation.py +162 -0
- data-finder/datafinder/src/datafinder/output.py +15 -0
- data-finder/datafinder/src/datafinder/runner.py +30 -0
- data-finder/datafinder/src/datafinder/typed_attributes.py +43 -0
- data-finder/datafinder/src/datafinder/typed_operations.py +80 -0
- data-finder/datafinder_duckdb/__init__.py +0 -0
- data-finder/datafinder_duckdb/duckdb_engine.py +30 -0
- data-finder/datafinder_generator/src/datafinder_generator/generator.py +25 -0
- data-finder/datafinder_ibis/src/datafinder_ibis/__init__.py +0 -0
- data-finder/datafinder_ibis/src/datafinder_ibis/ibis_engine.py +31 -0
- data-finder/datafinder_ibis/tests/test_ibis_engine.py +8 -0
- data-finder/datafinder_ibis_duckdb/tests/test_datafinder_ibis_duckdb.py +53 -0
- data-finder/example/__init__.py +0 -0
- data-finder/example/account_finder.py +37 -0
- data-finder/example/contractualposition_finder.py +56 -0
- data-finder/example/instrument_finder.py +37 -0
- data-finder/example/mappings.py +106 -0
- data-finder/example/queries.py +29 -0
- data-finder/example/trade_finder.py +47 -0
- data-finder/example_duckdb/__init__.py +0 -0
- data-finder/example_duckdb/duckdb_example.py +20 -0
- data-finder/example_duckdb/duckdb_trade_finder.py +24 -0
- data-finder/model/src/model/__init__.py +0 -0
- data-finder/model/src/model/m3.py +51 -0
- data-finder/model/src/model/mapping.py +19 -0
- data-finder/model/src/model/relational.py +41 -0
- data_finder-0.1.0.dist-info/METADATA +38 -0
- data_finder-0.1.0.dist-info/RECORD +37 -0
- data_finder-0.1.0.dist-info/WHEEL +5 -0
- data_finder-0.1.0.dist-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from abc import ABC
|
|
2
|
+
|
|
3
|
+
from numpy import ndarray
|
|
4
|
+
|
|
5
|
+
from datafinder import Attribute
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DomainAwareCalcProtocol:
|
|
9
|
+
def name(self):
|
|
10
|
+
raise NotImplementedError
|
|
11
|
+
|
|
12
|
+
# TODO should this be an ordered set ? to avoid duplicates
|
|
13
|
+
def inputs_spec(self) -> list[Attribute]:
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
|
|
16
|
+
def output_spec(self) -> Attribute:
|
|
17
|
+
raise NotImplementedError
|
|
18
|
+
|
|
19
|
+
#TODO should have key and time?
|
|
20
|
+
def calculate(self, inputs:ndarray) -> []:
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# From https://charlesreid1.github.io/python-patterns-the-registry.html
|
|
25
|
+
class RegistryBase(type):
|
|
26
|
+
|
|
27
|
+
REGISTRY = {}
|
|
28
|
+
|
|
29
|
+
def __new__(cls, name, bases, attrs):
|
|
30
|
+
# instantiate a new type corresponding to the type of class being defined
|
|
31
|
+
# this is currently RegisterBase but in child classes will be the child class
|
|
32
|
+
new_cls = type.__new__(cls, name, bases, attrs)
|
|
33
|
+
cls.REGISTRY[new_cls.__name__] = new_cls
|
|
34
|
+
return new_cls
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def get_registry(cls):
|
|
38
|
+
return dict(cls.REGISTRY)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class BaseRegisteredClass(metaclass=RegistryBase):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CalcEngineRegistry(metaclass=RegistryBase):
|
|
46
|
+
calcs = {}
|
|
47
|
+
|
|
48
|
+
@staticmethod
|
|
49
|
+
def register(calc:DomainAwareCalcProtocol):
|
|
50
|
+
CalcEngineRegistry.calcs[calc.name()] = calc
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class DomainAwareCalc(DomainAwareCalcProtocol, ABC):
|
|
54
|
+
|
|
55
|
+
def __init__(self):
|
|
56
|
+
CalcEngineRegistry.register(self)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from calc.calc_protocol import DomainAwareCalc
|
|
2
|
+
from contractualposition_finder import ContractualPositionFinder
|
|
3
|
+
from datafinder import Attribute
|
|
4
|
+
from numpy import array
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def npv(quantity: array, price: array):
|
|
8
|
+
return quantity * price
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PositionNPVCalc(DomainAwareCalc):
|
|
12
|
+
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
return 'npv'
|
|
15
|
+
|
|
16
|
+
def inputs_spec(self) -> list[Attribute]:
|
|
17
|
+
return [ContractualPositionFinder.quantity(),
|
|
18
|
+
ContractualPositionFinder.instrument().price()]
|
|
19
|
+
|
|
20
|
+
def calculate(self, inputs):
|
|
21
|
+
return npv(inputs[0], inputs[1])
|
|
22
|
+
|
|
23
|
+
def output_spec(self) -> Attribute:
|
|
24
|
+
return ContractualPositionFinder.npv()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
import duckdb
|
|
4
|
+
import numpy
|
|
5
|
+
|
|
6
|
+
from calc.calc_protocol import CalcEngineRegistry
|
|
7
|
+
from numpy.testing import assert_array_almost_equal
|
|
8
|
+
|
|
9
|
+
from datafinder import QueryRunnerBase
|
|
10
|
+
from mappings import generate_mappings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestCalc:
|
|
14
|
+
|
|
15
|
+
def setup(self):
|
|
16
|
+
#Register the Ibis engine
|
|
17
|
+
from datafinder_ibis.ibis_engine import IbisConnect
|
|
18
|
+
assert QueryRunnerBase.get_runner() == IbisConnect
|
|
19
|
+
|
|
20
|
+
generate_mappings()
|
|
21
|
+
con = duckdb.connect('test.db')
|
|
22
|
+
con.execute("DROP TABLE IF EXISTS contractualposition;")
|
|
23
|
+
con.execute(
|
|
24
|
+
"CREATE TABLE contractualposition(DATE DATE, INSTRUMENT VARCHAR, CPTY_ID INT, QUANTITY DOUBLE); COPY contractualposition FROM 'data/contractualpositions.csv'")
|
|
25
|
+
con.sql("SELECT * from contractualposition").show()
|
|
26
|
+
|
|
27
|
+
con.execute("DROP TABLE IF EXISTS price;")
|
|
28
|
+
con.execute(
|
|
29
|
+
"CREATE TABLE price(DATE_TIME DATETIME, SYM VARCHAR, PRICE DOUBLE); COPY price FROM 'data/prices.csv'")
|
|
30
|
+
|
|
31
|
+
def test_price(self):
|
|
32
|
+
self.setup()
|
|
33
|
+
# TODO - we have to import this first for it to register due to Python's dynamic nature
|
|
34
|
+
# need to do imports to force the load = https://stackoverflow.com/questions/73829483/register-classes-in-different-files-to-a-class-factory
|
|
35
|
+
from calc.simple_price import PositionNPVCalc
|
|
36
|
+
calc = PositionNPVCalc()
|
|
37
|
+
assert len(CalcEngineRegistry.calcs) == 1
|
|
38
|
+
|
|
39
|
+
inputs = calc.inputs_spec()
|
|
40
|
+
|
|
41
|
+
#Calc run
|
|
42
|
+
from contractualposition_finder import ContractualPositionFinder
|
|
43
|
+
input_data = ContractualPositionFinder.find_all(datetime.date.today(), datetime.date.today(), "LATEST",
|
|
44
|
+
inputs).to_numpy()
|
|
45
|
+
|
|
46
|
+
output = calc.calculate(input_data)
|
|
47
|
+
|
|
48
|
+
assert_array_almost_equal(output, numpy.array([200000.0, 54999.95]), decimal=2)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Attribute:
|
|
5
|
+
__name: str
|
|
6
|
+
__column_db_type: str
|
|
7
|
+
__owner: str
|
|
8
|
+
__parent: Any
|
|
9
|
+
|
|
10
|
+
def __init__(self, name: str, column_db_type: str, owner:str, parent=None):
|
|
11
|
+
self.__name = name
|
|
12
|
+
self.__column_db_type = column_db_type
|
|
13
|
+
self.__owner = owner
|
|
14
|
+
self.__parent = parent
|
|
15
|
+
|
|
16
|
+
def column_name(self) -> str:
|
|
17
|
+
return self.__name
|
|
18
|
+
|
|
19
|
+
def column_type(self) -> str:
|
|
20
|
+
return self.__column_db_type
|
|
21
|
+
|
|
22
|
+
def owner(self) -> str:
|
|
23
|
+
return self.__owner
|
|
24
|
+
|
|
25
|
+
def parent(self) -> Any:
|
|
26
|
+
return self.__parent
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
from datafinder.attribute import Attribute
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TableAlias:
|
|
7
|
+
def __init__(self, table: str, alias: str):
|
|
8
|
+
self.table = table
|
|
9
|
+
self.alias = alias
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ColumnAlias:
|
|
13
|
+
def __init__(self, column_name: str, table_alias: TableAlias):
|
|
14
|
+
self.column_name = column_name
|
|
15
|
+
self.table_alias = table_alias
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Join:
|
|
19
|
+
def __init__(self, source: ColumnAlias, target: ColumnAlias):
|
|
20
|
+
self.source = source
|
|
21
|
+
self.target = target
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class QueryEngine:
|
|
25
|
+
_select: list[ColumnAlias]
|
|
26
|
+
_from: set[TableAlias]
|
|
27
|
+
_where: list[str]
|
|
28
|
+
_join: list[Join]
|
|
29
|
+
__table_alias_incr: int
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self._where = []
|
|
33
|
+
self._select = []
|
|
34
|
+
self._from = set()
|
|
35
|
+
self._join = []
|
|
36
|
+
self.__table_alias_incr = 0
|
|
37
|
+
self.__table_aliases_by_table = {}
|
|
38
|
+
|
|
39
|
+
def select(self, cols: list[Attribute]):
|
|
40
|
+
for col in cols:
|
|
41
|
+
table = col.owner()
|
|
42
|
+
ta = self.__table_alias_for_table(table)
|
|
43
|
+
parent: JoinOperation = col.parent()
|
|
44
|
+
if parent is not None:
|
|
45
|
+
left = parent.left
|
|
46
|
+
sc = ColumnAlias(left.column_name(), self.__table_alias_for_table(left.owner()))
|
|
47
|
+
right = parent.right
|
|
48
|
+
tc = ColumnAlias(right.column_name(), self.__table_alias_for_table(right.owner()))
|
|
49
|
+
self._join.append(Join(sc, tc))
|
|
50
|
+
else:
|
|
51
|
+
self._from.add(ta)
|
|
52
|
+
ca = ColumnAlias(col.column_name(), ta)
|
|
53
|
+
self._select.append(ca)
|
|
54
|
+
|
|
55
|
+
def __table_alias_for_table(self, table: str) -> TableAlias:
|
|
56
|
+
ta = None
|
|
57
|
+
if table in self.__table_aliases_by_table:
|
|
58
|
+
ta = self.__table_aliases_by_table[table]
|
|
59
|
+
else:
|
|
60
|
+
ta = TableAlias(table, "t" + str(self.__table_alias_incr))
|
|
61
|
+
self.__table_alias_incr = self.__table_alias_incr + 1
|
|
62
|
+
self.__table_aliases_by_table[table] = ta
|
|
63
|
+
return ta
|
|
64
|
+
|
|
65
|
+
def append_where_binary_clause(self, op: str):
|
|
66
|
+
self._where.append(op)
|
|
67
|
+
|
|
68
|
+
def append_where_clause(self, attr: Attribute, op: str, value: str):
|
|
69
|
+
ta = self.__table_alias_for_table(attr.owner())
|
|
70
|
+
self._where.append(ta.alias + '.' + attr.column_name() + ' ' + op + ' ' + value)
|
|
71
|
+
|
|
72
|
+
def build_query_string(self) -> str:
|
|
73
|
+
joins = map(lambda j: ' LEFT OUTER JOIN ' + j.target.table_alias.table + ' AS ' + j.target.table_alias.alias +
|
|
74
|
+
' ON ' + j.source.table_alias.alias + '.' + j.source.column_name + ' = ' +
|
|
75
|
+
j.target.table_alias.alias + '.' + j.target.column_name, self._join)
|
|
76
|
+
return 'SELECT ' + ','.join(map(lambda ca: ca.table_alias.alias + '.' + ca.column_name, self._select)) \
|
|
77
|
+
+ ' FROM ' + ','.join(map(lambda ta: ta.table + ' AS ' + ta.alias, self._from)) \
|
|
78
|
+
+ ''.join(joins) \
|
|
79
|
+
+ self.__build_where()
|
|
80
|
+
|
|
81
|
+
def __build_where(self) -> str:
|
|
82
|
+
if len(self._where) > 0:
|
|
83
|
+
return ' WHERE ' + ''.join(self._where)
|
|
84
|
+
else:
|
|
85
|
+
return ''
|
|
86
|
+
|
|
87
|
+
def where_clauses(self):
|
|
88
|
+
return self._where
|
|
89
|
+
|
|
90
|
+
def start_and(self):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
def end_and(self):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# Interface
|
|
98
|
+
class Operation:
|
|
99
|
+
|
|
100
|
+
def generate_query(self, query: QueryEngine):
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class NoOperation(Operation):
|
|
105
|
+
def __init__(self):
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class SelectOperation(Operation):
|
|
110
|
+
def __init__(self, display: list[Attribute], table: str, filter: Operation):
|
|
111
|
+
self.__display = display
|
|
112
|
+
self.__table = table
|
|
113
|
+
self.__filter = filter
|
|
114
|
+
|
|
115
|
+
def generate_query(self, qe: QueryEngine):
|
|
116
|
+
qe.select(self.__display)
|
|
117
|
+
self.__filter.generate_query(qe)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class AndOperation(Operation):
|
|
121
|
+
__left: Operation
|
|
122
|
+
__right: Operation
|
|
123
|
+
|
|
124
|
+
def __init__(self, lhs: Operation, rhs: Operation):
|
|
125
|
+
self.__left = lhs
|
|
126
|
+
self.__right = rhs
|
|
127
|
+
|
|
128
|
+
def generate_query(self, query: QueryEngine):
|
|
129
|
+
query.start_and()
|
|
130
|
+
self.__left.generate_query(query)
|
|
131
|
+
query.append_where_binary_clause(" and ")
|
|
132
|
+
self.__right.generate_query(query)
|
|
133
|
+
query.end_and()
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class BusinessTemporalOperation(Operation):
|
|
137
|
+
# TODO - which date format should we use
|
|
138
|
+
__business_date_from_inclusive: datetime.date
|
|
139
|
+
__business_date_to_inclusive: datetime.date
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class BaseOperation(Operation):
|
|
143
|
+
|
|
144
|
+
def and_op(self, rhs: Operation):
|
|
145
|
+
return AndOperation(self, rhs)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class JoinOperation(Operation):
|
|
149
|
+
left: Attribute
|
|
150
|
+
right: Attribute
|
|
151
|
+
|
|
152
|
+
def __init__(self, lhs: Attribute, rhs: Attribute):
|
|
153
|
+
self.left = lhs
|
|
154
|
+
self.right = rhs
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def select_sql_to_string(columns: list[Attribute], table: str, op: Operation) -> str:
|
|
158
|
+
qe = QueryEngine()
|
|
159
|
+
select = SelectOperation(columns, table, op)
|
|
160
|
+
select.generate_query(qe)
|
|
161
|
+
return qe.build_query_string()
|
|
162
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from datafinder import Attribute, Operation, DataFrame
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class RegistryBase(type):
|
|
5
|
+
REGISTRY = {}
|
|
6
|
+
|
|
7
|
+
def __new__(cls, name, bases, attrs):
|
|
8
|
+
# instantiate a new type corresponding to the type of class being defined
|
|
9
|
+
# this is currently RegisterBase but in child classes will be the child class
|
|
10
|
+
new_cls = type.__new__(cls, name, bases, attrs)
|
|
11
|
+
cls.REGISTRY[new_cls.__name__] = new_cls
|
|
12
|
+
return new_cls
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def get_registry(cls):
|
|
16
|
+
return dict(cls.REGISTRY)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class QueryRunnerBase(metaclass=RegistryBase):
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def select(columns: list[Attribute], table: str, op: Operation) -> DataFrame:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def get_runner():
|
|
27
|
+
for k in RegistryBase.REGISTRY.keys():
|
|
28
|
+
if k != 'QueryRunnerBase':
|
|
29
|
+
return RegistryBase.REGISTRY[k]
|
|
30
|
+
raise Exception("No query runner registered")
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .operation import *
|
|
2
|
+
from .typed_operations import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class StringAttribute(Attribute):
|
|
6
|
+
|
|
7
|
+
def __init__(self, name: str, column_db_type: str, owner:str, parent=None):
|
|
8
|
+
super().__init__(name, column_db_type, owner, parent)
|
|
9
|
+
|
|
10
|
+
def eq(self, value: str) -> Operation:
|
|
11
|
+
return StringEqOperation(self, value)
|
|
12
|
+
|
|
13
|
+
def __eq__(self, value: str) -> Operation:
|
|
14
|
+
return StringEqOperation(self, value)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class FloatAttribute(Attribute):
|
|
18
|
+
|
|
19
|
+
def __init__(self, name: str, column_db_type: str, owner:str, parent=None):
|
|
20
|
+
super().__init__(name, column_db_type, owner, parent)
|
|
21
|
+
|
|
22
|
+
def eq(self, value: float) -> Operation:
|
|
23
|
+
return PrimitiveEqOperation(self, value)
|
|
24
|
+
|
|
25
|
+
def __eq__(self, value: float) -> Operation:
|
|
26
|
+
return PrimitiveEqOperation(self, value)
|
|
27
|
+
|
|
28
|
+
def __gt__(self, value: float) -> Operation:
|
|
29
|
+
return PrimitiveGreaterThanOperation(self, value)
|
|
30
|
+
|
|
31
|
+
class IntegerAttribute(Attribute):
|
|
32
|
+
|
|
33
|
+
def __init__(self, name: str, column_db_type: str, owner:str, parent=None):
|
|
34
|
+
super().__init__(name, column_db_type, owner, parent)
|
|
35
|
+
|
|
36
|
+
def eq(self, value: float) -> Operation:
|
|
37
|
+
return PrimitiveEqOperation(self, value)
|
|
38
|
+
|
|
39
|
+
def __eq__(self, value: float) -> Operation:
|
|
40
|
+
return PrimitiveEqOperation(self, value)
|
|
41
|
+
|
|
42
|
+
def __gt__(self, value: float) -> Operation:
|
|
43
|
+
return PrimitiveGreaterThanOperation(self, value)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from .operation import BaseOperation, QueryEngine
|
|
2
|
+
from .attribute import Attribute
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class EqOperation(BaseOperation):
|
|
6
|
+
__attribute: Attribute
|
|
7
|
+
|
|
8
|
+
def __init__(self, attrib: Attribute):
|
|
9
|
+
self.__attribute = attrib
|
|
10
|
+
|
|
11
|
+
def attribute(self) -> Attribute:
|
|
12
|
+
return self.__attribute
|
|
13
|
+
|
|
14
|
+
def column_type(self) -> str:
|
|
15
|
+
return self.__attribute.column_type()
|
|
16
|
+
|
|
17
|
+
def column_name(self) -> str:
|
|
18
|
+
return self.__attribute.column_name()
|
|
19
|
+
|
|
20
|
+
def prepare_value(self) -> str:
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PrimitiveEqOperation(EqOperation):
|
|
25
|
+
__value: []
|
|
26
|
+
|
|
27
|
+
def __init__(self, attrib: Attribute, value):
|
|
28
|
+
super().__init__(attrib)
|
|
29
|
+
self.__value = value
|
|
30
|
+
|
|
31
|
+
def generate_query(self, query: QueryEngine):
|
|
32
|
+
query.append_where_clause(self.attribute(), '=', self.prepare_value())
|
|
33
|
+
|
|
34
|
+
def prepare_value(self) -> str:
|
|
35
|
+
return str(self.__value)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class StringEqOperation(EqOperation):
|
|
39
|
+
__value: str
|
|
40
|
+
|
|
41
|
+
def __init__(self, attrib: Attribute, value: str):
|
|
42
|
+
super().__init__(attrib)
|
|
43
|
+
self.__value = value
|
|
44
|
+
|
|
45
|
+
def generate_query(self, query: QueryEngine):
|
|
46
|
+
query.append_where_clause(self.attribute(), 'LIKE', self.prepare_value())
|
|
47
|
+
|
|
48
|
+
def prepare_value(self) -> str:
|
|
49
|
+
#TODO - String escaper
|
|
50
|
+
if self.column_type() == 'kx_symbol':
|
|
51
|
+
return "`" + self.__value
|
|
52
|
+
else:
|
|
53
|
+
return "'" + self.__value + "'"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class GreaterThanOperation(BaseOperation):
|
|
57
|
+
__attribute: Attribute
|
|
58
|
+
|
|
59
|
+
def column_type(self) -> str:
|
|
60
|
+
return self.__attribute.column_type()
|
|
61
|
+
|
|
62
|
+
def __init__(self, attrib: Attribute):
|
|
63
|
+
self.__attribute = attrib
|
|
64
|
+
|
|
65
|
+
def generate_query(self, query: QueryEngine):
|
|
66
|
+
query.append_where_clause(self.__attribute, '>', self.prepare_value())
|
|
67
|
+
|
|
68
|
+
def prepare_value(self) -> str:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class PrimitiveGreaterThanOperation(GreaterThanOperation):
|
|
73
|
+
__value: []
|
|
74
|
+
|
|
75
|
+
def __init__(self, attrib: Attribute, value):
|
|
76
|
+
super().__init__(attrib)
|
|
77
|
+
self.__value = value
|
|
78
|
+
|
|
79
|
+
def prepare_value(self) -> str:
|
|
80
|
+
return str(self.__value)
|
|
File without changes
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from datafinder import Operation, DataFrame, Attribute, select_sql_to_string
|
|
2
|
+
|
|
3
|
+
import duckdb
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class DuckDbConnect:
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def select(columns: list[Attribute], table: str, op: Operation) -> list:
|
|
12
|
+
conn = duckdb.connect('test.db')
|
|
13
|
+
query = select_sql_to_string(columns, table, op)
|
|
14
|
+
print(query)
|
|
15
|
+
# TODO this is inefficient, could convert straight to desired output - such as numpy, instead of list
|
|
16
|
+
return conn.sql(query).fetchall()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DuckDbOutput(DataFrame):
|
|
20
|
+
__table: list
|
|
21
|
+
|
|
22
|
+
def __init__(self, t: list):
|
|
23
|
+
self.__table = t
|
|
24
|
+
|
|
25
|
+
def to_numpy(self) -> np.array:
|
|
26
|
+
return np.array(self.__table)
|
|
27
|
+
|
|
28
|
+
def to_pandas(self) -> pd.DataFrame:
|
|
29
|
+
#todo - this needs to be better, to ensure types and column names
|
|
30
|
+
return pd.DataFrame(self.__table)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from model.m3 import PrimitiveType, Property
|
|
4
|
+
from jinja2 import Environment, PackageLoader
|
|
5
|
+
|
|
6
|
+
from model.mapping import Mapping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def is_primitive(prop: Property) -> bool:
|
|
10
|
+
return isinstance(prop.type, PrimitiveType)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def generate(mapping:Mapping, output_directory):
|
|
14
|
+
environment = Environment(loader=PackageLoader("datafinder_generator"), trim_blocks=True, lstrip_blocks=True)
|
|
15
|
+
template = environment.get_template("finder_template.txt")
|
|
16
|
+
|
|
17
|
+
for rcm in mapping.mappings:
|
|
18
|
+
filename = f"{rcm.clazz.name.lower()}_finder.py"
|
|
19
|
+
filepath = os.path.join(output_directory, filename)
|
|
20
|
+
content = template.render(rcm=rcm,is_primitive=is_primitive)
|
|
21
|
+
with open(filepath, mode="w", encoding="utf-8") as message:
|
|
22
|
+
message.write(content)
|
|
23
|
+
print(f"... wrote {filename}")
|
|
24
|
+
|
|
25
|
+
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from datafinder import Operation, DataFrame, Attribute, select_sql_to_string
|
|
2
|
+
|
|
3
|
+
import ibis
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pandas as pd
|
|
6
|
+
|
|
7
|
+
from datafinder import QueryRunnerBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IbisConnect(QueryRunnerBase):
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def select(columns: list[Attribute], table: str, op: Operation) -> DataFrame:
|
|
14
|
+
conn = ibis.connect('duckdb://test.db')
|
|
15
|
+
query = select_sql_to_string(columns, table, op)
|
|
16
|
+
print(query)
|
|
17
|
+
t = conn.table(table)
|
|
18
|
+
#todo - can also do this with the dataframe API
|
|
19
|
+
return IbisOutput(t.sql(query))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class IbisOutput(DataFrame):
|
|
23
|
+
|
|
24
|
+
def __init__(self, t: ibis.Table):
|
|
25
|
+
self.__table = t
|
|
26
|
+
|
|
27
|
+
def to_numpy(self) -> np.array:
|
|
28
|
+
return self.__table.__array__()
|
|
29
|
+
|
|
30
|
+
def to_pandas(self) -> pd.DataFrame:
|
|
31
|
+
return self.__table.to_pandas()
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import duckdb
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from datafinder import QueryRunnerBase
|
|
7
|
+
from example import queries
|
|
8
|
+
from numpy.testing import assert_array_equal
|
|
9
|
+
|
|
10
|
+
from mappings import generate_mappings
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TestDataFinderIbisDuckDb:
|
|
14
|
+
|
|
15
|
+
def setup(self):
|
|
16
|
+
#Register the Ibis engine
|
|
17
|
+
from datafinder_ibis.ibis_engine import IbisConnect
|
|
18
|
+
assert QueryRunnerBase.get_runner() == IbisConnect
|
|
19
|
+
generate_mappings()
|
|
20
|
+
con = duckdb.connect('test.db')
|
|
21
|
+
con.sql("SELECT 42 AS x").show()
|
|
22
|
+
con.execute("DROP TABLE IF EXISTS trade;")
|
|
23
|
+
con.execute(
|
|
24
|
+
"CREATE TABLE trade(id INT, account_id INT, sym VARCHAR, price DOUBLE); COPY trade FROM 'data/trades.csv'")
|
|
25
|
+
con.sql("SELECT * from trade").show()
|
|
26
|
+
con.sql("SELECT * from trade where sym LIKE 'AAPL'").show()
|
|
27
|
+
|
|
28
|
+
con.execute("DROP TABLE IF EXISTS account;")
|
|
29
|
+
con.execute(
|
|
30
|
+
"CREATE TABLE account(id INT, name VARCHAR); COPY account FROM 'data/accounts.csv'")
|
|
31
|
+
|
|
32
|
+
def test_queries(self):
|
|
33
|
+
self.setup()
|
|
34
|
+
# Import after generation, so we get the latest version
|
|
35
|
+
from trade_finder import TradeFinder
|
|
36
|
+
queries.find_trades(TradeFinder)
|
|
37
|
+
from account_finder import AccountFinder
|
|
38
|
+
np_accts = AccountFinder \
|
|
39
|
+
.find_all(datetime.date.today(), datetime.date.today(), "LATEST",
|
|
40
|
+
[AccountFinder.id(), AccountFinder.name()],
|
|
41
|
+
AccountFinder.id().eq(211978)) \
|
|
42
|
+
.to_numpy()
|
|
43
|
+
print(np_accts)
|
|
44
|
+
assert_array_equal(np_accts, np.array([[211978, 'Trading Account 1']],dtype=object))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
trades_with_account = TradeFinder.find_all(datetime.date.today(), datetime.date.today(), "LATEST",
|
|
48
|
+
[TradeFinder.account().name(), TradeFinder.symbol(),
|
|
49
|
+
TradeFinder.price()],
|
|
50
|
+
TradeFinder.symbol().eq("AAPL"))
|
|
51
|
+
np_trades = trades_with_account.to_numpy()
|
|
52
|
+
print(np_trades)
|
|
53
|
+
assert_array_equal(np_trades, np.array([['Trading Account 1', 'AAPL', 84.11]], dtype=object))
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from datafinder.typed_attributes import *
|
|
2
|
+
from datafinder import QueryRunnerBase, DataFrame
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AccountFinder:
|
|
6
|
+
__table = 'account'
|
|
7
|
+
|
|
8
|
+
__id = IntegerAttribute('id', 'INT', 'account')
|
|
9
|
+
__name = StringAttribute('name', 'VARCHAR', 'account')
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def id() -> IntegerAttribute:
|
|
13
|
+
return AccountFinder.__id
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def name() -> StringAttribute:
|
|
17
|
+
return AccountFinder.__name
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def find_all(date_from: datetime.date, date_to: datetime.date, as_of: str,
|
|
21
|
+
display_columns: list[Attribute],
|
|
22
|
+
filter_op: Operation = NoOperation()) -> DataFrame:
|
|
23
|
+
return QueryRunnerBase.get_runner().select(display_columns, AccountFinder.__table, filter_op)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AccountRelatedFinder:
|
|
27
|
+
def __init__(self, source: Attribute, target: Attribute):
|
|
28
|
+
join = JoinOperation(source,target)
|
|
29
|
+
self.__id = IntegerAttribute('id', 'INT', 'account', join)
|
|
30
|
+
self.__name = StringAttribute('name', 'VARCHAR', 'account', join)
|
|
31
|
+
|
|
32
|
+
def id(self) -> IntegerAttribute:
|
|
33
|
+
return self.__id
|
|
34
|
+
|
|
35
|
+
def name(self) -> StringAttribute:
|
|
36
|
+
return self.__name
|
|
37
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from datafinder.typed_attributes import *
|
|
2
|
+
from datafinder import QueryRunnerBase, DataFrame
|
|
3
|
+
from instrument_finder import InstrumentRelatedFinder
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContractualPositionFinder:
|
|
7
|
+
__table = 'contractualposition'
|
|
8
|
+
|
|
9
|
+
__quantity = FloatAttribute('QUANTITY', 'DOUBLE', 'contractualposition')
|
|
10
|
+
__counterparty = IntegerAttribute('CPTY_ID', 'INT', 'contractualposition')
|
|
11
|
+
__npv = FloatAttribute('NPV', 'DOUBLE', 'contractualposition')
|
|
12
|
+
__instrument = InstrumentRelatedFinder(Attribute('INSTRUMENT', 'VARCHAR', 'contractualposition'),Attribute('SYM', 'VARCHAR', 'price'))
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def quantity() -> FloatAttribute:
|
|
16
|
+
return ContractualPositionFinder.__quantity
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def counterparty() -> IntegerAttribute:
|
|
20
|
+
return ContractualPositionFinder.__counterparty
|
|
21
|
+
|
|
22
|
+
@staticmethod
|
|
23
|
+
def instrument() -> InstrumentRelatedFinder:
|
|
24
|
+
return ContractualPositionFinder.__instrument
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def npv() -> FloatAttribute:
|
|
28
|
+
return ContractualPositionFinder.__npv
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def find_all(date_from: datetime.date, date_to: datetime.date, as_of: str,
|
|
32
|
+
display_columns: list[Attribute],
|
|
33
|
+
filter_op: Operation = NoOperation()) -> DataFrame:
|
|
34
|
+
return QueryRunnerBase.get_runner().select(display_columns, ContractualPositionFinder.__table, filter_op)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ContractualPositionRelatedFinder:
|
|
38
|
+
def __init__(self, source: Attribute, target: Attribute):
|
|
39
|
+
join = JoinOperation(source,target)
|
|
40
|
+
self.__quantity = FloatAttribute('QUANTITY', 'DOUBLE', 'contractualposition', join)
|
|
41
|
+
self.__counterparty = IntegerAttribute('CPTY_ID', 'INT', 'contractualposition', join)
|
|
42
|
+
self.__npv = FloatAttribute('NPV', 'DOUBLE', 'contractualposition', join)
|
|
43
|
+
self.__instrument = InstrumentRelatedFinder(Attribute('INSTRUMENT', 'VARCHAR', 'contractualposition'),Attribute('SYM', 'VARCHAR', 'price'))
|
|
44
|
+
|
|
45
|
+
def quantity(self) -> FloatAttribute:
|
|
46
|
+
return self.__quantity
|
|
47
|
+
|
|
48
|
+
def counterparty(self) -> IntegerAttribute:
|
|
49
|
+
return self.__counterparty
|
|
50
|
+
|
|
51
|
+
def instrument(self) -> InstrumentRelatedFinder:
|
|
52
|
+
return self.__instrument
|
|
53
|
+
|
|
54
|
+
def npv(self) -> FloatAttribute:
|
|
55
|
+
return self.__npv
|
|
56
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from datafinder.typed_attributes import *
|
|
2
|
+
from datafinder import QueryRunnerBase, DataFrame
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InstrumentFinder:
|
|
6
|
+
__table = 'price'
|
|
7
|
+
|
|
8
|
+
__symbol = StringAttribute('SYM', 'VARCHAR', 'price')
|
|
9
|
+
__price = FloatAttribute('PRICE', 'DOUBLE', 'price')
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def symbol() -> StringAttribute:
|
|
13
|
+
return InstrumentFinder.__symbol
|
|
14
|
+
|
|
15
|
+
@staticmethod
|
|
16
|
+
def price() -> FloatAttribute:
|
|
17
|
+
return InstrumentFinder.__price
|
|
18
|
+
|
|
19
|
+
@staticmethod
|
|
20
|
+
def find_all(date_from: datetime.date, date_to: datetime.date, as_of: str,
|
|
21
|
+
display_columns: list[Attribute],
|
|
22
|
+
filter_op: Operation = NoOperation()) -> DataFrame:
|
|
23
|
+
return QueryRunnerBase.get_runner().select(display_columns, InstrumentFinder.__table, filter_op)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class InstrumentRelatedFinder:
|
|
27
|
+
def __init__(self, source: Attribute, target: Attribute):
|
|
28
|
+
join = JoinOperation(source,target)
|
|
29
|
+
self.__symbol = StringAttribute('SYM', 'VARCHAR', 'price', join)
|
|
30
|
+
self.__price = FloatAttribute('PRICE', 'DOUBLE', 'price', join)
|
|
31
|
+
|
|
32
|
+
def symbol(self) -> StringAttribute:
|
|
33
|
+
return self.__symbol
|
|
34
|
+
|
|
35
|
+
def price(self) -> FloatAttribute:
|
|
36
|
+
return self.__price
|
|
37
|
+
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from datafinder_generator.generator import generate
|
|
4
|
+
from model.m3 import Class, Property, String, Float, Package, Integer, Date
|
|
5
|
+
from model.mapping import Mapping
|
|
6
|
+
from model.relational import Column, Table, RelationalClassMapping, RelationalPropertyMapping, Join
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def create_account_class() -> Class:
|
|
10
|
+
p1 = Property('id', Integer)
|
|
11
|
+
p2 = Property('name', String)
|
|
12
|
+
|
|
13
|
+
account_c = Class('Account', [p1, p2], Package('finance'))
|
|
14
|
+
return account_c
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def create_instrument_class() -> Class:
|
|
18
|
+
p1 = Property('symbol', String)
|
|
19
|
+
#TODO this doesn't belong here, but using for simple example
|
|
20
|
+
p2 = Property('price', Float)
|
|
21
|
+
|
|
22
|
+
instrument_c = Class('Instrument', [p1, p2], Package('finance'))
|
|
23
|
+
return instrument_c
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def create_trade_class(account:Class) -> Class:
|
|
27
|
+
p1 = Property('symbol', String)
|
|
28
|
+
p2 = Property('price', Float)
|
|
29
|
+
p3 = Property('account', account)
|
|
30
|
+
|
|
31
|
+
trade_c = Class('Trade', [p1, p2, p3], Package('finance'))
|
|
32
|
+
return trade_c
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def create_contractual_position_class(instrument:Class) -> Class:
|
|
36
|
+
p1 = Property('date', Date)
|
|
37
|
+
p2 = Property('quantity', Float)
|
|
38
|
+
p3 = Property('counterparty', Integer)
|
|
39
|
+
p4 = Property('instrument', instrument)
|
|
40
|
+
p5 = Property('npv', Float)
|
|
41
|
+
pos_c = Class('ContractualPosition', [p1, p2, p3, p4, p5], Package('finance'))
|
|
42
|
+
return pos_c
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def create_mappings_normalized() -> Mapping:
|
|
46
|
+
account_c = create_account_class()
|
|
47
|
+
|
|
48
|
+
ac1 = Column('id', 'INT')
|
|
49
|
+
ac2 = Column('name', 'VARCHAR')
|
|
50
|
+
account_t = Table('account', [ac1, ac2])
|
|
51
|
+
|
|
52
|
+
instrument_c = create_instrument_class()
|
|
53
|
+
ic1 = Column('SYM', 'VARCHAR')
|
|
54
|
+
ic2 = Column('PRICE', 'DOUBLE')
|
|
55
|
+
instrument_t = Table('price', [ic1,ic2])
|
|
56
|
+
|
|
57
|
+
c_position_c = create_contractual_position_class(instrument_c)
|
|
58
|
+
p1 = Column('DATE', 'DATE')
|
|
59
|
+
p2 = Column('INSTRUMENT', 'VARCHAR')
|
|
60
|
+
p3 = Column('CPTY_ID', 'INT')
|
|
61
|
+
p4 = Column('QUANTITY', 'DOUBLE')
|
|
62
|
+
p5 = Column('NPV', 'DOUBLE')
|
|
63
|
+
pos_t = Table('contractualposition', [p1, p2, p3, p4, p5])
|
|
64
|
+
|
|
65
|
+
trade_c = create_trade_class(account_c)
|
|
66
|
+
|
|
67
|
+
c1 = Column('id', 'INT')
|
|
68
|
+
c2 = Column('account_id', 'INT')
|
|
69
|
+
c3 = Column('sym', 'VARCHAR')
|
|
70
|
+
c4 = Column('price', 'DOUBLE')
|
|
71
|
+
|
|
72
|
+
trade_t = Table('trade', [c1, c2, c3, c4])
|
|
73
|
+
|
|
74
|
+
pm1 = RelationalPropertyMapping(trade_c.property('symbol'), c3)
|
|
75
|
+
pm2 = RelationalPropertyMapping(trade_c.property('price'), c4)
|
|
76
|
+
pm3 = RelationalPropertyMapping(trade_c.property('account'),Join(c2,ac1))
|
|
77
|
+
rm_t = RelationalClassMapping(trade_c, [pm1, pm2, pm3])
|
|
78
|
+
|
|
79
|
+
a_pm1 = RelationalPropertyMapping(account_c.property('id'), ac1)
|
|
80
|
+
a_pm2 = RelationalPropertyMapping(account_c.property('name'), ac2)
|
|
81
|
+
rm_a = RelationalClassMapping(account_c, [a_pm1, a_pm2])
|
|
82
|
+
|
|
83
|
+
i_pm1 = RelationalPropertyMapping(instrument_c.property('symbol'), ic1)
|
|
84
|
+
i_pm2 = RelationalPropertyMapping(instrument_c.property('price'), ic2)
|
|
85
|
+
rm_i = RelationalClassMapping(instrument_c, [i_pm1, i_pm2])
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
cpm1 = RelationalPropertyMapping(c_position_c.property('quantity'), p4)
|
|
89
|
+
cpm2 = RelationalPropertyMapping(c_position_c.property('counterparty'), p3)
|
|
90
|
+
cpm3 = RelationalPropertyMapping(c_position_c.property('instrument'), Join(p2,ic1))
|
|
91
|
+
cpm4 = RelationalPropertyMapping(c_position_c.property('npv'), p5)
|
|
92
|
+
rm_cp = RelationalClassMapping(c_position_c, [cpm1, cpm2, cpm3, cpm4])
|
|
93
|
+
|
|
94
|
+
return Mapping('Test Mapping 1', [rm_t,rm_a,rm_i,rm_cp])
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def generate_mappings():
|
|
98
|
+
rcms = create_mappings_normalized()
|
|
99
|
+
import sys
|
|
100
|
+
mn = sys.modules[__name__]
|
|
101
|
+
directory = os.path.dirname(mn.__file__)
|
|
102
|
+
generate(rcms, directory)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
if __name__ == '__main__':
|
|
106
|
+
generate_mappings()
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def find_trades(trade_finder):
|
|
5
|
+
print(f'Finding trades')
|
|
6
|
+
|
|
7
|
+
trades = trade_finder.find_all(datetime.date.today(), datetime.date.today(), "LATEST",
|
|
8
|
+
[trade_finder.symbol(), trade_finder.price()],
|
|
9
|
+
trade_finder.symbol().eq("AAPL"))
|
|
10
|
+
np_trades = trades.to_numpy()
|
|
11
|
+
print(np_trades)
|
|
12
|
+
df = trades.to_pandas()
|
|
13
|
+
print(df)
|
|
14
|
+
|
|
15
|
+
trades = trade_finder.find_all(datetime.date.today(), datetime.date.today(), "LATEST",
|
|
16
|
+
[trade_finder.symbol(), trade_finder.price()],
|
|
17
|
+
trade_finder.price() > 200.0,)
|
|
18
|
+
np_trades = trades.to_numpy()
|
|
19
|
+
print(np_trades)
|
|
20
|
+
df = trades.to_pandas()
|
|
21
|
+
print(df)
|
|
22
|
+
|
|
23
|
+
trades = trade_finder.find_all(datetime.date.today(), datetime.date.today(), "LATEST",
|
|
24
|
+
[trade_finder.symbol(), trade_finder.price()],
|
|
25
|
+
(trade_finder.symbol() == "AAPL").and_op(trade_finder.price() == 84.11))
|
|
26
|
+
np_trades = trades.to_numpy()
|
|
27
|
+
print(np_trades)
|
|
28
|
+
df = trades.to_pandas()
|
|
29
|
+
print(df)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from datafinder.typed_attributes import *
|
|
2
|
+
from datafinder import QueryRunnerBase, DataFrame
|
|
3
|
+
from account_finder import AccountRelatedFinder
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TradeFinder:
|
|
7
|
+
__table = 'trade'
|
|
8
|
+
|
|
9
|
+
__symbol = StringAttribute('sym', 'VARCHAR', 'trade')
|
|
10
|
+
__price = FloatAttribute('price', 'DOUBLE', 'trade')
|
|
11
|
+
__account = AccountRelatedFinder(Attribute('account_id', 'INT', 'trade'),Attribute('id', 'INT', 'account'))
|
|
12
|
+
|
|
13
|
+
@staticmethod
|
|
14
|
+
def symbol() -> StringAttribute:
|
|
15
|
+
return TradeFinder.__symbol
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def price() -> FloatAttribute:
|
|
19
|
+
return TradeFinder.__price
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def account() -> AccountRelatedFinder:
|
|
23
|
+
return TradeFinder.__account
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def find_all(date_from: datetime.date, date_to: datetime.date, as_of: str,
|
|
27
|
+
display_columns: list[Attribute],
|
|
28
|
+
filter_op: Operation = NoOperation()) -> DataFrame:
|
|
29
|
+
return QueryRunnerBase.get_runner().select(display_columns, TradeFinder.__table, filter_op)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TradeRelatedFinder:
|
|
33
|
+
def __init__(self, source: Attribute, target: Attribute):
|
|
34
|
+
join = JoinOperation(source,target)
|
|
35
|
+
self.__symbol = StringAttribute('sym', 'VARCHAR', 'trade', join)
|
|
36
|
+
self.__price = FloatAttribute('price', 'DOUBLE', 'trade', join)
|
|
37
|
+
self.__account = AccountRelatedFinder(Attribute('account_id', 'INT', 'trade'),Attribute('id', 'INT', 'account'))
|
|
38
|
+
|
|
39
|
+
def symbol(self) -> StringAttribute:
|
|
40
|
+
return self.__symbol
|
|
41
|
+
|
|
42
|
+
def price(self) -> FloatAttribute:
|
|
43
|
+
return self.__price
|
|
44
|
+
|
|
45
|
+
def account(self) -> AccountRelatedFinder:
|
|
46
|
+
return self.__account
|
|
47
|
+
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import duckdb
|
|
2
|
+
|
|
3
|
+
from duckdb_trade_finder import *
|
|
4
|
+
from example import queries
|
|
5
|
+
|
|
6
|
+
def duckdb_sample():
|
|
7
|
+
con = duckdb.connect('test.db')
|
|
8
|
+
con.sql("SELECT 42 AS x").show()
|
|
9
|
+
con.execute("DROP TABLE IF EXISTS trade;")
|
|
10
|
+
con.execute(
|
|
11
|
+
"CREATE TABLE trade(id INT, account_id INT, sym VARCHAR, price DOUBLE); COPY trade FROM '../data/trades.csv'")
|
|
12
|
+
con.sql("SELECT * from trade").show()
|
|
13
|
+
con.sql("SELECT * from trade where sym LIKE 'AAPL'").show()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if __name__ == '__main__':
|
|
19
|
+
duckdb_sample()
|
|
20
|
+
queries.find_trades(TradeFinder)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from datafinder.typed_attributes import *
|
|
2
|
+
from datafinder_duckdb.duckdb_engine import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class TradeFinder:
|
|
6
|
+
__table = 'trade'
|
|
7
|
+
__symbol = StringAttribute('sym', 'char')
|
|
8
|
+
__price = FloatAttribute('price', 'double precision')
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def symbol() -> StringAttribute:
|
|
12
|
+
return TradeFinder.__symbol
|
|
13
|
+
|
|
14
|
+
@staticmethod
|
|
15
|
+
def price() -> FloatAttribute:
|
|
16
|
+
return TradeFinder.__price
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def find_all(date_from: datetime.date, date_to: datetime.date, as_of: str,
|
|
20
|
+
filter_op: Operation,
|
|
21
|
+
display_columns: list[Attribute]) -> DataFrame:
|
|
22
|
+
|
|
23
|
+
out = DuckDbConnect.select(display_columns, TradeFinder.__table, filter_op)
|
|
24
|
+
return DuckDbOutput(out)
|
|
File without changes
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
class Package:
|
|
2
|
+
def __init__(self, name: str):
|
|
3
|
+
self.name = name
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PackagableElement:
|
|
7
|
+
def __init__(self, package: Package):
|
|
8
|
+
self.package = package
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Type:
|
|
12
|
+
def __init(self):
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PrimitiveType(Type):
|
|
17
|
+
def __init__(self, name: str):
|
|
18
|
+
self.name = name
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
Integer = PrimitiveType("Integer")
|
|
22
|
+
String = PrimitiveType("String")
|
|
23
|
+
Float = PrimitiveType("Float")
|
|
24
|
+
DateTime = PrimitiveType("DateTime")
|
|
25
|
+
Date = PrimitiveType("Date")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Property:
|
|
29
|
+
def __init__(self, name: str, type: Type):
|
|
30
|
+
self.name = name
|
|
31
|
+
self.type = type
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Class(PackagableElement, Type):
|
|
35
|
+
def __init__(self, name: str, properties: list[Property], package: Package):
|
|
36
|
+
super().__init__(package)
|
|
37
|
+
self.properties = {}
|
|
38
|
+
self.name = name
|
|
39
|
+
for prop in properties:
|
|
40
|
+
self.properties[prop.name] = prop
|
|
41
|
+
|
|
42
|
+
def property(self, name:str) -> Property:
|
|
43
|
+
return self.properties[name]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Association(PackagableElement):
|
|
47
|
+
def __init__(self, name: str, source: str, target: str, package: Package):
|
|
48
|
+
super().__init__(package)
|
|
49
|
+
self.name = name
|
|
50
|
+
self.source = source
|
|
51
|
+
self.target = target
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from model.m3 import Class, Property
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class PropertyMapping:
|
|
7
|
+
def __init__(self, property: Property, target: Any):
|
|
8
|
+
self.property = property
|
|
9
|
+
self.target = target
|
|
10
|
+
|
|
11
|
+
class ClassMapping:
|
|
12
|
+
def __init__(self, clazz: Class, property_mappings: list[PropertyMapping]):
|
|
13
|
+
self.clazz = clazz
|
|
14
|
+
self.property_mappings = property_mappings
|
|
15
|
+
|
|
16
|
+
class Mapping:
|
|
17
|
+
def __init__(self, name: str, mappings: list[ClassMapping]):
|
|
18
|
+
self.name = name
|
|
19
|
+
self.mappings = mappings
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from model.m3 import Property, Class
|
|
2
|
+
from model.mapping import ClassMapping, PropertyMapping
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RelationalElement:
|
|
6
|
+
def __init(self):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Column(RelationalElement):
|
|
11
|
+
def __init__(self, name: str, type: str):
|
|
12
|
+
self.name = name
|
|
13
|
+
self.type = type
|
|
14
|
+
self.table = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Table:
|
|
18
|
+
def __init__(self, name: str, columns: list[Column]):
|
|
19
|
+
self.name = name
|
|
20
|
+
self.columns = columns
|
|
21
|
+
for col in columns:
|
|
22
|
+
col.table = self
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Join(RelationalElement):
|
|
26
|
+
def __init__(self, lhs: Column, rhs: Column):
|
|
27
|
+
self.lhs = lhs
|
|
28
|
+
self.rhs = rhs
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RelationalPropertyMapping(PropertyMapping):
|
|
32
|
+
def __init__(self, property: Property, target: RelationalElement):
|
|
33
|
+
super().__init__(property, target)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class RelationalClassMapping(ClassMapping):
|
|
37
|
+
def __init__(self, clazz: Class, property_mappings: list[RelationalPropertyMapping]):
|
|
38
|
+
super().__init__(clazz, property_mappings)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-finder
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Model driven data finders
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
Requires-Dist: atpublic==4.1.0
|
|
8
|
+
Requires-Dist: bidict==0.23.1
|
|
9
|
+
Requires-Dist: duckdb==1.0.0
|
|
10
|
+
Requires-Dist: greenlet==3.0.3
|
|
11
|
+
Requires-Dist: ibis-framework==9.0.0
|
|
12
|
+
Requires-Dist: iniconfig==2.1.0
|
|
13
|
+
Requires-Dist: jinja2==3.1.6
|
|
14
|
+
Requires-Dist: markdown-it-py==3.0.0
|
|
15
|
+
Requires-Dist: markupsafe==3.0.2
|
|
16
|
+
Requires-Dist: mdurl==0.1.2
|
|
17
|
+
Requires-Dist: numpy==1.26.4
|
|
18
|
+
Requires-Dist: packaging==25.0
|
|
19
|
+
Requires-Dist: pandas==2.2.2
|
|
20
|
+
Requires-Dist: parsy==2.1
|
|
21
|
+
Requires-Dist: pluggy==1.6.0
|
|
22
|
+
Requires-Dist: pyarrow==16.1.0
|
|
23
|
+
Requires-Dist: pyarrow-hotfix==0.7
|
|
24
|
+
Requires-Dist: pygments==2.19.1
|
|
25
|
+
Requires-Dist: pytest==8.3.4
|
|
26
|
+
Requires-Dist: python-dateutil==2.9.0.post0
|
|
27
|
+
Requires-Dist: pytz==2024.1
|
|
28
|
+
Requires-Dist: rich==13.9.4
|
|
29
|
+
Requires-Dist: six==1.16.0
|
|
30
|
+
Requires-Dist: sqlglot==23.12.2
|
|
31
|
+
Requires-Dist: toml==0.10.2
|
|
32
|
+
Requires-Dist: toolz==0.12.1
|
|
33
|
+
Requires-Dist: typing-extensions==4.12.2
|
|
34
|
+
Requires-Dist: tzdata==2024.1
|
|
35
|
+
|
|
36
|
+
# Experimental typed finders
|
|
37
|
+
|
|
38
|
+
This generates helper classes to query data using a logical model and mapping
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
data-finder/calc/src/calc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
data-finder/calc/src/calc/calc_protocol.py,sha256=fEvRa27Jb2HyjrM9jKugUDhtwCUhxzyUWZ4bR4vhccE,1412
|
|
3
|
+
data-finder/calc/src/calc/simple_price.py,sha256=K0xGKbo3F86xsMbLRXAgsTTrrm3JA8t0sBgDD-EHUwI,658
|
|
4
|
+
data-finder/calc/tests/test_calc.py,sha256=lo_dPD-clIJhjlCrU-Wgm_2U0tPoURDZa4lSpIuYNt4,1866
|
|
5
|
+
data-finder/datafinder/src/datafinder/__init__.py,sha256=z8UksHLDOjGDgPHlpufqpLWEI3-qmvHvhleq_aUTbfY,157
|
|
6
|
+
data-finder/datafinder/src/datafinder/attribute.py,sha256=BCN_A3pOwQefOn1UVI7wUY7LiuJCfuW-LisnA8mMKDA,584
|
|
7
|
+
data-finder/datafinder/src/datafinder/finder.py,sha256=jyo44zDnBX3FM8n-M1gX4NXhyuHxJe88kjeajXCX-7U,186
|
|
8
|
+
data-finder/datafinder/src/datafinder/operation.py,sha256=txGzabAvX8lAG7wZ2WLvXcbQWzqyUO_0wUw-BKvEsoA,4812
|
|
9
|
+
data-finder/datafinder/src/datafinder/output.py,sha256=CYbfNoVF8Hnq3G6rFcKfLHhdULXer-UON4CPcmHpTM4,196
|
|
10
|
+
data-finder/datafinder/src/datafinder/runner.py,sha256=9IDbRZ_xIAlnjB02uqXRQDtF48b8d-fJDrnwj8OnyV0,911
|
|
11
|
+
data-finder/datafinder/src/datafinder/typed_attributes.py,sha256=Bnx0PdQA77u6fmsgex9aNpbj3O4y5h90khZ0aD-JoN0,1381
|
|
12
|
+
data-finder/datafinder/src/datafinder/typed_operations.py,sha256=9plTaVJewfrjnn39OJcrCrhfJh8wiGVCki9B7hmycEQ,2068
|
|
13
|
+
data-finder/datafinder_duckdb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
data-finder/datafinder_duckdb/duckdb_engine.py,sha256=OAf1ym3AqY4_p7fohHfY1ENh5sIapFx377oyfiger-U,864
|
|
15
|
+
data-finder/datafinder_generator/src/datafinder_generator/generator.py,sha256=yUyif6M0jYmFsSHrn18Yx-RTLeoiDS3q7LIkJQmbvWU,825
|
|
16
|
+
data-finder/datafinder_ibis/src/datafinder_ibis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
data-finder/datafinder_ibis/src/datafinder_ibis/ibis_engine.py,sha256=Zlh2yUtVNhpcRGb4HVu9x01oC-1_xFhwJknPDzsA5Fc,815
|
|
18
|
+
data-finder/datafinder_ibis/tests/test_ibis_engine.py,sha256=Y0zAtk0wTpikzUR8FobBhEaVNFl2lD5kGEiVDGmeNAs,235
|
|
19
|
+
data-finder/datafinder_ibis_duckdb/tests/test_datafinder_ibis_duckdb.py,sha256=eXPR_MZWfwhScsAUKRljaqFQbGZDOfUi3RUQkUi4xZQ,2172
|
|
20
|
+
data-finder/example/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
data-finder/example/account_finder.py,sha256=bYw3itpmjT5PhqM252pR7Kqze4hdcUxOTOcRXXbIx4Y,1157
|
|
22
|
+
data-finder/example/contractualposition_finder.py,sha256=DzIoDNiJ5K7UBNzM6ajNvJ0GXR5lazcBYuAwVz07ti8,2235
|
|
23
|
+
data-finder/example/instrument_finder.py,sha256=DDPXeOTmXRyW7M6eZIjF-VUDrRhfGs28jNIv1eJkEtk,1194
|
|
24
|
+
data-finder/example/mappings.py,sha256=GtEcVwffcbxUXamKqasRVkEd4ZVtbovS49dfhUMUPkQ,3617
|
|
25
|
+
data-finder/example/queries.py,sha256=THu-RLldl6tEaowWt3pZKCxrhGTf564eQ0W1sZWEhN4,1134
|
|
26
|
+
data-finder/example/trade_finder.py,sha256=WLW62qbRAfxeJK_2OTvOAURz7BvCuadf9CvEQN8iaGk,1627
|
|
27
|
+
data-finder/example_duckdb/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
data-finder/example_duckdb/duckdb_example.py,sha256=tSV3uwH15XrGhefJldiIdKKX7Y84Bwu5Ny9auof5X-Y,548
|
|
29
|
+
data-finder/example_duckdb/duckdb_trade_finder.py,sha256=on6ug-tkUdIZ8W1NgIo7-RvmCVwDAjm8BN702kTCOfI,736
|
|
30
|
+
data-finder/model/src/model/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
data-finder/model/src/model/m3.py,sha256=h1dyACsgcXDSc8bKXWXBDkZ8BvvPtLMdCVQ6CdnAWfg,1202
|
|
32
|
+
data-finder/model/src/model/mapping.py,sha256=UPE8ZPZBFPQ_v79WCWsJ6fluHWxCZztAZKzxZCZZYaM,522
|
|
33
|
+
data-finder/model/src/model/relational.py,sha256=mniAfpmVXojWtNjxDj6jGjUU2UIetl1Hc_R-qCxnKCo,983
|
|
34
|
+
data_finder-0.1.0.dist-info/METADATA,sha256=REbMiO6hykkP48tzsZ3_D1I1B6K2qPCI90EcSxPIDcw,1138
|
|
35
|
+
data_finder-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
36
|
+
data_finder-0.1.0.dist-info/top_level.txt,sha256=TkqwsWs5BMfXjQjd97MD6YQY8D5nmv5wZ7OlXupzHFQ,12
|
|
37
|
+
data_finder-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
data-finder
|