Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 332b7b0

Browse files
feat: improve recipe with a sample strategy
1 parent 3d63b84 commit 332b7b0

File tree

5 files changed

+237
-46
lines changed

5 files changed

+237
-46
lines changed

‎.editorconfig

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
end_of_line = lf
6+
insert_final_newline = true
7+
trim_trailing_whitespace = true
8+
9+
[*.php]
10+
indent_style = space
11+
indent_size = 4
12+
13+
[*.yml]
14+
indent_style = space
15+
indent_size = 4
16+
17+
[*.yaml]
18+
indent_style = space
19+
indent_size = 4
20+
21+
[*.md]
22+
trim_trailing_whitespace = false
23+
24+
[.github/workflows/*.yaml]
25+
indent_style = space
26+
indent_size = 2
27+
28+
[cook.yaml]
29+
indent_style = space
30+
indent_size = 2

‎composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
"symfony/doctrine-messenger": "^7.3",
4444
"symfony/orm-pack": "^2.4",
4545
"symfony/cache": "^7.3",
46-
"williarin/cook": "^1.3"
46+
"williarin/cook": "^2.0"
4747
},
4848
"require-dev": {
4949
"roave/security-advisories": "dev-latest",

‎composer.lock

Lines changed: 33 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎cook.yaml

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
files:
2-
Makefile: recipe/Makefile
2+
Makefile:
3+
if_exists: ignore
4+
source: recipe/Makefile
35

4-
.env:
5-
content: |-
6-
DATABASE_URL="sqlite:///%kernel.project_dir%/data/queue_%kernel.environment%.db"
6+
.env:
7+
type: env
8+
if_exists: comment
9+
content: |-
10+
DATABASE_URL="sqlite:///%kernel.project_dir%/data/queue_%kernel.environment%.db"
711
812
directories:
9-
'%ROOT_DIR%/data/': recipe/data/
10-
'%CONFIG_DIR%/': recipe/config/
13+
'%ROOT_DIR%/data/': recipe/data/
14+
'%CONFIG_DIR%/': recipe/config/
15+
'%SRC_DIR%/': recipe/src/
1116

1217
post_install_output: |
13-
<bg=blue;fg=white> </>
14-
<bg=blue;fg=white> What's next? </>
15-
<bg=blue;fg=white> </>
18+
<bg=blue;fg=white> </>
19+
<bg=blue;fg=white> What's next? </>
20+
<bg=blue;fg=white> </>
1621
17-
* <fg=blue>Read</> the full documentation at <comment>https://phpquant.github.io/stochastix-docs</>
22+
* <fg=blue>Read</> the full documentation at <comment>https://phpquant.github.io/stochastix-docs</>

‎recipe/src/Strategy/SampleStrategy.php

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
namespace App\Strategy;
4+
5+
use Psr\Log\LoggerInterface;
6+
use Stochastix\Domain\Common\Enum\DirectionEnum;
7+
use Stochastix\Domain\Common\Enum\TALibFunctionEnum;
8+
use Stochastix\Domain\Common\Model\OhlcvSeries;
9+
use Stochastix\Domain\Indicator\Model\TALibIndicator;
10+
use Stochastix\Domain\Order\Enum\OrderTypeEnum;
11+
use Stochastix\Domain\Plot\Series\Line;
12+
use Stochastix\Domain\Strategy\AbstractStrategy;
13+
use Stochastix\Domain\Strategy\Attribute\AsStrategy;
14+
use Stochastix\Domain\Strategy\Attribute\Input;
15+
16+
#[AsStrategy(alias: 'sample_strategy', name: 'EMA Crossover')]
17+
final class SampleStrategy extends AbstractStrategy
18+
{
19+
#[Input(description: 'Period for the fast EMA', min: 1)]
20+
private int $emaFastPeriod = 12;
21+
22+
#[Input(description: 'Period for the slow EMA', min: 1)]
23+
private int $emaSlowPeriod = 26;
24+
25+
#[Input(description: 'Stop-loss percentage', min: 0.001, max: 0.5)]
26+
private float $stopLossPercentage = 0.02;
27+
28+
#[Input(description: 'Stake amount as a percentage of capital', min: 0.001, max: 1.0)]
29+
private float $stakeAmount = 0.02;
30+
31+
public function __construct(private readonly LoggerInterface $logger)
32+
{
33+
}
34+
35+
protected function defineIndicators(): void
36+
{
37+
$this
38+
->addIndicator(
39+
'ema_fast',
40+
new TALibIndicator(TALibFunctionEnum::Ema, ['timePeriod' => $this->emaFastPeriod])
41+
)
42+
->addIndicator(
43+
'ema_slow',
44+
new TALibIndicator(TALibFunctionEnum::Ema, ['timePeriod' => $this->emaSlowPeriod])
45+
)
46+
// ->addIndicator(
47+
// 'macd',
48+
// new TALibIndicator(TALibFunctionEnum::Macd, [
49+
// 'fastPeriod' => 12,
50+
// 'slowPeriod' => 26,
51+
// 'signalPeriod' => 9
52+
// ])
53+
// )
54+
->definePlot(
55+
indicatorKey: 'ema_fast',
56+
name: "EMA ($this->emaFastPeriod)",
57+
overlay: true,
58+
plots: [
59+
new Line(color: '#4e79a7'),
60+
]
61+
)
62+
->definePlot(
63+
indicatorKey: 'ema_slow',
64+
name: "EMA ($this->emaSlowPeriod)",
65+
overlay: true,
66+
plots: [
67+
new Line(color: '#f28e2b'),
68+
]
69+
)
70+
// ->definePlot(
71+
// indicatorKey: 'macd',
72+
// name: 'MACD (12, 26, 9)',
73+
// overlay: false,
74+
// plots: [
75+
// new Line(key: 'macd', color: '#2962FF'),
76+
// new Line(key: 'signal', color: '#FF6D00'),
77+
// new Histogram(key: 'hist', color: 'rgba(178, 181, 190, 0.5)'),
78+
// ],
79+
// annotations: [
80+
// new HorizontalLine(value: 0, color: '#787b86', style: HorizontalLineStyleEnum::Dashed)
81+
// ]
82+
// )
83+
;
84+
}
85+
86+
public function onBar(OhlcvSeries $bars): void
87+
{
88+
$currentSymbol = $this->context->getCurrentSymbol();
89+
$currentClose = $bars->close[0];
90+
91+
if ($currentClose === null) {
92+
return;
93+
}
94+
95+
$fastEma = $this->getIndicatorSeries('ema_fast');
96+
$slowEma = $this->getIndicatorSeries('ema_slow');
97+
98+
if ($fastEma[0] === null || $slowEma[0] === null) {
99+
return;
100+
}
101+
102+
$isUpwardCross = $fastEma->crossesOver($slowEma);
103+
$isDownwardCross = $fastEma->crossesUnder($slowEma);
104+
105+
$currentCloseStr = (string) $currentClose;
106+
107+
if (!$this->isInPosition()) {
108+
$availableCash = $this->orderManager->getPortfolioManager()->getAvailableCash();
109+
$stakeInCash = bcmul($availableCash, (string) $this->stakeAmount);
110+
111+
if (bccomp($currentCloseStr, '0') <= 0) {
112+
$this->logger->warning('Current close price is zero or negative, cannot calculate quantity.');
113+
114+
return;
115+
}
116+
$tradeQuantity = bcdiv($stakeInCash, $currentCloseStr);
117+
118+
if (bccomp($tradeQuantity, '0.00000001') < 0) {
119+
$this->logger->info('Calculated trade quantity ({qty}) too small with cash {cash}, skipping trade.', [
120+
'qty' => $tradeQuantity,
121+
'cash' => $availableCash,
122+
]);
123+
124+
return;
125+
}
126+
127+
if ($isUpwardCross) {
128+
$slFactor = bcsub('1', (string) $this->stopLossPercentage);
129+
$stopLossPrice = bcmul($currentCloseStr, $slFactor);
130+
$this->entry(
131+
direction: DirectionEnum::Long,
132+
orderType: OrderTypeEnum::Market,
133+
quantity: $tradeQuantity,
134+
stopLossPrice: $stopLossPrice,
135+
);
136+
} elseif ($isDownwardCross) {
137+
$slFactor = bcadd('1', (string) $this->stopLossPercentage);
138+
$stopLossPrice = bcmul($currentCloseStr, $slFactor);
139+
$this->entry(
140+
direction: DirectionEnum::Short,
141+
orderType: OrderTypeEnum::Market,
142+
quantity: $tradeQuantity,
143+
stopLossPrice: $stopLossPrice,
144+
);
145+
}
146+
} else {
147+
$openPosition = $this->orderManager->getPortfolioManager()->getOpenPosition($currentSymbol);
148+
149+
if ($openPosition) {
150+
if ($openPosition->direction === DirectionEnum::Long && $isDownwardCross) {
151+
$this->exit($openPosition->quantity);
152+
} elseif ($openPosition->direction === DirectionEnum::Short && $isUpwardCross) {
153+
$this->exit($openPosition->quantity);
154+
}
155+
}
156+
}
157+
}
158+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /