Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Key contributors include:

- [**Hongyang (Bruce) Yang**](https://www.linkedin.com/in/brucehy/) – research and development on financial reinforcement learning frameworks, market environments, and quantitative trading applications
- [other contributors…]

## Overview

FinRL has three layers: market environments, agents, and applications. For a trading task (on the top), an agent (in the middle) interacts with a market environment (at the bottom), making sequential decisions.
Expand Down
16 changes: 8 additions & 8 deletions finrl/meta/paper_trading/alpaca.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,8 @@ def run(self):
qty = abs(int(float(position.qty)))
respSO = []
tSubmitOrder = threading.Thread(
target=self.submitOrder(qty, position.symbol, orderSide, respSO)
target=self.submitOrder,
args=(qty, position.symbol, orderSide, respSO),
)
tSubmitOrder.start()
threads.append(tSubmitOrder) # record thread for joining later
Expand Down Expand Up @@ -239,9 +240,8 @@ def trade(self):
qty = abs(int(sell_num_shares))
respSO = []
tSubmitOrder = threading.Thread(
target=self.submitOrder(
qty, self.stockUniverse[index], "sell", respSO
)
target=self.submitOrder,
args=(qty, self.stockUniverse[index], "sell", respSO),
)
tSubmitOrder.start()
threads.append(tSubmitOrder) # record thread for joining later
Expand All @@ -266,9 +266,8 @@ def trade(self):
qty = abs(int(buy_num_shares))
respSO = []
tSubmitOrder = threading.Thread(
target=self.submitOrder(
qty, self.stockUniverse[index], "buy", respSO
)
target=self.submitOrder,
args=(qty, self.stockUniverse[index], "buy", respSO),
)
tSubmitOrder.start()
threads.append(tSubmitOrder) # record thread for joining later
Expand All @@ -289,7 +288,8 @@ def trade(self):
qty = abs(int(float(position.qty)))
respSO = []
tSubmitOrder = threading.Thread(
target=self.submitOrder(qty, position.symbol, orderSide, respSO)
target=self.submitOrder,
args=(qty, position.symbol, orderSide, respSO),
)
tSubmitOrder.start()
threads.append(tSubmitOrder) # record thread for joining later
Expand Down
52 changes: 52 additions & 0 deletions unit_tests/test_alpaca_threading.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from __future__ import annotations

import ast
from pathlib import Path

ALPACA_PATH = (
Path(__file__).resolve().parents[1]
/ "finrl"
/ "meta"
/ "paper_trading"
/ "alpaca.py"
)


def _thread_calls(tree: ast.AST) -> list[ast.Call]:
calls: list[ast.Call] = []
for node in ast.walk(tree):
if not isinstance(node, ast.Call):
continue
func = node.func
if isinstance(func, ast.Attribute) and func.attr == "Thread":
calls.append(node)
return calls


def _is_submit_order_attr(node: ast.AST) -> bool:
return isinstance(node, ast.Attribute) and node.attr == "submitOrder"


def test_submit_order_thread_target_is_not_called_immediately() -> None:
source = ALPACA_PATH.read_text(encoding="utf-8")
tree = ast.parse(source)

thread_calls = _thread_calls(tree)
assert thread_calls, "Expected threading.Thread usage in alpaca.py"

for call in thread_calls:
target_kw = next((kw for kw in call.keywords if kw.arg == "target"), None)
if target_kw is None:
continue

target = target_kw.value

# Regression check: ensure submitOrder is not invoked when creating Thread.
if isinstance(target, ast.Call) and _is_submit_order_attr(target.func):
raise AssertionError(
"submitOrder should be passed as target, not called immediately"
)

if _is_submit_order_attr(target):
args_kw = next((kw for kw in call.keywords if kw.arg == "args"), None)
assert args_kw is not None, "Thread target submitOrder should pass args=..."