Skip to content

Commit

Permalink
๐Ÿ—๏ธ feat(strategy): optimize portfolio ๐Ÿ—๏ธ (#247)
Browse files Browse the repository at this point in the history
* progress

* add test

* try robin-stocks branch and python 3.10

* Revert "try robin-stocks branch and python 3.10"

This reverts commit 99e557d.

* restore robin-stocks branch install

* try again

* one more time

* update robinhood

* use ltc for tests for 24hr market

* missed one

* add optimize portfolio test

* fix crypto time_in_force

* fix optimize port test

* increase order size for test

* dont wait if test

* try this
  • Loading branch information
alkalescent authored Jan 24, 2025
1 parent 1d387bf commit 8b9aa90
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 21 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,9 @@ timeframe = '1y'
df = md.get_ohlc(symbol=symbol, timeframe=timeframe)
holding = hist.buy_and_hold(df[C.CLOSE])
holding = hist.from_holding(df[C.CLOSE])
signals = hist.get_optimal_signals(df[C.CLOSE])
my_strat = hist.create_portfolio(df[C.CLOSE], signals)
my_strat = hist.from_signals(df[C.CLOSE], signals)
metrics = [
'Total Return [%]', 'Benchmark Return [%]',
Expand Down
2 changes: 1 addition & 1 deletion encrypted/optimize_portfolio.py.encrypted
Original file line number Diff line number Diff line change
@@ -1 +1 @@
gAAAAABnir3tdXUXt8Dua-T_yCKU2RnCZLha9auJewTmsa9oI4LsER1lSUms9_ZMDsA9jjbugs1zYKGG9q2TPmYdZypWB9n7NWvzvnv1XKV6aeM7t3SJyQwO0SOrzZdzDt4XvpA3GNQ1P4Ai8aJ7wiucY-FMs4oSAvA6YokS7buvDFWvsiNggXEDt2Az0nNLolc6YUxXyrQbF_iq2RthWu-WpluvLUkoc-qLtBlvW0O4m_Sglgz-Yc2ntbgUYgTod-odp18crY3uw6HYsKVaXkunJlc1D2axq9aVc_Y0Vf8ibrL4n2e01LUgLnKXLTq-OFRdBJB7K_G8nm9CV4jyXMDCUHufYwgfgE610ITBcP1JBg8fn8mckT1RKWoyLtIIkZrg1ynMEJAe4tk92iF9jnYpDtoDGAVDoEAALpzywPpOJ2Vywpy_MXq4loBlyOy1EGUPzKvODp_qSl9YV92gr1_uc_3Kh5TtR4hmSHryOuakrdXV6FqRDAXiYRqld1eQieM2GFbDvFsxeOYJwRjWtGdauaukgPHIFsdA0azxBV3V3F3EcSNdacwvowzmj86D0Yw_oj-Tr42i-TFXOil6dbY5nOGYDUaJ823ceWJKLK3HF5Tpz16nGfdWU9zgjf5iAGJFUzrHfLusDrYPWqkLIX-hAHMrWHGJP7V346jtMFbYCk73YN1lv_z4Pfh0ocGL1qHdJeqn8L804VdiznWWKvZHmMIM-7bVLTeOmRCROLe0_oKckb9Bj0PbUXSRiSFl4YLGI16bjLtKneoq_1ENrpz7kn7TaC0gI1cRdTNUmySs-3_PlHqaF7GbnxhSRX7mEx20Csk9Sh5B47LNi_nCQZ2JGkrBruOvyZXCZlxxXQruFuMw6C4Y1X2tjq1OuAyoaf_Pd9oKiyQDV5ctM7OS_i3WNlbhwapI-aAkE0UibusCP33qNKCtDFn2qYSfBlETuN6dhOLK-l-xjLq10vpuLTDWOA_WLSoezobwkk3NmBjp_jaLZYzBkCZTUkq8If3bQQOyPKEJ6TGK3lTq2IWH0-9KGF7Gqj4V6lgZb0nGdzzBB1Vd7uXQDhkwv93JMSGS7k8oT8Tmcaigp2YyncNAjNmOPaBTjG04KiySaUNsvz5efMUYkyxdRNOMyu9OmED13GKQwt15LcEyLZhuzAvR6KAPYgiZRASO8bgEbU258i8rSFUf39ihG-2FSddxWgwd9x69lHUNXeEP4Y-TkRNxuxdhHPtJvJT5msu0wRVawaxE8Iy7-D-TTktEz7lHrUhdePicitJDFzVArB_SJ_Vi4hmZQTZIW5qGsf53C4m9eG2I9lLChTpcg7mp5iclY5o6O7W65qG8OHtHbpX8VZPLhxarhPCUrAT66NYmd2OLXd_Dsm-INdIvwpinPGrpl0sxYule0en_shBPsNRQ4d3RGtMPiMbINM5HhymqPzi-hRkKBpm54-g0p-20ZAM2aOnVgeG8_0nG_9Fyv0yjUqZf8yaMiIaROk2OfqEzoDt4J1Zs5SOg5E1Fv-98EX2QtcvuEGBXxpduB4Ii0sYdkFpASPmea1Dgt_nj3ij2c0rLXjJ4j2nojDnOBoyWrQ519nx9VW889GNCIQCrMZkKpzXpfBMxFeK0p3VMPvVsi75sCfV8xbPdwD4hu4uL0HK5fUPijwAyhESlaDdKqtFuDmZ3oHkEG1t2zefr_QqvC9bu0aMNE1MWzQ3yJlo0SRMk9n6Cxbr8DZCK4qKVvT32WoY1p3fL7WBjYU4lO_bKlKylz3na5IE1UCB_-L96mYXOc9gwxq7q6A9oCYH8nUibncFjWMJyikniSSjXoYSLxqcCuXfKfMuyhIzyl3LV080TTRHxCxi_KEPrLG1772yPM2e9NWc2iwaCjDb4U6m327yu2S-OyZqVvw7pvbzGyL6iu9QgDjX1Jwmvk_8HGL8b6Z7VCChBrmFwGFRu1uoecmYDOdbvguXvRU7aG15H9k543YfZuSHDmSp0OSVQucHq_PViD7l-ynXHGmK_XQeH8pD7VWMHGQmESFFsPpvlE0PfoX0MJZh0_UfhMgnOk0sEDPu-Rv5LtEKFK8ktph8MMAlFae4vbTcP7crhbsK1vQvoJwQfswgRKNWK1XTJ6UX_kKyastawawDnWEEdrpWIqE__-Ayust9GZFN0ujvW13IHRsFuKXyh-ObQYm2RqIqTDjOvsddN60wmFo0WkcHqXv_rdJiHf0OO9ECxOGfiCEmGuJJB55JfTanVQB6DLfbf-l__qHQAyhZEfTKRQlr_H-6wbCC0Sa2d2jM8LD-LzaiOEiJPsqgvhTj3WIDN3xgU3BUm9PggOjVi4lEtCJ--eJ56XU3tes_P421-Z2yG8wufhyvxXWeLQkBWDH-cl5ke4Komt_DqiE4PZDXE8wMWeO5nxtUo-5ib12Ut_1b5yWYLycJT7h82Qn-evxrI58RbXe0idi8vvUQB0gq0CiDQ4_i8C61yhegm6b9MwQ0iJOKhzc0O_hJLuzhX5TvyZfJ2OYPgn9WRmvMhKDb7OTT9pyBLVZuz9fiFFdW80U2ihga5GGkfVgIwHfWopmE5y3fyDIZi-MntsD6v4clrLNSS8eWPmVQDqG3nSMD_GirEKACCo2gJi9mrkHyvNT_P4ysVDMuP_cLwdcnsekrKgFBLAqOZQRJpqsCQcFHUugGSbL0GO44NvSSp43DqZKrfRwrwLJKC3p9LAAdEpd2eqJek9fQVnO9vcDV2Pnyh3Sj7Go9v097QALTraRLFuyWgmiVGMtda65murMKuei7aaaoz-wiDqn_11eCoLEG6yHRglLXaY-HC6nyD78rOYJ3eBvtZ-P5wqRJXzaYr84Q8WE99ZZTp-wYQolFZ7ZWNX-YzyPtCu2nNp8WDSnQZlYOK7hB2iomDpMBlQ5p-5h-FsMkVUw5g1HNkpOmcQUy1aialXejeZnS_wbTTGvCbaHXcQX8y1gqL0YoK7SxkBF0BZ1w99YubpEwY_uXmu4qUsmoerEVJ69ONposfzb3IBFwzDofXlb-Ef7_5OAHexvSVlGfLXNyaqFVjc46yGgKqgEB_noTdjgHSCvjCyYpBqe1xDzqfun-Vj923BMXlB_-aJgTWo7Gdcdtd86R-e88PcQLCkT3absJxKf2V2kPTqEieiEIaqbztnopoBnkiezTO5lim0ZzyVnIeEf43LoupHeW2PcMN01ohI5em3zJRvMTdyMvMko_-UJp5--hvDM_SnvcSxZ6ROj1D59i4JLUwkaOH7xN5D28CNfjYm5AHFBCGyMJ7GYg4DHSlqhiVdcj236Sa18tnP_e1fcsOExOiUIWFbpd51iSXr3Hxn7vm-x4hyifypNCjGntC5LVJIbjnjAAwZxcZJEnEPdekSJ1l25mlHot9BqPQTkuyJQKazVC8nQyo2YzVBch5ThT70O7JQYrNV9iUMpNP0JgWfe_TAzybMHOex0WU2OXP8cVOnQNr7JbUGLXIDINQg02z_I0d00nHoqUaOpaHKI5OJiOMIWSSu60dcAFu2KTz7IlpQGJa_MLb0b21I84L9hKC3ms_kqzy7G_Qiss1UbnR-e9vHW5UF6MqSTZ50NqTD8KUbcNVEhB1h65sJOdzru4o2TeLze5PY-3JHFl6P_GQ6F7aZHRBOOPf6sH27dYN0z25prPZ4y0o94WSugcUG2iCJOekDCK9HQ_xzjxzgnf0icTIqouTxpZAd124ZbLg3wUToy_NZKACj2IGlFdlnICndOx61ROIvXCPgpNNLadFKLJojXX9FJkejU8OqIl17K9TLNreXbCM1Isyr0lvQ8Rf8eAkyJgIAKsyKhwR2n33WvhTxnOZqi2LdC3idK7bASppOUqAmxANvZ_4jkkomWyLJsN6e_eEFv-8aFmKSoKJEHIFUqMuaTd-RSbD4nbCxs1k6oDW6wRkHEzQRfLe58sTxW8TSKVX5m-3XS1iYS2HwUXPaWAy3Tu4Snfqz56PSm3SVvsOKdiCzJof9udeK28j3dVRg8EjIlQTtMXLxe-PuZ-1OT87SYqeONg1o28_8-lgAbv78MBwLScLLLi_iGNpdT8bBlHvIJQElkNJkF0-wW_97AUTC4eir4RKbUslTsgPp1Pn
gAAAAABnk1xQzWSAT0Vf77Vrl7IWhzfgK3L0X4FssVTP-qNrVZMuniiANHgTeRl1RJDmImV3BPA4ZHu7AsOP3QryfS3dE31h0ggfBio4we76ui3VqgJqgzt2P7AA3U7bxdZXQDd6peKShMDJDO6ssYflmGEpgYkpSSHezcYxVq3x5hmew1TYBl0ssOqKID9lrV9ZesO8TWcNGHEo4AE53kBq0K-3vA51MWrGXM9qCS2IuVXoV6yuAKryju97rMu7kFwnKIEgg8DuQqmWSI9f7acUHzuFK4vOSmgryJxGdXvqwshy822n94xLnRPEpQHaeTEVyoJRtA3gnu3Q1FOg6Z7Xe1Ne_lY3dmn8KBrt9ghf5GmbAefKllsW-dDhutL--5vdBgSBu0WcqqXE2LGXM2-g-Vlc4MBmtyJGv1icP6MRFbV0vKd5KlTwD-3hcSxDkqUsU5-xSojeXuXHsvvP0nPIrZJXSJgAkmYtQ4y5dRjPKsg3qQd_g7YXsblzH9GQufcd891k4aAQrQNMjKOlUiNycduxC0bsWuDBsEGBYh7kVh43oOJZOrCX8h-JfKxJWU3k82fJkDhdTGSVVD62BsK77s1FkwbV6NGQk-HCyxFat6j4u8byB-HDXG8xGx5FwH0OdgZHcEuOrDo4JUsagJ_D61Ooo2ysuvVu0V-xTU660nkqg6t14kPl2aXzHWlAjv6mrKgvFYD56XJ1HD7uuRJWdwtDi2ANfnYBWyzYx26R_fCjnaBGb_n_CNZtc1rz933Ssky9WvUu3FduAJ7RV4Y0aqBJ17BXrT_HEpdr7kAIijqp60lDh0U2CZ7I-6gwJ0LhqNWqL_xnH8YIKkPoVh-9Hx7UaIHtmxJrVihsdtZOS6T_b5OduL3rv4K1i6vspoTSqyywlFaTXVLppFTepHoXUR1ep4z9mPfiSrYlyvD9L0OWAXt5sk78yELF0MBQa7wvRHxP3sLAke0kgVZwjdBKB5uECdpDgDlmzS8A9ZxvH2xs4ImgUqm_kDN4EhB45M1Go3UJWeo8HwHBQ1Y7avCORX7OIDYOjXdjVw31lhiUNYlVVgBuUTi2AM4wMgmMgIR_QrYxC414VRPQ8K32R0MoyBD5DrTZH7-ptFa9PxtTrN7D_sMK8khrIKv4HYZ6ysKRNifoQVeucriNt6URRJtGfMlHgk9oqi7uClfRHfObbchw6biXgrJIAlvOIFO7EzO6_SWkqYjTF2zxZL_wTJA-EkNSsIwxpL4gXlIRpNTAaf8EZw17fYpFmy_GA4RL8tbjku3q7Ik55mhsgK_D5eLMuJmj_qy_q4zs5BMAikfPguP-E7NlNS62TIGTwsHOKt6uMgPDAGL3TEwEIYkx9GmJnGJ-mDYD5mJI400BWj_4LIh3e-J1VwcCdzGBa04rVEyXz0YzTiAG4O2gWQrrPnTw1ZeWaqmNG9_pFJF5tuWR1BYk95qM0VBWIkmJPnpDRZRPn3Eso74TgwmlfIRVtjKyWyI_ZGOIJwK3WrAUwG-W-OxEnzdqp-sTjZNF-R2AGD-VlkZev9L_jWNFKibdlmXW1zRmy_A7w-_Jg5cQ5lpr4ggBsjDwx8sHaUlVjd6CLgrM2knJPQJRac2YYOYGGyeCj3IkebDHv9LeckUieNGdajmV6pooIyyXEp0BFWn16YOGmVxC1Rs0H0pnd5eiZq5HdKZ1Vy_FQAufUGKyXn_OZ0DUfeq-Aw5gZF4qqZOsniv-caVc4Du8I8SsFT5SwJ2Sq2RwUfWEiprrR38oOB8TPeaaOhd2veYdn77c9x7AsvsZvQnPjFj3rAIv9Q3g8i79-x4GsaLrhYx12_zs9GlNJPr0kHHFD1a0hpPxjR13azGoNy-N-5Oci7GbaAQDZttq9dfx4Y5M18H3DLupnhU421P8jikJsKIT8f4xns7G8a3lbKRoB2gge3y8H58vHhvmQKjwd_lFeQJaD-Ix2wNH2uyh9H1YKYhTg10c4nIa1IxohyMmyIrfCGdVFM_hRLOv2nd3Dn4Mtd-Ad-4Tksml0t39RX5npcEEvFfzEeoO4A5ci2GGS8mHtNV6_s9KhTd7p_Ch82X6PFz6AetlTeNmjT46xYypjlTHvTp5qqgVs9F-gvy0LeR4oywq3iAL8a-_HmtXXRUqqi5j0gRySIEiF4wsjz4t6F3DPtyrQcKLtdN1d1ZwMi6GUXsZS5dp7grC7l72elEDV66KvvrUx-BVSPAhPN5nN_lyTkXgOu_yWGhR2u7q5fGJQfwZrmhlabNPLNgOn7fee69AGMWvfGeVGwTCaY68NWIWpDOo4Su0VEM11HqTVnhunXt9a-jUNQgFJxWyBmQRbGRUs8nvYLj4InK_uh3hxfbZQwBOqpMFeg3c-TZk-yaksGp57pD1t5PVPCyMjZVJSQhyQewHqwzgMsANsh3ZpeVKVR2-PhouKFKpDRVpnl5gWMovD4aXUhMdyA1xIdVnZ9sSH4bLkCtqixJ-_yvMnAIu-PMAzooPLV9cYpBoUPTNYUYesgya0q5WfKyDvYrCd23yIy98YO4H5I_Yz7EFwj895ca3p1r5pxxO1HxHBUgB3pwlsPkDWh4DfFVKEtTHiO1it3c-mEZVeWCOQArmV-4O6tTsAk6zsPdcYYdPRMjrMtLGqNgDQNY_lNE08TIqIi48-NkN4yZB9UFE6mZdjrxvsMbeyE2yPPbkqVpdQ-IdSECjekXS10v5nUzmu0ZA-JXsev8oItVn8BtmalnbYdx5dQDpUakBhjrAHMbQB2FYp2DxnXop6vBtNTi_9mC6IdkuoKgJda8TChFp7ab7O1yoRlq8m4OK66xseH_gyfmocroDmlIRDl9En7o8Pz4XPOImtkr3gDPA7Pi5aEqxpoRkZxmApPHtTP3Tp6QWS0VQzFYHwQDv-L8rpffvvZ0z7g21G6ozH3g0Y1b5CJlMxFRyx9b2tOn3L4g0iRAZ1EtU-AzVLhc5NEIiVofIcfMXCzsamf0lCGCfOSE7xGgHHfM4vyGo07OvZdEg9kqfOTaIeM6meR9G0W5LR0zwRGuvAL4Weg575rIrq-AQoRkiXJIG55fjJNKwx8s1wzD5Q6vCoBOzfWMVuoSlC3HpUMXWTiItq5fHKU_oSutWFdrdJ7gLSbLRJzdTO7DH00Wnu4lh-KBStDJxbkOjxzuefnT6RHgNbdJUBtBLoMGbtdcjLLjZFrhR2s8BpbT-3Axc91EyqgD23a3GO5reOVnOL89t-g0XknKVplCArvHXI6hjkCvi6BuNna4TVkKVJOp_n9fnDw0sXL1quyH6nBp7OZ-fyl2VskgWbEbZPLpD4xDTo2siKSGcKkM_Dqmzt9njagifVQPj828dI0Fb7YjByqwwDjedUz0Z3Is9tHGAp0EdXJEbQNhxivqgAu8DVd83Ghc5kRXnzs-bxbWBjxiLb_oFqi_yxLevtav8y1_de9sZZ45ZYyVOLvueAGl3T8FajeSqEsGCIK0tF2dIfXDIEG5xwjp8nE0hK8XxAJ7aDp3CILp-TaKVZ7Sgc2HsWlDR5XzweeYIqyicIdYBOJ6uPxZJfRfZqeVm1axSYWlHaUKhFsgCrOVZGWM-EnZoO-AYR3Yh_xI_Oek-Te9tEPUS5JAH3vSlsXH9nh-TfhQI02Qbr3uvIsJGPKnusqk23hW51P_vsXGpHelAE6Ea-gAfvac-7BrcVWmEX98MSm_BLcxGthftfFtOaZNfcdmVn_F5czeXPCohA7fzYOK8-Miavhb47l0lL8zR0fWhrRU0ylCZTXESlZStiC6UbPvzN-QtOq0-BU7S_i5MAdNE5MOH47D3PEscQjIv2p4D8xPRTnRzvFqFM9Rl3M8HU9xbDum3_cg_rCWURwWQxwrvoGy_kQq1Vl2cz03vOmkUfCMmiGuqI0Q2kL8Sex8ZilUFKmMu4-zFvTaOoQy9eSC-yqUNp8CFDG0B7C0zYYCT96NszR4YS-EH-EfCNYBFbbeTA-EANb8ZWitMPS3skqpAwIl3dVcGyRCBqb1vtMiSrbyywQVijlk=
1 change: 1 addition & 0 deletions hyperdrive/Constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ def get_orders_path(self):
def get_new_orders_path(self, provider):
return os.path.join(
DATA_DIR,
'orders',
f'{provider}.csv'
)

Expand Down
4 changes: 3 additions & 1 deletion hyperdrive/Exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ def create_order(self, symbol, side, notional):
'side': side.lower(),
'type': 'market',
'notional': str(notional),
'time_in_force': 'day'
'time_in_force': (
'gtc' if symbol in C.ALPC_CRYPTO_SYMBOLS else 'day'
)
}
return self.make_request('POST', 'orders', payload)

Expand Down
73 changes: 71 additions & 2 deletions hyperdrive/History.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from sklearn.metrics import classification_report
from imblearn.over_sampling import SMOTE
from Calculus import Calculator
from collections.abc import Callable
import Constants as C


class Historian:
Expand All @@ -26,19 +28,86 @@ def __init__(self):
# add fx to perform calculations on columns
# takes calc.fx, df, and column names as args, fx args

def buy_and_hold(self, close, init_cash=1000):
def from_holding(self, close, init_cash=1000):
# returns a portfolio based on buy and hold strategy
portfolio = vbt.Portfolio.from_holding(
close, init_cash=init_cash, freq='D')
return portfolio

def create_portfolio(self, close, signals, init_cash=1000, fee=0):
def from_signals(self, close, signals, init_cash=1000, fee=0):
# returns a portfolio based on signals
portfolio = vbt.Portfolio.from_signals(
close, signals, ~signals, init_cash=init_cash, freq='D', fees=fee
)
return portfolio

def optimize_portfolio(
self,
close: pd.DataFrame,
indicator: Callable,
top_n: int,
period: str,
init_cash: float,
**kwargs: dict[str, any]
) -> vbt.Portfolio:
if C.CLOSE in close.columns:
close = close.set_index(C.CLOSE)
signals = close.apply(indicator, **kwargs)
close = close.dropna()
positions = pd.DataFrame(
0, index=close.index, columns=close.columns)
holdings = {"cash": init_cash}
prev_period = None
prev_symbols = set()
for day in close.index:
curr_period = getattr(day, period)
# if is first of the period
if prev_period != curr_period:
# Rank symbols by indicator and select top_n
top_symbols = set(signals.loc[day].nlargest(top_n).index)
# Sell old positions for the top symbols
minus = prev_symbols.difference(top_symbols)
for symbol in minus:
size = holdings[symbol]
positions.loc[day, symbol] = - size
holdings["cash"] += close.loc[day][symbol] * size
del holdings[symbol]
# Buy new positions for the top symbols
plus = top_symbols.difference(prev_symbols)
notional = holdings["cash"] / len(plus)
for symbol in plus:
size = notional / close.loc[day][symbol]
positions.loc[day, symbol] = size
holdings[symbol] = size
holdings["cash"] -= notional
# Update prev values
prev_period = curr_period
prev_symbols = top_symbols

# Forward fill positions to maintain holdings
positions = positions.ffill().fillna(0)

# Convert to orders format
portfolio = vbt.Portfolio.from_orders(
close=close,
size=positions,
freq='D',
init_cash=0,
group_by=True
)
return portfolio

def from_orders(
self,
close: pd.DataFrame,
size: pd.DataFrame,
fee: float = 0
) -> vbt.Portfolio:
portfolio = vbt.Portfolio.from_orders(
close, size, freq='D', fees=fee, init_cash=0, group_by=True
)
return portfolio

def fill(self, arr, method='ffill', type='bool'):
# forward fills or nearest fills an array
df = pd.DataFrame(arr)
Expand Down
10 changes: 5 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
python-dotenv == 1.0.0
python-dotenv == 1.0.1
pandas == 1.5.3
robin-stocks == 3.3.0
boto3 == 1.26.165
git+https://github.com/bhyman67/robin_stocks_bh67.git@0d4574fb11ba4f1ff0acc8046174924bfd7ec532
boto3 == 1.36.5
polygon-api-client == 1.10.1
pytz == 2023.3
pytz == 2024.2
vectorbt == 0.25.4
scipy == 1.11.1
scikit-learn == 0.24.2
auto-sklearn == 0.15.0
cryptography == 41.0.1
ta == 0.10.2
python-binance == 1.0.17
python-binance == 1.0.27
imbalanced-learn == 0.8.1
icosphere == 0.1.3
pynisher == 0.6.4
Expand Down
4 changes: 2 additions & 2 deletions scripts/update_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ def create_portfolio_preview(close, signals, invert):

holding_signals = np.full(len(signals), not invert)

holding_pf = hist.create_portfolio(close, holding_signals, init_cash)
holding_pf = hist.from_signals(close, holding_signals, init_cash)
if C.PREF_EXCHANGE == C.BINANCE:
fee = C.BINANCE_FEE
else:
base = C.KRAKEN_SYMBOLS['BTC']
quote = C.KRAKEN_SYMBOLS['USD']
pair = kr.create_pair(base, quote)
fee = kr.get_fee(pair) / 100
hyper_pf = hist.create_portfolio(
hyper_pf = hist.from_signals(
close, ~signals if invert else signals, init_cash, fee)

holding_values = holding_pf.value()
Expand Down
8 changes: 4 additions & 4 deletions test/test_Exchange.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ def test_get_positions(self):

def test_close_position(self):
positions = alpc.get_positions()
if any([position['symbol'] == 'QQQ' for position in positions]):
order = alpc.close_position('QQQ')
if any([position['symbol'] == 'LTC/USD' for position in positions]):
order = alpc.close_position('LTC/USD')
assert 'id' in order

def test_get_order(self):
Expand All @@ -48,9 +48,9 @@ def test_get_account(self):
def test_create_order(self):
positions = alpc.get_positions()
side = 'buy'
if 'QQQ' in [position['symbol'] for position in positions]:
if 'LTC/USD' in [position['symbol'] for position in positions]:
side = 'sell'
order = alpc.create_order('QQQ', side, 1)
order = alpc.create_order('LTC/USD', side, 10)
assert 'id' in order


Expand Down
30 changes: 26 additions & 4 deletions test/test_History.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pandas as pd
sys.path.append('hyperdrive')
from History import Historian # noqa autopep8
import Constants as C # noqa autopep8


hist = Historian()
Expand All @@ -27,14 +28,35 @@
X = pd.DataFrame({'i': data, 'j': data})
y = np.array([True] * majority + [False] * minority)

orders_index = pd.to_datetime(
pd.Series(['2025-01-01', '2025-01-02'], name=C.TIME))
orders_close = pd.DataFrame({
'AAPL': [200, 100],
'META': [25, 50]
}, index=orders_index)


class TestHistorian:
def test_buy_and_hold(self):
stats = hist.buy_and_hold(close).stats()
def test_from_holding(self):
stats = hist.from_holding(close).stats()
assert 'Sortino Ratio' in stats

def test_from_signals(self):
stats = hist.from_signals(close, test_ffill).stats()
assert 'Sortino Ratio' in stats

def test_from_orders(self):
size = pd.DataFrame({
'AAPL': [1, 0],
'META': [0, 1]
}, index=orders_index)
stats = hist.from_orders(orders_close, size).stats()
assert 'Sortino Ratio' in stats

def test_create_portfolio(self):
stats = hist.create_portfolio(close, test_ffill).stats()
def test_optimize_portfolio(self):
indicator = pd.Series.diff
stats = hist.optimize_portfolio(
orders_close, indicator, 1, 'day', 225).stats()
assert 'Sortino Ratio' in stats

def test_fill(self):
Expand Down

0 comments on commit 8b9aa90

Please sign in to comment.