lecrapaud 0.4.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.

Potentially problematic release.


This version of lecrapaud might be problematic. Click here for more details.

Files changed (60) hide show
  1. lecrapaud/__init__.py +0 -0
  2. lecrapaud/config.py +16 -0
  3. lecrapaud/db/__init__.py +0 -0
  4. lecrapaud/db/alembic/README +1 -0
  5. lecrapaud/db/alembic/env.py +78 -0
  6. lecrapaud/db/alembic/script.py.mako +26 -0
  7. lecrapaud/db/alembic/versions/2025_04_06_1738-7390745388e4_initial_setup.py +295 -0
  8. lecrapaud/db/alembic/versions/2025_04_06_1755-40cd8d3e798e_unique_constraint_for_data.py +30 -0
  9. lecrapaud/db/alembic/versions/2025_05_23_1724-2360941fa0bd_longer_string.py +52 -0
  10. lecrapaud/db/alembic/versions/2025_05_27_1159-b96396dcfaff_add_env_to_trading_tables.py +34 -0
  11. lecrapaud/db/alembic/versions/2025_05_27_1337-40cbfc215f7c_fix_nb_character_on_portfolio.py +39 -0
  12. lecrapaud/db/alembic/versions/2025_05_27_1526-3de994115317_to_datetime.py +36 -0
  13. lecrapaud/db/alembic/versions/2025_05_27_2003-25c227c684f8_add_fees_to_transactions.py +30 -0
  14. lecrapaud/db/alembic/versions/2025_05_27_2047-6b6f2d38e9bc_double_instead_of_float.py +132 -0
  15. lecrapaud/db/alembic/versions/2025_05_31_1111-c175e4a36d68_generalise_stock_to_group.py +36 -0
  16. lecrapaud/db/alembic/versions/2025_05_31_1256-5681095bfc27_create_investment_run_and_portfolio_.py +62 -0
  17. lecrapaud/db/alembic/versions/2025_05_31_1806-339927587383_add_investment_run_id.py +107 -0
  18. lecrapaud/db/alembic/versions/2025_05_31_1834-52b809a34371_make_nullablee.py +38 -0
  19. lecrapaud/db/alembic/versions/2025_05_31_1849-3b8550297e8e_change_date_to_datetime.py +44 -0
  20. lecrapaud/db/alembic/versions/2025_05_31_1852-e6b8c95d8243_add_date_to_portfolio_history.py +30 -0
  21. lecrapaud/db/alembic/versions/2025_06_10_1136-db8cdd83563a_addnewsandoptiontodata.py +32 -0
  22. lecrapaud/db/crud.py +179 -0
  23. lecrapaud/db/models/__init__.py +11 -0
  24. lecrapaud/db/models/base.py +6 -0
  25. lecrapaud/db/models/dataset.py +124 -0
  26. lecrapaud/db/models/feature.py +46 -0
  27. lecrapaud/db/models/feature_selection.py +126 -0
  28. lecrapaud/db/models/feature_selection_rank.py +80 -0
  29. lecrapaud/db/models/model.py +41 -0
  30. lecrapaud/db/models/model_selection.py +56 -0
  31. lecrapaud/db/models/model_training.py +54 -0
  32. lecrapaud/db/models/score.py +62 -0
  33. lecrapaud/db/models/target.py +59 -0
  34. lecrapaud/db/services.py +0 -0
  35. lecrapaud/db/setup.py +58 -0
  36. lecrapaud/directory_management.py +28 -0
  37. lecrapaud/feature_engineering.py +1119 -0
  38. lecrapaud/feature_selection.py +1229 -0
  39. lecrapaud/jobs/__init__.py +13 -0
  40. lecrapaud/jobs/config.py +17 -0
  41. lecrapaud/jobs/scheduler.py +36 -0
  42. lecrapaud/jobs/tasks.py +57 -0
  43. lecrapaud/model_selection.py +1571 -0
  44. lecrapaud/predictions.py +292 -0
  45. lecrapaud/search_space.py +844 -0
  46. lecrapaud/services/__init__.py +0 -0
  47. lecrapaud/services/embedding_categorical.py +71 -0
  48. lecrapaud/services/indicators.py +309 -0
  49. lecrapaud/speed_tests/experiments.py +139 -0
  50. lecrapaud/speed_tests/test-gpu-bilstm.ipynb +261 -0
  51. lecrapaud/speed_tests/test-gpu-resnet.ipynb +166 -0
  52. lecrapaud/speed_tests/test-gpu-transformers.ipynb +254 -0
  53. lecrapaud/speed_tests/tests.ipynb +145 -0
  54. lecrapaud/speed_tests/trash.py +37 -0
  55. lecrapaud/training.py +151 -0
  56. lecrapaud/utils.py +246 -0
  57. lecrapaud-0.4.0.dist-info/LICENSE +201 -0
  58. lecrapaud-0.4.0.dist-info/METADATA +103 -0
  59. lecrapaud-0.4.0.dist-info/RECORD +60 -0
  60. lecrapaud-0.4.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,132 @@
1
+ """double_instead_of_float
2
+
3
+ Revision ID: 6b6f2d38e9bc
4
+ Revises: 25c227c684f8
5
+ Create Date: 2025-05-27 20:47:45.303669
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ from sqlalchemy.dialects import mysql
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = '6b6f2d38e9bc'
16
+ down_revision: Union[str, None] = '25c227c684f8'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.alter_column('operations', 'price_in',
24
+ existing_type=mysql.FLOAT(),
25
+ type_=sa.Double(),
26
+ existing_nullable=False)
27
+ op.alter_column('operations', 'price_out',
28
+ existing_type=mysql.FLOAT(),
29
+ type_=sa.Double(),
30
+ existing_nullable=False)
31
+ op.alter_column('operations', 'pl',
32
+ existing_type=mysql.FLOAT(),
33
+ type_=sa.Double(),
34
+ existing_nullable=False)
35
+ op.alter_column('operations', 'cash',
36
+ existing_type=mysql.FLOAT(),
37
+ type_=sa.Double(),
38
+ existing_nullable=False)
39
+ op.alter_column('operations', 'portfolio_value',
40
+ existing_type=mysql.FLOAT(),
41
+ type_=sa.Double(),
42
+ existing_nullable=False)
43
+ op.alter_column('operations', 'total_value',
44
+ existing_type=mysql.FLOAT(),
45
+ type_=sa.Double(),
46
+ existing_nullable=False)
47
+ op.alter_column('operations', 'roi',
48
+ existing_type=mysql.FLOAT(),
49
+ type_=sa.Double(),
50
+ existing_nullable=False)
51
+ op.alter_column('operations', 'cumulated_roi',
52
+ existing_type=mysql.FLOAT(),
53
+ type_=sa.Double(),
54
+ existing_nullable=False)
55
+ op.alter_column('operations', 'cagr',
56
+ existing_type=mysql.FLOAT(),
57
+ type_=sa.Double(),
58
+ existing_nullable=False)
59
+ op.alter_column('portfolios', 'price_in',
60
+ existing_type=mysql.FLOAT(),
61
+ type_=sa.Double(),
62
+ existing_nullable=False)
63
+ op.alter_column('portfolios', 'value',
64
+ existing_type=mysql.FLOAT(),
65
+ type_=sa.Double(),
66
+ existing_nullable=False)
67
+ op.alter_column('transactions', 'price',
68
+ existing_type=mysql.FLOAT(),
69
+ type_=sa.Double(),
70
+ existing_nullable=False)
71
+ op.alter_column('transactions', 'value',
72
+ existing_type=mysql.FLOAT(),
73
+ type_=sa.Double(),
74
+ existing_nullable=False)
75
+ # ### end Alembic commands ###
76
+
77
+
78
+ def downgrade() -> None:
79
+ # ### commands auto generated by Alembic - please adjust! ###
80
+ op.alter_column('transactions', 'value',
81
+ existing_type=sa.Double(),
82
+ type_=mysql.FLOAT(),
83
+ existing_nullable=False)
84
+ op.alter_column('transactions', 'price',
85
+ existing_type=sa.Double(),
86
+ type_=mysql.FLOAT(),
87
+ existing_nullable=False)
88
+ op.alter_column('portfolios', 'value',
89
+ existing_type=sa.Double(),
90
+ type_=mysql.FLOAT(),
91
+ existing_nullable=False)
92
+ op.alter_column('portfolios', 'price_in',
93
+ existing_type=sa.Double(),
94
+ type_=mysql.FLOAT(),
95
+ existing_nullable=False)
96
+ op.alter_column('operations', 'cagr',
97
+ existing_type=sa.Double(),
98
+ type_=mysql.FLOAT(),
99
+ existing_nullable=False)
100
+ op.alter_column('operations', 'cumulated_roi',
101
+ existing_type=sa.Double(),
102
+ type_=mysql.FLOAT(),
103
+ existing_nullable=False)
104
+ op.alter_column('operations', 'roi',
105
+ existing_type=sa.Double(),
106
+ type_=mysql.FLOAT(),
107
+ existing_nullable=False)
108
+ op.alter_column('operations', 'total_value',
109
+ existing_type=sa.Double(),
110
+ type_=mysql.FLOAT(),
111
+ existing_nullable=False)
112
+ op.alter_column('operations', 'portfolio_value',
113
+ existing_type=sa.Double(),
114
+ type_=mysql.FLOAT(),
115
+ existing_nullable=False)
116
+ op.alter_column('operations', 'cash',
117
+ existing_type=sa.Double(),
118
+ type_=mysql.FLOAT(),
119
+ existing_nullable=False)
120
+ op.alter_column('operations', 'pl',
121
+ existing_type=sa.Double(),
122
+ type_=mysql.FLOAT(),
123
+ existing_nullable=False)
124
+ op.alter_column('operations', 'price_out',
125
+ existing_type=sa.Double(),
126
+ type_=mysql.FLOAT(),
127
+ existing_nullable=False)
128
+ op.alter_column('operations', 'price_in',
129
+ existing_type=sa.Double(),
130
+ type_=mysql.FLOAT(),
131
+ existing_nullable=False)
132
+ # ### end Alembic commands ###
@@ -0,0 +1,36 @@
1
+ """generalise_stock_to_group
2
+
3
+ Revision ID: c175e4a36d68
4
+ Revises: 6b6f2d38e9bc
5
+ Create Date: 2025-05-31 11:11:42.103206
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ from sqlalchemy.dialects import mysql
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = 'c175e4a36d68'
16
+ down_revision: Union[str, None] = '6b6f2d38e9bc'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.add_column('datasets', sa.Column('number_of_groups', sa.Integer(), nullable=False))
24
+ op.add_column('datasets', sa.Column('list_of_groups', sa.JSON(), nullable=False))
25
+ op.drop_column('datasets', 'number_of_stocks')
26
+ op.drop_column('datasets', 'list_of_companies')
27
+ # ### end Alembic commands ###
28
+
29
+
30
+ def downgrade() -> None:
31
+ # ### commands auto generated by Alembic - please adjust! ###
32
+ op.add_column('datasets', sa.Column('list_of_companies', mysql.JSON(), nullable=False))
33
+ op.add_column('datasets', sa.Column('number_of_stocks', mysql.INTEGER(), autoincrement=False, nullable=False))
34
+ op.drop_column('datasets', 'list_of_groups')
35
+ op.drop_column('datasets', 'number_of_groups')
36
+ # ### end Alembic commands ###
@@ -0,0 +1,62 @@
1
+ """create_investment_run_and_portfolio_history
2
+
3
+ Revision ID: 5681095bfc27
4
+ Revises: c175e4a36d68
5
+ Create Date: 2025-05-31 12:56:05.041307
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = '5681095bfc27'
16
+ down_revision: Union[str, None] = 'c175e4a36d68'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.create_table('portfolio_history',
24
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
25
+ sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
26
+ sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
27
+ sa.Column('env', sa.String(length=50), nullable=False),
28
+ sa.Column('portfolio', sa.JSON(), nullable=False),
29
+ sa.Column('total_value', sa.Double(), nullable=False),
30
+ sa.Column('total_cash', sa.Double(), nullable=False),
31
+ sa.Column('total_cash_eur', sa.Double(), nullable=False),
32
+ sa.Column('total_cash_usd', sa.Double(), nullable=False),
33
+ sa.Column('total_portfolio_value', sa.Double(), nullable=False),
34
+ sa.PrimaryKeyConstraint('id')
35
+ )
36
+ op.create_index(op.f('ix_portfolio_history_id'), 'portfolio_history', ['id'], unique=False)
37
+ op.create_table('investment_runs',
38
+ sa.Column('id', sa.BigInteger(), autoincrement=True, nullable=False),
39
+ sa.Column('created_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
40
+ sa.Column('updated_at', sa.TIMESTAMP(timezone=True), server_default=sa.text('now()'), nullable=False),
41
+ sa.Column('env', sa.String(length=50), nullable=False),
42
+ sa.Column('start_date', sa.Date(), nullable=False),
43
+ sa.Column('end_date', sa.Date(), nullable=True),
44
+ sa.Column('dataset_id', sa.BigInteger(), nullable=False),
45
+ sa.Column('initial_investment', sa.Double(), nullable=False),
46
+ sa.Column('portfolio_max_size', sa.Integer(), nullable=False),
47
+ sa.Column('buffer_days_rebuy', sa.Integer(), nullable=False),
48
+ sa.Column('initial_portfolio', sa.JSON(), nullable=False),
49
+ sa.ForeignKeyConstraint(['dataset_id'], ['datasets.id'], ),
50
+ sa.PrimaryKeyConstraint('id')
51
+ )
52
+ op.create_index(op.f('ix_investment_runs_id'), 'investment_runs', ['id'], unique=False)
53
+ # ### end Alembic commands ###
54
+
55
+
56
+ def downgrade() -> None:
57
+ # ### commands auto generated by Alembic - please adjust! ###
58
+ op.drop_index(op.f('ix_investment_runs_id'), table_name='investment_runs')
59
+ op.drop_table('investment_runs')
60
+ op.drop_index(op.f('ix_portfolio_history_id'), table_name='portfolio_history')
61
+ op.drop_table('portfolio_history')
62
+ # ### end Alembic commands ###
@@ -0,0 +1,107 @@
1
+ """add_investment_run_id
2
+
3
+
4
+ Revision ID: 339927587383
5
+ Revises: 5681095bfc27
6
+ Create Date: 2025-05-31 18:06:13.741056
7
+
8
+ """
9
+
10
+ from typing import Sequence, Union
11
+
12
+ from alembic import op
13
+ import sqlalchemy as sa
14
+ from sqlalchemy.dialects import mysql
15
+
16
+ # revision identifiers, used by Alembic.
17
+ revision: str = "339927587383"
18
+ down_revision: Union[str, None] = "5681095bfc27"
19
+ branch_labels: Union[str, Sequence[str], None] = None
20
+ depends_on: Union[str, Sequence[str], None] = None
21
+
22
+
23
+ def upgrade() -> None:
24
+ # ### commands auto generated by Alembic - please adjust! ###
25
+ op.add_column(
26
+ "operations", sa.Column("investment_run_id", sa.BigInteger(), nullable=False)
27
+ )
28
+ op.create_foreign_key(
29
+ None,
30
+ "operations",
31
+ "investment_runs",
32
+ ["investment_run_id"],
33
+ ["id"],
34
+ ondelete="CASCADE",
35
+ )
36
+ op.drop_column("operations", "env")
37
+ op.add_column(
38
+ "portfolio_history",
39
+ sa.Column("investment_run_id", sa.BigInteger(), nullable=False),
40
+ )
41
+ op.create_foreign_key(
42
+ None,
43
+ "portfolio_history",
44
+ "investment_runs",
45
+ ["investment_run_id"],
46
+ ["id"],
47
+ ondelete="CASCADE",
48
+ )
49
+ op.drop_column("portfolio_history", "env")
50
+ op.add_column(
51
+ "portfolios", sa.Column("investment_run_id", sa.BigInteger(), nullable=False)
52
+ )
53
+ op.drop_constraint("uq_portfolio_composite", "portfolios", type_="unique")
54
+ op.create_unique_constraint(
55
+ "uq_portfolio_composite", "portfolios", ["stock", "investment_run_id"]
56
+ )
57
+ op.create_foreign_key(
58
+ None,
59
+ "portfolios",
60
+ "investment_runs",
61
+ ["investment_run_id"],
62
+ ["id"],
63
+ ondelete="CASCADE",
64
+ )
65
+ op.drop_column("portfolios", "env")
66
+ op.add_column(
67
+ "transactions", sa.Column("investment_run_id", sa.BigInteger(), nullable=False)
68
+ )
69
+ op.create_foreign_key(
70
+ None,
71
+ "transactions",
72
+ "investment_runs",
73
+ ["investment_run_id"],
74
+ ["id"],
75
+ ondelete="CASCADE",
76
+ )
77
+ op.drop_column("transactions", "env")
78
+ # ### end Alembic commands ###
79
+
80
+
81
+ def downgrade() -> None:
82
+ # ### commands auto generated by Alembic - please adjust! ###
83
+ op.add_column(
84
+ "transactions", sa.Column("env", mysql.VARCHAR(length=50), nullable=False)
85
+ )
86
+ op.drop_constraint(None, "transactions", type_="foreignkey")
87
+ op.drop_column("transactions", "investment_run_id")
88
+ op.add_column(
89
+ "portfolios", sa.Column("env", mysql.VARCHAR(length=50), nullable=False)
90
+ )
91
+ op.drop_constraint(None, "portfolios", type_="foreignkey")
92
+ op.drop_constraint("uq_portfolio_composite", "portfolios", type_="unique")
93
+ op.create_unique_constraint(
94
+ "uq_portfolio_composite", "portfolios", ["stock", "env"]
95
+ )
96
+ op.drop_column("portfolios", "investment_run_id")
97
+ op.add_column(
98
+ "portfolio_history", sa.Column("env", mysql.VARCHAR(length=50), nullable=False)
99
+ )
100
+ op.drop_constraint(None, "portfolio_history", type_="foreignkey")
101
+ op.drop_column("portfolio_history", "investment_run_id")
102
+ op.add_column(
103
+ "operations", sa.Column("env", mysql.VARCHAR(length=50), nullable=False)
104
+ )
105
+ op.drop_constraint(None, "operations", type_="foreignkey")
106
+ op.drop_column("operations", "investment_run_id")
107
+ # ### end Alembic commands ###
@@ -0,0 +1,38 @@
1
+ """make_nullablee
2
+
3
+ Revision ID: 52b809a34371
4
+ Revises: 339927587383
5
+ Create Date: 2025-05-31 18:34:58.962966
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+ from sqlalchemy.dialects import mysql
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = '52b809a34371'
16
+ down_revision: Union[str, None] = '339927587383'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.alter_column('investment_runs', 'initial_portfolio',
24
+ existing_type=mysql.JSON(),
25
+ nullable=True)
26
+ op.create_index(op.f('ix_investment_runs_id'), 'investment_runs', ['id'], unique=False)
27
+ op.create_foreign_key(None, 'portfolios', 'investment_runs', ['investment_run_id'], ['id'], ondelete='CASCADE')
28
+ # ### end Alembic commands ###
29
+
30
+
31
+ def downgrade() -> None:
32
+ # ### commands auto generated by Alembic - please adjust! ###
33
+ op.drop_constraint(None, 'portfolios', type_='foreignkey')
34
+ op.drop_index(op.f('ix_investment_runs_id'), table_name='investment_runs')
35
+ op.alter_column('investment_runs', 'initial_portfolio',
36
+ existing_type=mysql.JSON(),
37
+ nullable=False)
38
+ # ### end Alembic commands ###
@@ -0,0 +1,44 @@
1
+ """change_date_To_datetime
2
+
3
+ Revision ID: 3b8550297e8e
4
+ Revises: 52b809a34371
5
+ Create Date: 2025-05-31 18:49:59.238283
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = '3b8550297e8e'
16
+ down_revision: Union[str, None] = '52b809a34371'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.alter_column('investment_runs', 'start_date',
24
+ existing_type=sa.DATE(),
25
+ type_=sa.DateTime(),
26
+ existing_nullable=False)
27
+ op.alter_column('investment_runs', 'end_date',
28
+ existing_type=sa.DATE(),
29
+ type_=sa.DateTime(),
30
+ existing_nullable=True)
31
+ # ### end Alembic commands ###
32
+
33
+
34
+ def downgrade() -> None:
35
+ # ### commands auto generated by Alembic - please adjust! ###
36
+ op.alter_column('investment_runs', 'end_date',
37
+ existing_type=sa.DateTime(),
38
+ type_=sa.DATE(),
39
+ existing_nullable=True)
40
+ op.alter_column('investment_runs', 'start_date',
41
+ existing_type=sa.DateTime(),
42
+ type_=sa.DATE(),
43
+ existing_nullable=False)
44
+ # ### end Alembic commands ###
@@ -0,0 +1,30 @@
1
+ """add_date_to_portfolio_history
2
+
3
+ Revision ID: e6b8c95d8243
4
+ Revises: 3b8550297e8e
5
+ Create Date: 2025-05-31 18:52:23.021149
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = 'e6b8c95d8243'
16
+ down_revision: Union[str, None] = '3b8550297e8e'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.add_column('portfolio_history', sa.Column('date', sa.DateTime(), nullable=False))
24
+ # ### end Alembic commands ###
25
+
26
+
27
+ def downgrade() -> None:
28
+ # ### commands auto generated by Alembic - please adjust! ###
29
+ op.drop_column('portfolio_history', 'date')
30
+ # ### end Alembic commands ###
@@ -0,0 +1,32 @@
1
+ """AddNewsAndOptionToData
2
+
3
+ Revision ID: db8cdd83563a
4
+ Revises: e6b8c95d8243
5
+ Create Date: 2025-06-10 11:36:57.478070
6
+
7
+ """
8
+ from typing import Sequence, Union
9
+
10
+ from alembic import op
11
+ import sqlalchemy as sa
12
+
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = 'db8cdd83563a'
16
+ down_revision: Union[str, None] = 'e6b8c95d8243'
17
+ branch_labels: Union[str, Sequence[str], None] = None
18
+ depends_on: Union[str, Sequence[str], None] = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ # ### commands auto generated by Alembic - please adjust! ###
23
+ op.add_column('datas', sa.Column('news', sa.JSON(), nullable=True))
24
+ op.add_column('datas', sa.Column('options', sa.JSON(), nullable=True))
25
+ # ### end Alembic commands ###
26
+
27
+
28
+ def downgrade() -> None:
29
+ # ### commands auto generated by Alembic - please adjust! ###
30
+ op.drop_column('datas', 'options')
31
+ op.drop_column('datas', 'news')
32
+ # ### end Alembic commands ###
lecrapaud/db/crud.py ADDED
@@ -0,0 +1,179 @@
1
+ from sqlalchemy import desc, asc
2
+ from sqlalchemy.inspection import inspect
3
+ from functools import wraps
4
+ from sqlalchemy.orm.attributes import InstrumentedAttribute
5
+ from sqlalchemy import or_, and_, delete
6
+
7
+ from src.db.setup import get_db
8
+
9
+
10
+ def with_db(func):
11
+ """Decorator to allow passing an optional db session"""
12
+
13
+ @wraps(func)
14
+ def wrapper(*args, **kwargs):
15
+ db = kwargs.pop("db", None)
16
+ if db:
17
+ return func(*args, db=db, **kwargs)
18
+ with get_db() as db:
19
+ return func(*args, db=db, **kwargs)
20
+
21
+ return wrapper
22
+
23
+
24
+ class CRUDMixin:
25
+
26
+ @classmethod
27
+ @with_db
28
+ def create(cls, db, **kwargs):
29
+ instance = cls(**kwargs)
30
+ db.add(instance)
31
+ db.commit()
32
+ db.refresh(instance)
33
+ return instance
34
+
35
+ @classmethod
36
+ @with_db
37
+ def get(cls, id: int, db=None):
38
+ return db.get(cls, id)
39
+
40
+ @classmethod
41
+ @with_db
42
+ def find_by(cls, db=None, **kwargs):
43
+ return db.query(cls).filter_by(**kwargs).first()
44
+
45
+ @classmethod
46
+ @with_db
47
+ def get_all(
48
+ cls, raw=False, db=None, limit: int = 100, order: str = "desc", **kwargs
49
+ ):
50
+ order_by_field = (
51
+ desc(cls.created_at) if order == "desc" else asc(cls.created_at)
52
+ )
53
+
54
+ query = db.query(cls)
55
+
56
+ # Apply filters from kwargs
57
+ for key, value in kwargs.items():
58
+ if hasattr(cls, key):
59
+ query = query.filter(getattr(cls, key) == value)
60
+
61
+ results = query.order_by(order_by_field).limit(limit).all()
62
+
63
+ if raw:
64
+ return [
65
+ {
66
+ column.name: getattr(row, column.name)
67
+ for column in cls.__table__.columns
68
+ }
69
+ for row in results
70
+ ]
71
+
72
+ return results
73
+
74
+ @classmethod
75
+ @with_db
76
+ def filter(cls, db=None, **kwargs):
77
+ filters = []
78
+
79
+ for key, value in kwargs.items():
80
+ if "__" in key:
81
+ field, op = key.split("__", 1)
82
+ else:
83
+ field, op = key, "eq"
84
+
85
+ if not hasattr(cls, field):
86
+ raise ValueError(f"{field} is not a valid field on {cls.__name__}")
87
+
88
+ column: InstrumentedAttribute = getattr(cls, field)
89
+
90
+ if op == "eq":
91
+ filters.append(column == value)
92
+ elif op == "in":
93
+ filters.append(column.in_(value))
94
+ elif op == "gt":
95
+ filters.append(column > value)
96
+ elif op == "lt":
97
+ filters.append(column < value)
98
+ elif op == "gte":
99
+ filters.append(column >= value)
100
+ elif op == "lte":
101
+ filters.append(column <= value)
102
+ else:
103
+ raise ValueError(f"Unsupported operator: {op}")
104
+
105
+ return db.query(cls).filter(and_(*filters)).all()
106
+
107
+ @classmethod
108
+ @with_db
109
+ def update(cls, id: int, db=None, **kwargs):
110
+ instance = db.get(cls, id)
111
+ if not instance:
112
+ return None
113
+ for key, value in kwargs.items():
114
+ setattr(instance, key, value)
115
+ db.commit()
116
+ db.refresh(instance)
117
+ return instance
118
+
119
+ @classmethod
120
+ @with_db
121
+ def upsert(cls, match_fields: list[str], db=None, **kwargs):
122
+ """
123
+ Upsert an instance of the model: update if found, else create.
124
+
125
+ :param match_fields: list of field names to use for matching
126
+ :param kwargs: all fields for creation or update
127
+ """
128
+ filters = [
129
+ getattr(cls, field) == kwargs[field]
130
+ for field in match_fields
131
+ if field in kwargs
132
+ ]
133
+
134
+ instance = db.query(cls).filter(*filters).first()
135
+
136
+ if instance:
137
+ for key, value in kwargs.items():
138
+ setattr(instance, key, value)
139
+ else:
140
+ instance = cls(**kwargs)
141
+ db.add(instance)
142
+
143
+ db.commit()
144
+ db.refresh(instance)
145
+ return instance
146
+
147
+ @classmethod
148
+ @with_db
149
+ def delete(cls, id: int, db=None):
150
+ instance = db.get(cls, id)
151
+ if instance:
152
+ db.delete(instance)
153
+ db.commit()
154
+ return True
155
+ return False
156
+
157
+ @classmethod
158
+ @with_db
159
+ def delete_all(cls, db=None, **kwargs):
160
+ stmt = delete(cls)
161
+
162
+ for key, value in kwargs.items():
163
+ if hasattr(cls, key):
164
+ stmt = stmt.where(getattr(cls, key) == value)
165
+
166
+ db.execute(stmt)
167
+ db.commit()
168
+ return True
169
+
170
+ @with_db
171
+ def save(self, db=None):
172
+ self = db.merge(self)
173
+ db.add(self)
174
+ db.commit()
175
+ db.refresh(self)
176
+ return self
177
+
178
+ def to_json(self):
179
+ return {c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs}
@@ -0,0 +1,11 @@
1
+ from src.db.models.base import Base
2
+
3
+ from src.db.models.dataset import Dataset
4
+ from src.db.models.feature_selection_rank import FeatureSelectionRank
5
+ from src.db.models.feature_selection import FeatureSelection
6
+ from src.db.models.feature import Feature
7
+ from src.db.models.model_selection import ModelSelection
8
+ from src.db.models.model_training import ModelTraining
9
+ from src.db.models.model import Model
10
+ from src.db.models.score import Score
11
+ from src.db.models.target import Target
@@ -0,0 +1,6 @@
1
+ from sqlalchemy.orm import DeclarativeBase
2
+
3
+
4
+ # declarative base class
5
+ class Base(DeclarativeBase):
6
+ pass